"""
***************************************************************************
    ParametersPanel.py
    ---------------------
    Date                 : August 2012
    Copyright            : (C) 2012 by Victor Olaya
                           (C) 2013 by CS Systemes d'information (CS SI)
    Email                : volayaf at gmail dot com
                           otb at c-s dot fr (CS SI)
    Contributors         : Victor Olaya

***************************************************************************
*                                                                         *
*   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,
    QgsProcessingOutputLayerDefinition,
    QgsProcessingParameterDefinition,
    QgsProcessingParameterExtent,
    QgsProject,
)
from qgis.gui import (
    QgsAbstractProcessingParameterWidgetWrapper,
    QgsGui,
    QgsProcessingContextGenerator,
    QgsProcessingGui,
    QgsProcessingHiddenWidgetWrapper,
    QgsProcessingParametersGenerator,
    QgsProcessingParametersWidget,
    QgsProcessingParameterWidgetContext,
)
from qgis.PyQt.QtWidgets import QLabel, QSizePolicy, QVBoxLayout, QWidget
from qgis.utils import iface

from processing.core.exceptions import InvalidOutputExtension, InvalidParameterValue
from processing.gui.wrappers import WidgetWrapper, WidgetWrapperFactory
from processing.tools.dataobjects import createContext


class ParametersPanel(QgsProcessingParametersWidget):
    def __init__(self, parent, alg, in_place=False, active_layer=None):
        super().__init__(alg, parent)
        self.in_place = in_place

        self.active_layer = active_layer

        self.wrappers = {}

        self.extra_parameters = {}

        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)

        self.initWidgets()

        QgsProject.instance().layerWasAdded.connect(self.layerRegistryChanged)
        QgsProject.instance().layersWillBeRemoved.connect(self.layerRegistryChanged)

    def layerRegistryChanged(self, layers):
        for wrapper in list(self.wrappers.values()):
            try:
                wrapper.refresh()
            except AttributeError:
                pass

    def initWidgets(self):
        super().initWidgets()

        widget_context = QgsProcessingParameterWidgetContext()
        widget_context.setProject(QgsProject.instance())
        if iface is not None:
            widget_context.setMapCanvas(iface.mapCanvas())
            widget_context.setBrowserModel(iface.browserModel())
            widget_context.setActiveLayer(iface.activeLayer())

        widget_context.setMessageBar(self.parent().messageBar())
        if isinstance(self.algorithm(), QgsProcessingModelAlgorithm):
            widget_context.setModel(self.algorithm())

        in_place_input_parameter_name = "INPUT"
        if hasattr(self.algorithm(), "inputParameterName"):
            in_place_input_parameter_name = self.algorithm().inputParameterName()

        # If there are no parameters to show because it's in-place, we add the info label.
        # Still, it needs the following steps to create the parameter widgets (even when hidden).
        if self.in_place and not [
            d
            for d in self.algorithm().parameterDefinitions()
            if d.name() not in (in_place_input_parameter_name, "OUTPUT")
        ]:
            widget = QWidget(self)
            layout = QVBoxLayout(widget)
            label = QLabel(widget)
            label.setWordWrap(True)
            info_text = self.tr(
                "<i>No additional parameters are required. This algorithm will activate edit mode and modify the features on layer <b>{layername}</b> in place.</i>"
            ).format(layername=self.active_layer.name())
            label.setText(info_text)
            layout.addWidget(label)
            layout.addStretch()
            self.addExtraWidget(widget)

        # Create widgets and put them in layouts
        for param in self.algorithm().parameterDefinitions():
            if param.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden:
                continue

            if param.isDestination():
                continue
            else:
                if self.in_place and param.name() in (
                    in_place_input_parameter_name,
                    "OUTPUT",
                ):
                    # don't show the input/output parameter widgets in in-place mode
                    # we still need to CREATE them, because other wrappers may need to interact
                    # with them (e.g. those parameters which need the input layer for field
                    # selections/crs properties/etc)
                    self.wrappers[param.name()] = QgsProcessingHiddenWidgetWrapper(
                        param, QgsProcessingGui.WidgetType.Standard, self
                    )
                    self.wrappers[param.name()].setLinkedVectorLayer(self.active_layer)
                    continue

                wrapper = WidgetWrapperFactory.create_wrapper(param, self.parent())
                wrapper.setWidgetContext(widget_context)
                wrapper.registerProcessingContextGenerator(self.context_generator)
                wrapper.registerProcessingParametersGenerator(self)
                self.wrappers[param.name()] = wrapper

                # For compatibility with 3.x API, we need to check whether the wrapper is
                # the deprecated WidgetWrapper class. If not, it's the newer
                # QgsAbstractProcessingParameterWidgetWrapper class
                # TODO QGIS 5.0 - remove
                is_python_wrapper = issubclass(wrapper.__class__, WidgetWrapper)
                stretch = 0
                if not is_python_wrapper:
                    widget = wrapper.createWrappedWidget(self.processing_context)
                    wrapper.widgetValueHasChanged.connect(self.parameterChanged)
                    stretch = wrapper.stretch()
                else:
                    widget = wrapper.widget

                if widget is not None:
                    if is_python_wrapper:
                        widget.setToolTip(param.toolTip())

                    label = None
                    if not is_python_wrapper:
                        label = wrapper.createWrappedLabel()
                    else:
                        label = wrapper.label

                    if label is not None:
                        self.addParameterLabel(param, label)
                    elif is_python_wrapper:
                        desc = param.description()
                        if isinstance(param, QgsProcessingParameterExtent):
                            desc += self.tr(" (xmin, xmax, ymin, ymax)")
                        if (
                            param.flags()
                            & QgsProcessingParameterDefinition.Flag.FlagOptional
                        ):
                            desc += self.tr(" [optional]")
                        widget.setText(desc)

                    self.addParameterWidget(param, widget, stretch)

        for output in self.algorithm().destinationParameterDefinitions():
            if output.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden:
                continue

            if self.in_place and output.name() in (
                in_place_input_parameter_name,
                "OUTPUT",
            ):
                continue

            wrapper = QgsGui.processingGuiRegistry().createParameterWidgetWrapper(
                output, QgsProcessingGui.WidgetType.Standard
            )
            wrapper.setWidgetContext(widget_context)
            wrapper.registerProcessingContextGenerator(self.context_generator)
            wrapper.registerProcessingParametersGenerator(self)
            self.wrappers[output.name()] = wrapper

            label = wrapper.createWrappedLabel()
            if label is not None:
                self.addOutputLabel(label)

            widget = wrapper.createWrappedWidget(self.processing_context)
            self.addOutputWidget(widget, wrapper.stretch())

            #    def skipOutputChanged(widget, checkbox, skipped):
            # TODO
            #        enabled = not skipped
            #
            #        # Do not try to open formats that are write-only.
            #        value = widget.value()
            #        if value and isinstance(value, QgsProcessingOutputLayerDefinition) and isinstance(output, (
            #                QgsProcessingParameterFeatureSink, QgsProcessingParameterVectorDestination)):
            #            filename = value.sink.staticValue()
            #            if filename not in ('memory:', ''):
            #                path, ext = os.path.splitext(filename)
            #                format = QgsVectorFileWriter.driverForExtension(ext)
            #                drv = gdal.GetDriverByName(format)
            #                if drv:
            #                    if drv.GetMetadataItem(gdal.DCAP_OPEN) is None:
            #                        enabled = False
            #
            #        checkbox.setEnabled(enabled)
            #        checkbox.setChecked(enabled)

        for wrapper in list(self.wrappers.values()):
            wrapper.postInitialize(list(self.wrappers.values()))

    def createProcessingParameters(
        self, flags=QgsProcessingParametersGenerator.Flags()
    ):
        include_default = not (
            flags & QgsProcessingParametersGenerator.Flag.SkipDefaultValueParameters
        )
        validate = not (flags & QgsProcessingParametersGenerator.Flag.SkipValidation)
        parameters = {}
        for p, v in self.extra_parameters.items():
            parameters[p] = v

        for param in self.algorithm().parameterDefinitions():
            if param.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden:
                continue
            if not param.isDestination():
                try:
                    wrapper = self.wrappers[param.name()]
                except KeyError:
                    continue

                # For compatibility with 3.x API, we need to check whether the wrapper is
                # the deprecated WidgetWrapper class. If not, it's the newer
                # QgsAbstractProcessingParameterWidgetWrapper class
                # TODO QGIS 5.0 - remove
                if issubclass(wrapper.__class__, WidgetWrapper):
                    widget = wrapper.widget
                else:
                    widget = wrapper.wrappedWidget()

                if (
                    not isinstance(wrapper, QgsProcessingHiddenWidgetWrapper)
                    and widget is None
                ):
                    continue

                value = wrapper.parameterValue()
                if param.defaultValue() != value or include_default:
                    parameters[param.name()] = value

                if validate and not param.checkValueIsAcceptable(value):
                    raise InvalidParameterValue(param, widget)
            else:
                if self.in_place and param.name() == "OUTPUT":
                    parameters[param.name()] = "memory:"
                    continue

                try:
                    wrapper = self.wrappers[param.name()]
                except KeyError:
                    continue

                widget = wrapper.wrappedWidget()
                value = wrapper.parameterValue()

                dest_project = None
                if wrapper.customProperties().get("OPEN_AFTER_RUNNING"):
                    dest_project = QgsProject.instance()

                if value and isinstance(value, QgsProcessingOutputLayerDefinition):
                    value.destinationProject = dest_project
                if value and (param.defaultValue() != value or include_default):
                    parameters[param.name()] = value

                    context = createContext()
                    if validate:
                        ok, error = param.isSupportedOutputValue(value, context)
                        if not ok:
                            raise InvalidOutputExtension(widget, error)

        return self.algorithm().preprocessParameters(parameters)

    def setParameters(self, parameters):
        self.extra_parameters = {}
        for param in self.algorithm().parameterDefinitions():
            if param.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden:
                if param.name() in parameters:
                    self.extra_parameters[param.name()] = parameters[param.name()]
                continue

            if not param.name() in parameters:
                continue

            value = parameters[param.name()]

            wrapper = self.wrappers[param.name()]
            wrapper.setParameterValue(value, self.processing_context)

    def parameterChanged(self):
        """
        Called when a parameter value is changed in the panel
        """
        wrapper: QgsAbstractProcessingParameterWidgetWrapper = self.sender()
        default_values = self.algorithm().autogenerateParameterValues(
            self.createProcessingParameters(
                QgsProcessingParametersGenerator.Flag.SkipValidation
            ),
            wrapper.parameterDefinition().name(),
            Qgis.ProcessingMode.Standard,
        )
        if default_values:
            self.setParameters(default_values)
