# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Silanalyses
                                 A QGIS plugin
 Plugin pour l'analyse de réseaux issus, ou non, de l'outil SilLCP
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin        : 2024-06-03
        git sha      : $Format:%H$
        copyright    : (C) 2024 by Thomas ANDRE
        email        : thomas.andre.archgeo@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import os
from qgis import *
from qgis.PyQt import *
from qgis.core import *
from qgis.utils import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from processing.tools import dataobjects
from distutils.dir_util import copy_tree
from processing.tools.system import mkdir, userFolder
import processing
from math import *
from .Fonctions.Utilitaires import *
from .Fonctions.Fonctions_QGIS import *


# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'Silanalyses_dialog_base.ui'))


class SilanalysesDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(SilanalysesDialog, self).__init__(parent)
        # Set up the user interface from Designer through FORM_CLASS.
        # After self.setupUi() you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        # Initialisation de certaines fonctions et outils
        self.layer_list()
        self.combo_network.setFilters(QgsMapLayerProxyModel.LineLayer)
        self.combo_network_2.setFilters(QgsMapLayerProxyModel.LineLayer)
        self.start.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.inter.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.end.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.inter.setAllowEmptyLayer(True)
        self.start_point.setAllowNull(True)
        self.stops.setAllowNull(True)
        self.end_point.setAllowNull(True)
        
        # Couches_vide=QgsVectorLayer("Point?crs=epsg:2154","Vide", "memory")
        # self.inter.setAdditionalLayers([Couches_vide])
        self.inter.setCurrentIndex(0) 

        # Initialisation of some functions and tools
        message = "Avancement dans l'utilisation des outils"
        percent = 0
        Utilitaires.progression(self.bar_2, self.message_progres, message, percent)
        
        # Protection of the buttons to prevent misuses
        self.combo_attribute.setEnabled(False)
        self.ok_attribute.setEnabled(False)
        self.b_time.setEnabled(False)
        self.b_km.setEnabled(False)
        self.b_other.setEnabled(False)
        self.day.setEnabled(False)
        self.hour.setEnabled(False)
        self.minute.setEnabled(False)
        self.km.setEnabled(False)
        self.other.setEnabled(False)
        self.ok_resume.setEnabled(False)

        self.day.hide()
        self.hour.hide()
        self.minute.hide()
        self.km.hide()
        self.other.hide()
        self.stops.hide()

        # Linking buttons and functions
        self.ok_network.clicked.connect(self.select_network)
        self.ok_attribute.clicked.connect(self.select_attribute)
        self.b_time.toggled.connect(self.select_unit)
        self.b_km.toggled.connect(self.select_unit)
        self.b_other.toggled.connect(self.select_unit)
        self.ok_resume.clicked.connect(self.resume)
        self.ok_new.clicked.connect(self.fusion)
        self.refresh.clicked.connect(self.layer_list)
        self.ok_path.clicked.connect(self.itinerary)
        self.start.layerChanged.connect(self.prepa_list)
        self.inter.layerChanged.connect(self.prepa_list)
        self.end.layerChanged.connect(self.prepa_list)


        path = os.path.dirname(os.path.abspath(__file__))
        path = path + '//Couches//T//'
        if os.path.isfile(path + 't1.shp') or os.path.isfile(path + 't2.shp'):
            files = os.listdir(path)
            for f in files:
                os.remove(path + f) 
 

    def prepa_list(self):
        if self.start.currentLayer() != None:
            dep=self.start.currentLayer()
            self.start_point.setLayer(dep)
            self.start_point.update()
        if self.inter.currentLayer() != None:
            inter=self.inter.currentLayer()
            self.stops.setLayer(inter)
            self.stops.show()
        else:
            self.stops.hide()
        if self.end.currentLayer() != None:
            fin=self.end.currentLayer()
            self.end_point.setLayer(fin)
        

    def layer_list(self):
        """
        Function: Retrieve layers from the project to refresh the list and offer only vector layers
        """
        try:
            # Resetting the list to zero
            self.combo_layers.clear()
            # Récupération des couches du projet
            self.layers = QgsProject.instance().mapLayers().values()
            for layer in self.layers:
                # Tri des couches pour ne garder que les couches de vecteurs
                if layer.type() == QgsMapLayer.VectorLayer:
                    if layer.geometryType() == 1:
                        self.combo_layers.addItemWithCheckState(str(layer.name()),False)
        except:
            pass


    def reindex(self, index, list, n):
        """
        Input: index (str) = Name of column used as index
                list (list de QGSfeatures) = List of items to be reindexed
                n (int) = Index of where to start
        Function: Change the index column for selected items to avoid duplication problems
        Output: List of reindexed items
        """
        # Change the index of each entity in the list
        for feat in list:
            feat[index] = n
            n += 1
            # print(str(n)) #debugtest
        return list


    def fusion(self):
        """
        Function: Tool for merging different networks into a single harmonised network
        """
        # Retrieving selected layers
        layer_list = self.combo_layers.checkedItems()
        # Recovery of the given name
        name = self.name_network.text()
        if name == '':
            name = 'Reseau_fusion'

        try:
            # Creating a merged layer from a reference layer
            ref = layer_list[0]
            layer = QgsProject.instance().mapLayersByName(ref)[0]
            SCR = layer.crs().authid().replace("EPSG:", "")
            fused_layer = Utilitaires.layer_creation(self, "LineString", SCR, name, layer)
            layer_data = fused_layer.dataProvider()
            # print(str(layer.fields().names())) #debugtest

            # Recover the name of the entity identification layer to modify it and avoid duplicates at the time of merging
            self.index = 1
            for elem in layer_list:
                layer = QgsProject.instance().mapLayersByName(elem)[0]
                layer.selectAll()

                Fonctions_QGIS.SelectEqual(self, layer, fused_layer, 3)

                elem_list =layer.selectedFeatures()

                #Changement des ID des couches pour éviter les problèmes de doublons
                attribute_list = layer.fields().names()
                # print(str(attribute_list)) #debugtest

                if "ID" in attribute_list:
                    index = "ID"
                    index_liste = self.reindex(index, elem_list, self.index)
                if "Id" in attribute_list:
                    index = "Id"
                    index_liste = self.reindex(index, elem_list, self.index)
                if "id" in attribute_list:
                    index="id"
                    index_liste = self.reindex(index, elem_list, self.index)
                if "fid" in attribute_list:
                    index = "fid"
                    index_liste = self.reindex(index, elem_list, self.index)
                if "FID" in attribute_list:
                    index = "FID"
                    index_liste = self.reindex(index, elem_list, self.index)
                else:
                    index_liste = elem_list

                layer_data.addFeatures(index_liste)
                self.index=self.index+len(elem_list)
                layer.removeSelection()

            QgsProject.instance().addMapLayer(fused_layer)
            style_path = os.path.dirname(os.path.abspath(__file__))+'//Styles//Liens.qml'
            if os.path.exists(style_path):
                fused_layer.loadNamedStyle(style_path)
            else:
                pass
            self.name_network.clear()

            iface.messageBar().pushMessage("Succès:", "La layer fusionnée a été créée avec succès", level=3)

        except:
            message = "Impossible de changer l'identifiant des entités, vérifiez que les couchent aient bien un champ id, Id, ID, fid ou FID"
            Utilitaires.progression(self.bar_2,self.message_progres,message,0)


    def select_network(self):
        """
        Function: Selection of the layer containing the network to be simplified
        """
        # Search for the layer corresponding to the given name
        self.network = Utilitaires.layer_finder(self.combo_network)
        # Definition of the source of the drop-down list of entities in the selected layer
        self.combo_attribute.setLayer(self.network)
        self.combo_attribute.setEnabled(True)
        self.ok_attribute.setEnabled(True)


    def select_attribute(self):
        """
        Function: Selecting an attribute field for selecting the point of origin of LCPs
        """
        self.attribute=self.combo_attribute.currentField()
        self.b_time.setEnabled(True)
        self.b_km.setEnabled(True)
        self.b_other.setEnabled(True)
        self.b_time.setChecked(True)
        self.day.setEnabled(True)
        self.hour.setEnabled(True)
        self.minute.setEnabled(True)
        self.ok_resume.setEnabled(True)

        self.day.show()
        self.hour.show()
        self.minute.show()


    def select_unit(self):
        """
        Function: Choice of unit used for analyses
        """
        if self.b_time.isChecked():
            self.b_km.setChecked(False)
            self.b_other.setChecked(False)

            self.day.setEnabled(True)
            self.hour.setEnabled(True)
            self.minute.setEnabled(True)
            self.day.show()
            self.hour.show()
            self.minute.show()

            self.choice=1
            self.ok_resume.setEnabled(True)

            self.km.setEnabled(False)
            self.other.setEnabled(False)
            self.km.hide()
            self.other.hide()

            self.day.setText("0")
            self.hour.setText("0")
            self.minute.setText("0")
            self.km.setText("0")
            self.other.setText("0")

        elif self.b_km.isChecked():
            self.b_time.setChecked(False)
            self.b_other.setChecked(False)

            self.km.setEnabled(True)
            self.km.show()

            self.choice=2
            self.ok_resume.setEnabled(True)

            self.day.setEnabled(False)
            self.hour.setEnabled(False)
            self.minute.setEnabled(False)
            self.other.setEnabled(False)
            self.day.hide()
            self.hour.hide()
            self.minute.hide()
            self.other.hide()

            self.day.setText("0")
            self.hour.setText("0")
            self.minute.setText("0")
            self.km.setText("0")
            self.other.setText("0")

        elif self.b_other.isChecked():
            self.b_time.setChecked(False)
            self.b_km.setChecked(False)

            self.other.setEnabled(True)
            self.other.show()

            self.choice=3
            self.ok_resume.setEnabled(True)

            self.day.setEnabled(False)
            self.hour.setEnabled(False)
            self.minute.setEnabled(False)
            self.km.setEnabled(False)
            self.day.hide()
            self.hour.hide()
            self.minute.hide()
            self.km.hide()

            self.day.setText("0")
            self.hour.setText("0")
            self.minute.setText("0")
            self.km.setText("0")
            self.other.setText("0")

    
    def resume(self):
        """
        Function: Selection of network edges to be retained according to the value chosen
        """
        message = "Lancement de l'outil de simplification du réseau"
        percent = 10
        Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

        self.network.selectAll()
        list_features = []
        if self.choice == 1:
            message = "Tri des liens en fonction du temps..."
            percent = 25
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            for feature in self.network.selectedFeatures():
                # Using the time sorting function
                new_liste = self.time_selection(feature, self.attribute, list_features)
    
        elif self.choice == 2:
            message = "Tri des liens en fonction de la distance..."
            percent = 25
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)
            
            for feature in self.network.selectedFeatures():
                # Using the distance sorting function
                new_liste = self.distance_selection(feature, self.attribute, list_features)
            
        elif self.choice == 3:
            message = "Tri des liens en fonction du coût..."
            percent = 25
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            for feature in self.network.selectedFeatures():
                # Using the specific function for custom sorting
                new_liste = self.cost_selection(feature, self.attribute, list_features)

        # Creating the layer
        Simplifie=QgsVectorLayer("LineString?crs=epsg:2154", "Reseau_simplifie", "memory")
        layer_data = Simplifie.dataProvider()

        # Check whether entities correspond to the search
        message = "Ajout des entités sélectionnées dans une nouvelle layer"
        percent = 50
        Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

        if len(new_liste) > 0:
            # Retrieving columns from the attribute table of the reference layer
            attr = self.network.dataProvider().fields().toList()
            # Updating the columns in the new layer
            layer_data.addAttributes(attr)
            Simplifie.updateFields()
            # Adding the layer to the menu
            QgsProject.instance().addMapLayer(Simplifie)
            style_path = os.path.dirname(os.path.abspath(__file__)) + '//Styles//Liens.qml'
            if os.path.exists(style_path):
                Simplifie.loadNamedStyle(style_path)
            else:
                pass
            
            # Adding selected edges to the new layer
            layer_data.addFeatures(new_liste)

            message = "Calcule de la centralité du réseau simplifié"
            percent = 75
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            try:
                # Calculating the centrality of the new network
                # print("start") #debugtest
                centralite=Fonctions_QGIS.Centrality(self,Simplifie)
                QgsProject.instance().addMapLayer(centralite)
                # Defining the style of the layer
                style_path = os.path.dirname(os.path.abspath(__file__)) + '//Styles//Noeuds.qml'
                if os.path.exists(style_path):
                    centralite.loadNamedStyle(style_path)
                else:
                    pass
                # print("end") #debugtest
                message = "Avancement dans l'utilisation des outils"
                
            except:
                iface.messageBar().pushMessage("Attention:", "GRASS n'ayant pas été activé, le calcul de centralité n'a pas été réalisé", level=1)
                message = "Attention: GRASS n'ayant pas été activé, le calcul de centralité n'a pas été réalisé"
        else:
            iface.messageBar().pushMessage("Attention:", "Aucun élément dans la layer", level=1)


        self.b_time.setChecked(True)
        self.b_time.setEnabled(False)
        self.day.setText("0")
        self.hour.setText("0")
        self.minute.setText("0")
        self.day.setEnabled(False)
        self.hour.setEnabled(False)
        self.minute.setEnabled(False)
        self.day.hide()
        self.hour.hide()
        self.minute.hide()
        self.b_km.setEnabled(False)
        self.km.setText("0")
        self.km.setEnabled(False)
        self.km.hide()
        self.b_other.setEnabled(False)
        self.other.setText("0")
        self.other.setEnabled(False)
        self.other.hide()

        self.combo_attribute.setEnabled(False)
        self.ok_attribute.setEnabled(False)
        self.ok_resume.setEnabled(False)
        self.network.removeSelection()

        self.message_progres.setText(message)
        self.bar_2.reset()
        iface.messageBar().pushMessage("Succès:", "Le réseau a été trié avec succès", level=3)


    def time_selection(self, feature, champ, list):
        """
        Input: feature (QGSfeature) = Element of the layer to be checked
                champ (str) = Field containing the value to be compared
                list (list) = Fill in the list with the items you want
        Function: Check whether the time linked to the edge is less than the time required to select it
        Output: List of selected edges
        """
        # Link value recovery
        val = feature[champ]
        # print(str(val)) #debugtest
        
        # Retrieving input values
        if self.day.text() != "":
            j = int(self.day.text())
        else: 
            j = 0
        if self.hour.text() != "":
            h = int(self.hour.text())
        else: 
            h = 0
        if self.minute.text() != "": 
            m = int(self.minute.text())
        else: 
            m = 0

        # print("a {},{},{}".format(j,h,m)) #debugtest

        # Sorting the edeges by reference value
        # Identifying time values from a character string
        if isinstance(val, str):
            # Format "--j --h --min"
            if "j" in val: 
                day  =  int(val.split("j")[0])
                hour  =  int(val.split("j")[1].split("h")[0])
                min  =  int(val.split("j")[1].split("h")[1].split("min")[0])

                # print("b {},{},{}".format(day,hour,min)) #debugtest
                if day < j:
                    list.append(feature)
                elif day == j:
                    if hour < h:
                        list.append(feature)
                    elif hour == h:
                        if min <= m:
                            list.append(feature)
                        else:
                            pass
                    else:
                        pass
                else:
                    pass
            else:
                # Format "--h --min"
                hour = int(val.split("h")[0])
                min = int(val.split("h")[1].split("min")[0])
                # print("c {},{}".format(hour,min)) #debugtest
                h = h + j*24
                # print(h) #debugtest

                if hour < h:
                    list.append(feature)
                elif hour == h:
                    if min <= m:
                        list.append(feature)
                    else:
                        pass
                else:
                    pass
        # Identify time values from a whole or decimal number
        elif isinstance(val,float):
            hour = floor(val)
            min = val - hour
            h = h + j*24
            # print(h) #debugtest
            # print("d {},{}".format(hour,min)) #debugtest

            if hour < h:
                list.append(feature)
            elif hour == h:
                if min <= m:
                    list.append(feature)
        else:
            iface.messageBar().pushMessage("Erreur:", "Le champ choisi n'est ni un float, ni un string", level=2)

        
        # print(str(len(list))) #debugtest
        return list


    def distance_selection(self,feature,champ,list):
        """
        Input: feature (QGSfeature) = Element of the layer to be checked
                champ (str) = Field containing the value to be compared
                list (list) = Fill in the list with the items you want
        Function: Check whether the distance linked to the edge is less than the distance required to select it
        Output: List of selected edges
        """
        # Link value recovery
        val = float(feature[champ])
        # print(str(val)) #debugtest

        # Recovery of the input value
        ref = float(self.km.text())
        # print(str(ref)) #debugtest

        # Sorting the edges by reference value
        if val < ref:
            list.append(feature)
        else:
            pass

        # print(str(len(list))) #debugtest
        return list


    def cost_selection(self,feature,champ,list):
        """
        Input: feature (QGSfeature) = Element of the layer to be checked
                champ (str) = Field containing the value to be compared
                list (list) = Fill in the list with the items you want
        Function: Check whether the cost of the link is lower than the one you are looking for to select it
        Output: List of selected edges
        """
        # Link value recovery
        val = float(feature["{}".format(champ)])
        # print(str(val)) #debugtest
    
        # Recovery of the input value
        ref = float(self.other.text())
        # print(str(ref)) #debugtest

        # Sorting the edges by reference value
        if val < ref:
            list.append(feature)
        else:
            pass

        # print(str(len(list))) #debugtest
        return list
    

    def itinerary(self):
        """
        Function: Create a route between 2 or more points using the shortest path in a network
        """
        message = "Préparation du réseau"
        percent = 5
        Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

        network = Utilitaires.layer_finder(self.combo_network_2)
        print(self.combo_network_2.currentText()) #debugtest
        # print(str(network)) #debugtest
        # Aggregation of the network into a single entity to be able to use the shortest path tool between two points
        # network=Fonctions_QGIS.Aggregation_simple(self,network)
        try:
            message = "Récupération de la couche du point de départ"
            percent = 10
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            start = Utilitaires.layer_finder(self.start)

            message = "Récupération de la couche des points intermédiaires"
            percent = 20
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            # print(str(start)) #debugtest
            if self.inter.currentText() != "":
                inter = Utilitaires.layer_finder(self.inter)
            else:
                inter = ""
            # print(str(inter)) #debugtest

            message = "Récupération de la couche du point d'arrivée"
            percent = 30
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            end = Utilitaires.layer_finder(self.end)
            # print(str(end)) #debugtest

            message = "Récupération des nœuds du réseau"
            percent = 40
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)
        except:
            iface.messageBar().pushMessage("Attention:", "Une des couches chosies n'a pas été trouvée, veuillez vérifier vos entrées", level=2)
            message = "Problème avec une des couches choisies"
            Utilitaires.progression(self.bar_2, self.message_progres, message, 0) 
            return

        try:
            # Recovery of network points if the points selected are not directly on the network
            # print("start") #debugtest
            nodes = Fonctions_QGIS.NodesExtraction(self, network)
            # QgsProject.instance().addMapLayer(nodes) #debugtest
            # print("end") #debugtest

            # Checking tool in the event of a problem with the start or finish point
            PB = False
        except:
            iface.messageBar().pushMessage("Attention:", "GRASS n'ayant pas été activé, l'outil ne peut fonctionner", level=2)
            QgsProject.instance().removeMapLayer(nodes)
            message = "GRASS n'ayant pas été activé, l'outil ne peut fonctionner"
            Utilitaires.progression(self.bar_2, self.message_progres, message, 0) 
            return

        # Search for the network nodes closest to the route points
        try:
            if self.inter.currentText() != "":
                coords_start, coords_end, liste_inter_tri, PB = self.extremities_search(nodes, start, inter, end, PB)
            else:
                coords_start, coords_end, PB = self.extremities_search(nodes, start, inter, end, PB)
        except:
            iface.messageBar().pushMessage("Attention:", "Une des entités chosies n'a pas été trouvée, veuillez vérifier vos entrées", level=2)
            QgsProject.instance().removeMapLayer(nodes)
            message = "Problème avec une des entités choisies"
            Utilitaires.progression(self.bar_2, self.message_progres, message, 0) 
            return


        if not PB:
            message = "Recherche du chemin le plus court entre les points sélectionnés"
            percent = 75
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            missing = 0
            # Search for the shortest path within the aggregated network between the various points on the route
            if self.inter.currentText() == "":
                try:
                    # print(coords_start,coords_end) #debugtest
                    path = Fonctions_QGIS.PathExtraction(self, network, coords_start, coords_end)
                    # print("path OK") #debugtest
                    # QgsProject.instance().addMapLayer(path) # debugtest

                    # Create or select a layer to store the routes created
                    if not QgsProject.instance().mapLayersByName("Trajet"):
                        SCR = network.crs().authid().replace("EPSG:", "")
                        shortest = Utilitaires.layer_creation(self, "LineString", SCR, "Trajet", path)
                        # QgsProject.instance().addMapLayer(shortest) # debugtest
                    else:
                        shortest = QgsProject.instance().mapLayersByName("Trajet")[0]

                    style_path = os.path.dirname(os.path.abspath(__file__)) + '//Styles//Trajet.qml'
                    if os.path.exists(style_path):
                        shortest.loadNamedStyle(style_path)
                    else:
                        pass
                        
                    path.selectAll()
                    layer_data = shortest.dataProvider()
                    layer_data.addFeatures(path.selectedFeatures())
                except:
                    iface.messageBar().pushMessage("Erreur:", "Aucun chemin n'a été trouvé", level=2)
                    message = "Aucun chemin n'a été trouvé"
                    Utilitaires.progression(self.bar_2, self.message_progres, message, 0) 
                    return

            else:
                # print("Lancement tri") #debugtest
                # Gathering the points of the route into a list
                points_list = []
                points_list.append(coords_start)
                for feat in liste_inter_tri:
                    points_list.append(feat)
                points_list.append(coords_end)
                print(str(len(points_list))) #debugtest

                message = "Recherche du chemin le plus court entre les étapes du trajet..."
                percent = 90
                Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

                for n in range(0, len(points_list) - 1):
                    try:
                        # print(str(n)) #debugtest
                        path = Fonctions_QGIS.PathExtraction(self, network, points_list[n], points_list[n+1])
                        # print("path OK ARR") #debugtest
                        # QgsProject.instance().addMapLayer(path) # debugtest

                        # Create or select a layer to store the routes created
                        if not QgsProject.instance().mapLayersByName("Trajet"):
                            SCR = network.crs().authid().replace("EPSG:", "")
                            shortest = Utilitaires.layer_creation(self, "LineString", SCR, "Trajet", path)
                        else:
                            shortest = QgsProject.instance().mapLayersByName("Trajet")[0]

                        style_path = os.path.dirname(os.path.abspath(__file__)) + '//Styles//Trajet.qml'
                        if os.path.exists(style_path):
                            shortest.loadNamedStyle(style_path)
                        
                        path.selectAll()
                        layer_data = shortest.dataProvider()
                        layer_data.addFeatures(path.selectedFeatures())

                        message = "{} chemin(s) sur {} créé(s)...".format(str(n),str(len(points_list)-1))
                        self.message_progres.setText(message)  
                    except:
                        missing = missing + 1
                try:
                    shortest.featureCount() == 0
                except:
                    iface.messageBar().pushMessage("Erreur:", "Aucun chemin n'a été trouvé", level=2)
                    message = "Aucun chemin n'a été trouvé"
                    self.message_progres.setText(message)  

            if missing > 0:
                if missing >= 2:
                    missing = missing//2 + 1
                else:
                    missing = 1
                iface.messageBar().pushMessage("Attention:", "Tous les chemins n'ont pu être créés. {} point(s) hors du réseau".format(missing), level=1)
            else:
                iface.messageBar().pushMessage("Succès:", "L'itinéraire a été créé avec succès", level=3)
            
            message = "Avancement dans l'utilisation des outils"
            
        else:
            message = "/!\ Problème avec le point de départ ou d'arrivée ! Vérifier qu'un seul point est ou peut être sélectionné"
            iface.messageBar().pushMessage("Erreur:", message, level=2)
            

        PB = True
        QgsProject.instance().removeMapLayer(nodes)
        network.removeSelection()
        self.inter.setCurrentIndex(0) 
        self.stops.setFeature(0)
        start.removeSelection()
        end.removeSelection()
        self.stops.hide()

        self.message_progres.setText(message)        
        self.bar_2.reset()


    def extremities_search(self, nodes, start, inter, end, PB):
        """
        Input:  nodes (QgsVectorLayer) = All the nodes in the network to identify those corresponding to the required points
                start (QgsFeature) = Starting point
                inter (QgsFeature) = Stops during the travel
                end (QgsFeature) = End point
                PB (bool): Check that the start and finish points have been found
        Function: Search for the coordinates of the network nodes corresponding to the points on the route if they are not the same
        Output: coords_start (str) = Coordinates of the starting point
                coords_end (str) = Coordinates of the ending point
                liste_inter_tri (list) = List of coordinates of sorted intermediate points
                PB (bool) = Check that the start and finish points have been found
        """
        message = "Localisation du point de départ"
        percent = 50
        Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

        nodes.selectAll()
        nodes = nodes.selectedFeatures()

        # Check that there is only one point selected or selectable for the start
        if self.start_point.feature().hasGeometry():
            ref_start = self.start_point.feature().id()
            start.selectByIds([ref_start])
            starting_point = start.selectedFeatures()[0]
        else:
            if len(start.selectedFeatures()) == 0:
                # print(len(start.selectedFeatures())) #debugtest
                start.selectAll()
            if len(start.selectedFeatures()) == 1:
                starting_point=start.selectedFeatures()[0]
            else:
                PB = True
            # print("Départ OK") #debugtest
        # Retrieve the coordinates of the network node corresponding to the point
        #starting_point = Utilitaires.closest_point(self,ref_start,nodes)
        coords_start = "{},{} [{}]".format(str(starting_point.geometry().asPoint()[0]), str(starting_point.geometry().asPoint()[1]), start.crs().authid())
        # print(coords_start) #debugtest

        message = "Localisation du point d'arrivée"
        percent = 55
        Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

        # Check that there is only one point selected or selectable for the finish
        if self.end_point.feature().hasGeometry():
            ref_end = self.end_point.feature().id()
            end.selectByIds([ref_end])
            ending_point = end.selectedFeatures()[0]
        else:
            if len(end.selectedFeatures()) == 0:
                # print(len(end.selectedFeatures())) #debugtest
                end.selectAll()
            if len(end.selectedFeatures()) == 1:
                ending_point = end.selectedFeatures()[0]
            else:
                PB = True
            # print("Arrivée OK") #debugtest
        # Retrieve the coordinates of the network node corresponding to the point
        #ending_point = Utilitaires.closest_point(self,ref_end,nodes)
        coords_end = "{},{} [{}]".format(str(ending_point.geometry().asPoint()[0]), str(ending_point.geometry().asPoint()[1]), end.crs().authid())
        # print(coords_end) #debugtest

        # Checking for the presence of intermediate points
        if self.inter.currentText() != "":
            message = "Localisation et tri des points du trajet"
            percent = 50
            Utilitaires.progression(self.bar_2, self.message_progres, message, percent)

            liste_inter_tri = []
            # Check whether points have already been selected, or whether the entire layer is to be taken into account
            if self.stops.feature().hasGeometry():
                list_inter = []
                list_tempo = []
                ref_stop = self.stops.feature().id()
                inter.selectByIds([ref_stop])
                stop = inter.selectedFeatures()[0]
                list_inter.append(stop)
                list_tempo.append(stop)
            else:
                if len(inter.selectedFeatures()) == 0:
                    # print(len(inter.selectedFeatures())) #debugtest
                    inter.selectAll()
                    list_inter=inter.selectedFeatures()
                else:
                    list_inter=inter.selectedFeatures()
                # Listing points for sorting
                list_tempo=inter.selectedFeatures()

            if not PB:
                # print(str(len(list_tempo))) #debugtest
                liste_tri = []
                # Defining the first point on the route as a reference
                point_A = starting_point
                while len(liste_tri) != len(list_inter):
                    # Recovery of the previous point
                    print(point_A)
                    print(list_tempo)
                    point_I = Utilitaires.furthest_point(self, point_A, list_tempo)
                    liste_tri.append(point_I)
                    # Deletion of the point from the temporary list so that it cannot be selected again
                    list_tempo.remove(point_I)
                    # Change of reference point

                for point_I in liste_tri:
                    # Retrieve the coordinates of the network node corresponding to the point
                    coords_I = "{},{} [{}]".format(str(point_I.geometry().asPoint()[0]), str(point_I.geometry().asPoint()[1]), inter.crs().authid())
                    liste_inter_tri.append(coords_I)
            # print("Intermédiaire OK") #debugtest

            return coords_start, coords_end, liste_inter_tri, PB
        else:
            return coords_start, coords_end, PB