from qgis.core import QgsFeatureSink
from qgis.core import QgsField
from qgis.core import QgsProcessing
from qgis.core import QgsProcessingException
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterFeatureSource
from qgis.core import QgsProcessingParameterField
from qgis.core import QgsProcessingParameterDistance
from qgis.core import QgsProcessingParameterFeatureSink
from qgis.core import QgsProcessingUtils
from qgis.core import QgsWkbTypes
from qgis.PyQt.QtCore import QVariant
import processing

from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm


class JoinPointsToProfilesAlgorithm(PreCourlisAlgorithm):
    SOURCE_POINTS = "SOURCE_POINTS"
    TARGET_PROFILES = "TARGET_PROFILES"
    TARGET_PROFILES_FIELD_SEC_ID = "TARGET_PROFILES_FIELD_SEC_ID"
    BUFFER_DISTANCE = "BUFFER_DISTANCE"
    OUTPUT = "OUTPUT"

    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.SOURCE_POINTS,
                self.tr("Source points"),
                types=[QgsProcessing.TypeVectorPoint],
                defaultValue=None,
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.TARGET_PROFILES,
                self.tr("Target profiles"),
                types=[QgsProcessing.TypeVectorLine],
                defaultValue=None,
            )
        )
        self.addParameter(
            QgsProcessingParameterField(
                self.TARGET_PROFILES_FIELD_SEC_ID,
                self.tr("Target profiles unique identifier field"),
                parentLayerParameterName=self.TARGET_PROFILES,
                defaultValue="sec_id",
                optional=True,
            )
        )
        self.addParameter(
            QgsProcessingParameterDistance(
                self.BUFFER_DISTANCE,
                self.tr("Buffer distance"),
                parentParameterName="profils",
                minValue=0.0,
                maxValue=1000.0,
                defaultValue=10.0,
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr("Points prepared"),
            ),
            True,
        )

    def processAlgorithm(self, parameters, context, model_feedback):
        feedback = QgsProcessingMultiStepFeedback(3, model_feedback)
        outputs = {}

        target_profiles_field_sec_id = self.parameterAsFields(
            parameters, self.TARGET_PROFILES_FIELD_SEC_ID, context
        )

        source_points_source = self.parameterAsSource(
            parameters, self.SOURCE_POINTS, context
        )
        output_fields = source_points_source.fields()
        output_fields.append(QgsField("sec_name", QVariant.String, len=80))

        sink, dest_id = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            output_fields,
            QgsWkbTypes.Point,
            source_points_source.sourceCrs(),
        )
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        # Buffer on target profiles
        alg_params = {
            "DISSOLVE": False,
            "DISTANCE": parameters[self.BUFFER_DISTANCE],
            "END_CAP_STYLE": 0,  # Rond
            "INPUT": parameters[self.TARGET_PROFILES],
            "JOIN_STYLE": 0,  # Rond
            "MITER_LIMIT": 2,
            "SEGMENTS": 5,
            "SEPARATE_DISJOINT": False,
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }
        outputs["Buffer"] = processing.run(
            "native:buffer",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # Join by location
        outputs["JoinAttributesByLocation"] = processing.run(
            "native:joinattributesbylocation",
            {
                "INPUT": parameters[self.SOURCE_POINTS],
                "PREDICATE": [0],
                "JOIN": outputs["Buffer"]["OUTPUT"],
                "JOIN_FIELDS": target_profiles_field_sec_id,
                "METHOD": 0,
                "DISCARD_NONMATCHING": False,
                "PREFIX": "",
                "OUTPUT": "TEMPORARY_OUTPUT",
                "NON_MATCHING": "TEMPORARY_OUTPUT",
            },
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        joined_points_layer = QgsProcessingUtils.mapLayerFromString(
            outputs["JoinAttributesByLocation"]["OUTPUT"],
            context,
        )
        orphan_points_layer = QgsProcessingUtils.mapLayerFromString(
            outputs["JoinAttributesByLocation"]["NON_MATCHING"],
            context,
        )

        sink.addFeatures(joined_points_layer.getFeatures(), QgsFeatureSink.FastInsert)

        # Keep orphan points
        sink.addFeatures(orphan_points_layer.getFeatures(), QgsFeatureSink.FastInsert)

        return {self.OUTPUT: dest_id}

    def name(self):
        return "join_points_to_profiles"

    def displayName(self):
        return self.tr("Join points to profiles")

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

    def groupId(self):
        return "Import"

    def createInstance(self):
        return JoinPointsToProfilesAlgorithm()

    def shortHelpString(self):
        return self.tr(
            "This algorithm create a relation between points and profiles"
            " using a buffer around profiles."
        )
