"""
/***************************************************************************
 CornelisPlugin QGIS plugin

 Help produce 'cartographic' tessellations of the plan,
 and try to imitate M.C. Escher.

 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2024-11-04
        git sha              : $Format:%H$
        copyright            : (C) 2024 by Xavier Culos
        email                : xavier.culos@mailo.fr
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

"""

import os.path
from functools import partial

from qgis.core import (
    Qgis,
    QgsApplication,
    QgsExpression,
    QgsExpressionFunction,
    QgsSettings,
)
from qgis.gui import QgsMapTool
from qgis.PyQt.QtCore import QCoreApplication, QLocale, Qt, QTranslator
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QFileDialog, QMenu, QMessageBox, QToolButton
from qgis.utils import iface

from .__about__ import DIR_PLUGIN_ROOT
from .logic.typo import TYPES_PAVAGES, Typo, Typo1, TypoTree
from .TDMapTool import TDMapTool


class CornelisGeometryFunction(QgsExpressionFunction):

    def __init__(self, plugin):
        [QgsExpressionFunction.Parameter]

        QgsExpressionFunction.__init__(
            self,
            "cornelis",
            [
                QgsExpressionFunction.Parameter(name="geometry", optional=False),
            ],
            "Python",
            self.tr(
                """<h1>cornelis</h1>
Expression function added by Cornelis plugin.<br>
<br>
Duplicates the geometry according to the transformation rules of the current tessellation scheme.
<br>
<h2>Return value</h2>
Geom<br/>
<h2>Usage</h2>
cornelis($geometry)<br>
<br>
or if a transformation is necessary (Here 2154 is the map projection, 4326 the layer projection) :<br>
<br>
transform(<br>
    cornelis(transform($geometry, 'EPSG:4326', 'EPSG:2154')),<br>
    'EPSG:2154','EPSG:4326'<br>
)<br>
...
        """
            ),
        )
        self.plugin = plugin

    def tr(self, message):
        return QCoreApplication.translate("CornelisGeometryFunction", message)

    def func(self, values, context, parent, node):
        return self.plugin.buildPavageGeometry(context.feature(), geom=values[0])


