"""
***************************************************************************
    EditScriptDialog.py
    ---------------------
    Date                 : December 2012
    Copyright            : (C) 2012 by Alexander Bruy
    Email                : alexander dot bruy at gmail dot com
***************************************************************************
*                                                                         *
*   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.                                   *
*                                                                         *
***************************************************************************
"""

__author__ = 'Alexander Bruy'
__date__ = 'December 2012'
__copyright__ = '(C) 2012, Alexander Bruy'

import os
import codecs
import inspect
import traceback
import warnings

from qgis.PyQt import uic, sip
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (
    QMessageBox,
    QFileDialog,
    QVBoxLayout
)

from qgis.gui import (
    QgsGui,
    QgsErrorDialog,
    QgsCodeEditorWidget
)
from qgis.core import (
    QgsApplication,
    QgsFileUtils,
    QgsSettings,
    QgsError,
    QgsProcessingAlgorithm,
    QgsProcessingFeatureBasedAlgorithm
)
from qgis.utils import iface, OverrideCursor
from qgis.processing import alg as algfactory

from processing.gui.AlgorithmDialog import AlgorithmDialog
from processing.script import ScriptUtils

from .ScriptEdit import ScriptEdit

pluginPath = os.path.split(os.path.dirname(__file__))[0]

with warnings.catch_warnings():
    warnings.filterwarnings("ignore", category=DeprecationWarning)
    WIDGET, BASE = uic.loadUiType(
        os.path.join(pluginPath, "ui", "DlgScriptEditor.ui"))


