#!/usr/bin/env python
# coding: utf-8
########################################################################
# Copyright (c) 2016
# Author: Robin David <robin.david<at>cea<dot>com>
# CEA (Commissariat à l'énergie atomique et aux énergies alternatives)
# All rights reserved.
########################################################################
#
#  This file is part of IDASec
#
#  IDAsec 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, version 2.1
#
#  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 sys
import datetime
import re
import sip

from PyQt5 import QtCore, QtWidgets
from path import Path

from idasec.widgets.MainWidget import MainWidget
from idasec.widgets.TraceWidget import TraceWidget
from idasec.idasec_core import IDASecCore
from idasec.widgets.AnalysisWidget import AnalysisWidget
from idasec.analysis.default_analysis import DefaultAnalysis
from idasec.analysis.generic_analysis import GenericAnalysis
from idasec.analysis.callret_analysis import CallRetAnalysis
from idasec.analysis.opaque_analysis import OpaqueAnalysis
from idasec.analysis.static_opaque_analysis import StaticOpaqueAnalysis

IDA_ENABLED = False
try:
    import idaapi
    from idaapi import PluginForm, plugin_t
    IDA_ENABLED = True
except ImportError:
    class PluginForm:
        def __init__(self):
            pass

    class plugin_t:
        def __init__(self):
            pass

    class idaapi:
        PLUGIN_UNL = None
        PLUGIN_OK = None

        def __init__(self):
            pass
    IDA_ENABLED = False


IDASEC = None
NAME = "IDASec"


class IDASecForm(PluginForm):
    def __init__(self):
        super(IDASecForm, self).__init__()
        global HOTKEYS
        HOTKEYS = []

    def OnCreate(self, form):
        # Internal data structures
        self.running_analyses = {}
        self.core = IDASecCore()
        self.parent = self.FormToPyQtWidget(form)
        self.setupUi(self.parent)
        self.main_widget = MainWidget(self)
        self.trace_widget = TraceWidget(self)
        self.analysis_widget = AnalysisWidget(self)
        # ---------------------------------

        # -- ui stuff
        self.tab_widget.setTabsClosable(True)
        self.tab_widget.tabCloseRequested.connect(self.close_tab_action)
        self.tab_widget.addTab(self.main_widget, "Main")
        self.tab_widget.addTab(self.trace_widget, "Trace")
        self.tab_widget.addTab(self.analysis_widget, "Analysis")
        self.tab_widget.tabBar().tabButton(0, self.tab_widget.tabBar().RightSide).hide()
        self.tab_widget.tabBar().tabButton(1, self.tab_widget.tabBar().RightSide).hide()
        self.tab_widget.tabBar().tabButton(2, self.tab_widget.tabBar().RightSide).hide()

    def OnClose(self, form):
        global IDASEC
        try:
            del IDASEC
        except NameError:
            print "IDASec apparently already deleted !"

    def setTabFocus(self, name):
        widget = {"Main": self.main_widget, "Trace": self.trace_widget, "Analysis": self.analysis_widget}[name]
        index = self.tab_widget.indexOf(widget)
        self.tab_widget.setCurrentIndex(index)

    def close_tab_action(self, i):
        idx = id(self.tab_widget.widget(i))
        analyse = self.running_analyses[idx]
        analyse.stop()
        self.tab_widget.removeTab(i)
        self.running_analyses.pop(idx)

    def start_analysis(self, name, conf, is_stream=False, trace=None):
        binsec_ip, binsec_port = self.main_widget.binsec_ip_field.text(), self.main_widget.binsec_port_field.text()
        pinsec_ip, pinsec_port = self.main_widget.pinsec_ip_field.text(), self.main_widget.pinsec_port_field.text()

        if binsec_ip == "" or binsec_port == "":
            print "No IP or port specified for Binsec"
        elif is_stream and (pinsec_ip == "" or pinsec_port == ""):
            print "No IP or port specified for Pinsec"
        else:
            analysis = self.analysis_from_name(name)(self, conf, is_stream, trace)
            index = self.tab_widget.addTab(analysis.result_widget, name.capitalize())
            self.tab_widget.setCurrentIndex(index)
            self.running_analyses[id(analysis.result_widget)] = analysis

            analysis.broker.connect_binsec(binsec_ip, binsec_port)
            if is_stream:
                analysis.broker.connect_pinsec(pinsec_ip, pinsec_port)
            analysis.run()

    def get_current_analysis(self):
        widget = self.tab_widget.widget(self.tab_widget.currentIndex())
        return self.running_analyses[id(widget)]

    @staticmethod
    def analysis_from_name(name):
        name = name.upper()
        if name == "GENERIC":
            return GenericAnalysis
        elif name == "CALLRET":
            return CallRetAnalysis
        elif name == "OPAQUE":
            return OpaqueAnalysis
        elif name == "STATIC OPAQUE":
            return StaticOpaqueAnalysis
        else:
            return DefaultAnalysis

    '''
    def Show(self):
        return PluginForm.Show(self, NAME, options=(PluginForm.FORM_CLOSE_LATER | PluginForm.FORM_RESTORE | PluginForm.FORM_SAVE))
    '''

    def add_trace(self, t):
        index = self.core.add_trace(t)
        self.analysis_widget.trace_selector.addItem("#%d %s" % (index, Path(t.filename).name))
        return index

    def add_solvers(self, solvers):
        for s in solvers:
            self.analysis_widget.solver_selector.addItem(s)
        self.core.solvers = solvers

    def add_analyses(self, analyses):
        self.analysis_widget.analysis_name_selector.clear()
        for a in analyses:
            self.analysis_widget.analysis_name_selector.addItem(a)
        self.core.analyses = analyses
        self.add_internal_analyses(analyses)

    def add_internal_analyses(self, dyn_analyses):
        if "OPAQUE" in dyn_analyses:
            self.analysis_widget.analysis_name_selector.addItem("STATIC OPAQUE")
            self.core.analyses.append("STATIC OPAQUE")

    def remove_trace(self, tr_id):
        tr = self.core.traces[tr_id]
        index = self.analysis_widget.trace_selector.findText("#%d %s" % (tr_id, Path(tr.filename).name))
        self.analysis_widget.trace_selector.removeItem(index)
        self.core.remove_trace(tr_id)

    def setupUi(self, Master):
        Master.setObjectName("Master")
        Master.resize(718, 477)
        self.verticalLayout = QtWidgets.QVBoxLayout(Master)
        self.verticalLayout.setObjectName("verticalLayout")
        self.splitter = QtWidgets.QSplitter(Master)
        self.splitter.setOrientation(QtCore.Qt.Vertical)
        self.splitter.setObjectName("splitter")
        self.tab_widget = QtWidgets.QTabWidget(self.splitter)
        self.tab_widget.setObjectName("tab_widget")

        self.docker = QtWidgets.QDockWidget(self.splitter)
        self.docker.setObjectName("docker")
        self.docker.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea)

        self.log_widget = QtWidgets.QTreeWidget(self.docker)
        self.log_widget.setHeaderItem(QtWidgets.QTreeWidgetItem(["date", "origin", "type", "message"]))
        self.docker.setWidget(self.log_widget)

        self.verticalLayout.addWidget(self.splitter)
        self.tab_widget.setCurrentIndex(-1)
        QtCore.QMetaObject.connectSlotsByName(Master)
        Master.setWindowTitle("IDASec")

    def log(self, type, message, origin="IDASec"):
        date = datetime.datetime.now().strftime("%H:%M:%S")
        res = re.match("^(\[[A-Za-z]*\])",message)
        if res:
            type = res.groups()[0]
            message = message[len(type):].lstrip()
        message = message.rstrip()
        self.log_widget.addTopLevelItem(QtWidgets.QTreeWidgetItem([date, origin, type, message]))
        self.log_widget.scrollToBottom()


