# Copyright (C) 2002-2022
# The MeqTree Foundation &
# ASTRON (Netherlands Foundation for Research in Astronomy)
# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
#
# 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 2 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/>,
# or write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

from PyQt5.Qt import QObject, Qt, QActionGroup
from PyQt5.QtCore import pyqtSignal

import TigGUI.kitties.utils
from TigGUI.init import Config, ConfigFile, ConfigFileName, pixmaps
from TigGUI.kitties.utils import PersistentCurrier

_verbosity = TigGUI.kitties.utils.verbosity(name="mmod")
dprint = _verbosity.dprint
dprintf = _verbosity.dprintf

_Contexts = dict(image=1, model=2)

WHEELUP = "WheelUp"
WHEELDOWN = "WheelDown"

MM_ZWIN = "zoom-window"
MM_ZUNDO = "zoom-undo"
MM_ZREDO = "zoom-redo"
MM_UNZOOM = "unzoom"
MM_MEAS = "measure"
MM_STATS = "stats"
MM_SELSRC = "select-source"
MM_SELWIN = "select-window"
MM_SELWINPLUS = "select-window-plus"
MM_DESEL = "deselect-window"

_AllFuncs = [MM_ZWIN, MM_ZUNDO, MM_ZREDO, MM_UNZOOM,
             MM_MEAS, MM_STATS, MM_SELSRC, MM_SELWIN, MM_SELWINPLUS, MM_DESEL]

FuncDoc = {
    MM_ZWIN: "Zoom into window (click-drag) or zoom in at point (double-click)",
    MM_ZUNDO: "Zoom out to previous view",
    MM_ZREDO: "Zoom in to previous view",
    MM_UNZOOM: "Zoom fully out",
    MM_MEAS: "Measuring ruler (click and drag)",
    MM_STATS: "Stats in box (click and drag)",
    MM_SELSRC: "Select nearest source",
    MM_SELWIN: "Select sources in window",
    MM_SELWINPLUS: "Extend selection with sources in window",
    MM_DESEL: "Deselect sources in window"
}

_DefaultModes = "Mouse3,Mouse2,Mouse1"
_DefaultInitialMode = "Mouse3"


