from __future__ import absolute_import, print_function

# Standard modules
import logging
from enum import Enum

from qtpy import QtGui
from qtpy.QtWidgets import QMenu, QAction, QDockWidget, QTreeWidget, QTreeWidgetItem
from qtpy.QtCore import Slot, Signal, QPointF, Qt, QVariant

# Custom modules
from .. import base
from ...core.base import Element
from .canvas.flowgraph import FlowgraphScene
from .canvas import colors

# Logging
log = logging.getLogger(f"grc.application.{__name__}")


class VariableEditorAction(Enum):
    # Actions that are handled by the editor
    ADD_IMPORT = 0
    ADD_VARIABLE = 1
    OPEN_PROPERTIES = 2
    DELETE_BLOCK = 3
    DELETE_CONFIRM = 4
    ENABLE_BLOCK = 5
    DISABLE_BLOCK = 6


class VariableEditor(QDockWidget, base.Component):
    all_editor_actions = Signal([VariableEditorAction])

    def __init__(self):
        super(VariableEditor, self).__init__()

        self.qsettings = self.app.qsettings

        self.setObjectName('variable_editor')
        self.setWindowTitle('Variable Editor')

        self.right_click_menu = VariableEditorContextMenu(self)
        self.scene = None

        # GUI Widgets
        self._tree = QTreeWidget()
        self._model = self._tree.model()
        self._tree.setObjectName('variable_editor::tree_widget')
        self._tree.setHeaderLabels(["ID", "Value", ""])
        self.setWidget(self._tree)
        self.currently_rebuilding = True
        self._model.dataChanged.connect(self.handle_change)

        self.var_add = VariableEditorAction.ADD_VARIABLE
        self.import_add = VariableEditorAction.ADD_IMPORT
        self.del_block = VariableEditorAction.DELETE_BLOCK
        self._tree.itemClicked.connect(self.handle_click)

        imports = QTreeWidgetItem(self._tree)
        imports.setText(0, "Imports")
        imports.setData(2, Qt.UserRole, self.import_add)
        imports.setIcon(2, QtGui.QIcon.fromTheme("list-add"))

        variables = QTreeWidgetItem(self._tree)
        variables.setText(0, "Variables")
        variables.setData(2, Qt.UserRole, self.var_add)
        variables.setIcon(2, QtGui.QIcon.fromTheme("list-add"))
        self._tree.expandAll()

        # TODO: Move to the base controller and set actions as class attributes
        # Automatically create the actions, menus and toolbars.
        # Child controllers need to call the register functions to integrate into the mainwindow
        self.actions = {}
        self.menus = {}
        self.toolbars = {}
        self.createActions(self.actions)
        self.createMenus(self.actions, self.menus)
        self.createToolbars(self.actions, self.toolbars)
        self.connectSlots()

        # Register the dock widget through the AppController.
        # The AppController then tries to find a saved dock location from the preferences
        # before calling the MainWindow Controller to add the widget.
        self.app.registerDockWidget(self, location=self.settings.window.VARIABLE_EDITOR_DOCK_LOCATION)
        self.currently_rebuilding = False
        if not self.qsettings.value("appearance/display_variable_editor", True, type=bool):
            self.hide()

    # Actions

    def createActions(self, actions):
        log.debug("Creating actions")

    def createMenus(self, actions, menus):
        log.debug("Creating menus")

    def createToolbars(self, actions, toolbars):
        log.debug("Creating toolbars")

    def contextMenuEvent(self, event):
        self.right_click_menu.exec_(self.mapToGlobal(event.pos()))

    def keyPressEvent(self, event):
        super(VariableEditor, self).keyPressEvent(event)

    def handle_click(self, item, col):
        # we only care about the add/remove icons being clicked
        if (col != 2):
            return

        action = item.data(2, Qt.UserRole)

        if action is None:
            log.warn("Item %s does not contain any actions!!", item)

        self.handle_action(action)

    def set_scene(self, scene: FlowgraphScene):
        self.scene = scene
        self.update_gui(self.scene.core.blocks)
        self._tree.resizeColumnToContents(0)
        self._tree.resizeColumnToContents(1)

    # the handler is required by the signal to take two arguments even
    # if we only use one
    def handle_change(self, tl, br):
        if self.currently_rebuilding:
            return

        c_block = self._tree.model().data(tl, role=Qt.UserRole)
        if not c_block:
            return

        new_text = self._tree.model().data(tl)
        c_block.old_data = c_block.export_data()
        if tl.column() == 0:  # The name (id) changed
            c_block.params['id'].set_value(new_text)
        else:  # column == 1, i.e. the value changed
            if c_block.is_import:
                c_block.params['imports'].set_value(new_text)
            else:
                c_block.params['value'].set_value(new_text)
        self.scene.blockPropsChange.emit(c_block)
        self._tree.resizeColumnToContents(0)
        self._tree.resizeColumnToContents(1)

    def _rebuild(self):
        # TODO: The way we update block params here seems suboptimal
        self.currently_rebuilding = True
        self._tree.clear()
        imports = QTreeWidgetItem(self._tree, 2)
        imports.setText(0, "Imports")
        imports.setForeground(0, self._tree.palette().color(self.palette().WindowText))
        imports.setIcon(2, QtGui.QIcon.fromTheme("list-add"))
        imports.setData(2, Qt.UserRole, self.import_add)
        for block in self._imports:
            import_ = QTreeWidgetItem(imports, 0)
            import_.setText(0, block.name)
            import_.setData(0, Qt.UserRole, block)
            import_.setText(1, block.params['imports'].get_value())
            import_.setData(1, Qt.UserRole, block)
            import_.setIcon(2, QtGui.QIcon.fromTheme("list-remove"))
            import_.setData(2, Qt.UserRole, self.del_block)
            if block.enabled:
                import_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled)
                import_.setForeground(0, self._tree.palette().color(self.palette().WindowText))
                import_.setForeground(1, self._tree.palette().color(self.palette().WindowText))
            else:
                import_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                import_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
                import_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))

        variables = QTreeWidgetItem(self._tree, 2)
        variables.setText(0, "Variables")
        variables.setForeground(0, self._tree.palette().color(self.palette().WindowText))
        variables.setIcon(2, QtGui.QIcon.fromTheme("list-add"))
        variables.setData(2, Qt.UserRole, self.var_add)
        for block in sorted(self._variables, key=lambda v: v.name):
            variable_ = QTreeWidgetItem(variables, 1)
            variable_.setText(0, block.name)
            variable_.setData(0, Qt.UserRole, block)
            variable_.setData(2, Qt.UserRole, self.del_block)
            if block.key == 'variable':
                variable_.setText(1, block.params['value'].get_value())
                variable_.setData(1, Qt.UserRole, block)
                variable_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled)
                if block.enabled:
                    variable_.setForeground(0, self._tree.palette().color(self.palette().WindowText))
                    variable_.setForeground(1, self._tree.palette().color(self.palette().WindowText))
                else:
                    variable_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
                    variable_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
            else:
                variable_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                if block.enabled:
                    try:
                        variable_.setText(1, str(block.evaluate(block.value)))
                        variable_.setForeground(0, self._tree.palette().color(self.palette().WindowText))
                        variable_.setForeground(1, self._tree.palette().color(self.palette().WindowText))
                    except Exception:
                        log.exception(f'Failed to evaluate variable block {block.name}', exc_info=True)
                        variable_.setText(1, '<Open Properties>')
                        variable_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
                        variable_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
                else:
                    variable_.setText(1, '<Open Properties>')
                    variable_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
                    variable_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
            variable_.setIcon(2, QtGui.QIcon.fromTheme("list-remove"))

        self.currently_rebuilding = False

    def update_gui(self, blocks):
        self._imports = [block for block in blocks if block.is_import]
        self._variables = [block for block in blocks if block.is_variable]
        self._rebuild()
        self._tree.expandAll()

    Slot(VariableEditorAction)

    def handle_action(self, action):
        log.debug(f"{action} triggered!")
        """
        Single handler for the different actions that can be triggered by the context menu,
        key presses or mouse clicks. Also triggers an update of the flow graph and editor.
        """
        if action == VariableEditorAction.ADD_IMPORT:
            self.all_editor_actions.emit(action)
            return
        elif action == VariableEditorAction.ADD_VARIABLE:
            self.all_editor_actions.emit(action)
            return
        elif action == VariableEditorAction.OPEN_PROPERTIES:
            if self._tree.currentItem().type() == 2:  # Import or Variable header line was selected
                return
            self.scene.clearSelection()
            if self._tree.currentItem().type() == 0:
                to_handle = self.scene.core.blocks.index(self._imports[self._tree.currentIndex().row()])
            else:
                to_handle = self.scene.core.blocks.index(self._variables[self._tree.currentIndex().row()])
            self.scene.core.blocks[to_handle].gui.setSelected(True)
            self.all_editor_actions.emit(action)
            return

        if self._tree.currentItem().type() == 2:  # Import or Variable header line was selected
            return

        self.scene.clearSelection()
        if self._tree.currentItem().type() == 0:
            to_handle = self.scene.core.blocks.index(self._imports[self._tree.currentIndex().row()])
        else:
            to_handle = self.scene.core.blocks.index(self._variables[self._tree.currentIndex().row()])
        self.scene.core.blocks[to_handle].gui.setSelected(True)
        self.all_editor_actions.emit(action)


