# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020 Eduardo Aguiar
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import pymobius.xml
import pymobius.json_serializer
import mobius
import inspect
import os.path

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Create new case
# @param folder case folder path
# @return case object
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def new_case (folder):
  case_manager = mobius.model.case_manager ()
  impl = case_manager.new_case (folder)
  return case (impl)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Open case
# @param folder Case folder path
# @return Case object
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def open_case (folder):
  case_manager = mobius.model.case_manager ()
  impl = case_manager.open_case (folder)
  return case (impl)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Close case
# @param case Case object
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def close_case (case):
  case_manager = mobius.model.case_manager ()
  impl = case.get_impl ()
  case_manager.close_case (impl)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Create .sqlite case database from .case XML file
# @param filename .case URL
# @return case object
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def import_case_from_xml (filename):

  # get all sub-items from a given item
  def get_items (item):
    for child in item.children:
      yield child
      for i in get_items (child):
        yield i

  # re-parent items
  def reparent (xml_parent, items):
    for idx, xml_child in enumerate (xml_parent.children):
      parent = items.get (xml_parent.uid)
      child = items.get (xml_child.uid)
      child.move (idx + 1, parent)
      reparent (xml_child, items)
      
  # decode .case XML file
  pickle = pymobius.xml.Pickle ()
  xml_case = pickle.load (filename)

  # get base folder from case data or from .case dirname  
  if os.path.exists (xml_case.base_folder):
    base_folder = xml_case.base_folder

  else:
    url = mobius.io.uri (filename)
    base_folder = os.path.dirname (url.get_path ())

  # create case object
  case_manager = mobius.model.case_manager ()
  impl = case_manager.new_case (base_folder)
  c = case (impl)

  # add items to case item
  xml_items = dict ((i.uid, i) for i in get_items (xml_case.root))
  transaction = c.new_transaction ()

  root = c.root_item
  root.id = xml_case.root.id
  root.name = xml_case.root.name
  
  items = {1 : root}
  NON_IMPORTED_ATTRS = set (['case', 'class_id', 'ctime', 'is_item', 'mtime', 'parent', 'children', 'uid', 'category'])

  last_uid = 1

  for uid, xml_item in sorted (xml_items.iteritems ()):

    # fill UID's gap, if any
    while last_uid < uid - 1:
      item = root.new_child (xml_item.category)
      item.remove ()	# only to increase UID
      last_uid += 1
    last_uid = uid

    # create item
    item = root.new_child (xml_item.category)
    items[item.uid] = item

    # set attributes
    for attr_id, attr_value in inspect.getmembers (xml_item):
      if not callable (attr_value) and not attr_id.startswith ('_') and attr_id not in NON_IMPORTED_ATTRS:
        
        if attr_id == 'datasource':
          del attr_value.item

        item.set_attribute (attr_id, attr_value, signal=False)

  # re-parent items
  reparent (xml_case.root, items)

  # commit changes  
  transaction.commit ()

  return c

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Adaptor class for mobius.model.case
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class case (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, impl):
    self.__impl = impl

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get attribute
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __getattr__ (self, name):
      
    if name in self.__dict__:
      return self.__dict__.get (name)

    else:
      return getattr (self.__impl, name)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get mobius.model.case underlying object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_impl (self):
    return self.__impl
    
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get item by UID
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_item_by_uid (self, uid):
    return item (self.__impl.get_item_by_uid (uid), self)
    
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Implicit getter for root, root_item attributes
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_root (self):
    return item (self.__impl.root_item, self)

  root = property (__get_root, None)
  root_item = property (__get_root, None)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Adaptor class for item.children
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class itemlist (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, parent):
    self.__parent = parent
    self.__data = [ item (c, parent.case) for c in parent.get_children () ]

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __iter__ (self):
    return iter (self.__data)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Adaptor class for mobius.model.item
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class item (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, impl, case):
    self.__dict__['_item__case'] = case
    self.__dict__['_item__impl'] = impl

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get attribute
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __getattr__ (self, name):
      
    if name == 'case':
      return self.__case

    elif name == 'uid':
      return self.__impl.uid

    elif name == 'category':
      return self.__impl.category

    elif name == 'metadata':
      return self.__impl.metadata

    elif name in self.__dict__:
      return self.__dict__.get (name)

    elif self.__impl.has_attribute (name):
      return self.get_attribute (name)

    else:
      return getattr (self.__impl, name, None)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief set attribute
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __setattr__ (self, name, value):

    if name == 'metadata':
      self.__impl.metadata = value
    else:
      self.set_attribute (name, value)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get children
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_children (self):
    return [ item (c, self.__case) for c in self.__impl.get_children () ]

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get parent, if any
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_parent (self):
    impl = self.__impl.get_parent ()

    if impl:
      return item (impl, self.__case)

    return None

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Create new child
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def new_child (self, category, pos=-1):
    impl = self.__impl.new_child (category, pos)

    return item (impl, self.__case)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Move child to another position/parent
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def move (self, idx, parent):
    self.__impl.move (idx, parent.__impl)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get attribute
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_attribute (self, name):

    if self.__impl.has_attribute (name):
      value = self.__impl.get_attribute (name)

      if value.startswith ('\x09JSON\x09'):
        value = pymobius.json_serializer.unserialize (value[6:])

    else:
      value = None

    return value

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Set attribute
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def set_attribute (self, name, value, signal=True):

    # get old value
    if self.__impl.has_attribute (name):
      old_value = self.__impl.get_attribute (name)
    else:
      old_value = None

    # convert value to string
    if value == None:
      pass

    elif isinstance (value, str):
      pass

    elif isinstance (value, (int, bool, float, long)):
      value = str (value)
      
    elif isinstance (value, unicode):
      value = value.encode ('utf-8')

    else:
      value = '\x09JSON\x09' + pymobius.json_serializer.serialize (value)

    # set new value, if necessary
    if old_value != value:
      
      if value == None:
        self.__impl.remove_attribute (name)
      
      else:
        self.__impl.set_attribute (name, value)

      if signal:
        pymobius.mediator.emit ('object.attribute-modified', self, name, old_value, value)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get attributes
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_attributes (self):
    values = self.__impl.get_attributes ()

    for name, value in values.iteritems ():
      if value.startswith ('\x09JSON\x09'):
        values[name] = pymobius.json_serializer.unserialize (value[6:])

    return values

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Expand object derivated attributes
  # Libmobius' expand_masks lacks signals, so we have to do it here...
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def expand_masks (self):
    old_values = self.__impl.get_attributes ()
    self.__impl.expand_masks ()
    values = self.__impl.get_attributes ()
    
    for name, value in values.iteritems ():
      old_value = old_values.get (name)

      if old_value != value:
        pymobius.mediator.emit ('object.attribute-modified', self, name, old_value, value)
