from qgis.core import (
    QgsProcessing,
    QgsProcessingParameterVectorLayer,
    QgsProcessingParameterExpression,
    QgsProcessingMultiStepFeedback,
    QgsProcessingParameterFeatureSource,
)
from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm

import processing


class DefineTopoBathAlgorithm(PreCourlisAlgorithm):
    SECTIONS = "SECTIONS"
    POLYGONS = "POLYGONS"
    TOPO_BATH_EXPRESSION = "TOPO_BATH_EXPRESSION"

    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                self.SECTIONS,
                self.tr("Sections"),
                types=[QgsProcessing.TypeVectorLine],
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.POLYGONS,
                self.tr("Polygons"),
                types=[QgsProcessing.TypeVectorPolygon],
            )
        )
        self.addParameter(
            QgsProcessingParameterExpression(
                self.TOPO_BATH_EXPRESSION,
                self.tr("Topography / bathymetry expression"),
                parentLayerParameterName=self.POLYGONS,
            )
        )

    def processAlgorithm(self, parameters, context, model_feedback):
        from qgis.core import QgsVectorLayer

        sections: QgsVectorLayer = self.parameterAsVectorLayer(
            parameters, self.SECTIONS, context
        )
        topo_bath_expression = self.parameterAsExpression(
            parameters, self.TOPO_BATH_EXPRESSION, context
        )

        outputs = {}
        feedback = QgsProcessingMultiStepFeedback(7, model_feedback)
        TOPO_BAT_ATTRIBUTE_NAME = "topo_bat"

        # Convert lines to points:
        feedback.setProgressText(self.tr("Converting sections lines to points:"))

        alg_params = {"INPUT": sections, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT}
        outputs["lines_to_points"] = processing.run(
            "precourlis:lines_to_points",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        # Delete old topo_bat attribute:
        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}
        feedback.setProgressText(self.tr("Deleting old topo_bat attribute:"))

        alg_params = {
            "INPUT": outputs["lines_to_points"]["OUTPUT"],
            "COLUMN": [TOPO_BAT_ATTRIBUTE_NAME],
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }
        outputs["drop_topo_bat_field"] = processing.run(
            "native:deletecolumn",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        # Evalutate expression for polygons location
        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}
        feedback.setProgressText(self.tr("Evaluating expression on polygons:"))

        alg_params = {
            "INPUT": parameters[self.POLYGONS],
            "FIELD_NAME": TOPO_BAT_ATTRIBUTE_NAME,
            "FIELD_TYPE": 2,  # String
            "FIELD_LENGTH": 1,
            "FIELD_PRECISION": 0,
            "FORMULA": topo_bath_expression,
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }
        outputs["evaluate_expression"] = processing.run(
            "native:fieldcalculator",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        # Join topo_bath attribute by polygons location:
        feedback.setCurrentStep(3)
        if feedback.isCanceled():
            return {}
        feedback.setProgressText(self.tr("Joining topo_bath attribute:"))

        alg_params = {
            "DISCARD_NONMATCHING": False,
            "INPUT": outputs["drop_topo_bat_field"]["OUTPUT"],
            "JOIN": outputs["evaluate_expression"]["OUTPUT"],
            "JOIN_FIELDS": [TOPO_BAT_ATTRIBUTE_NAME],
            "METHOD": 0,  # Create separate feature for each matching feature (one-to-many)
            "PREDICATE": [0],  # intersects
            "PREFIX": "",
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }

        outputs["join_attributes_by_location"] = processing.run(
            "native:joinattributesbylocation",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        # Refactor fields to move topo_bat back to id 7
        feedback.setCurrentStep(4)
        if feedback.isCanceled():
            return {}
        feedback.setProgressText(self.tr("Fields refactoring:"))

        alg_params = {
            "INPUT": outputs["join_attributes_by_location"]["OUTPUT"],
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
            "FIELDS_MAPPING": [
                {
                    "expression": '"sec_id"',
                    "length": 0,
                    "name": "sec_id",
                    "precision": 0,
                    "type": 2,
                },
                {
                    "expression": '"sec_name"',
                    "length": 80,
                    "name": "sec_name",
                    "precision": 0,
                    "type": 10,
                },
                {
                    "expression": '"abs_long"',
                    "length": 0,
                    "name": "abs_long",
                    "precision": 0,
                    "type": 6,
                },
                {
                    "expression": '"axis_x"',
                    "length": 0,
                    "name": "axis_x",
                    "precision": 0,
                    "type": 6,
                },
                {
                    "expression": '"axis_y"',
                    "length": 0,
                    "name": "axis_y",
                    "precision": 0,
                    "type": 6,
                },
                {
                    "expression": '"layers"',
                    "length": 254,
                    "name": "layers",
                    "precision": 0,
                    "type": 10,
                },
                {
                    "expression": '"p_id"',
                    "length": 0,
                    "name": "p_id",
                    "precision": 0,
                    "type": 2,
                },
                {
                    "expression": '"topo_bat"',
                    "length": 0,
                    "name": "topo_bat",
                    "precision": 0,
                    "type": 10,
                },
                {
                    "expression": '"abs_lat"',
                    "length": 0,
                    "name": "abs_lat",
                    "precision": 0,
                    "type": 6,
                },
                {
                    "expression": '"x"',
                    "length": 0,
                    "name": "x",
                    "precision": 0,
                    "type": 6,
                },
                {
                    "expression": '"y"',
                    "length": 0,
                    "name": "y",
                    "precision": 0,
                    "type": 6,
                },
                {
                    "expression": '"zfond"',
                    "length": 0,
                    "name": "zfond",
                    "precision": 0,
                    "type": 6,
                },
                {
                    "expression": '"Layer1"',
                    "length": 0,
                    "name": "Layer1",
                    "precision": 0,
                    "type": 6,
                },
            ],
        }

        outputs["refactorfields"] = processing.run(
            "native:refactorfields",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        # Convert points back to lines
        feedback.setCurrentStep(5)
        if feedback.isCanceled():
            return {}
        feedback.setProgressText(self.tr("Converting points back to lines:"))

        alg_params = {
            "INPUT": outputs["refactorfields"]["OUTPUT"],
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }
        outputs["points_to_lines"] = processing.run(
            "precourlis:points_to_lines",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        # Apply changes to sections
        feedback.setCurrentStep(6)
        if feedback.isCanceled():
            return {}
        feedback.setProgressText(self.tr("Applying changes to sections:"))

        # Notes: This algorithm directly edit input layer in place using edit buffer
        sections.startEditing()
        sections.beginEditCommand("Define topography / bathymetry from a polygon")

        temp_layer = context.getMapLayer(outputs["points_to_lines"]["OUTPUT"])
        topo_bat_field_index = sections.fields().indexFromName(TOPO_BAT_ATTRIBUTE_NAME)

        for new_feature, actual_feature in zip(
            temp_layer.getFeatures(), sections.getFeatures()
        ):
            new_topo_bat_values = new_feature[TOPO_BAT_ATTRIBUTE_NAME].split(",")
            actual_topo_bat_values = actual_feature[TOPO_BAT_ATTRIBUTE_NAME].split(",")

            new_list = [
                new_topo_bat if new_topo_bat in ("B", "T") else actual_topo_bat
                for new_topo_bat, actual_topo_bat in zip(
                    new_topo_bat_values, actual_topo_bat_values
                )
            ]

            sections.changeAttributeValue(
                actual_feature.id(),
                topo_bat_field_index,
                ",".join(new_list),
            )

        sections.endEditCommand()

        return {}

    def name(self):
        return "define_topo_bath"

    def displayName(self):
        return self.tr("Define topography / bathymetry from a polygon")

    def group(self):
        return self.tr("Profiles")

    def groupId(self):
        return "Profiles"

    def createInstance(self):
        return DefineTopoBathAlgorithm()

    def shortHelpString(self):
        return self.tr(
            "This algorithm update the topo_bath attribute of the input sections layer "
            "according to their location inside the provided polygons source, or selection."
            "\n"
            "Each polygons should either have a Topography / Bathymetry expression set as an "
            "attribute or we should give to the algorithm a default value ('T' or 'B')."
            "\n\n"
            "It writes the result directly on the input sections layer."
        )