class VariableEditorContextMenu(QMenu):
    def __init__(self, var_edit: VariableEditor):
        super(QMenu, self).__init__()

        self.imports = QAction(_("variable_editor_add_import"), self)
        self.imports.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.ADD_IMPORT))
        self.addAction(self.imports)

        self.variables = QAction(_("variable_editor_add_variable"), self)
        self.variables.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.ADD_VARIABLE))
        self.addAction(self.variables)

        self.addSeparator()

        self.enable = QAction(_("variable_editor_enable"), self)
        self.enable.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.ENABLE_BLOCK))
        self.addAction(self.enable)

        self.disable = QAction(_("variable_editor_disable"), self)
        self.disable.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.DISABLE_BLOCK))
        self.addAction(self.disable)

        self.addSeparator()

        self.delete = QAction(_("variable_editor_delete"), self)
        self.delete.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.DELETE_BLOCK))
        self.addAction(self.delete)

        self.addSeparator()

        self.properties = QAction(_("variable_editor_properties"), self)
        self.properties.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.OPEN_PROPERTIES))
        self.addAction(self.properties)

    def update_enabled(self, selected, enabled=False):
        self.delete.setEnabled(selected)
        self.properties.setEnabled(selected)
        self.enable.setEnabled(selected and not enabled)
        self.disable.setEnabled(selected and enabled)