################################################################################
# Usage as plugin
################################################################################

def PLUGIN_ENTRY():
    return IDASecPlugin()


class IDASecPlugin(plugin_t):

    flags = idaapi.PLUGIN_UNL
    comment = NAME
    help = "IDASec - IDA Interface for the Binsec platform"
    wanted_name = "IDASec"
    wanted_hotkey = "Ctrl-F1"

    def init(self):
        self.icon_id = 0
        return idaapi.PLUGIN_OK

    def run(self, arg=0):
        print "Run IDASec"
        f = IDASecForm()
        f.Show()
        return

    def term(self):
        pass


class IDASecStandalone:
    def __init__(self):
        self.core = IDASecCore()
        self.traces = []

    def remove_trace(self,tr_id):
        print "remove trace %d" % tr_id

    def add_trace(self, tr):
        print "Add trace"

################################################################################
# Usage as script
################################################################################
def main():
    idaapi.msg("Loading IDASEC\n")
    global IDASEC
    try:
        IDASEC
        IDASEC.OnClose(IDASEC)
        idaapi.msg("reloading IDASec\n")
        IDASEC = IDASecForm()
        return
    except Exception:
        IDASEC = IDASecForm()
    IDASEC.Show("Idasec")


def main_standalone():
    app = QtWidgets.QApplication(sys.argv)
    ida_app = IDASecStandalone()
    form = AnalysisWidget(ida_app)
    form.show()
    app.exec_()

if __name__ == "__main__":
    if IDA_ENABLED:
        main()
    else:
        main_standalone()