# -*- coding: utf-8 -*-

"""
/***************************************************************************
 ForestRoads
                                 A QGIS plugin
 Create a network of forest roads based on zones to access, roads to connect
 them to, and a cost matrix.
 The code of the plugin is based on the "LeastCostPath" plugin available on
 https://github.com/Gooong/LeastCostPath. We thank their team for the template.
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 10-07-2019
        copyright            : (C) 2019 by Clement Hardy
        email                : clem.hardy@outlook.fr
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
 This script describes the algorithm used to determine the type of a road in
 a forest road network, given that all of these roads are associated to a flux of
 wood that have been calculated in advance.
"""

__author__ = 'clem.hardy@outlook.fr'
__date__ = 'Currently in work'
__copyright__ = '(C) 2019 by Clement Hardy'

# We load every function necessary from the QIS packages.
import random
import queue
import math
from PyQt5.QtCore import QCoreApplication, QVariant
from PyQt5.QtGui import QIcon
from qgis.core import (
    QgsFeature,
    QgsGeometry,
    QgsPoint,
    QgsPointXY,
    QgsField,
    QgsFields,
    QgsWkbTypes,
    QgsProcessing,
    QgsFeatureSink,
    QgsProcessingException,
    QgsProcessingAlgorithm,
    QgsProcessingParameterFeatureSource,
    QgsProcessingParameterField,
    QgsProcessingParameterFeatureSink,
    QgsProcessingParameterRasterLayer,
    QgsProcessingParameterBand,
    QgsProcessingParameterBoolean,
    QgsProcessingParameterNumber,
    QgsProcessingParameterMatrix,
    QgsProcessingParameterEnum,
    QgsProcessingParameterString
)


