# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021 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 os
import os.path
import tempfile
import sqlite3
import shutil
import pymobius
import mobius
import re
import decoder_messages

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Decode %UUUU chars from string
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def pct_decode (s):
  def repl (matchobj):
    return unichr (int (matchobj.group (1), 16))

  if isinstance (s, str):
    s = unicode (s, 'utf-8')

  s = re.sub ('%([0-9a-f]{4})', repl, s)

  return s

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Get database schema version
# @param db Database object
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_schema_version (db):
  version = None

  stmt = '''
       SELECT value
         FROM metadata 
        WHERE name  = 'schemaVersion'
        '''

  try:
    cursor = db.execute (stmt)
    row = cursor.fetchone ()
    
    if row:
      version = int (row[0])

  except Exception, e:
    mobius.core.logf ('WRN ' + str (e))

  return version

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Skype Profile class
# @author Eduardo Aguiar
# @see https://bebinary4n6.blogspot.com/2019/07/
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Profile (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, f, item):
    self.__db = None
    self.__item = item
    self.__schema_version = None
    self.__f = f

    # set profile attributes
    self.name = pct_decode (f.name[4:-3])
    self.path = mobius.io.to_win_path (f.path)
    self.metadata = mobius.pod.map ()
    self.metadata.set ('db_path', self.path)

    # set data attributes
    self.__account_loaded = False
    self.__calls_loaded = False
    self.__chat_messages_loaded = False
    self.__file_transfers_loaded = False
    self.__account = None
    self.__calls = []
    self.__chat_messages = []
    self.__file_transfers = []
    self.__skype_names = {}
    self.__conversation_members = {}

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get account
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_account (self):
    if not self.__account_loaded:
      self.__load_account ()

    return self.__account

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get calls
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_calls (self):
    if not self.__calls_loaded:
      self.__load_calls ()

    return self.__calls

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get chat messages
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_chat_messages (self):
    if not self.__chat_messages_loaded:
      self.__load_chat_messages ()

    return self.__chat_messages

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get file transfers
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_file_transfers (self):
    if not self.__file_transfers_loaded:
      self.__load_file_transfers ()

    return self.__file_transfers

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Load account
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __load_account (self):
    return 
    db = self.__get_db ()

    cursor = db.execute ('''
       SELECT skypename,
              fullname
         FROM accounts
        WHERE id = 1''')

    row = cursor.fetchone ()

    if row:
      self.__account = pymobius.Data ()
      self.__account.id = row[0]
      self.__account.name = row[1]
      self.__account_loaded = True
      self.__skype_names[self.__account.id] = self.__account.name

    self.__account_loaded = True

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Load calls
  # @todo Implement
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __load_calls (self):
    if not self.__calls_loaded:          
      db = self.__get_db ()

      # @todo call decoder_calllogs...
      self.__calls_loaded = True

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Load file transfers
  # @todo Implement
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __load_file_transfers (self):
    self.__file_transfers_loaded = True

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Load chat messages
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __load_chat_messages (self):

    if not self.__chat_messages_loaded:
      db = self.__get_db ()

      self.__chat_messages, skype_names = decoder_messages.decode (db, self.name)
      self.__skype_names.update (skype_names)

      self.__chat_messages_loaded = True

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get database files
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_database_files (self):
    files = []

    if self.__f and not self.__f.is_reallocated ():
      db = self.__get_db ()
      db_schema_version = get_schema_version (db)
      files.append (('s4l.db', db_schema_version, self.__f))

    return files

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve database file
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_db (self):

    if not self.__db:
      reader = self.__f.new_reader ()
      if not reader:
        return

      # create temporary .sqlite local file
      ext = os.path.splitext (self.__f.name)[1]
      fd, path = tempfile.mkstemp (suffix=ext)

      fp = open (path, 'w')
      fp.write (reader.read ())
      fp.close ()

      # connect to db
      self.__db = sqlite3.connect (path)
      db_schema_version = get_schema_version (self.__db)
      self.metadata.set ('db_path', mobius.io.to_win_path (self.__f.path))

      if db_schema_version:
        self.metadata.set ('db_schema_version', db_schema_version)
        mobius.core.logf ('INF s4l.db schema version=%d' % db_schema_version)

      # set handled
      #self.__f.set_handled ()

    return self.__db
