#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )

This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),

This application 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 <http://www.gnu.org/licenses/>.
"""

import logging; logger = logging.getLogger(__name__); logger.info("import")


#Standard Library

#Third Party
from PyQt5 import QtCore, QtGui, QtWidgets


#Template Modules
from .about import About
from .resources import *

#User Modules
import engine.api as api
from engine.config import * #imports METADATA

class Menu(object):

    def __init__(self, mainWindow):
        """We can receive a dict of extra menu actions, which also override our hardcoded ones"""

        self.mainWindow = mainWindow
        self.ui = mainWindow.ui #just shorter to write.

        #Hover Shortcuts
        self.hoverShortcutDict = HoverActionDictionary(self.mainWindow) #for temporary hover shortcuts. key=actual key, value=QAction
        self.lastHoverAction = None #the last time the mouse hovered over a menu it was this QAction. This gets never empty again, but works because we only check it during menu KeyPressEvent
        def _rememberHover(action): self.lastHoverAction = action
        self.ui.menubar.hovered.connect(_rememberHover)
        self._originalMenuKeyPressEvent = self.ui.menubar.keyPressEvent
        self.ui.menubar.keyPressEvent = self.menuKeyPressInterceptor

        #Order Matters. First is inserted first
        self._setupFileMenu()
        self._setupEditMenu()
        self._setupHelpMenu()
        self._setupDebugMenu() #Setup the provided debug menu, keep it separate from actual code

        #Set the label of undo and redo to show the info the api provides
        api.callbacks.historyChanged.append(self.renameUndoRedoByHistory)

    def renameUndoRedoByHistory(self, undoList, redoList):
        undoString = QtCore.QCoreApplication.translate("TemplateMainWindow", "Undo")
        redoString = QtCore.QCoreApplication.translate("TemplateMainWindow", "Redo")

        if undoList:
            undoInfo = QtCore.QCoreApplication.translate("NOOPengineHistory", undoList[-1])
            self.ui.actionUndo.setText(undoString +": {}".format(undoInfo))
            self.ui.actionUndo.setEnabled(True)
        else:
            self.ui.actionUndo.setText(undoString)
            self.ui.actionUndo.setEnabled(False)

        if redoList:
            redoInfo = QtCore.QCoreApplication.translate("NOOPengineHistory", redoList[-1])
            self.ui.actionRedo.setText(redoString + ": {}".format(redoInfo))
            self.ui.actionRedo.setEnabled(True)
        else:
            self.ui.actionRedo.setText(redoString)
            self.ui.actionRedo.setEnabled(False)

    def _setupHelpMenu(self):
        self.addSubmenu("menuHelp", QtCore.QCoreApplication.translate("TemplateMainWindow", "Help"))
        self.addMenuEntry("menuHelp", "actionUser_Manual", QtCore.QCoreApplication.translate("TemplateMainWindow", "User Manual"), connectedFunction=self.mainWindow.userManual.show)
        self.addSeparator("menuHelp")
        self.addMenuEntry("menuHelp", "actionAbout", QtCore.QCoreApplication.translate("TemplateMainWindow", "About and Tips"), connectedFunction=self.mainWindow.about.show)

    def _setupEditMenu(self):
        self.addSubmenu("menuEdit", QtCore.QCoreApplication.translate("TemplateMainWindow", "Edit"))
        self.addMenuEntry("menuEdit", "actionUndo", QtCore.QCoreApplication.translate("TemplateMainWindow", "Undo"), connectedFunction=api.undo, shortcut="Ctrl+Z")
        self.addMenuEntry("menuEdit", "actionRedo", QtCore.QCoreApplication.translate("TemplateMainWindow", "Redo"), connectedFunction=api.redo, shortcut="Ctrl+Shift+Z")

    def _setupFileMenu(self):
        self.addSubmenu("menuFile", QtCore.QCoreApplication.translate("TemplateMainWindow", "File"))
        self.addMenuEntry("menuFile", "actionSave", QtCore.QCoreApplication.translate("TemplateMainWindow", "Save"), connectedFunction=api.save, shortcut="Ctrl+S")
        self.addMenuEntry("menuFile", "actionQuit", QtCore.QCoreApplication.translate("TemplateMainWindow", "Quit (without saving)"), connectedFunction=self.mainWindow.nsmClient.serverSendExitToSelf, shortcut="Ctrl+Q")

    def _setupDebugMenu(self):
        """Debug entries are not translated"""
        def guiCallback_menuDebugVisibility(state):
            self.ui.menuDebug.menuAction().setVisible(state)
            api.session.guiSharedDataToSave["actionToggle_Debug_Menu_Visibility"] = state

        self.addSubmenu("menuDebug", "Debug")
        self.addMenuEntry("menuDebug", "actionToggle_Debug_Menu_Visibility", "Toggle Debug Menu Visibility", connectedFunction=guiCallback_menuDebugVisibility, shortcut="Ctrl+Alt+Shift+D")
        self.ui.actionToggle_Debug_Menu_Visibility.setCheckable(True)
        self.ui.actionToggle_Debug_Menu_Visibility.setEnabled(True)

        self.addMenuEntry("menuDebug", "actionRun_Script_File", "Run Script File", connectedFunction=self.mainWindow.debugScriptRunner.run) #runs the file debugscript.py in our session dir.
        self.addMenuEntry("menuDebug", "actionEmpty_Menu_Entry", "Empty Menu Entry")
        deleteSessionText = "Delete our session data and exit: {}".format(self.mainWindow.nsmClient.ourPath) #Include the to-be-deleted path into the menu text. Better verbose than sorry.
        self.addMenuEntry("menuDebug", "actionReset_Own_Session_Data", deleteSessionText, connectedFunction=self.mainWindow.nsmClient.debugResetDataAndExit)

        #Now that the actions are connected we can create the initial setup
        if not "actionToggle_Debug_Menu_Visibility" in api.session.guiSharedDataToSave:
            api.session.guiSharedDataToSave["actionToggle_Debug_Menu_Visibility"] = False
        state = api.session.guiSharedDataToSave["actionToggle_Debug_Menu_Visibility"]
        self.ui.actionToggle_Debug_Menu_Visibility.setChecked(state) #does not trigger the connected action...
        self.ui.menuDebug.menuAction().setVisible(state) #...therefore we hide manually.

    def menuKeyPressInterceptor(self, event):
        """
        Triggered when a menu is open and mouse hovers over a menu.

        Menu Entries without their own shortcut can be temporarily assigned one of the ten
        number keypad keys as shortcuts.

        If the numpad key is already in use it will be rerouted (freed first).
        """
        strings = set(("Num+1", "Num+2", "Num+3", "Num+4", "Num+5", "Num+6", "Num+7", "Num+8", "Num+9", "Num+0"))
        if self.lastHoverAction and (not self.lastHoverAction.menu()) and event.modifiers() == QtCore.Qt.KeypadModifier and ((not self.lastHoverAction.shortcut().toString()) or self.lastHoverAction.shortcut().toString() in strings):
            key = event.text() #just number 0-9  #event.key() is 49, 50...
            self.hoverShortcutDict[key] = self.lastHoverAction
            event.accept()
        else:
            self._originalMenuKeyPressEvent(event)


    def addSubmenu(self, submenuActionAsString, text):
        """
        Returns the submenu.


        submenuActionAsString is something like menuHelp

        It is not possible to create nested submenus yet.
        text must already be translated.

        If the menu already exists (decided by submenuActionAsString, not by text) it will be
        returned instead."""

        try:
            return getattr(self.ui, submenuActionAsString)
        except AttributeError:
            pass

        setattr(self.ui, submenuActionAsString, QtWidgets.QMenu(self.ui.menubar))
        menu = getattr(self.ui, submenuActionAsString)
        menu.setObjectName(submenuActionAsString)
        menu.menuAction().setObjectName(submenuActionAsString)
        menu.setTitle(text)
        self.ui.menubar.addAction(menu.menuAction())
        return menu

    def getSubmenuOrder(self):
        """Returns a list of strings with submenu names, compatible with orderSubmenus"""
        #text() shows the translated string. We wan't the attribute name like actionEdit
        #action.objectName() gives us the name, but only if set. QtDesigner does not set.
        #action.menu().objectName() works.
        listOfStrings = []
        for a in self.ui.menubar.actions():
            if a.menu():
                listOfStrings.append(a.menu().objectName())
            else: #separator
                listOfStrings.append(None)
        return listOfStrings

    def getTemplateSubmenus(self):
        """Returns a list of strings with submenu names, compatible with orderSubmenus.
        Only contains the ones set in the template.
        Not in order."""
        #Hardcoded.
        return ["menuFile", "menuEdit", "menuHelp", "menuDebug"]

    def getClientSubmenus(self):
        """opposite of getTemplateSubmenus.
        In order"""
        allMenus = self.getSubmenuOrder()
        templateMenus = self.getTemplateSubmenus()
        return [st for st in allMenus if st not in templateMenus]

    def orderSubmenus(self, listOfStrings):
        """sort after listOfStrings.

        List of strings can contain None to insert a separator at this position.

        It is expected that you give the complete list of menus."""
        self.ui.menubar.clear()
        for submenuActionAsString in listOfStrings:
            if submenuActionAsString:
                menu = getattr(self.ui, submenuActionAsString)
                self.ui.menubar.addAction(menu.menuAction())
            else:
                self.ui.menubar.addSeparator()

    def hideSubmenu(self, submenu:str):
        menuAction = getattr(self.ui, submenu).menuAction()
        menuAction.setVisible(False)

    def removeSubmenu(self, submenuAction:str):
        menuAction = getattr(self.ui, submenu).menuAction()
        raise NotImplementedError #TODO

    def addMenuEntry(self, submenu, actionAsString:str, text:str, connectedFunction=None, shortcut:str="", tooltip:str="", iconResource:str="", checkable=False):
        """
        parameterstrings must already be translated.

        Returns the QAction

        Add a new menu entry to an existing submenu.
        If you want a new submenu use the create submenu function first.

        submenu is the actual menu action name, like "menuHelp". You need to know the variable names.

        actionAsString is the unique action name including the word "action", text is the display name. Text must be already translated,
        actionAsString must never be translated.

        shortcut is an optional string like "Ctrl+Shift+Z"

        If the action already exists (decided by submenuActionAsString, not by text) it will be
        returned instead.
        """

        try:
            return getattr(self.ui, actionAsString)
        except AttributeError:
            pass

        setattr(self.ui, actionAsString, QtWidgets.QAction(self.mainWindow))
        action = getattr(self.ui, actionAsString)

        action.setObjectName(actionAsString)
        action.setText(text) #Text must already be translated
        action.setToolTip(tooltip) #Text must already be translated
        if iconResource:
            assert QtCore.QFile.exists(iconResource)
            ico = QtGui.QIcon(iconResource)
            action.setIcon(ico)
        if checkable:
            action.setCheckable(True)

        self.connectMenuEntry(actionAsString, connectedFunction, shortcut)

        submenu = getattr(self.ui, submenu)
        submenu.addAction(action)

        return action

    def hideMenuEntry(self, menuAction:str):
        action = getattr(self.ui, menuAction)
        action.setVisible(False)

    def removeMenuEntry(self, menuAction:str):
        raise NotImplementedError #TODO

    def addSeparator(self, submenu):
        submenu = getattr(self.ui, submenu)
        submenu.addSeparator()

    def connectMenuEntry(self, actionAsString, connectedFunction, shortcut=""):
        """
        Returns the submenu.
        Disconnects the old action first

        shortcut is an optional string like "Ctrl+Shift+Z"

        You can give None for connectedFunction. That makes automatisation in the program easier.
        However it is possible to set a shortcut without a connectedFunction.
        Also this helps to disconnect a menu entry of all its function.
        """

        action = getattr(self.ui, actionAsString)
        try:
            action.triggered.disconnect()
        except TypeError:
            #Action has no connected function on triggered-signal.
            pass

        if connectedFunction:
            action.triggered.connect(connectedFunction)

        if shortcut:
            action.setShortcut(shortcut)

        return action

    def disable(self):
        """This is meant for a short temporary disabling."""

        for a in self.mainWindow.findChildren(QtWidgets.QAction):
            if not a.menu():
                a.setEnabled(False)

    def enable(self):
        for a in self.mainWindow.findChildren(QtWidgets.QAction):
            a.setEnabled(True)

        self.renameUndoRedoByHistory(*api.getUndoLists())



class HoverActionDictionary(dict):
    """Key = number from 0-9 as string
     Value = QAction

     Singleton.
     """

    def __init__(self, mainWindow, *args):
        self.mainWindow = mainWindow

        if not "hoverActionDictionary" in api.session.guiSharedDataToSave:
            # key = action.text() , value = key sequence as text 'Num3+' key. Yes, the plus is in the string
            # Basically both values and keys should be unique.
            api.session.guiSharedDataToSave["hoverActionDictionary"] = {}

        dict.__init__(self, args)

        self.qShortcuts = {
            "1" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_1,
            "2" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_2,
            "3" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_3,
            "4" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_4,
            "5" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_5,
            "6" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_6,
            "7" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_7,
            "8" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_8,
            "9" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_9,
            "0" : QtCore.Qt.KeypadModifier+QtCore.Qt.Key_0,
        }

        dict.__setitem__(self, "0", None)
        dict.__setitem__(self, "1", None)
        dict.__setitem__(self, "2", None)
        dict.__setitem__(self, "3", None)
        dict.__setitem__(self, "4", None)
        dict.__setitem__(self, "5", None)
        dict.__setitem__(self, "6", None)
        dict.__setitem__(self, "7", None)
        dict.__setitem__(self, "8", None)
        dict.__setitem__(self, "9", None)
        """
        for i in range(10):
            emptyAction = QtWidgets.QAction("empty{}".format(i))
            self.mainWindow.addAction(emptyAction) #no actions without a widget
            emptyAction.triggered.connect(api.nothing)
            emptyAction.setShortcut(QtGui.QKeySequence(self.qShortcuts[str(i)]))
            dict.__setitem__(self, str(i), emptyAction)
        """

        #Load the hover shortcuts from last session.
        #We assume the shortcuts are clean and there is no mismatch or ambiguity.
        if "hoverActionDictionary" in api.session.guiSharedDataToSave: # key = action.text() , value = key sequence as text   'Num3+' key. Yes, the plus is in the string
            for action in self.mainWindow.findChildren(QtWidgets.QAction): #search the complete menu
                #TODO: if the menu label changes, maybe through translation, this will break. Non-critical, but annoying. Find a way to find unique ids.
                if action.text() in api.session.guiSharedDataToSave["hoverActionDictionary"]: #A saved action was found
                    keySequenceAsString = api.session.guiSharedDataToSave["hoverActionDictionary"][action.text()]
                    justTheNumberAsString = keySequenceAsString[-1 ] #-1 is a number as string
                    action.setShortcut(QtGui.QKeySequence(self.qShortcuts[justTheNumberAsString]))
                    dict.__setitem__(self, justTheNumberAsString, action) #does not trigger our own set item!

    def __setitem__(self, key, menuAction):
        """key is a string of a single number 0-9"""
        assert key in "0 1 2 3 4 5 6 7 8 9".split()

        try:
            self[key].setShortcut(QtGui.QKeySequence()) #reset the action that previously had that shortcut
        except AttributeError:
            pass

        #Remove old entry from save dict if shortcut already in there. shortcut is the value, so we have to do it by iteration
        for k,v in api.session.guiSharedDataToSave["hoverActionDictionary"].items():
            if v == "Num+"+key:
                del api.session.guiSharedDataToSave["hoverActionDictionary"][k]
                break
        assert not key in  api.session.guiSharedDataToSave["hoverActionDictionary"].values()

        try:
            dict.__setitem__(self, menuAction.shortcut().toString()[-1], None) #-1 is a number as string
        except IndexError:
            pass

        menuAction.setShortcut(QtGui.QKeySequence()) #reset the new action to remove existing shortcuts
        menuAction.setShortcut(QtGui.QKeySequence(self.qShortcuts[key]))
        api.session.guiSharedDataToSave["hoverActionDictionary"][menuAction.text()] = "Num+"+key
        dict.__setitem__(self, key, menuAction)