class ScriptEditorDialog(BASE, WIDGET):
    hasChanged = False

    DIALOG_STORE = []

    def __init__(self, filePath=None, parent=None):
        super().__init__(parent)
        # SIP is totally messed up here -- the dialog wrapper or something
        # is always prematurely cleaned which results in broken QObject
        # connections throughout.
        # Hack around this by storing dialog instances in a global list to
        # prevent too early wrapper garbage collection
        ScriptEditorDialog.DIALOG_STORE.append(self)

        def clean_up_store():
            ScriptEditorDialog.DIALOG_STORE =\
                [d for d in ScriptEditorDialog.DIALOG_STORE if d != self]

        self.destroyed.connect(clean_up_store)

        self.setupUi(self)
        self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)

        QgsGui.instance().enableAutoGeometryRestore(self)

        vl = QVBoxLayout()
        vl.setContentsMargins(0, 0, 0, 0)
        self.editor_container.setLayout(vl)

        self.editor = ScriptEdit()
        self.code_editor_widget = QgsCodeEditorWidget(self.editor)
        vl.addWidget(self.code_editor_widget)

        if iface is not None:
            self.toolBar.setIconSize(iface.iconSize())
            self.setStyleSheet(iface.mainWindow().styleSheet())

        self.actionOpenScript.setIcon(
            QgsApplication.getThemeIcon('/mActionScriptOpen.svg'))
        self.actionSaveScript.setIcon(
            QgsApplication.getThemeIcon('/mActionFileSave.svg'))
        self.actionSaveScriptAs.setIcon(
            QgsApplication.getThemeIcon('/mActionFileSaveAs.svg'))
        self.actionRunScript.setIcon(
            QgsApplication.getThemeIcon('/mActionStart.svg'))
        self.actionCut.setIcon(
            QgsApplication.getThemeIcon('/mActionEditCut.svg'))
        self.actionCopy.setIcon(
            QgsApplication.getThemeIcon('/mActionEditCopy.svg'))
        self.actionPaste.setIcon(
            QgsApplication.getThemeIcon('/mActionEditPaste.svg'))
        self.actionUndo.setIcon(
            QgsApplication.getThemeIcon('/mActionUndo.svg'))
        self.actionRedo.setIcon(
            QgsApplication.getThemeIcon('/mActionRedo.svg'))
        self.actionFindReplace.setIcon(
            QgsApplication.getThemeIcon('/mActionFindReplace.svg'))
        self.actionIncreaseFontSize.setIcon(
            QgsApplication.getThemeIcon('/mActionIncreaseFont.svg'))
        self.actionDecreaseFontSize.setIcon(
            QgsApplication.getThemeIcon('/mActionDecreaseFont.svg'))
        self.actionToggleComment.setIcon(
            QgsApplication.getThemeIcon('console/iconCommentEditorConsole.svg'))

        # Connect signals and slots
        self.actionOpenScript.triggered.connect(self.openScript)
        self.actionSaveScript.triggered.connect(self.save)
        self.actionSaveScriptAs.triggered.connect(self.saveAs)
        self.actionRunScript.triggered.connect(self.runAlgorithm)
        self.actionCut.triggered.connect(self.editor.cut)
        self.actionCopy.triggered.connect(self.editor.copy)
        self.actionPaste.triggered.connect(self.editor.paste)
        self.actionUndo.triggered.connect(self.editor.undo)
        self.actionRedo.triggered.connect(self.editor.redo)
        self.actionFindReplace.toggled.connect(
            self.code_editor_widget.setSearchBarVisible
        )
        self.code_editor_widget.searchBarToggled.connect(
            self.actionFindReplace.setChecked
        )

        self.actionIncreaseFontSize.triggered.connect(self.editor.zoomIn)
        self.actionDecreaseFontSize.triggered.connect(self.editor.zoomOut)
        self.actionToggleComment.triggered.connect(self.editor.toggleComment)
        self.editor.modificationChanged.connect(self._on_text_modified)

        self.run_dialog = None

        if filePath is not None:
            self._loadFile(filePath)

        self.setHasChanged(False)

    def update_dialog_title(self):
        """
        Updates the script editor dialog title
        """
        if self.code_editor_widget.filePath():
            path, file_name = os.path.split(
                self.code_editor_widget.filePath()
            )
        else:
            file_name = self.tr('Untitled Script')

        if self.hasChanged:
            file_name = '*' + file_name

        self.setWindowTitle(self.tr('{} - Processing Script Editor').format(file_name))

    def closeEvent(self, event):
        settings = QgsSettings()
        settings.setValue("/Processing/stateScriptEditor", self.saveState())
        settings.setValue("/Processing/geometryScriptEditor", self.saveGeometry())

        if self.hasChanged:
            ret = QMessageBox.question(
                self, self.tr('Save Script?'),
                self.tr('There are unsaved changes in this script. Do you want to keep those?'),
                QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Cancel | QMessageBox.StandardButton.Discard, QMessageBox.StandardButton.Cancel)

            if ret == QMessageBox.StandardButton.Save:
                self.saveScript(False)
                event.accept()
            elif ret == QMessageBox.StandardButton.Discard:
                event.accept()
            else:
                event.ignore()
        else:
            event.accept()

    def openScript(self):
        if self.hasChanged:
            ret = QMessageBox.warning(self,
                                      self.tr("Unsaved changes"),
                                      self.tr("There are unsaved changes in the script. Continue?"),
                                      QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)
            if ret == QMessageBox.StandardButton.No:
                return

        scriptDir = ScriptUtils.scriptsFolders()[0]
        fileName, _ = QFileDialog.getOpenFileName(self,
                                                  self.tr("Open script"),
                                                  scriptDir,
                                                  self.tr("Processing scripts (*.py *.PY)"))

        if fileName == "":
            return

        with OverrideCursor(Qt.CursorShape.WaitCursor):
            self._loadFile(fileName)

    def save(self):
        self.saveScript(False)

    def saveAs(self):
        self.saveScript(True)

    def saveScript(self, saveAs):
        newPath = None
        if not self.code_editor_widget.filePath() or saveAs:
            scriptDir = ScriptUtils.scriptsFolders()[0]
            newPath, _ = QFileDialog.getSaveFileName(self,
                                                     self.tr("Save script"),
                                                     scriptDir,
                                                     self.tr("Processing scripts (*.py *.PY)"))

            if newPath:
                newPath = QgsFileUtils.ensureFileNameHasExtension(newPath, ['py'])
                self.code_editor_widget.save(newPath)
        elif self.code_editor_widget.filePath():
            self.code_editor_widget.save()

        self.setHasChanged(False)
        QgsApplication.processingRegistry().providerById("script").refreshAlgorithms()

    def _on_text_modified(self, modified):
        self.setHasChanged(modified)

    def setHasChanged(self, hasChanged):
        self.hasChanged = hasChanged
        self.actionSaveScript.setEnabled(hasChanged)
        self.update_dialog_title()

    def runAlgorithm(self):
        if self.run_dialog and not sip.isdeleted(self.run_dialog):
            self.run_dialog.close()
            self.run_dialog = None

        _locals = {}
        try:
            exec(self.editor.text(), _locals)
        except Exception as e:
            error = QgsError(traceback.format_exc(), "Processing")
            QgsErrorDialog.show(error,
                                self.tr("Execution error")
                                )
            return

        alg = None
        try:
            alg = algfactory.instances.pop().createInstance()
        except IndexError:
            for name, attr in _locals.items():
                if inspect.isclass(attr) and issubclass(attr, (QgsProcessingAlgorithm, QgsProcessingFeatureBasedAlgorithm)) and attr.__name__ not in ("QgsProcessingAlgorithm", "QgsProcessingFeatureBasedAlgorithm"):
                    alg = attr()
                    break

        if alg is None:
            QMessageBox.warning(self,
                                self.tr("No script found"),
                                self.tr("Seems there is no valid script in the file.")
                                )
            return

        alg.setProvider(QgsApplication.processingRegistry().providerById("script"))
        alg.initAlgorithm()

        self.run_dialog = alg.createCustomParametersWidget(self)
        if not self.run_dialog:
            self.run_dialog = AlgorithmDialog(alg, parent=self)

        canvas = iface.mapCanvas()
        prevMapTool = canvas.mapTool()

        self.run_dialog.show()

        if canvas.mapTool() != prevMapTool:
            try:
                canvas.mapTool().reset()
            except:
                pass
            canvas.setMapTool(prevMapTool)

    def _loadFile(self, filePath):

        self.code_editor_widget.loadFile(filePath)
        self.hasChanged = False

        self.update_dialog_title()
