"""
***************************************************************************
    ModelerGraphicItem.py
    ---------------------
    Date                 : August 2012
    Copyright            : (C) 2012 by Victor Olaya
    Email                : volayaf 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__ = "Victor Olaya"
__date__ = "August 2012"
__copyright__ = "(C) 2012, Victor Olaya"

from qgis.core import (
    Qgis,
    QgsProcessingModelAlgorithm,
    QgsProcessingModelOutput,
    QgsProcessingParameterDefinition,
    QgsProject,
)
from qgis.gui import (
    QgsModelChildAlgorithmGraphicItem,
    QgsModelOutputGraphicItem,
    QgsModelParameterGraphicItem,
    QgsProcessingContextGenerator,
    QgsProcessingParameterDefinitionDialog,
    QgsProcessingParameterWidgetContext,
)
from qgis.PyQt.QtCore import QCoreApplication
from qgis.utils import iface

from processing.modeler.ModelerParameterDefinitionDialog import (
    ModelerParameterDefinitionDialog,
)
from processing.modeler.ModelerParametersDialog import ModelerParametersDialog
from processing.tools.dataobjects import createContext


class ModelerInputGraphicItem(QgsModelParameterGraphicItem):
    """
    IMPORTANT! This is intentionally a MINIMAL class, only containing code which HAS TO BE HERE
    because it contains Python code for compatibility with deprecated methods ONLY.

    Don't add anything here -- edit the c++ base class instead!
    """

    def __init__(self, element, model):
        super().__init__(element, model, None)

        self.processing_context = createContext()

        class ContextGenerator(QgsProcessingContextGenerator):
            def __init__(self, context):
                super().__init__()
                self.processing_context = context

            def processingContext(self):
                return self.processing_context

        self.context_generator = ContextGenerator(self.processing_context)

    def create_widget_context(self):
        """
        Returns a new widget context for use in the model editor
        """
        widget_context = QgsProcessingParameterWidgetContext()
        widget_context.setProject(QgsProject.instance())
        if iface is not None:
            widget_context.setMapCanvas(iface.mapCanvas())
            widget_context.setActiveLayer(iface.activeLayer())

        widget_context.setModel(self.model())
        return widget_context

    def edit(self, edit_comment=False):
        existing_param = self.model().parameterDefinition(
            self.component().parameterName()
        )
        old_name = existing_param.name()
        old_description = existing_param.description()

        comment = self.component().comment().description()
        comment_color = self.component().comment().color()
        new_param = None
        if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param):
            # boo, old api
            dlg = ModelerParameterDefinitionDialog(self.model(), param=existing_param)
            dlg.setComments(comment)
            dlg.setCommentColor(comment_color)
            if edit_comment:
                dlg.switchToCommentTab()
            if dlg.exec():
                new_param = dlg.create_parameter()
                comment = dlg.comments()
                comment_color = dlg.commentColor()
                self.apply_new_param(
                    new_param, old_description, old_name, comment, comment_color
                )

        else:
            # yay, use new API!
            context = createContext()
            widget_context = self.create_widget_context()
            dlg = QgsProcessingParameterDefinitionDialog(
                type=existing_param.type(),
                context=context,
                widgetContext=widget_context,
                definition=existing_param,
                algorithm=self.model(),
            )
            dlg.setComments(comment)
            dlg.setCommentColor(comment_color)
            dlg.registerProcessingContextGenerator(self.context_generator)

            if edit_comment:
                dlg.switchToCommentTab()

            if dlg.exec():
                new_param = dlg.createParameter(existing_param.name())
                comment = dlg.comments()
                comment_color = dlg.commentColor()

                safeName = QgsProcessingModelAlgorithm.safeName(new_param.description())
                new_param.setName(safeName.lower())

                self.apply_new_param(
                    new_param, old_description, old_name, comment, comment_color
                )

    def apply_new_param(
        self, new_param, old_description, old_name, comment, comment_color
    ):
        undo_command_id = f"param:{self.component().parameterName()}"
        self.aboutToChange.emit(
            self.tr("Edit {}").format(new_param.description()), undo_command_id
        )
        self.model().removeModelParameter(self.component().parameterName())

        if new_param.description() != old_description:
            # only update name if user has changed the description -- we don't force this, as it may cause
            # unwanted name updates which could potentially break the model's API
            name = new_param.name()

            base_name = name
            i = 2
            while self.model().parameterDefinition(name):
                name = base_name + str(i)
                i += 1

            new_param.setName(name)

            self.model().changeParameterName(old_name, new_param.name())

        self.component().setParameterName(new_param.name())
        self.component().setDescription(new_param.name())
        self.component().comment().setDescription(comment)
        self.component().comment().setColor(comment_color)
        self.model().addModelParameter(new_param, self.component())
        self.setLabel(new_param.description())
        self.requestModelRepaint.emit()
        self.changed.emit()
        return new_param.name()

    def editComponent(self):
        self.edit()

    def editComment(self):
        self.edit(edit_comment=True)


class ModelerChildAlgorithmGraphicItem(QgsModelChildAlgorithmGraphicItem):
    """
    IMPORTANT! This is intentionally a MINIMAL class, only containing code which HAS TO BE HERE
    because it contains Python code for compatibility with deprecated methods ONLY.

    Don't add anything here -- edit the c++ base class instead!
    """

    def __init__(self, element, model):
        super().__init__(element, model, None)

    def edit(self, edit_comment=False):
        elemAlg = self.component().algorithm()
        dlg = ModelerParametersDialog(
            elemAlg,
            self.model(),
            self.component().childId(),
            self.component().configuration(),
        )
        dlg.setComments(self.component().comment().description())
        dlg.setCommentColor(self.component().comment().color())
        if edit_comment:
            dlg.switchToCommentTab()
        if dlg.exec():
            alg = dlg.createAlgorithm()
            self.apply_new_alg(alg)

    def apply_new_alg(self, alg):
        alg.setChildId(self.component().childId())
        alg.copyNonDefinitionPropertiesFromModel(self.model())
        if alg.toVariant() == self.component().toVariant():
            # nothing changed, treat as cancel was pressed
            return

        # TODO -- ideally we'd include the changed parameter name in the
        # command ID, so that we get more granular undo/redo (i.e. per
        # parameter change, not per child algorithm change)
        undo_command_id = f"alg:{self.component().childId()}"
        self.aboutToChange.emit(
            self.tr("Edit {}").format(alg.description()), undo_command_id
        )
        self.model().setChildAlgorithm(alg)
        self.requestModelRepaint.emit()
        self.changed.emit()

    def editComponent(self):
        self.edit()

    def editComment(self):
        self.edit(edit_comment=True)


class ModelerOutputGraphicItem(QgsModelOutputGraphicItem):
    """
    IMPORTANT! This is intentionally a MINIMAL class, only containing code which HAS TO BE HERE
    because it contains Python code for compatibility with deprecated methods ONLY.

    Don't add anything here -- edit the c++ base class instead!
    """

    def __init__(self, element, model):
        super().__init__(element, model, None)

    def edit(self, edit_comment=False):
        child_alg = self.model().childAlgorithm(self.component().childId())
        dlg = ModelerParameterDefinitionDialog(
            self.model(),
            param=self.model().modelParameterFromChildIdAndOutputName(
                self.component().childId(), self.component().name()
            ),
        )
        dlg.setComments(self.component().comment().description())
        dlg.setCommentColor(self.component().comment().color())
        if edit_comment:
            dlg.switchToCommentTab()

        if dlg.exec():
            new_param = dlg.create_parameter()
            self.apply_new_output(
                name=new_param.description(),
                description=new_param.description(),
                default=new_param.defaultValue(),
                mandatory=not (
                    new_param.flags()
                    & QgsProcessingParameterDefinition.Flag.FlagOptional
                ),
                comments=dlg.comments(),
                comment_color=dlg.commentColor(),
                child_alg=child_alg,
            )

    def apply_new_output(
        self,
        name: str,
        description: str,
        default,
        mandatory: bool,
        comments: str,
        comment_color,
        child_alg,
    ):
        model_outputs = child_alg.modelOutputs()

        model_output = QgsProcessingModelOutput(model_outputs[self.component().name()])
        del model_outputs[self.component().name()]

        model_output.setName(name)
        model_output.setDescription(description)
        model_output.setDefaultValue(default)
        model_output.setMandatory(mandatory)
        model_output.comment().setDescription(comments)
        model_output.comment().setColor(comment_color)
        model_outputs[model_output.name()] = model_output
        child_alg.setModelOutputs(model_outputs)

        undo_command_id = f"output:{name}"
        self.aboutToChange.emit(
            self.tr("Edit {}").format(model_output.description()), undo_command_id
        )

        self.model().updateDestinationParameters()
        self.requestModelRepaint.emit()
        self.changed.emit()

    def editComponent(self):
        self.edit()

    def editComment(self):
        self.edit(edit_comment=True)