class CornelisPlugin:
    """QGIS Plugin Implementation."""

    def __init__(self):
        """Constructor."""
        # Save reference to the QGIS interface
        self.canvas = iface.mapCanvas()
        # initialize locale
        locale = QgsSettings().value("locale/userLocale", QLocale().name())
        locale_path = str(DIR_PLUGIN_ROOT / "i18n" / "{}.qm".format(locale))

        self.typo = Typo.T1a

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = {}
        self.menu = self.tr("&Cornelis")

        self.ACTION_PAVAGE_INIT = "init"
        self.ACTION_PAVAGE = "pavage"
        self.ACTION_DRAW = "draw"
        self.ACTION_CALC = "calc"
        self.ACTION_SAVE = "save"
        self.ACTION_LOAD = "load"

        self.mt = TDMapTool(self, self.canvas)
        self.__oldMapTool = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate("CornelisPlugin", message)

    def changeTypo(self, typo):
        self.typo = typo
        self.toolButton.setText(TYPES_PAVAGES[typo]["name"])

    def createTypeAction(self, typo) -> QAction:
        icon = QIcon(str(DIR_PLUGIN_ROOT / "resources" / "vectorDeformInit.svg"))

        name = TYPES_PAVAGES[typo]["name"]
        form = self.tr(TYPES_PAVAGES[typo]["form"])
        tilesnum = 1 + len(TYPES_PAVAGES[typo]["pattern"])
        if tilesnum == 1:
            actionName = f"{name} - {form}"
        else:
            tiles = self.tr("Tiles")
            actionName = f"{name} - {form} - {tilesnum} {tiles}"

        action = QAction(icon, actionName)
        nf = str(DIR_PLUGIN_ROOT / "resources" / f"T{typo.value}-250.png")
        action.setToolTip(f'{actionName}<br><img src="{nf}"></img>')
        run = partial(self.changeTypo, typo)
        action.triggered.connect(run)
        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        self.toolButton = QToolButton()
        self.toolButton.setMenu(QMenu())
        self.toolButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        self.toolButton.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
        toolButtonMenu = self.toolButton.menu()
        toolButtonMenu.setToolTipsVisible(True)

        self.actionTypes = []
        for typo1 in Typo1:
            subButtonMenu = toolButtonMenu.addMenu(TypoTree[typo1]["name"])
            subButtonMenu.setToolTipsVisible(True)
            for t in TypoTree[typo1]["types"]:
                a = self.createTypeAction(t)
                self.actionTypes.append(a)
                subButtonMenu.addAction(a)

        self.toolButton.setText(TYPES_PAVAGES[Typo.T1a]["name"])

        # New pavage
        self.actions[self.ACTION_PAVAGE_INIT] = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "vectorDeformInit.svg")),
            "init",
            iface.mainWindow(),
        )
        self.actions[self.ACTION_PAVAGE_INIT].setToolTip(self.tr("New pattern"))
        self.actions[self.ACTION_PAVAGE_INIT].triggered.connect(
            lambda: self.initPavage()
        )

        # Show source pavage
        self.actions[self.ACTION_PAVAGE] = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "vectorDeformSource.svg")),
            "source",
            iface.mainWindow(),
        )
        self.actions[self.ACTION_PAVAGE].setToolTip(self.tr("Show/Hide pattern"))
        self.actions[self.ACTION_PAVAGE].setCheckable(True)
        self.actions[self.ACTION_PAVAGE].setChecked(False)
        self.actions[self.ACTION_PAVAGE].triggered.connect(lambda: self.showPavage())

        # Drawing
        self.actions[self.ACTION_DRAW] = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "brush.svg")),
            "source",
            iface.mainWindow(),
        )
        self.actions[self.ACTION_DRAW].setToolTip(self.tr("Draw a sketch"))
        self.actions[self.ACTION_DRAW].setCheckable(True)
        self.actions[self.ACTION_DRAW].setChecked(False)
        self.actions[self.ACTION_DRAW].triggered.connect(lambda: self.draw())

        # Start deformation
        self.actions[self.ACTION_CALC] = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "vectorDeformCalc.svg")),
            "deform",
            iface.mainWindow(),
        )
        self.actions[self.ACTION_CALC].setToolTip(self.tr("Build tesselation"))
        self.actions[self.ACTION_CALC].triggered.connect(lambda: self.do())

        # Load pavage
        self.actions[self.ACTION_LOAD] = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "vectorDeformLoad.svg")),
            "load",
            iface.mainWindow(),
        )
        self.actions[self.ACTION_LOAD].setToolTip(self.tr("Load a existing pattern"))
        self.actions[self.ACTION_LOAD].triggered.connect(lambda: self.loadPavage())

        # Save pavage
        self.actions[self.ACTION_SAVE] = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "vectorDeformSave.svg")),
            "save",
            iface.mainWindow(),
        )
        self.actions[self.ACTION_SAVE].setToolTip(self.tr("Save pattern"))
        self.actions[self.ACTION_SAVE].triggered.connect(lambda: self.savePavage())

        self.toolbar = iface.addToolBar("Cornelis")

        self.toolbar.addWidget(self.toolButton)
        for a in self.actions.values():
            self.toolbar.addAction(a)

        self.setActionsEnabled(False)

        self.cornelis_function = CornelisGeometryFunction(self)
        QgsExpression.registerFunction(self.cornelis_function)

    def setActionsEnabled(self, enabled: bool = True):
        self.actions[self.ACTION_PAVAGE].setEnabled(enabled)
        self.actions[self.ACTION_DRAW].setEnabled(enabled)
        self.actions[self.ACTION_CALC].setEnabled(enabled)
        self.actions[self.ACTION_SAVE].setEnabled(enabled)

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions.values():
            iface.removePluginMenu(self.tr("&CornelisPlugin"), action)
            iface.removeToolBarIcon(action)
            self.toolbar.removeAction(action)

        self.toolbar = None
        self.deactivateMapTool()

    def activateMapTool(self):
        if not isinstance(self.canvas.mapTool(), TDMapTool):
            self.__oldMapTool = self.canvas.mapTool()
            self.mt.activate()
            self.canvas.setMapTool(self.mt)

    def deactivateMapTool(self):
        if isinstance(self.canvas.mapTool(), TDMapTool):
            self.mt.deactivate()
            self.canvas.unsetMapTool(self.canvas.mapTool())
        if isinstance(self.__oldMapTool, QgsMapTool):
            self.canvas.setMapTool(self.__oldMapTool)

    def deactivate(self):
        for action in self.actions.values():
            action.setChecked(False)

    def initPavage(self):
        self.activateMapTool()
        self.mt.initPavage(self.typo)
        self.actions[self.ACTION_PAVAGE].setChecked(True)
        self.showPavage()
        self.setActionsEnabled(True)

    def loadPavage(self):
        self.activateMapTool()
        abandon = False

        if self.mt.pavage is not None:
            promptReply = QMessageBox.question(
                iface.mainWindow(),
                self.tr("New pavage"),
                self.tr("Abandon the current tessellation ?"),
                QMessageBox.StandardButton.Yes,
                QMessageBox.StandardButton.No,
            )
            abandon = promptReply == QMessageBox.StandardButton.Yes

        if self.mt.pavage is None or abandon:
            options = QFileDialog.Options()
            options |= QFileDialog.Option.DontUseNativeDialog
            dlg = QFileDialog()
            dlg.setDefaultSuffix(".pav")
            fileName, _ = dlg.getOpenFileName(
                None, self.tr("Load Pattern"), "Pattern (*.pav)", options=options
            )
            if fileName:
                self.mt.loadPavage(fileName)

                self.actions[self.ACTION_PAVAGE].setChecked(True)

                self.mt.showPavage(True)
                self.setActionsEnabled(True)

                # zoom to pavage
                self.mt.zoomToPavage()

    def savePavage(self):
        self.activateMapTool()
        options = QFileDialog.Options()
        options |= QFileDialog.Option.DontUseNativeDialog
        dlg = QFileDialog()
        dlg.setDefaultSuffix(".pav")
        fileName, _ = dlg.getSaveFileName(
            None, self.tr("Save Pattern"), "", "Pattern (*.pav)", options=options
        )

        if fileName:
            if not fileName.endswith(".pav"):
                fileName = f"{fileName}.pav"
            self.mt.savePavage(fileName)

    def showPavage(self):
        """ """
        self.activateMapTool()
        self.mt.showPavage(self.actions[self.ACTION_PAVAGE].isChecked())

    def draw(self):
        """ """
        r = self.mt.modeDrawing(self.actions[self.ACTION_DRAW].isChecked())
        if not r:
            self.actions[self.ACTION_DRAW].setChecked(False)

    def do(self):
        self.activateMapTool()
        try:
            QgsApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
            self.mt.do()
        except Exception as e:
            iface.messageBar().pushMessage(
                "Cornelis Plugin",
                "{} - {}".format(self.tr("Error during process"), str(e)),
                level=Qgis.MessageLevel.Warning,
                duration=5,
            )
            raise e

    def buildPavageGeometry(self, feature, geom=None):
        return self.mt.buildPavageGeometry(geom)