class MouseModeManager(QObject):
    class MouseMode:

        def __init__(self, mid):
            self.id = mid
            self.name = self.icon = self.tooltip = None
            self.contexts = []
            self.submodes = []
            self.patterns = {}
            self.qa = None

        def addAction(self, menu, qag, callback, toolbar=None):
            self.qa = menu.addAction(self.name, callback)
            icon = self.icon and getattr(pixmaps, self.icon, None)
            icon and self.qa.setIcon(icon.icon())
            self.qa.setCheckable(True)
            qag.addAction(self.qa)
            toolbar and toolbar.addAction(self.qa)

    setMouseMode = pyqtSignal(MouseMode)

    def __init__(self, parent, menu, toolbar):
        QObject.__init__(self, parent)
        self._currier = PersistentCurrier()
        # get list of mouse modes from config
        modelist = []
        for mid in Config.get("mouse-modes", _DefaultModes).split(","):
            if not ConfigFile.has_section(mid):
                print("ERROR: unknown mouse-mode '%s', skipping. Check your %s." % (mid, ConfigFileName))
            else:
                modelist.append(self._readModeConfig(mid))
        self._modes = dict([(mode.id, mode) for mode in modelist])
        self._qag_mode = QActionGroup(self)
        self._qag_submode = QActionGroup(self)
        self._all_submodes = []
        # make entries for main modes
        for mode in modelist:
            mode.addAction(menu, self._qag_mode, callback=self._currier.curry(self._setMode, mode.id))
            if mode.submodes:
                self._all_submodes += list(mode.submodes)
        # make entries for submodes
        self._qa_submode_sep = menu.addSeparator()
        self._modes.update([(mode.id, mode) for mode in self._all_submodes])
        for mode in self._all_submodes:
            mode.addAction(menu, self._qag_submode, toolbar=toolbar,
                           callback=self._currier.curry(self._setSubmode, mode.id))
        # other init
        self._current_context = None
        self._available_submodes = []
        # set initial mode
        initmode = Config.get("current-mouse-mode", _DefaultInitialMode)
        if initmode not in self._modes:
            initmode = modelist[0].id
        self._modes[initmode].qa.setChecked(True)
        self._setMode(initmode, write_config=False)

    def currentMode(self):
        return self._current_submode or self._current_mode

    def setContext(self, has_image, has_model):
        self._current_context = (has_image and _Contexts['image']) | (has_model and _Contexts['model'])
        self._ensureValidSubmodes()

    def _ensureValidSubmodes(self):
        current = None
        self._valid_submodes = []
        # accumulate list of valid submodes, and find the checked-on one
        for mode in self._available_submodes:
            if not mode.contexts or not self._current_context or self._current_context & mode.contexts:
                self._valid_submodes.append(mode)
                mode.qa.setVisible(True)
                if mode.qa.isChecked():
                    current = mode.id
            else:
                mode.qa.setVisible(False)
        if self._valid_submodes:
            self._setSubmode(current or self._valid_submodes[0].id)

    def _setMode(self, mid, write_config=True):
        """Called when the mouse mode changes"""
        if write_config:
            Config.set("current-mouse-mode", mid)
        self._current_mode = mode = self._modes[mid]
        # hide submodes if any
        for mm in self._all_submodes:
            mm.qa.setVisible(False)
        self._qa_submode_sep.setVisible(bool(mode.submodes))
        self._current_submode = None
        self._available_submodes = mode.submodes
        # make relevant submodes visible, and make sure one is enabled
        if mode.submodes:
            self._ensureValidSubmodes()
        else:
            self.setMouseMode.emit(mode)

    def _setSubmode(self, mid):
        """Called when the mouse submode changes"""
        self._current_submode = mode = self._modes[mid]
        mode.qa.setChecked(True)
        # hide submodes if any
        for mm in self._all_submodes:
            mm.qa.setShortcuts([])
        # set F4 shortcut to next submode
        if len(self._valid_submodes) > 1:
            for i, mm in enumerate(self._valid_submodes):
                if mm is mode:
                    self._valid_submodes[(i + 1) % len(self._valid_submodes)].qa.setShortcut(Qt.Key_F4)
                    break
        self.setMouseMode.emit(mode)

    def _readModeConfig(self, section, main_tooltip=None):
        """Reads the given config section (and uses the supplied defaults dict)
        and returns a dict of mouse_patterns,key_patterns per each function."""
        # read basic stuff
        mode = self.MouseMode(section)
        config = TigGUI.kitties.config.SectionParser(ConfigFile, section)
        mode.name = config.get("name", section)
        mode.icon = config.get("icon", "") or None
        mode.contexts = sum([_Contexts.get(x, 0) for x in config.get("contexts", "").split(",")])
        submodes = config.get("submodes", "") or None
        # eiher a mode with submodes, or a main mode
        if submodes:
            mode.tooltip = "<P>Your current mouse scheme is \"%s\".</P>" % mode.name
            for mid in submodes.split(","):
                if ConfigFile.has_section(mid):
                    mode.submodes.append(self._readModeConfig(mid, main_tooltip=mode.tooltip))
                else:
                    print("ERROR: unknown submode '%s' in mode config section '%s', skipping/ Check your %s." % (
                        mid, section, ConfigFileName))
        else:
            if main_tooltip:
                mode.tooltip = main_tooltip + """<P>In this scheme, available mouse functions depend on the selected mode.
        The current mode is %s. Use F4 to cycle through other modes.</P>""" % mode.name
            else:
                mode.tooltip = "<P>Your current mouse scheme is: \"%s\".</P>" % mode.name
            mode.tooltip += """<P>The following mouse functions are available:</P><BR><TABLE>\n"""
            patterns = {}
            # get basic patterns
            for func in _AllFuncs:
                # get pattern
                pattern = config.get(func, "")
                if not pattern:
                    continue
                mouse_pattern = key_pattern = None
                for pat in pattern.split(";"):
                    pat = pat.strip()
                    if pat and pat.lower() != "none":
                        # split by "+" and lookup each identifier in the Qt namespace
                        scomps = pat.split("+")
                        try:
                            comps = [x if x in (WHEELUP, WHEELDOWN) else getattr(Qt, x) for x in scomps]
                        except AttributeError:
                            print(
                                "WARNING: can't parse '%s' for function '%s' in mode config section '%s', disabling. Check your %s." % (
                                    pat, func, section, ConfigFileName))
                            continue
                        # append key/button code and sum of modifiers to the key or keyboard pattern list
                        if scomps[-1].startswith("Key_"):
                            if key_pattern:
                                print(
                                    "WARNING: more than one key pattern for function '%s' in mode config section '%s', ignoring. Check your %s." % (
                                        func, section, ConfigFileName))
                            else:
                                key_pattern = comps[-1], sum(comps[:-1])
                        else:
                            if mouse_pattern:
                                print(
                                    "WARNING: more than one mouse pattern for function '%s' in mode config section '%s', ignoring. Check your %s." % (
                                        func, section, ConfigFileName))
                            else:
                                mouse_pattern = comps[-1], sum(comps[:-1])
                mode.tooltip += "<TR><TD>%s:&nbsp;&nbsp;</TD><TD>%s</TD></TR>\n" % (pattern, FuncDoc[func])
                mode.patterns[func] = (mouse_pattern or (0, 0), key_pattern or (0, 0))
            mode.tooltip += "</TABLE><BR>"
        return mode
