import math as m

from qgis.core import (
    QgsProcessingParameterEnum,
    QgsProcessingParameterFeatureSource,
    QgsProcessingParameterFeatureSink,
    QgsProcessingParameterFile,
    QgsProcessingParameterFileDestination,
)
import processing

from PreCourlis.lib.mascaret.mascaretgeo_file import MascaretGeoFile
from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm


class GeoreferencingAlgorithm(PreCourlisAlgorithm):

    INPUT = "INPUT"
    TRACKS = "TRACKS"
    ALIGNMENT = "ALIGNMENT"
    OUTPUT_GEOREF = "OUTPUT_GEOREF"
    OUTPUT_LAYER = "OUTPUT_LAYER"

    def initAlgorithm(self, config):
        self.addParameter(
            QgsProcessingParameterFile(self.INPUT, self.tr("Input .geo file"))
        )

        self.addParameter(
            QgsProcessingParameterFeatureSource(self.TRACKS, self.tr("Tracks"))
        )

        self.addParameter(
            QgsProcessingParameterEnum(
                self.ALIGNMENT,
                self.tr("Alignment"),
                options=[
                    self.tr("left"),
                    self.tr("axis"),
                ],
                allowMultiple=False,
                defaultValue=0,
                optional=True,
            )
        )

        self.addParameter(
            QgsProcessingParameterFileDestination(
                self.OUTPUT_GEOREF,
                self.tr("Output .georef file"),
                fileFilter="Georef file (*.georef)",
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT_LAYER, self.tr("Output layer"), optional=True
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        input_path = self.parameterAsString(parameters, self.INPUT, context)
        traces = self.parameterAsSource(parameters, self.TRACKS, context)
        ouput_path = self.parameterAsString(parameters, self.OUTPUT_GEOREF, context)

        # File with a single reach
        input_file = MascaretGeoFile(input_path)

        # Initialise output properties from input file
        output_file = MascaretGeoFile(input_path)

        bief = input_file.reaches[1]

        alignment = self.parameterAsEnum(parameters, self.ALIGNMENT, context)
        if alignment == 0:
            centered_shp = 0
        elif alignment == 1:
            centered_shp = 1

        centered_profile = 0

        total = 100.0 / traces.featureCount() if traces.featureCount() else 0
        features = traces.getFeatures()
        for i, feature in enumerate(features):
            # Take only the first parts (QgsMultiLineString => QgsLineString)
            line = next(feature.geometry().constParts()).clone()
            start_point = line.startPoint()
            end_point = line.endPoint()

            X0, Y0 = start_point.x(), start_point.y()
            X1, Y1 = end_point.x(), end_point.y()

            profil_X = []
            profil_Y = []
            X = X1 - X0
            Y = Y1 - Y0
            distance = m.sqrt((X1 - X0) ** 2.0 + (Y1 - Y0) ** 2.0)

            shift_dist = (
                centered_shp * (distance - bief.sections[i + 1].distances[-1]) / 2.0
            )
            centroid_dist = (
                centered_shp * (distance / 2.0)
                + centered_profile * (bief.sections[i + 1].distances[-1]) / 2.0
            )

            if X > 0 and Y > 0:
                theta = m.acos((X1 - X0) / (distance))

                for dist in bief.sections[i + 1].distances:
                    dist = dist + shift_dist

                    profil_X.append(X0 + dist * m.cos(theta))
                    profil_Y.append(Y0 + dist * m.sin(theta))

                centroid = [
                    X0 + centroid_dist * m.cos(theta),
                    Y0 + centroid_dist * m.sin(theta),
                ]

            elif X < 0 and Y > 0:
                theta = m.acos((Y1 - Y0) / (distance))
                for dist in bief.sections[i + 1].distances:
                    dist = dist + shift_dist

                    profil_X.append(X0 - dist * m.sin(theta))
                    profil_Y.append(Y0 + dist * m.cos(theta))

                centroid = [
                    X0 - centroid_dist * m.sin(theta),
                    Y0 + centroid_dist * m.cos(theta),
                ]

            elif X < 0 and Y < 0:
                theta = m.acos(-(X1 - X0) / (distance))
                for dist in bief.sections[i + 1].distances:
                    dist = dist + shift_dist

                    profil_X.append(X0 - dist * m.cos(theta))
                    profil_Y.append(Y0 - dist * m.sin(theta))

                centroid = [
                    X0 - centroid_dist * m.cos(theta),
                    Y0 - centroid_dist * m.sin(theta),
                ]

            else:
                theta = m.asin((X1 - X0) / (distance))
                for dist in bief.sections[i + 1].distances:
                    dist = dist + shift_dist

                    profil_X.append(X0 + dist * m.sin(theta))
                    profil_Y.append(Y0 - dist * m.cos(theta))

                centroid = [
                    X0 + centroid_dist * m.sin(theta),
                    Y0 - centroid_dist * m.cos(theta),
                ]

            output_file.reaches[1].sections[i + 1].x = profil_X
            output_file.reaches[1].sections[i + 1].y = profil_Y
            output_file.reaches[1].sections[i + 1].set_axis(centroid[0], centroid[1])

            # Update the progress bar
            feedback.setProgress(int(i * total))

        output_file.has_ref = True
        output_file.save(ouput_path)

        outputs = {}

        # Import resulting georef file
        alg_params = {
            "INPUT": ouput_path,
            "CRS": self.parameterAsCrs(parameters, self.TRACKS, context),
            "OUTPUT": parameters[self.OUTPUT_LAYER],
        }
        outputs["import_georef"] = processing.run(
            "precourlis:import_georef",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        return {
            self.OUTPUT_GEOREF: ouput_path,
            self.OUTPUT_LAYER: outputs["import_georef"]["OUTPUT"],
        }

    def name(self):
        return "georeferencing"

    def displayName(self):
        return self.tr("georeferencing")

    def group(self):
        return self.tr(self.groupId())

    def groupId(self):
        return "Import"

    def createInstance(self):
        return GeoreferencingAlgorithm()
