import networkx as nx

from awesome.context import ignored
import sark
import idaapi
import idautils
import idc
from idaapi import PluginForm
from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5

if use_qt5:
    _QSortFilterProxyModel = QtCore.QSortFilterProxyModel
    _MatchRecursive = QtCore.Qt.MatchRecursive
    _MatchExactly = QtCore.Qt.MatchExactly
    _PositionAtTop = QtWidgets.QAbstractItemView.PositionAtTop
else:
    _QSortFilterProxyModel = QtGui.QSortFilterProxyModel
    _MatchRecursive = QtCore.Qt.MatchFlag.MatchRecursive
    _MatchExactly = QtCore.Qt.MatchFlag.MatchExactly
    _PositionAtTop = QtWidgets.QAbstractItemView.ScrollHint.PositionAtTop

import DIE.UI.Die_Icons
import DIE.UI.ValueViewEx
import DIE.UI.ParserView
import DIE.UI.BPView

import DIE.Lib.IDAConnector
import DIE.Lib.DIEDb
import DIE.Lib.BpHandler

import sark.ui

class FunctionView(PluginForm):
    """
    DIE Function View
    """

    def __init__(self):

        super(FunctionView, self).__init__()
        self.value_view = None
        self.bp_handler = None
        self.die_icons = None
        self.die_db = None
        self.highligthed_items = []

    def Show(self):
        # Reset highlighted items
        self.highligthed_items = []

        return PluginForm.Show(self,
                               "Function View",
                               options=PluginForm.FORM_PERSIST)
    def OnCreate(self, form):
        """
        Called when the plugin form is created
        """
        self.value_view = DIE.UI.ValueViewEx.get_view()
        self.bp_handler = DIE.Lib.BpHandler.get_bp_handler()
        self.die_icons = DIE.UI.Die_Icons.get_die_icons()
        self.die_db = DIE.Lib.DIEDb.get_db()

        # Get parent widget
        self.parent = form_to_widget(form)

        self.functionModel = QtGui.QStandardItemModel()
        self.functionTreeView = QtWidgets.QTreeView()
        self.functionTreeView.setExpandsOnDoubleClick(False)
        #self.functionTreeView.setSortingEnabled(True)

        delegate = TreeViewDelegate(self.functionTreeView)
        self.functionTreeView.setItemDelegate(delegate)

        self.functionTreeView.doubleClicked.connect(self.itemDoubleClickSlot)

        self._model_builder(self.functionModel)
        self.functionTreeView.setModel(self.functionModel)

        self.functionTreeView.setColumnWidth(0, 200)
        self.functionTreeView.setColumnWidth(1, 20)
        self.functionTreeView.setColumnWidth(2, 20)
        self.functionTreeView.setColumnWidth(3, 20)
        self.functionTreeView.setColumnWidth(4, 250)
        self.functionTreeView.setColumnWidth(5, 100)
        self.functionTreeView.setColumnWidth(6, 20)
        self.functionTreeView.setColumnWidth(7, 450)
        self.functionTreeView.setColumnWidth(8, 20)
        self.functionTreeView.setColumnWidth(9, 450)

        # Context menus
        self.functionTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.functionTreeView.customContextMenuRequested.connect(self.onCustomContextMenu)

        # Actions
        self.context_menu_param = None  # Parameter to be passed to context menu slots

        action_exclude_func = QtWidgets.QAction("Exclude Function", self.functionTreeView, triggered=lambda: self.on_exclude_func(self.context_menu_param))
        action_exclude_func_adrs = QtWidgets.QAction("Exclude All Function Calls", self.functionTreeView, triggered=lambda: self.on_exclude_func_adrs(self.context_menu_param))
        action_exclude_ea = QtWidgets.QAction("Exclude Address", self.functionTreeView, triggered=lambda: self.on_exclude_ea(self.context_menu_param))
        action_exclude_library = QtWidgets.QAction("Exclude Library", self.functionTreeView, triggered=lambda: self.on_exclude_library(self.context_menu_param))
        action_value_detail = QtWidgets.QAction("Inspect Value Details", self.functionTreeView, triggered=lambda: self.on_value_detail(self.context_menu_param))

        action_show_callgraph = QtWidgets.QAction("Show Call-Graph", self.functionTreeView, triggered=lambda: self.on_show_callgraph(self.context_menu_param))

        # Function ContextMenu
        self.function_context_menu = QtWidgets.QMenu(self.functionTreeView)
        self.function_context_menu.addAction(action_exclude_func)
        self.function_context_menu.addAction(action_exclude_library)
        self.function_context_menu.addAction(action_exclude_func_adrs)

        # Function ea ContextMenu
        self.ea_context_menu = QtWidgets.QMenu(self.functionTreeView)
        self.ea_context_menu.addAction(action_exclude_ea)
        self.ea_context_menu.addAction(action_show_callgraph)

        # Argument value ContextMenu
        self.value_context_menu = QtWidgets.QMenu(self.functionTreeView)
        self.value_context_menu.addAction(action_value_detail)

        # Therad ComboBox
        threads = []
        if self.die_db is not None:
            threads = self.die_db.get_thread_list()

        thread_id_list = []
        thread_id_list.append("All Threads")
        for thread in threads:
            thread_id_list.append(str(thread.thread_num))

        self.thread_id_combo = QtWidgets.QComboBox()
        self.thread_id_combo.addItems(thread_id_list)
        self.thread_id_combo.activated[str].connect(self.on_thread_combobox_change)

        self.thread_id_label = QtWidgets.QLabel("Thread:  ")

        # Toolbar
        self.function_toolbar = QtWidgets.QToolBar()
        self.function_toolbar.addWidget(self.thread_id_label)
        self.function_toolbar.addWidget(self.thread_id_combo)

        # Grid
        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.function_toolbar)
        layout.addWidget(self.functionTreeView)

        self.parent.setLayout(layout)

    def OnClose(self, form):
        idaapi.msg("Closed\n")

    def isVisible(self):
        """
        Is functionview visible
        @return: True if visible, otherwise False
        """
        try:
            return self.functionTreeView.isVisible()
        except:
            return False

    def _model_builder(self, model):
        """
        Build the function model.
        @param model: QStandardItemModel object
        """
        model.clear()  # Clear the model
        root_node = model.invisibleRootItem()

        self._make_model_headers(model)

        if self.die_db is None:
            return

        # Add db functions to the model
        for function in self.die_db.get_functions():
            item_list_func = self._make_function_item(function)

            if function.is_lib_func:  # Color library function
                for tmp_item in item_list_func:
                    tmp_item.setBackground(QtGui.QColor(184, 223, 220))

            item_function = item_list_func[0]
            root_node.appendRow(item_list_func)

            # Add function contexts ea\occurrences for the current function
            func_context_dict = self.die_db.get_function_context_dict(function)

            for function_context_ea in func_context_dict:
                function_context_list = func_context_dict[function_context_ea]
                if not len(function_context_list) > 0:
                    continue

                item_func_context_list = self._make_function_ea_item(function_context_list[0])
                item_func_context_ea = item_func_context_list[0]
                item_function.appendRow(item_func_context_list)

                occurrence_num = 0
                for function_context in function_context_list:
                    item_func_context_list = self._make_func_occur_item(function_context, occurrence_num)
                    item_func_context = item_func_context_list[0]
                    item_func_context_ea.appendRow(item_func_context_list)

                    self._insert_thread_data(item_function, function_context.thread_id)
                    self._insert_thread_data(item_func_context_ea, function_context.thread_id)

                    # Add function arguments to each context
                    current_call_values = self.die_db.get_call_values(function_context)
                    current_ret_values = self.die_db.get_return_values(function_context)
                    curret_ret_arg_value = self.die_db.get_return_arg_value(function_context)

                    for arg_index in xrange(0, function.arg_num):
                        try:
                            current_arg = self.die_db.get_function_arg(function, arg_index)
                            self._add_model_arg_value(item_func_context,
                                                       current_call_values[arg_index],
                                                       current_ret_values[arg_index],
                                                       current_arg.name,
                                                       current_arg.type)
                        except IndexError:
                            break

                    ret_arg = self.die_db.get_function_arg(function, -1)
                    if ret_arg is None:
                        ret_arg_type = "VOID"
                    else:
                        ret_arg_type = ret_arg.type

                    # Add return argument
                    self._add_model_arg_value(item_func_context,
                                               None,
                                               curret_ret_arg_value,
                                               "ret_arg",
                                               ret_arg_type)

                    # Increment occurrence counter
                    occurrence_num += 1

        # Add non-executed function to the model
        # for func_ea in idautils.Functions():
        #     func_name = DIE.Lib.IDAConnector.get_function_name(func_ea)
        #
        #     if self.die_db.get_function_by_name(func_name) is None:
        #         item_list_func = self._make_nonexec_function_time(func_name)
        #
        #         if function.is_lib_func:  # Color library function
        #             for tmp_item in item_list_func:
        #                 tmp_item.setBackground(QtGui.QColor(255, 0, 0, 127))
        #
        #         root_node.appendRow(item_list_func)


    def _make_model_headers(self, model):
        """
        Set the model horizontal header data
        @param model: the QStandardItemModel which headers should be set
        """
        ### Function Header
        item_header = QtGui.QStandardItem("Function")
        item_header.setToolTip("Function Name")
        model.setHorizontalHeaderItem(0, item_header)

        ### Call number header
        item_header = QtGui.QStandardItem("#")
        item_header.setToolTip("Number of calls preformed to this function")
        model.setHorizontalHeaderItem(1, item_header)

        ### Indirect Header
        item_header = QtGui.QStandardItem("I")
        item_header.setToolTip("Indirect Call")
        model.setHorizontalHeaderItem(2, item_header)

        ### Indirect Header
        item_header = QtGui.QStandardItem("N")
        item_header.setToolTip("New Function")
        model.setHorizontalHeaderItem(3, item_header)

        ### Indirect Header
        item_header = QtGui.QStandardItem("Type")
        item_header.setToolTip("Argument Type")
        model.setHorizontalHeaderItem(4, item_header)

        ### New Function Header
        item_header = QtGui.QStandardItem("Name")
        item_header.setToolTip("Argument Name")
        model.setHorizontalHeaderItem(5, item_header)

        ### Call Value Icon Header
        item_header = QtGui.QStandardItem("")
        model.setHorizontalHeaderItem(6, item_header)

        ### Call Value Header
        item_header = QtGui.QStandardItem("Call Value")
        item_header.setToolTip("Argument`s value on function call")
        model.setHorizontalHeaderItem(7, item_header)

        ### Return Value Icon Header
        item_header = QtGui.QStandardItem("")
        model.setHorizontalHeaderItem(8, item_header)

        ### Return Value Header
        item_header = QtGui.QStandardItem("Return Value")
        item_header.setToolTip("Argument`s value on function return")
        model.setHorizontalHeaderItem(9, item_header)

    def _make_thread_id_data(self, thread_id):
        """
        Delimit thread_id data in order to support filtering\sorting on multi-thread data items
        @param thread_id: thread id to normalize
        @return: a normalized string of the thread_id to be used sa data for ThreadId_Role
        """
        return "t%st" % str(thread_id)

    def _insert_thread_data(self, item, thread_id):
        """
        Insert thread_id data into a model item.
        The value found in thread_id argument will be delimited by the _make_thread_id_data function
        (e.g: thread_id 123 will become 't123t')
        the delimited value will then be appended to a string of concatenated (unique) child-item thread-ids
        (for example a item data value can be "a123aa5672aa11112a") for threads 123, 5672 and 111112
        @param item: the model item to add the data to
        @param thread_id: thread_id number
        @return: True if thread data was successfully added to item, otherwise False
        """
        try:
            current_thread_id = self._make_thread_id_data(thread_id)
            thread_data = item.data(role=DIE.UI.ThreadId_Role)


            if thread_data is None:
                item.setData(current_thread_id, role=DIE.UI.ThreadId_Role)

            elif not current_thread_id in thread_data:
                item.setData(thread_data + current_thread_id, role=DIE.UI.ThreadId_Role)

            return True

        except Exception as ex:
            idaapi.msg("Error while inserting thread data: %s\n" %ex)
            return False

    def _make_function_item(self, function):
        """
        Build a tree item for a function name (level-0)
        @param function: dbFunction object
        @return: QStandradItemModel item for the function
        """
        function_txt = "%s" % function.function_name

        item_function = QtGui.QStandardItem(self.die_icons.icon_function, function_txt)
        item_function.setData(function, role=DIE.UI.Function_Role)

        function_count = self.die_db.count_function_occurs(function)
        item_function_count = QtGui.QStandardItem(str(function_count))

        item_function_count.setEditable(False)
        item_function.setEditable(False)

        item_list = [item_function,
                     item_function_count,
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem()]

        return item_list

    def _make_nonexec_function_time(self, function_name):
        """
        Build a tree item for a function name (for a non-executed function)
        @type: String
        @param function_name: Function name
        @return:
        """

        item_function = QtGui.QStandardItem(self.die_icons.icon_function, function_name)
        item_function_count = QtGui.QStandardItem("0")

        item_function_count.setEditable(False)
        item_function.setEditable(False)

        item_list = [item_function, item_function_count]

        return item_list

    def _make_function_ea_item(self, function_context):
        """
        Build a tree item for a function_ea node (level-1)
        @param function_context: a dbFunction_Context object
        @return: QStandradItemModel item for the function context
        """
        calling_function_start = None
        with ignored(sark.exceptions.SarkNoFunction):
            calling_function_start = sark.Function(function_context.calling_ea).startEA

        if calling_function_start is not None:
            call_offset = function_context.calling_ea - calling_function_start
            func_ea_txt = "%s+%s" % (function_context.calling_func_name, hex(call_offset))
        else:
            func_ea_txt = "[%s]:%s" % (function_context.calling_func_name, hex(function_context.calling_ea))

        item_func_context_ea = QtGui.QStandardItem(func_ea_txt)
        item_func_context_ea.setEditable(False)
        item_func_context_ea.setData(hex(function_context.calling_ea), role=QtCore.Qt.ToolTipRole)
        item_func_context_ea.setData(function_context, role=DIE.UI.FunctionContext_Role)
        item_func_context_ea.setData(id(function_context), role=DIE.UI.ContextId_Role)  # Used for module look-ups

        item_func_is_indirect = QtGui.QStandardItem()
        item_func_is_indirect.setEditable(False)
        if function_context.is_indirect:
            item_func_is_indirect.setIcon(self.die_icons.icon_v)

        item_func_is_new = QtGui.QStandardItem()
        item_func_is_new.setEditable(False)
        if function_context.is_new_func:
            item_func_is_new.setIcon(self.die_icons.icon_v)

        item_list = [item_func_context_ea,
                     QtGui.QStandardItem(),
                     item_func_is_indirect,
                     item_func_is_new,
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem()]

        return item_list

    def _make_func_occur_item(self, function_context, occur_num):
        """
        Build a tree item for function occurrence (level-2)
        @param function_context: a dbFunction_Context object
        @param occur_num: occurrence number
        @return: QStandradItemModel item for the function occurrence
        """
        func_occur_txt = "Occur %s" % str(occur_num)
        item_func_context = QtGui.QStandardItem(func_occur_txt)
        item_func_context.setColumnCount(5)
        item_func_context.setEditable(False)
        item_func_context.setData(function_context, role=DIE.UI.FunctionContext_Role)
        item_func_context.setData(id(function_context), role=DIE.UI.ContextId_Role)  # Used for module look-ups
        item_func_context.setData(self._make_thread_id_data(function_context.thread_id), role=DIE.UI.ThreadId_Role)

        item_list = [item_func_context,
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem(),
                     QtGui.QStandardItem()]

        return item_list

    def _add_model_arg_value(self, parent, call_value, ret_value, arg_name, arg_type, nest_depth=0):
        """
        Add a debug value
        @param parent:
        @param call_value:
        @param ret_value:
        @param arg_name:
        @param arg_type:
        @return:
        """
        arg_count = parent.rowCount()
        this_row_item = QtGui.QStandardItem("")
        this_row_item.setData(parent.data(role=DIE.UI.ThreadId_Role), role=DIE.UI.ThreadId_Role)  # Inherit thread data from parent

        # Set indentation for argument types (for nested values)
        arg_ident = "  " * nest_depth
        arg_ident_type = arg_ident + arg_type

        item_parsed_val_flag_call = QtGui.QStandardItem()
        item_parsed_val_call = QtGui.QStandardItem()
        item_parsed_val_flag_ret = QtGui.QStandardItem()
        item_parsed_val_ret = QtGui.QStandardItem()

        # Get Call Value
        if call_value is not None:
            parsed_vals = self.die_db.get_parsed_values(call_value)
            this_row_item.setData(parsed_vals, role=DIE.UI.CallValue_Role)

            if parsed_vals is not None and len(parsed_vals) > 0:
                is_guessed, best_val = self.die_db.get_best_parsed_val(parsed_vals)
                item_parsed_val_call = QtGui.QStandardItem(best_val.data)
                if is_guessed:
                    item_parsed_val_flag_call.setIcon(self.die_icons.icon_question)

                if len(parsed_vals) > 1:  # If more the 1 item, show a combo-box
                    item_parsed_val_call.setData(parsed_vals, role=DIE.UI.ParsedValuesRole)
                    item_parsed_val_flag_call.setIcon(self.die_icons.icon_more)
                else:
                    item_parsed_val_call.setData(parsed_vals[0], role=DIE.UI.ParsedValueRole)

            else:
                parsed_val_data = "NULL"

                if call_value.derref_depth == 0:
                    parsed_val_data = "!MAX_DEREF!"

                if call_value.raw_value is not None:
                    parsed_val_data = hex(call_value.raw_value)

                if len(call_value.nested_values) > 0 or call_value.reference_flink is not None:
                    parsed_val_data = ""

                item_parsed_val_call = QtGui.QStandardItem(parsed_val_data)

        # Get return value
        if ret_value is not None:
            parsed_vals = self.die_db.get_parsed_values(ret_value)
            this_row_item.setData(parsed_vals, role=DIE.UI.RetValue_Role)

            # If len(parsed_vals)>1 create a combobox delegate.
            if parsed_vals:
                is_guessed, best_val = self.die_db.get_best_parsed_val(parsed_vals)
                item_parsed_val_ret = QtGui.QStandardItem(best_val.data)
                if is_guessed:
                    item_parsed_val_flag_ret.setIcon(self.die_icons.icon_question)

                if len(parsed_vals) > 1:  # If more the 1 item, show a combo-box
                    item_parsed_val_ret.setData(parsed_vals, role=DIE.UI.ParsedValuesRole)
                    item_parsed_val_flag_ret.setIcon(self.die_icons.icon_more)
                else:
                    item_parsed_val_ret.setData(parsed_vals[0], role=DIE.UI.ParsedValueRole)
            else:
                parsed_val_data = "NULL"

                if ret_value.derref_depth == 0:
                    parsed_val_data = "!MAX_DEREF!"

                if ret_value.raw_value is not None:
                    parsed_val_data = hex(ret_value.raw_value)

                if ret_value.nested_values or ret_value.reference_flink is not None:
                    parsed_val_data = ""

                item_parsed_val_ret = QtGui.QStandardItem(parsed_val_data)

            parent.setChild(arg_count, 0, this_row_item)
            parent.setChild(arg_count, 1, QtGui.QStandardItem())
            parent.setChild(arg_count, 2, QtGui.QStandardItem())
            parent.setChild(arg_count, 3, QtGui.QStandardItem())
            parent.setChild(arg_count, 4, QtGui.QStandardItem(arg_ident_type))
            parent.setChild(arg_count, 5, QtGui.QStandardItem(arg_name))

            parent.setChild(arg_count, 6, item_parsed_val_flag_call)
            parent.setChild(arg_count, 7, item_parsed_val_call)
            parent.setChild(arg_count, 8, item_parsed_val_flag_ret)
            parent.setChild(arg_count, 9, item_parsed_val_ret)

        # If current object contains reference values, add them to the module
        self._add_model_arg_ref(this_row_item, call_value, ret_value, nest_depth)

        # If current object is a container object, Add its members to the module
        self._add_model_container_members(this_row_item, call_value, ret_value, nest_depth)

    def _add_model_arg_ref(self, parent, call_value, ret_value, nest_depth=0):
        """
        Add a reference value to module
        @param parent:
        @param call_value:
        @param ret_value:
        @param nest_depth:
        @return:
        """
        # If call debug value is a reference
        if call_value is not None:
            if call_value.reference_flink is not None and not call_value.is_definitely_parsed:
                ref_val_call = self.die_db.get_dbg_value(call_value.reference_flink)
                ref_val_ret = None

                # Try to get the same reference from the return debug value.
                if ret_value is not None and ret_value.type == call_value.type:
                    if ret_value.reference_flink is not None and not ret_value.is_definitely_parsed:
                        ref_val_ret = self.die_db.get_dbg_value(ret_value.reference_flink)

                self._add_model_arg_value(parent, ref_val_call, ref_val_ret, ref_val_call.name, ref_val_call.type, nest_depth+1)

        # If return debug value is a reference (and call value is not)
        elif ret_value is not None:
            if ret_value.reference_flink is not None and not ret_value.is_definitely_parsed:
                ref_val = self.die_db.get_dbg_value(ret_value.reference_flink)
                self._add_model_arg_value(parent, None, ref_val, ref_val.name, ref_val.type, nest_depth+1)

    def _add_model_container_members(self, parent, call_value, ret_value, nest_depth=0):
        """
        Add container members to module
        @param parent:
        @param call_value:
        @param ret_value:
        @param nest_depth:
        @return:
        """
        # If call value is a container type (struct\union\etc)
        if call_value is not None and call_value.nested_values is not None:
            if call_value.nested_values:
                for index in xrange(0, len(call_value.nested_values)):
                    nested_val_call = self.die_db.get_dbg_value(call_value.nested_values[index])
                    nested_val_ret = None

                     # Try to get the same member from the return debug value.
                    if ret_value is not None and ret_value.type == call_value.type:
                        if ret_value.nested_values is not None:
                            if ret_value.nested_values:
                                nested_val_ret = self.die_db.get_dbg_value(ret_value.nested_values[index])

                    self._add_model_arg_value(parent, nested_val_call, nested_val_ret, nested_val_call.name, nested_val_call.type, nest_depth+1)

        # If return value is a container type (and call value is not)
        elif ret_value is not None:
            if ret_value.nested_values is not None:
                if ret_value.nested_values:
                    for nested_value in ret_value.nested_values:
                        nested_val_ret = self.die_db.get_dbg_value(nested_value)

                        self._add_model_arg_value(parent,
                                                  None,
                                                  nested_val_ret,
                                                  nested_val_ret.name,
                                                  nested_val_ret.type,
                                                  nest_depth+1)

    def reset_function_count(self, thread_id=None):
        """
        Reset the function count and set the count according to currently selected thread_id
        @param thread_id: currently selected thread_id
        """
        root_item = self.functionModel.item(0, 0)
        rows = root_item.rowCount()

        thread_id = self.thread_id_combo.currentText()

        for row in xrange(0, rows):
            cur_item = root_item.child(row, 0)
            function = cur_item.data(role=DIE.UI.Function_Role)

            if function is not None:
                count = 0
                if thread_id is None:
                     count = self.die_db.count_function_occurs(function)
                else:
                    count = self.die_db.count_function_occurs(function, int(thread_id))

                func_count_item = root_item.child(row, 1)
                func_count_item.setText(str(count))






