# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# 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 os
import os.path
import tempfile
import sqlite3
import shutil
import pymobius
import mobius
import message_parser

DEBUG = False

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Message types
# @see https://skpy.t.allofti.me/protocol/chat.html
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
MESSAGE_TYPE_B = {
      2 : 'RichText',
      3 : 'RichText/UriObject',
      6 : 'RichText/GenericFile',
      8 : 'VIDEO_SHARED',
      9 : 'RichText/Media_AudioMsg',
     10 : 'EVENT',
    501 : 'PopCard',
   1001 : 'ThreadActivity/DeleteMember'
}

SYSTEM_MESSAGES = {
}

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Get column names from table
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_table_columns (db, table):
  columns = set ()
  SQL_STATEMENT = 'PRAGMA TABLE_INFO (%s)' % table
  
  for row in db.execute (SQL_STATEMENT):
    columns.add (row[1])

  return columns

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Skype App (v1-13) Profile class
# @author Eduardo Aguiar
# @see https://answers.microsoft.com/en-us/skype/forum/all/where-is-the-maindb-file-for-new-skype/b4d3f263-a97e-496e-aa28-e1dbb63e7687
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Profile (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, folder, item):
    self.__folder = folder
    self.__item = item
    self.__schema_version = None
    self.__main_db = None
    self.__skype_db = None

    # set profile attributes
    self.name = folder.name
    self.path = folder.path.replace ('/', '\\')
    self.folder = folder

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

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

    return self.__account

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @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):
    db = self.__get_main_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 file transfers
  # @todo Implement file transfers
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __load_file_transfers (self):
    self.__file_transfers_loaded = True

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Load chat messages
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __load_chat_messages (self):
    if self.__chat_messages_loaded:
      return
     
    db = self.__get_skype_db ()

    if not db:
      return

    account = self.get_account ()

    # get contact Ids and names
    skype_names = {}

    SQL_STATEMENT = '''
       SELECT mri, display_name
         FROM contacts'''

    for row in db.execute (SQL_STATEMENT):
      mri = row[0]
      display_name = row[1]
      skype_names[mri] = display_name

    # get messages
    SQL_STATEMENT = '''
       SELECT m.id,
              m.originalarrivaltime,
              m.author,
              m.content,
              m.sendingstatus,
              m.messagetype,
              c.type,
              c.id
         FROM messages m,
              conversations c
        WHERE c.dbid = m.convdbid'''
  
    for row in db.execute (SQL_STATEMENT):
      message = pymobius.Data ()
      message.id = row[0]
      message.chatname = row[0]	# @deprecated
      message.timestamp = mobius.datetime.new_datetime_from_unix_timestamp (row[1]//1000)
      message.sender_id = row[2].split (':', 1)[1]
      message.sender_name = skype_names.get (row[2])
      message.status = row[4]
      message.raw_text = (row[3] or u'').rstrip ()
      message.type = MESSAGE_TYPE_B.get (row[5], 'Unknown type (%d)' % row[5])
      
      if row[5] not in MESSAGE_TYPE_B:
        mobius.core.log ('app.skype: unknown MESSAGE_TYPE_B %d. Text: %s' % (row[5], message.raw_text.encode ('utf-8')))

      # recipients
      if message.sender_id == account.id:
        recipient_id = row[7]
        recipient_mri = '%s:%s' % (row[6], row[7])
        recipient_name = skype_names.get (recipient_mri)
      else:
        recipient_id = account.id
        recipient_name = account.name

      message.recipients = [ (recipient_id, recipient_name) ]

      # text
      parser = message_parser.MessageParser (message.raw_text)

      text = SYSTEM_MESSAGES.get (message.type)

      if text:
        text = text.replace ('_', ' ')
        text = text.capitalize ()
        parser.add_element ({u'type' : u'system', u'text' : text})

      if message.type == 'PopCard':
        message.text = parser.parse_popcard ()

      else:
        message.text = parser.parse ()

      # add message
      self.__chat_messages.append (message)

    # Set loaded
    self.__chat_messages_loaded = True

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve database file
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_db_file (self, filename):

    f = self.__folder.get_child_by_name (filename)
    if not f:
      return

    reader = f.new_reader ()
    if not reader:
      return

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

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

    # development only
    if DEBUG:
      shutil.copy (path, '/tmp/skype/%s-%s' % (self.name, filename))

    # set handled
    #f.set_handled ()
    db = sqlite3.connect (path)

    return db

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve main.db database
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_main_db (self):
    if not self.__main_db:
      self.__main_db = self.__get_db_file ('main.db')

    return self.__main_db

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve skype.db database
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_skype_db (self):
    if not self.__skype_db:
      self.__skype_db = self.__get_db_file ('skype.db')

    return self.__skype_db
