"""
    Dwarf - Copyright (C) 2018-2020 Giovanni Rocca (iGio90)

    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 3 of the License, 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 <https://www.gnu.org/licenses/>
"""

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QWidget, QHeaderView, QHBoxLayout, QMenu

from dwarf_debugger.ui.widgets.list_view import DwarfListView


class ObjCInspector(QWidget):
    """ ObjC Class/Methods Lists
    """

    def __init__(self, parent=None):
        super(ObjCInspector, self).__init__(parent)

        self._app_window = parent

        self._app_window.dwarf.onEnumerateObjCModules.connect(self._on_enumerate_objc_modules)
        self._app_window.dwarf.onEnumerateObjCMethodsStart.connect(
            self._on_method_enumeration_start)
        self._app_window.dwarf.onEnumerateObjCMethodsMatch.connect(
            self._on_method_enumeration_match)
        self._app_window.dwarf.onEnumerateObjCMethodsComplete.connect(
            self._on_method_enumeration_complete)

        self._app_window.dwarf.onEnumerateObjCClassesStart.connect(
            self._on_class_enumeration_start)
        self._app_window.dwarf.onEnumerateObjCClassesMatch.connect(
            self._on_class_enumeration_match)
        self._app_window.dwarf.onEnumerateObjCClassesComplete.connect(
            self._on_class_enumeration_complete)

        self._ObjC_modules = DwarfListView(self)
        self._ObjCmodule_model = QStandardItemModel(0, 1)
        self._ObjCmodule_model.setHeaderData(0, Qt.Horizontal, 'Modules')
        self._ObjC_modules.setModel(self._ObjCmodule_model)
        self._ObjC_modules.selectionModel().selectionChanged.connect(
            self._module_clicked)
        self._ObjC_modules.header().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self._ObjC_modules.setContextMenuPolicy(Qt.CustomContextMenu)
        self._ObjC_modules.customContextMenuRequested.connect(
            self._on_module_contextmenu)
        self._ObjC_modules.doubleClicked.connect(self._class_dblclicked)

        self._ObjC_classes = DwarfListView(self)
        self._ObjCclass_model = QStandardItemModel(0, 1)
        self._ObjCclass_model.setHeaderData(0, Qt.Horizontal, 'Class')
        self._ObjC_classes.setModel(self._ObjCclass_model)
        self._ObjC_classes.selectionModel().selectionChanged.connect(
            self._class_clicked)
        self._ObjC_classes.header().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self._ObjC_classes.setContextMenuPolicy(Qt.CustomContextMenu)
        self._ObjC_classes.customContextMenuRequested.connect(
            self._on_class_contextmenu)
        self._ObjC_classes.doubleClicked.connect(self._class_dblclicked)

        self._ObjC_methods = DwarfListView(self)
        self._ObjCmethod_model = QStandardItemModel(0, 1)
        self._ObjCmethod_model.setHeaderData(0, Qt.Horizontal, 'Method')
        self._ObjC_methods.setModel(self._ObjCmethod_model)
        self._ObjC_methods.header().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self._ObjC_methods.setContextMenuPolicy(Qt.CustomContextMenu)
        self._ObjC_methods.customContextMenuRequested.connect(
            self._on_method_contextmenu)
        self._ObjC_methods.doubleClicked.connect(self._method_dblclicked)

        h_box = QHBoxLayout()
        h_box.setContentsMargins(0, 0, 0, 0)
        h_box.addWidget(self._ObjC_modules)
        h_box.addWidget(self._ObjC_classes)
        h_box.addWidget(self._ObjC_methods)
        self.setLayout(h_box)

    # ************************************************************************
    # **************************** Functions *********************************
    # ************************************************************************
    def update_classes(self, module_name):
        """ Refresh Classeslist
        """
        self._app_window.dwarf.dwarf_api('enumerateObjCClasses', module_name)

    def update_methods(self, class_name):
        """ Refresh Methodslist
        """
        if class_name:
            self._app_window.dwarf.dwarf_api('enumerateObjCMethods',
                                             class_name)

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    def _module_clicked(self):
        index = self._ObjC_modules.selectionModel().currentIndex().row()
        _module = self._ObjCmodule_model.item(index, 0)
        if _module is None:
            return

        self._app_window.dwarf.dwarf_api('enumerateObjCClasses', _module.text())

    def _class_clicked(self):
        index = self._ObjC_classes.selectionModel().currentIndex().row()
        _class = self._ObjCclass_model.item(index, 0)
        if _class is None:
            return

        self._app_window.dwarf.dwarf_api('enumerateObjCMethods', _class.text())

    def _on_class_enumeration_start(self):
        self._ObjC_classes.clear()
        self._ObjC_methods.clear()

    def _on_method_enumeration_start(self):
        self._ObjC_methods.clear()

    def _on_class_enumeration_match(self, ObjC_class):
        _class_name = QStandardItem()
        _class_name.setText(ObjC_class)
        self._ObjCclass_model.appendRow(_class_name)

    def _on_method_enumeration_match(self, ObjC_method):
        _method_name = QStandardItem()
        _method_name.setText(ObjC_method)
        self._ObjCmethod_model.appendRow(_method_name)

    def _on_class_enumeration_complete(self):
        self._ObjC_classes.sortByColumn(0, 0)

    def _on_method_enumeration_complete(self):
        self._ObjC_methods.sortByColumn(0, 0)

    def _class_dblclicked(self):
        """ Class DoubleClicked
        """
        index = self._ObjC_classes.selectionModel().currentIndex().row()
        if index:
            class_item = self._ObjCclass_model.item(index, 0)
            if class_item:
                class_name = class_item.text()
                if class_name:
                    self._breakpoint_class(class_name)

    def _method_dblclicked(self):
        """ Function DoubleClicked
        """
        class_index = self._ObjC_classes.selectionModel().currentIndex().row()
        method_index = self._ObjC_methods.selectionModel().currentIndex().row()
        if class_index and method_index:
            class_item = self._ObjCclass_model.item(class_index, 0)
            method_item = self._ObjCmethod_model.item(method_index, 0)
            if class_item and method_item:
                class_name = class_item.text()
                method_name = method_item.text()
                if class_name and method_name:
                    self._app_window.dwarf.breakpoint_objc(class_name + '.'
                                                           + method_name)

    def _breakpoint_class(self, class_name):
        if class_name:
            self._app_window.dwarf.breakpoint_objc(class_name)

    def _breakpoint_class_functions(self, class_name):
        if class_name:
            self._app_window.dwarf.dwarf_api('breakpointAllObjCMethods', class_name)

    def _on_class_contextmenu(self, pos):
        """ Class ContextMenu
        """
        index = self._ObjC_classes.indexAt(pos).row()
        glbl_pt = self._ObjC_classes.mapToGlobal(pos)
        context_menu = QMenu(self)
        if index != -1:
            context_menu.addAction(
                'Breakpoint constructor', lambda: self._breakpoint_class(
                    self._ObjCclass_model.item(index, 0).text()))
            context_menu.addAction(
                'Breakpoint all methods', lambda: self._breakpoint_class_functions(
                    self._ObjCclass_model.item(index, 0).text()))
            context_menu.addSeparator()

            if self._ObjC_classes.search_enabled:
                context_menu.addSeparator()
                context_menu.addAction(
                    'Search', self._ObjC_classes._on_cm_search)

        context_menu.addAction('Refresh', self._cm_refresh_classes)
        context_menu.exec_(glbl_pt)

    def _breakpoint_method(self, method_name):
        class_index = self._ObjC_classes.selectionModel().currentIndex().row()
        if class_index:
            class_item = self._ObjCclass_model.item(class_index, 0)
            if class_item:
                class_name = class_item.text()
                if class_name and method_name:
                    self._app_window.dwarf.breakpoint_objc(class_name + '.' + method_name)

    def _cm_refresh_methods(self):
        index = self._ObjC_classes.selectionModel().currentIndex().row()
        _class = self._ObjCclass_model.item(index, 0)
        if _class is None:
            return

        self.update_methods(_class.text())

    def _on_method_contextmenu(self, pos):
        """ Method ContextMenu
        """
        index = self._ObjC_methods.indexAt(pos).row()
        glbl_pt = self._ObjC_methods.mapToGlobal(pos)
        context_menu = QMenu(self)
        if index != -1:
            context_menu.addAction(
                'Breakpoint method', lambda: self._breakpoint_method(
                    self._ObjCmethod_model.item(index, 0).text()))
            context_menu.addSeparator()

            if self._ObjC_methods.search_enabled:
                context_menu.addSeparator()
                context_menu.addAction(
                    'Search', self._ObjC_methods._on_cm_search)

        context_menu.addAction('Refresh', self._cm_refresh_methods)
        context_menu.exec_(glbl_pt)

    def _cm_refresh_classes(self):
        index = self._ObjC_modules.selectionModel().currentIndex().row()
        _module = self._ObjCmodule_model.item(index, 0)
        if _module is None:
            return

        self.update_classes(_module.text())

    def _enumerate_objc_modules(self):
        """ DwarfApiCall enumerateObjCModules
        """
        return self._app_window.dwarf.dwarf_api('enumerateObjCModules')

    def _on_module_contextmenu(self, pos):
        """ Module ContextMenu
        """
        index = self._ObjC_modules.indexAt(pos).row()
        glbl_pt = self._ObjC_modules.mapToGlobal(pos)
        context_menu = QMenu(self)

        context_menu.addAction('Refresh', self._enumerate_objc_modules)
        context_menu.exec_(glbl_pt)

    def _on_enumerate_objc_modules(self, modules):
        """ Fills the ModulesList with data
        """
        if self._ObjC_modules is None:
            return

        self._ObjC_modules.clear()
        for module in modules:
            self.add_module(module)

    def add_module(self, module):
        _module_name = QStandardItem()
        _module_name.setText(module)
        self._ObjCmodule_model.appendRow(_module_name)