###############################################################################################
#  Highlight Items.

    def highlight_item(self, item):
        """
        Highlight a single item
        @param item: module item
        """
        try:
            item.setBackground(QtGui.QColor('yellow'))
            cur_font = item.font()
            cur_font.setBold(True)
            item.setFont(cur_font)

        except Exception as ex:
            idaapi.msg("Error while highlighting item: %s\n" %ex)


    def highlight_item_row(self, item):
        """
        highlight the entire row containing a table item
        @param item: table item
        """
        try:
            if not item.index().isValid():
                return

            parent = item.parent()
            if parent is None:
                parent = item

            if not parent.hasChildren():
                self.highlight_item(parent)
                return

            row = item.row()
            column_num = parent.columnCount()

            for column in xrange(0, column_num):
                if self.functionModel.hasIndex(row, column, parent.index()):
                    cur_index = self.functionModel.index(row, column, parent.index())

                    self.highlight_item(self.functionModel.itemFromIndex(cur_index))
                    persistent_index = QtCore.QPersistentModelIndex(cur_index)
                    self.highligthed_items.append(persistent_index)

        except Exception as ex:
            idaapi.msg("Error while highlighting item row: %s\n" % ex)


    def clear_highlights(self):
        """
        Clear all highlighted items
        @return:
        """
        try:
            self.functionTreeView.collapseAll()

            for persistent_index in self.highligthed_items:
                if persistent_index.isValid():
                    item = self.functionModel.itemFromIndex(persistent_index)
                    item.setBackground(QtGui.QColor('white'))
                    cur_font = item.font()
                    cur_font.setBold(False)
                    item.setFont(cur_font)

            self.highligthed_items = []

        except Exception as ex:
            idaapi.msg("Error while clearing highlights: %s\n" % ex)


