# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 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 datetime
import struct

import mobius
import pymobius
from gi.repository import Gtk

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Code generated by Mobius. Do not edit
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
EXTENSION_ID = 'gigatribe-agent'
EXTENSION_NAME = 'Gigatribe Agent'
EXTENSION_AUTHOR = 'Eduardo Aguiar and Marcia Mendes'
EXTENSION_VERSION = '0.6'
EXTENSION_DESCRIPTION = 'Viewer for Gigatribe files'


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief markup SGML string
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def sgml_text_markup(text):
    return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief message data holder
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class MessageData(object):

    def __init__(self):
        self.text = ''
        self.timestamp = None
        self.offline = False
        self.sender = None
        self.receivers = []
        self.status_text = None


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief user data holder
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class UserData(object):

    def __init__(self):
        self.user_id = None
        self.user_name = None


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Gigatribe chat decoder
# @reference http://www.scribd.com/doc/57669321/Parsing-the-Chat-Logs-for-GigaTribe-Version-3
# @reference http://doc.qt.digia.com/qt-5.2/datastreamformat.html
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class GigaTribeChatFile(object):

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief initialize object
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __init__(self):
        self.version = None
        self.is_valid = False
        self.__messages = []

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief iterate through object
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __iter__(self):
        return iter(self.__messages)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief decode stream
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def decode(self, fp):
        if not fp:
            return

        signature = fp.read(2)
        if signature != b'ch':
            return

        decoder = mobius.decoder.qdatastream (fp, mobius.decoder.qdatastream.QT_4_0)

        # version
        self.is_valid = True
        self.version = decoder.get_qstring()

        # messages
        while not decoder.eof():
            message = self.__decode_message(decoder)
            self.__messages.append(message)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Decode message
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __decode_message(self, decoder):
        message = MessageData()
        message.id = decoder.get_qint32()
        message.offline = (decoder.get_qint8() == 1)
        message.timestamp = decoder.get_qdatetime()

        # sender
        message.sender = UserData()
        message.sender.user_id = decoder.get_qint32()
        message.sender.user_name = decoder.get_qstring()

        # receivers
        receiver_ids = decoder.get_qint32()

        for i in range(receiver_ids):
            receiver = UserData()
            receiver.user_id = decoder.get_qint32()
            message.receivers.append(receiver)

        receiver_names = decoder.get_qint32()

        for i in range(receiver_names):
            receiver = message.receivers[i]
            receiver.user_name = decoder.get_qstring()

        # text
        message.text = decoder.get_qstring()
        message.html = decoder.get_qstring()
        message.status_text = decoder.get_qstring()

        # msgsize in bytes
        msgsize = decoder.get_qint64()

        return message


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Extension widget
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Widget(object):

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Build widget
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __init__(self):
        self.__widget = mobius.ui.box(mobius.ui.box.orientation_vertical)
        self.__widget.set_border_width(10)
        self.__widget.set_spacing(5)
        self.__mediator = pymobius.mediator.copy()

        # menubar
        menubar = Gtk.MenuBar()
        menubar.show()
        self.__widget.add_child(menubar, mobius.ui.box.fill_none)

        item = Gtk.MenuItem.new_with_mnemonic('_File')
        item.show()
        menubar.append(item)

        menu = Gtk.Menu()
        menu.show()
        item.set_submenu(menu)

        item = Gtk.MenuItem.new_with_mnemonic('_Open')
        item.connect("activate", self.__on_file_open)
        item.show()
        menu.append(item)

        item = Gtk.SeparatorMenuItem.new()
        item.show()
        menu.append(item)

        item = Gtk.MenuItem.new_with_mnemonic('_Quit')
        item.connect("activate", self.__on_extension_quit)
        item.show()
        menu.append(item)

        # toolbar
        toolbar = Gtk.Toolbar()
        toolbar.set_style(Gtk.ToolbarStyle.ICONS)
        toolbar.show()
        self.__widget.add_child(toolbar, mobius.ui.box.fill_none)

        toolitem = Gtk.ToolButton.new()
        toolitem.set_icon_name('document-open')
        toolitem.connect("clicked", self.__on_file_open)
        toolitem.show()
        toolitem.set_tooltip_text("Open chat file(s)")
        toolbar.insert(toolitem, -1)

        # cookies listview
        self.__chat_tableview = pymobius.mediator.call('ui.new-widget', 'tableview')

        column = self.__chat_tableview.add_column('Timestamp')
        column.is_sortable = True

        column = self.__chat_tableview.add_column('Sender account ID', column_type='int')
        column.is_sortable = True

        column = self.__chat_tableview.add_column('Sender account name')
        column.is_sortable = True

        column = self.__chat_tableview.add_column('Receiver account ID', column_type='int')
        column.is_sortable = True

        column = self.__chat_tableview.add_column('Receiver account name')
        column.is_sortable = True

        column = self.__chat_tableview.add_column('Text')
        column.is_markup = True

        self.__chat_tableview.set_report_id('gigatribe.chat')
        self.__chat_tableview.set_report_name('Gigatribe Chat')
        self.__chat_tableview.set_report_app(f'{EXTENSION_NAME} v{EXTENSION_VERSION}')
        self.__chat_tableview.show()

        self.__widget.add_child(self.__chat_tableview.get_ui_widget(), mobius.ui.box.fill_with_widget)

        # status bar
        frame = Gtk.Frame()
        frame.set_shadow_type(Gtk.ShadowType.IN)
        frame.show()
        self.__widget.add_child(frame, mobius.ui.box.fill_none)

        self.__status_label = mobius.ui.label()
        self.__status_label.set_selectable(True)
        self.__status_label.set_halign(mobius.ui.label.align_left)
        self.__status_label.set_visible(True)
        frame.add(self.__status_label.get_ui_widget())

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Get internal widget
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def get_ui_widget(self):
        return self.__widget.get_ui_widget()

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Set status label
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __set_status_label(self, text):
        self.__status_label.set_text(text)
        mobius.ui.flush()

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Handle close button
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __on_extension_quit(self, widget, *args):
        self.__mediator.call('ui.working-area.close', self.working_area.id)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief show save/ignore/cancel dialog if there are modified items
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def on_widget_stopped(self):

        # show confirmation dialog
        dialog = mobius.ui.message_dialog(mobius.ui.message_dialog.type_question)
        dialog.text = f'Do you want to quit from {EXTENSION_NAME}?'
        dialog.add_button(mobius.ui.message_dialog.button_yes)
        dialog.add_button(mobius.ui.message_dialog.button_no)
        dialog.set_default_response(mobius.ui.message_dialog.button_no)
        rc = dialog.run()

        if rc != mobius.ui.message_dialog.button_yes:
            return True

        # close extension
        self.__mediator.clear()

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief handle file->open
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __on_file_open(self, widget, *args):

        # choose file(s)
        fs = Gtk.FileChooserDialog(title='Choose chat files')
        fs.set_select_multiple(True)
        fs.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
        fs.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)

        filefilter = Gtk.FileFilter()
        filefilter.set_name('chat file (*.dat)')
        filefilter.add_pattern('*.dat')
        fs.add_filter(filefilter)

        rc = fs.run()
        uri_list = fs.get_uris()
        fs.destroy()

        if rc != Gtk.ResponseType.OK:
            return

        # retrieve chat files
        self.__chat_tableview.clear()
        message_count = 0

        for uri in uri_list:

            try:
                f = mobius.io.new_file_by_url(uri)
                self.__set_status_label(f'Reading file {f.name}')

                chatfile = GigaTribeChatFile()
                chatfile.decode(f.new_reader())

                for message in chatfile:
                    timestamp = str(message.timestamp)[:19]

                    if message.text:
                        text = self.gigatribe_text_markup(message.text)

                    elif message.status_text:
                        text = '<span color="red" font_family="monospace">%s</span>' % sgml_text_markup(
                            message.status_text)

                    else:
                        text = None

                    for receiver in message.receivers:
                        self.__chat_tableview.add_row((timestamp, message.sender.user_id, message.sender.user_name,
                                                       receiver.user_id, receiver.user_name, text))

                    message_count += 1

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

        # update status label
        if message_count == 0:
            text = 'No message added'

        elif message_count == 1:
            text = '1 message has been added'

        else:
            text = f'{message_count:d} messages have been added'

        self.__set_status_label(text)

    def gigatribe_text_markup(self, text):
        outstr = ''
        pos = 0
        length = len(text)
        in_span = False

        while pos < length:
            idx = text.find('/', pos)

            if idx == -1:
                outstr += sgml_text_markup(text[pos:])
                pos = length

            else:
                outstr += sgml_text_markup(text[pos:idx])
                pos = idx

                # get escape sequence
                span_attr = []
                while pos < length and text[pos:pos + 1] == '/':
                    if text[pos + 1] == 'c':
                        color = text[pos + 2:pos + 8]
                        span_attr.append('foreground="#%s"' % color)
                        pos += 8
                    elif text[pos + 1] == 'b':
                        span_attr.append('weight="bold"')
                        pos += 2
                    elif text[pos + 1] == 'i':
                        span_attr.append('style="italic"')
                        pos += 2
                    elif text[pos + 1] == 'l':
                        span_attr.append('underline="single"')
                        span_attr.append('foreground="blue"')
                        pos += 2
                    elif text[pos + 1] == '/':
                        outstr += '/'
                        pos += 2
                    else:
                        pos += 1

                # open span, if necessary
                if span_attr:
                    if in_span:
                        outstr += '</span>'
                    outstr += '<span %s>' % (' '.join(span_attr))
                    in_span = True

        if in_span:
            outstr += '</span>'

        return outstr


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Start function
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def pvt_start():
    icon = pymobius.mediator.call('extension.get-icon-path', EXTENSION_ID)
    mobius.core.add_resource('menu.tools.' + EXTENSION_ID, 'Menu Tool: Gigatribe Agent',
                             (icon, EXTENSION_NAME, on_activate))


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Stop function
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def pvt_stop():
    mobius.core.remove_resource('menu.tools.' + EXTENSION_ID)
    pymobius.mediator.call('ui.working-area.del', EXTENSION_ID)


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief on_activate
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def on_activate(item_id):
    working_area = pymobius.mediator.call('ui.working-area.get', EXTENSION_ID)

    if not working_area:
        widget = Widget()
        icon_path = pymobius.mediator.call('extension.get-icon-path', EXTENSION_ID)

        working_area = pymobius.mediator.call('ui.working-area.new', EXTENSION_ID)
        working_area.set_default_size(800, 500)
        working_area.set_title(EXTENSION_NAME)
        working_area.set_icon(icon_path)
        working_area.set_widget(widget.get_ui_widget())

    working_area.show()