# The algorithm class heritates from the algorithm class of QGIS.
# There, it can register different parameter during initialization
# that can be put into variables using "
class roadTypeAlgorithm(QgsProcessingAlgorithm):
    """
    Class that described the algorithm, that will be launched
    via the provider, itself launched via initialization of
    the plugin.
    """

    # Constants used to refer to parameters and outputs. They will be
    # used when calling the algorithm from another algorithm, or when
    # calling from the QGIS console.

    INPUT_ROAD_NETWORK = 'INPUT_ROAD_NETWORK'

    FLUX_FIELD = 'FLUX_FIELD'

    ROAD_TYPES_AND_THRESHOLDS = 'ROAD_TYPES_AND_THRESHOLDS'

    TEMPORARY_ROAD_NAME = 'TEMPORARY_ROAD_NAME'

    TEMPORARY_ROAD_PERCENTAGE = 'TEMPORARY_ROAD_PERCENTAGE'

    TEMPORARY_ROADS_PRIORITY_POLYGONS = 'TEMPORARY_ROADS_PRIORITY_POLYGONS'

    TEMPORARY_ROADS_PRIORITY_FIELD = 'TEMPORARY_ROADS_PRIORITY_FIELD'

    OUTPUT = 'OUTPUT'

    def initAlgorithm(self, config):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties. Theses will be asked to the user.
        """

        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT_ROAD_NETWORK,
                self.tr('Forest Road network whose road types must be determined'),
                [QgsProcessing.TypeVectorLine]
            )
        )

        self.addParameter(
            QgsProcessingParameterField(
                self.FLUX_FIELD,
                self.tr('Attribute field of the forest road network layer that contains the flux information'),
                parentLayerParameterName=self.INPUT_ROAD_NETWORK,
                type=QgsProcessingParameterField.Numeric,
            )
        )


        self.addParameter(
            QgsProcessingParameterMatrix(self.ROAD_TYPES_AND_THRESHOLDS,
                                         "The different road types you want to use, and the associated thresholds of wood flux to select one of them",
                                         numberRows=3,
                                         hasFixedNumberRows=False,
                                         headers=["Lower threshold", "Upper threshold", "Road type name"],
                                         defaultValue=['0', '500', 'Tertiary',
                                                       '500', '5000', 'Secondary',
                                                       '5000', '1000000', 'Primary'],
                                         optional=False)
        )

        self.addParameter(
            QgsProcessingParameterString(
                self.TEMPORARY_ROAD_NAME,
                self.tr('Name of the road type that can is available to become temporary roads (leave empty for no temporary roads)'),
                multiLine = False,
                optional=True
            )
        )

        self.addParameter(
            QgsProcessingParameterNumber(
                self.TEMPORARY_ROAD_PERCENTAGE,
                self.tr('Percentage of temporary roads that can be accommodated as temporary roads (e.g. winter roads)'),
                type=QgsProcessingParameterNumber.Double,
                defaultValue=50,
                optional=True,
                minValue=0
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.TEMPORARY_ROADS_PRIORITY_POLYGONS,
                self.tr('Polygons that will indicates the zones where temporary roads can be built'),
                [QgsProcessing.TypeVectorPolygon],
                optional=True
            )
        )

        self.addParameter(
            QgsProcessingParameterField(
                self.TEMPORARY_ROADS_PRIORITY_FIELD,
                self.tr('Attribute field of the previous polygon layer defining in which polygon should the temporary roads be build in priority'),
                parentLayerParameterName=self.TEMPORARY_ROADS_PRIORITY_POLYGONS,
                type=QgsProcessingParameterField.Numeric,
                optional=True
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Output file of the algorithm')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        road_network = self.parameterAsVectorLayer(
            parameters,
            self.INPUT_ROAD_NETWORK,
            context
        )

        flux_field_index = road_network.fields().lookupField(
            self.parameterAsString(
            parameters,
            self.FLUX_FIELD,
            context
            )
        )

        road_type_and_thresholds = self.parameterAsMatrix(
            parameters,
            self.ROAD_TYPES_AND_THRESHOLDS,
            context
        )

        road_type_and_thresholds = RoadTypeDeterminationHelper.CollapsedTableToMatrix(road_type_and_thresholds, 3)

        if self.parameterAsString(
                parameters,
                self.TEMPORARY_ROAD_NAME,
                context
        ):
            temporary_road_name = self.parameterAsString(
                parameters,
                self.TEMPORARY_ROAD_NAME,
                context
                )
        else:
            temporary_road_name = None

        if self.parameterAsInt(
            parameters,
            self.TEMPORARY_ROAD_PERCENTAGE,
            context
            ):
            temporaryRoadPercentage = self.parameterAsInt(
            parameters,
            self.TEMPORARY_ROAD_PERCENTAGE,
            context
            )
        else:
            temporaryRoadPercentage = None

        if self.parameterAsVectorLayer(
            parameters,
            self.TEMPORARY_ROADS_PRIORITY_POLYGONS,
            context
        ):
                temporary_roads_priority_polygons = self.parameterAsVectorLayer(
                parameters,
                self.TEMPORARY_ROADS_PRIORITY_POLYGONS,
                context
                )
        else:
            temporary_roads_priority_polygons = None

        if self.parameterAsString(
            parameters,
            self.TEMPORARY_ROADS_PRIORITY_FIELD,
            context
            ):
                priority_field_index = temporary_roads_priority_polygons.fields().lookupField(
                self.parameterAsString(
                    parameters,
                    self.TEMPORARY_ROADS_PRIORITY_FIELD,
                    context
                    )
                )
        else:
            priority_field_index = None

        # If source was not found, throw an exception to indicate that the algorithm
        # encountered a fatal error. The exception text can be any string, but in this
        # case we use the pre-built invalidSourceError method to return a standard
        # helper text for when a source cannot be evaluated
        if road_network is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_ROAD_NETWORK))
        if flux_field_index is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.FLUX_FIELD))
        if road_type_and_thresholds is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.TERTIARY_SECONDARY_THRESHOLD))

        # We try to see if there are divergence between the CRSs of the inputs
        if temporary_roads_priority_polygons is not None:
            if road_network.crs() != temporary_roads_priority_polygons.sourceCrs():
                raise QgsProcessingException(self.tr("ERROR: The input layers have different CRSs."))

        RoadTypeDeterminationHelper.CheckThresholds(road_type_and_thresholds, "Road types and thresholds")

        if temporaryRoadPercentage < 0 or temporaryRoadPercentage > 100:
            raise QgsProcessingException(self.tr("ERROR: Temporary road percentage is not between 0 and 100."))

        # We initialize the "sink", an object that will make use able to create an output.
        # First, we create the fields for the attributes of our lines as outputs.
        # They will only have one field :
        sink_fields = RoadTypeDeterminationHelper.create_fields()
        # We indicate that our output will be a line, stored in WBK format.
        output_geometry_type = QgsWkbTypes.LineString
        # Finally, we create the field object and register the destination ID of it.
        # This sink will be based on the OUTPUT parameter we registered during initialization,
        # will have the fields and the geometry type we just created, and the same CRS as the cost raster.
        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            fields=sink_fields,
            geometryType=output_geometry_type,
            crs=road_network.crs(),
        )

        # If sink was not created, throw an exception to indicate that the algorithm
        # encountered a fatal error. The exception text can be any string, but in this
        # case we use the pre-built invalidSinkError method to return a standard
        # helper text for when a sink cannot be evaluated
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        feedback.pushInfo(self.tr("Checking attributes..."))
        # We get the features of the lines
        lineFeaturesOfRoadNetwork = list(road_network.getFeatures())

        # We check if each line have a flux value associated with it
        progress = 0
        feedback.setProgress(0)
        for line in lineFeaturesOfRoadNetwork:
            attributesOfLine = line.attributes()
            if attributesOfLine[flux_field_index] == 0 or attributesOfLine[flux_field_index] is None:
                raise QgsProcessingException(self.tr("ERROR: Some lines are missing a flux value ! Please make sure" +
                                                     " that all lines have a value of flux of wood superior to 0 in " +
                                                     "the field that you indicated."))
            progress += 1
            feedback.setProgress(100 * (progress / len(lineFeaturesOfRoadNetwork)))

        # If the user gave priority polygons, we also check them to see if one of them is missing a priority
        progress = 0
        feedback.setProgress(0)
        if temporary_roads_priority_polygons is not None and priority_field_index is not None:
            polygoneFeatures = list(temporary_roads_priority_polygons.getFeatures())

            for polygon in polygoneFeatures:
                attributesOfPolygon = polygon.attributes()
                if attributesOfPolygon[priority_field_index] == 0 or attributesOfPolygon[priority_field_index] is None:
                    raise QgsProcessingException(
                        self.tr("ERROR: Some polygons are missing a priority value ! Please make sure" +
                                " that all polygons have a value of priority for temporary roads superior to 0 in " +
                                "the field that you indicated."))
                progress += 1
                feedback.setProgress(100 * (progress / len(polygoneFeatures)))

        feedback.pushInfo(self.tr("Calculating the broad types of roads..."))
        progress = 0
        feedback.setProgress(0)
        errorMessages = list()
        # We now determine the broad type for each road.
        typeOfRoad = dict()

        for line in lineFeaturesOfRoadNetwork:
            attributesOfLine = line.attributes()

            typeOfRoad[line], errorMessage = RoadTypeDeterminationHelper.GetThresholdValue(road_type_and_thresholds,
                                                                             attributesOfLine[flux_field_index])
            if errorMessage is not None:
                errorMessages.append(errorMessage)

            progress += 1
            feedback.setProgress(100 * (progress / len(lineFeaturesOfRoadNetwork)))

        if temporary_road_name is not None and (temporaryRoadPercentage is not None and temporaryRoadPercentage != 0):
            progress = 0
            feedback.setProgress(0)
            # Now, to know which tertiary road can become temporary ones
            # First, we get all of the roads that have been indicated as tertiary.
            potentialTemporaryRoads = list()
            for keyLine in typeOfRoad:
                if typeOfRoad[keyLine] == temporary_road_name:
                    potentialTemporaryRoads.append(keyLine)

            feedback.pushInfo(self.tr("Calculating the temporary roads..."))
            # Then, there is the case where we do not have polygons to give us priority zones where temporary roads should
            # be. If that's the case, we order our list via the length of each line.
            if temporary_roads_priority_polygons is None and temporaryRoadPercentage > 0:
                # Smallest roads first ! It's intuitive that small roads can easily be made into temporary roads
                potentialTemporaryRoads.sort(key=lambda x: x.geometry().length())

            # If we have priority polygons, we will get the priority value of each tertiary road and use it to sort our list
            elif temporary_roads_priority_polygons is not None \
                    and priority_field_index is not None \
                    and temporaryRoadPercentage > 0:
                polygoneFeatures = list(temporary_roads_priority_polygons.getFeatures())
                potentialTemporaryRoads = RoadTypeDeterminationHelper.SortWithPriorityPolygons(potentialTemporaryRoads,
                                                                                    polygoneFeatures,
                                                                                    priority_field_index)

            if temporaryRoadPercentage > 0:
                # We loop and convert the lines until we reach the given percentage of length of tertiary roads that have
                # been converted
                percentageOfLengthConverted = 0
                index = 0
                totalLengthOfTertiaryRoads = 0
                for road in potentialTemporaryRoads:
                    totalLengthOfTertiaryRoads += road.geometry().length()

                while percentageOfLengthConverted < temporaryRoadPercentage and index < len(potentialTemporaryRoads):
                    currentRoad = potentialTemporaryRoads[index]
                    # If we have polygons indicating where the temporary can be build, we look if the road is inside one of them.
                    if temporary_roads_priority_polygons is not None:
                        if RoadTypeDeterminationHelper.IsThisLineInAPolygon(currentRoad, temporary_roads_priority_polygons):
                            typeOfRoad[currentRoad] = "Temporary"
                            percentageOfLengthConverted += (currentRoad.geometry().length() / totalLengthOfTertiaryRoads) * 100
                    else:
                        typeOfRoad[currentRoad] = "Temporary"
                        percentageOfLengthConverted += (currentRoad.geometry().length() / totalLengthOfTertiaryRoads) * 100

                    index += 1
                    progress += 1
                    feedback.setProgress(100 * (percentageOfLengthConverted / temporaryRoadPercentage))

        feedback.pushInfo(self.tr("Creating the output..."))
        # Once that all of this is done, we prepare the output.
        # For every path we create, we save it as a line and put it into the sink !
        ID = 0
        for line in lineFeaturesOfRoadNetwork:
            # With the total cost which is the last item in our accumulated cost list,
            # we create the PolyLine that will be returned as a vector.
            if line.geometry().wkbType() == QgsWkbTypes.MultiLineString:
                path_feature = RoadTypeDeterminationHelper.create_path_feature_from_Multipolyline(line.geometry().asMultiPolyline(),
                                                                                           ID,
                                                                                           line.attributes()[
                                                                                               flux_field_index],
                                                                                           typeOfRoad[line],
                                                                                           sink_fields, )
            elif line.geometry().wkbType() == QgsWkbTypes.LineString:
                path_feature = RoadTypeDeterminationHelper.create_path_feature_from_polyline(line.geometry().asPolyline(),
                                                                                            ID,
                                                                                            line.attributes()[
                                                                                               flux_field_index],
                                                                                            typeOfRoad[line],
                                                                                            sink_fields, )
            else:
                raise QgsProcessingException(self.tr("ERROR: Geometry type of a feature not line or multiline."))
            # Into the sink that serves as our output, we put the PolyLines from the list of lines we created
            # one by one
            sink.addFeature(path_feature, QgsFeatureSink.FastInsert)
            ID += 1

        # We display the warning messages about the thresholds
        if len(errorMessages) > 0:
            above = 0
            below = 0
            for errorMessage in errorMessages:
                if errorMessage == "Above":
                    above += 1
                elif errorMessage == "Below":
                    below += 1
            feedback.pushInfo(
                self.tr("WARNING : There were " + str(below) + " situations where the value of a pixel was under"
                                                               " the lowest threshold given as a parameter; and " + str(
                    above) + " when it was above the"
                             " highest. In those cases, the lowest or highest value of the parameter range was used."
                             " To avoid that, please make sure to use thresholds that cover all of the range of values in"
                             " your rasters."))

        return {self.OUTPUT: dest_id}

    # Here are different functions used by QGIS to name and define the algorithm
    # to the user.
    def name(self):
        """
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'Road Type Determination'

    def displayName(self):
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return self.tr(self.name())

    def group(self):
        """
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        """
        return self.tr(self.groupId())

    def groupId(self):
        """
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        # Not enough algorithms in this plugin for the need to make groups of algorithms
        return ''

    # Function used for translation. Called every time something needs to be
    # Translated
    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return roadTypeAlgorithm()

    def helpUrl(self):
        return 'https://github.com/Klemet/ForestRoadNetworkPluginForQGIS'

    def shortHelpString(self):
        return self.tr("""
        This algorithm determine the type of given forest roads according to the flux of wood traversing them. The wood flux can be calculated via the use of the "Wood Flux Determination" algorithm.
        
        **Parameters:**

          Please ensure all the input layers have the same CRS.

          - Road Network : a forest road network with an attribute field corresponding to the wood flux going to each of the line of network.
          
          - Flux field : The attribute field of the road network layer containing the information about the wood flux going through the lines.
          
          - Types of roads to use : A table describing the different type or classes of roads to define in the network, with a upper and a lower threshold of wood volume for each. Thresholds should have have any hole between them, and should be ordered in an increasing manner.

          - Name of the type of road that can be made temporary : A name corresponding to one of the type or class of roads indicated before that could be transformed into temporary roads. If nothing is entered, no temporary roads will be created.
            
          - Percentage of temporary roads that can be accommodated as temporary roads (e.g. winter roads) : straight-forward.

          - Polygons that will indicates the zones where temporary roads can be built (optional) : straight-forward.

          - Attribute field of the previous polygon layer defining in which polygon should the temporary roads be build first (optional) : the higher the number, the higher the priority will be. If no priority polygons or fields are indicated, then the algorithm will prioritize small roads for becoming temporary.
 
        """)

    def shortDescription(self):
        return self.tr('Determine the type of forest roads according to the wood flux traversing them.')

    # Path to the icon of the algorithm
    def svgIconPath(self):
        return '.icon.png'

    def tags(self):
        return ['wood', 'flux', 'type', 'primary', 'secondary', 'roads', 'analysis', 'road', 'network',
                'forest', 'tertiary', 'temporary']


# Methods to help the algorithm; all static, do not need to initialize an object of this class.
class RoadTypeDeterminationHelper:

    @staticmethod
    def CollapsedTableToMatrix(matrixOfThresholds, numberOfColumns):
        """This function exist to counter the fact that QGIS collapses the matrix of parameters into a list. It puts
        it back into a proper matrix, meaning a list of lists. It also transforms the strings in the matrix into numbers
        where needed."""

        numberOfRows = len(matrixOfThresholds) // numberOfColumns
        matrixToReturn = list()

        for indexing in range(numberOfRows):
            matrixToReturn.append(
                matrixOfThresholds[(indexing * numberOfColumns):(indexing * numberOfColumns + numberOfColumns)])

        # We convert only the threshold values into floats; the last value of each line is the road type as a string.
        for index in range(len(matrixToReturn)):
            matrixToReturn[index][0] = float(matrixToReturn[index][0])
            matrixToReturn[index][1] = float(matrixToReturn[index][1])

        return matrixToReturn

    @staticmethod
    def CheckThresholds(matrixOfThresholds, thresholdsName):
        """This function check if there are no holes in threshold parameters, and if every lower threshold is inferior
        to the associated upper threshold. If not, we raise an exception."""

        for index in range(len(matrixOfThresholds)):
            lowerThreshold = matrixOfThresholds[index][0]
            upperThreshold = matrixOfThresholds[index][1]
            if lowerThreshold >= upperThreshold:
                raise QgsProcessingException("ERROR: For the parameter " + str(thresholdsName) + ", the lower threshold of"
                                            " row " + str(index) + " is bigger or equal to the upper threshold of the same row."
                                            " Please, correct it before trying again.")
            if (index + 1) < len(matrixOfThresholds):
                lowerThresholdOfHigherRow = matrixOfThresholds[index + 1][0]
                if (lowerThresholdOfHigherRow - upperThreshold) != 0:
                    raise QgsProcessingException(
                        "ERROR: For the parameter " + str(thresholdsName) + ", there is a hole between the upper threshold of"
                        " row " + str(index) + " and the lower threshold of row " + str(index+1) + ". The thresholds must"
                        " be continuous and ordered from smaller to higher. Please, correct it before trying again.")

    @staticmethod
    def GetThresholdValue(matrixOfThresholds, valueInsideThresholds):
        """This function gets the associated value (additional value or multiplicative value) inside a matrix of thresholds
        (third column) given a value that will be inside one of the thresholds. Is also register error messages to warn
        the user about values that are not into its thresholds."""

        thresholdValue = None
        errorMessage = None

        # First, we treat the case of our value being under the lowest threshold, or higher than the highest threshold.
        if valueInsideThresholds < matrixOfThresholds[0][0]:
            thresholdValue = matrixOfThresholds[0][2]
            errorMessage = "Below"
        elif valueInsideThresholds > matrixOfThresholds[len(matrixOfThresholds) - 1][1]:
            thresholdValue = matrixOfThresholds[len(matrixOfThresholds) - 1][2]
            errorMessage = "Above"
        else:
            for index in range(len(matrixOfThresholds)):
                if valueInsideThresholds >= matrixOfThresholds[index][0] and valueInsideThresholds < \
                        matrixOfThresholds[index][1]:
                    thresholdValue = matrixOfThresholds[index][2]
                    break

        if thresholdValue is None:
            raise QgsProcessingException(
                "ERROR: Couldn't find value " + str(valueInsideThresholds) + " in the following"
                                                                             " matrix of thresholds : " + str(
                    matrixOfThresholds))
        else:
            return thresholdValue, errorMessage

    @staticmethod
    def SortWithPriorityPolygons(listOfLines, priorityPolygons, priorityFieldIndex):
        """This function sorts a list of roads according to the priority polygon
        they are into the most"""

        priorityOfLines = dict()

        for line in listOfLines:
            # we initialize useful variables
            containedInAPolygon = False
            meanPriorityValue = 0
            lineFeatures = list()

            # We take all of the lines present in this line feature

            # Case of multiline
            if line.geometry().wkbType() == QgsWkbTypes.MultiLineString:
                multi_line = line.geometry().asMultiPolyline()
                for singleLine in multi_line:
                    lineFeatures.append(singleLine)

            # Case of lines
            elif line.geometry().wkbType() == QgsWkbTypes.LineString:
                lineFeatures.append(line.geometry().asPolyline())

            numberOfPoints = 0
            for lineFeature in lineFeatures:
                for point in lineFeature:
                    numberOfPoints += 1

            # For each priority polygon, we look if a point of the line is inside of it. If it is, we add the priority
            # value of the polygon in the mean priority value of this line
            for polygon in priorityPolygons:
                for lineFeature in lineFeatures:
                    for point in lineFeature:
                        if polygon.geometry().contains(point):
                            meanPriorityValue += polygon.attributes()[priorityFieldIndex]
            # We end things by making a floor division for the mean priority value by the number of points in the line.
            meanPriorityValue = meanPriorityValue // numberOfPoints
            priorityOfLines[line] = meanPriorityValue

        # Higher value of priority = higher priority ! So we reverse-sort the list.
        sortedListOfLines = sorted(listOfLines, key=lambda x: priorityOfLines[x], reverse=True)

        return sortedListOfLines

    @staticmethod
    def IsThisLineInAPolygon(line, polygonLayer):
        """Check if a line is entirely inside a polygon layer."""

        polygongFeatures = polygonLayer.getFeatures()
        lineGeometry = line.geometry()
        isInPolygonLayer = False

        for polygon in polygongFeatures:
            if polygon.geometry().contains(lineGeometry):
                isInPolygonLayer = True
                break

        return isInPolygonLayer


    # Function to create the fields for the attributes that we register with the lines.
    @staticmethod
    def create_fields():
        # Create an ID field to identify the lines
        id_field = QgsField("id", QVariant.Int, "integer", 10, 1)
        # Create the field containing the flux value
        flux_field = QgsField("flux", QVariant.Double, "double", 20, 1)
        # Create the field containing the list of neighbors or the line (for debugging purposes)
        road_type_field = QgsField("road_type", QVariant.String, "text", 100, 1)
        # Then, we create a container of multiple fields
        fields = QgsFields()
        # We add the fields to the container
        fields.append(id_field)
        fields.append(flux_field)
        fields.append(road_type_field)
        # We return the container with our fields.
        return fields

    # Function to create a polyline with a polyLine
    @staticmethod
    def create_path_feature_from_polyline(polyLine, ID, flux, roadType, fields):
        # We create the geometry of the polyline
        polyline = QgsGeometry.fromPolylineXY(polyLine)
        # We retrieve the fields and add them to the feature
        feature = QgsFeature(fields)
        id_index = feature.fieldNameIndex("id")
        feature.setAttribute(id_index, ID)
        flux_index = feature.fieldNameIndex("flux")
        feature.setAttribute(flux_index, flux)
        road_type_index = feature.fieldNameIndex("road_type")
        feature.setAttribute(road_type_index, roadType)
        # We add the geometry to the feature
        feature.setGeometry(polyline)
        return feature

    # Function to create an output with a multipolyline
    @staticmethod
    def create_path_feature_from_Multipolyline(MultipolyLine, ID, flux, roadType, fields):
        # We create the geometry of the polyline
        polyline = QgsGeometry.fromMultiPolylineXY(MultipolyLine)
        # We retrieve the fields and add them to the feature
        feature = QgsFeature(fields)
        id_index = feature.fieldNameIndex("id")
        feature.setAttribute(id_index, ID)
        flux_index = feature.fieldNameIndex("flux")
        feature.setAttribute(flux_index, flux)
        road_type_index = feature.fieldNameIndex("road_type")
        feature.setAttribute(road_type_index, roadType)
        # We add the geometry to the feature
        feature.setGeometry(polyline)
        return feature