###############################################################################################
#  Find Items.

    def find_function(self, function_name):
        """
        Find and highlight a function in current module
        @param function_name: Function name
        """
        self.clear_highlights()

        matched_items = self.functionModel.findItems(function_name)

        for item in matched_items:
            self.functionTreeView.expand(item.index())
            self.functionTreeView.scrollTo(item.index(), _PositionAtTop)
            self.highlight_item_row(item)

    def find_context_list(self, context_list):
        """
        Find and highlight a list of function contexts
        @param context_list: list of function contexts (of type dbFunction_Context)
        """
        try:
            self.clear_highlights()
            root_index = self.functionModel.index(0, 0)
            if not root_index.isValid():
                return

            for func_context in context_list:
                context_id = id(func_context)
                matched_items = self.functionModel.match(root_index, DIE.UI.ContextId_Role, context_id, -1, _MatchRecursive | _MatchExactly)

                for index in matched_items:
                    if not index.isValid():
                        continue
                    # Do not highlight "ea root" items, only occurrences of it.
                    if not index.data().startswith("Occur"):
                        continue

                    item = self.functionModel.itemFromIndex(index)
                    self.functionTreeView.expand(index)
                    self.functionTreeView.scrollTo(index, _PositionAtTop)
                    self.highlight_item_row(item)

            return True

        except Exception as ex:
            idaapi.msg("Error while looking up function context in FunctionView: %s\n" % ex)
            return False

###############################################################################################
#  Slots.

    # @QtCore.Slot(QtCore.QModelIndex)
    def itemDoubleClickSlot(self, index):
        """
        TreeView DoubleClicked Slot.
        @param index: QModelIndex object of the clicked tree index item.
        @return:
        """
        function = index.data(role=DIE.UI.Function_Role)
        if function is not None:

            ea = function.function_start
            if function.is_lib_func:
                ea = function.proto_ea

            if ea is not None and ea is not idc.BADADDR:
                idc.Jump(ea)
                return True

        func_context = index.data(role=DIE.UI.FunctionContext_Role)
        if func_context is not None:
            ea = func_context.calling_ea
            if ea is not None and ea is not idc.BADADDR:
                idc.Jump(ea)
                return True

    # @QtCore.Slot(QtCore.QPoint)
    def onCustomContextMenu(self, point):
        index = self.functionTreeView.indexAt(point)
        is_function_item = index.data(role=DIE.UI.Function_Role)
        is_func_context_item = index.data(role=DIE.UI.FunctionContext_Role)
        is_value_item = index.data(role=DIE.UI.ParsedValueRole)

        if is_function_item is not None:
            self.context_menu_param = is_function_item
            self.function_context_menu.exec_(self.functionTreeView.mapToGlobal(point))

        if is_func_context_item is not None:
            self.context_menu_param = is_func_context_item
            self.ea_context_menu.exec_(self.functionTreeView.mapToGlobal(point))

        if is_value_item is not None:
            self.context_menu_param = is_value_item
            self.value_context_menu.exec_(self.functionTreeView.mapToGlobal(point))

    # @QtCore.Slot(str)
    def on_exclude_func(self, function):

        if not isinstance(function, DIE.Lib.DIEDb.dbFunction):
            if function is not None:
                raise ValueError("Wrong value sent to 'on_exclude_func_adrs': %s. excpected dbFunction_Context" % function.__class__)
            else:
                raise ValueError("Wrong value sent to 'on_exclude_func_adrs'")

        self.bp_handler.add_bp_funcname_exception(function.function_name)
        return

    # @QtCore.Slot(str)
    def on_exclude_func_adrs(self, function):

        if not isinstance(function, DIE.Lib.DIEDb.dbFunction):
            if function is not None:
                raise ValueError("Wrong value sent to 'on_exclude_func_adrs': %s. excpected dbFunction_Context" % function.__class__)
            else:
                raise ValueError("Wrong value sent to 'on_exclude_func_adrs'")

        func_context_list = self.die_db.get_function_context_list(function)
        for func_context in func_context_list:
            self.bp_handler.add_bp_ea_exception(func_context.calling_ea)

        return

    # @QtCore.Slot(str)
    def on_exclude_ea(self, function_context):

        if not isinstance(function_context, DIE.Lib.DIEDb.dbFunction_Context):
            if function_context is not None:
                raise ValueError("Wrong value sent to 'on_exclude_ea': %s. excpected dbFunction_Context" % function_context.__class__)
            else:
                raise ValueError("Wrong value sent to 'on_exclude_ea'")

        self.bp_handler.add_bp_ea_exception(function_context.calling_ea)
        return

    # @QtCore.Slot(str)
    def on_show_callgraph(self, function_context):

        if not isinstance(function_context, DIE.Lib.DIEDb.dbFunction_Context):
            if function_context is not None:
                raise ValueError("Wrong value sent to 'on_show_callgraph': %s. excpected dbFunction_Context" % function_context.__class__)
            else:
                raise ValueError("Wrong value sent to 'on_show_callgraph'")

        graph = nx.DiGraph()

        call_graph = self.die_db.get_call_graph_to(function_context)
        if not call_graph:
            idaapi.msg("No Execution Graph")
            return

        for ctxt_node in call_graph:
            (from_address, to_address) = ctxt_node
            graph.add_edge(from_address, to_address)

        function_name = self.die_db.get_function_name(function_context.function)
        viewer = sark.ui.NXGraph(graph, "Callgraph for {}".format(function_name), handler=sark.ui.AddressNodeHandler())
        viewer.Show()

        return

    # @QtCore.Slot(str)
    def on_exclude_library(self, function):

        if not isinstance(function, DIE.Lib.DIEDb.dbFunction):
            if function is not None:
                raise ValueError("Wrong value sent to 'on_exclude_func_adrs': %s. excpected dbFunction_Context" % function.__class__)
            else:
                raise ValueError("Wrong value sent to 'on_exclude_func_adrs'")

        if function.is_lib_func and function.lib_name is not None:
            self.bp_handler.add_module_exception(function.lib_name)

        return

    # @QtCore.Slot(str)
    def on_value_detail(self, value):
        if not self.value_view.isVisible():
            self.value_view.Show()

        self.value_view.find_value(value)
        return

    def on_thread_combobox_change(self, thread_id):

        self.reset_function_count(thread_id)  # reset function count according to currently selected thread
        if thread_id == "All Threads":
            if not self.functionTreeView.model() is self.functionModel:
                self.functionTreeView.setModel(self.functionModel)
            return

        hidden_threads = ".*" + self._make_thread_id_data(thread_id) + ".*"

        threadProxyModel = _QSortFilterProxyModel()
        threadProxyModel.setFilterRole(DIE.UI.ThreadId_Role)
        threadProxyModel.setFilterRegExp(hidden_threads)

        threadProxyModel.setSourceModel(self.functionModel)
        self.functionTreeView.setModel(threadProxyModel)

    def on_valueview_button(self):

        value_view = DIE.UI.ValueViewEx.get_view()
        value_view.Show()

    def on_pluginsview_button(self):

        plugins_view = DIE.UI.ParserView.get_view()
        plugins_view.Show()

    def on_bpview_button(self):

        bp_view = DIE.UI.BPView.get_view()
        bp_view.Show()

###############################################################################################
#  View Delegates.

class TreeViewDelegate(QtWidgets.QStyledItemDelegate):
    """
    Delegate for parsed value viewing in the tree view
    """

    def __init__(self, parent):
        QtWidgets.QStyledItemDelegate.__init__(self, parent)
        self.parent = parent

    def createEditor(self, parent, option, index):

        parsed_val_list = index.data(role=DIE.UI.ParsedValuesRole)

        # Show combobox only if parsed_value as two or more items.
        if parsed_val_list is not None and len(parsed_val_list) > 1:
            lines = []
            for parsed_val in parsed_val_list:
                line_txt = "%d, %s, %s" % (parsed_val.score, parsed_val.data, parsed_val.description)
                lines.append(line_txt)

            combo_box = QtWidgets.QComboBox(parent)
            combo_box.addItems(lines)

            return combo_box

    def setEditorData(self, editor, index):

            editor.blockSignals(True)
            editor.setCurrentIndex(int(index.model().data(index)))
            editor.blockSignals(False)

# Singelton
function_view = None
def initialize():
    global function_view
    function_view = FunctionView()


def get_view():
    return function_view



[(51, 'idaapi.PluginForm.Show', 'PluginForm.Show', False, 'from idaapi import PluginForm\n'), (64, 'sark.qt.form_to_widget', 'form_to_widget', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (66, 'sark.qt.QtGui.QStandardItemModel', 'QtGui.QStandardItemModel', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (67, 'sark.qt.QtWidgets.QTreeView', 'QtWidgets.QTreeView', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (106, 'sark.qt.QtWidgets.QMenu', 'QtWidgets.QMenu', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (112, 'sark.qt.QtWidgets.QMenu', 'QtWidgets.QMenu', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (117, 'sark.qt.QtWidgets.QMenu', 'QtWidgets.QMenu', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (130, 'sark.qt.QtWidgets.QComboBox', 'QtWidgets.QComboBox', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (134, 'sark.qt.QtWidgets.QLabel', 'QtWidgets.QLabel', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (137, 'sark.qt.QtWidgets.QToolBar', 'QtWidgets.QToolBar', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (142, 'sark.qt.QtWidgets.QGridLayout', 'QtWidgets.QGridLayout', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (149, 'idaapi.msg', 'idaapi.msg', False, 'import idaapi\n'), (258, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (263, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (268, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (273, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (278, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (283, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (288, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (292, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (297, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (301, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (349, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (379, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (380, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (405, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (411, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (416, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (442, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (473, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (480, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (481, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (482, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (483, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (877, 'networkx.DiGraph', 'nx.DiGraph', True, 'import networkx as nx\n'), (957, 'sark.qt.QtWidgets.QStyledItemDelegate.__init__', 'QtWidgets.QStyledItemDelegate.__init__', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (360, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (361, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (362, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (363, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (364, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (365, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (366, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (367, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (396, 'awesome.context.ignored', 'ignored', False, 'from awesome.context import ignored\n'), (422, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (425, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (426, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (427, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (428, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (429, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (430, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (450, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (451, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (452, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (453, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (454, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (455, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (456, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (457, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (458, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (881, 'idaapi.msg', 'idaapi.msg', False, 'import idaapi\n'), (971, 'sark.qt.QtWidgets.QComboBox', 'QtWidgets.QComboBox', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (338, 'idaapi.msg', 'idaapi.msg', False, 'import idaapi\n'), (397, 'sark.Function', 'sark.Function', False, 'import sark\n'), (492, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (514, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (524, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (545, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (548, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (549, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (550, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (551, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (552, 'sark.qt.QtGui.QStandardItem', 'QtGui.QStandardItem', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (669, 'sark.qt.QtGui.QColor', 'QtGui.QColor', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (675, 'idaapi.msg', 'idaapi.msg', False, 'import idaapi\n'), (707, 'idaapi.msg', 'idaapi.msg', False, 'import idaapi\n'), (729, 'idaapi.msg', 'idaapi.msg', False, 'import idaapi\n'), (779, 'idaapi.msg', 'idaapi.msg', False, 'import idaapi\n'), (800, 'idc.Jump', 'idc.Jump', False, 'import idc\n'), (807, 'idc.Jump', 'idc.Jump', False, 'import idc\n'), (889, 'sark.ui.AddressNodeHandler', 'sark.ui.AddressNodeHandler', False, 'import sark\n'), (703, 'sark.qt.QtCore.QPersistentModelIndex', 'QtCore.QPersistentModelIndex', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (180, 'sark.qt.QtGui.QColor', 'QtGui.QColor', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n'), (721, 'sark.qt.QtGui.QColor', 'QtGui.QColor', False, 'from sark.qt import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5\n')]