# -*- coding: utf-8 -*-
"""
/***************************************************************************
 SilLCP
                                 A QGIS plugin
 Plugin pour la création de LCP à partir des outils de la librairie movecost sur les bases du plugin MovecostToQGIS
 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
import shutil
import time
from qgis import *
from qgis.PyQt import *
from qgis.core import *
from qgis.utils import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets 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 .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__), 'SilLCP_dialog_base.ui'))


class SilLCPDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(SilLCPDialog, 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 of some functions and tools
        self.test()
        self.combo_site.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.combo_polygons.setFilters(QgsMapLayerProxyModel.PolygonLayer)
        self.combo_origin.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.combo_nodes.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.combo_star.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.combo_star.setAllowEmptyLayer(True)
        self.combo_star.setCurrentIndex(0) 

        # Protection of the buttons to prevent misuses
        self.combo_origin.setEnabled(False)
        self.ok_origin.setEnabled(False)
        self.p_furthest.setEnabled(False)
        self.p_centroide.setEnabled(False)
        self.p_closest.setEnabled(False)
        self.ok_2.setEnabled(False)
        self.full.setChecked(True)
        self.combo_star.setEnabled(False)

        self.combo_star.hide()

        # Linking buttons and functions
        self.ok_site.clicked.connect(self.select_sites)
        self.combo_attribute.fieldChanged.connect(self.select_attribute)
        self.ok_1.clicked.connect(self.select_origin)
        self.ok_polygones.clicked.connect(self.select_polygones)
        self.ok_origin.clicked.connect(self.select_origin_LCP)
        self.ok_2.clicked.connect(self.select_type)
        self.full.toggled.connect(self.switch)
        self.star.toggled.connect(self.switch)
        self.ok_3.clicked.connect(self.create_lines)
        

    def test(self):
        """
        Function: Vérifier que le module Processing R Provider est bien installé pour que les scripts puissent être lus
        """
        profile_home = QgsApplication.qgisSettingsDirPath()
        path = os.path.exists(os.path.join(profile_home, 'python', 'plugins', 'processing_r'))
        if path == False:
            QMessageBox.warning(self, "Attention",
                                     "Vous devez d'abord installer et paramétrer le module Processing R Provider pour utiliser celui-ci",
                                     QMessageBox.Ok)
        else:
            a = '//rscripts'
            b = "processing//rscripts"
            # Access to the folder where R scripts are stored in the module
            folder = os.path.dirname(os.path.abspath(__file__))
            source_profile = folder + a
            # Access to the folder where they must be copied to make the module work
            new_folder = os.path.dirname(os.path.abspath(folder))
            new_folder = os.path.dirname(os.path.abspath(new_folder))
            new_folder = os.path.dirname(os.path.abspath(new_folder))
            rs = os.path.join(new_folder,b)

            # Check that the file exists
            if not os.path.exists(rs):
                os.makedirs(rs)
            for filename in os.listdir(source_profile):
                # print(str(filename)) # debugtest
                source_file = os.path.join(source_profile, filename)
                dest_file = os.path.join(rs, filename)
                # print(dest_file) # debugtest

                shutil.copy2(source_file, dest_file)
            # print(os.listdir(rs)) # debugtest

        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)  


    """
    All scripts called up during analyses
    """    
    def on_movecost_pressed(self):
        self.access_Script("movecost")
        processing.execAlgorithmDialog('r:movecost')

    def on_movebound_pressed(self):
        self.access_Script("movebound")
        processing.execAlgorithmDialog('r:movebound')

    def on_movecorr_pressed(self):
        self.access_Script("movecorr")
        processing.execAlgorithmDialog('r:movecorr')

    def on_movealloc_pressed(self):
        self.access_Script("movealloc")
        processing.execAlgorithmDialog('r:movealloc')

    def on_movecomp_pressed(self):
        self.access_Script("movecomp")
        processing.execAlgorithmDialog('r:movecomp')

    def on_movenetw_pressed(self):
        self.access_Script("movenetw")
        processing.execAlgorithmDialog('r:movenetw')

    def on_moverank_pressed(self):
        self.access_Script("moverank")
        processing.execAlgorithmDialog('r:moverank')

    def on_LCP_pressed(self):
        self.access_Script("movecost")
        processing.execAlgorithmDialog('r:movecost')

    def on_network_pressed(self):
        self.access_Script("movenetw")
        processing.execAlgorithmDialog('r:movenetw')

    def on_corridors_pressed(self):
        self.access_Script("movecorr")
        processing.execAlgorithmDialog('r:movecorr')

    def on_isochrones_pressed(self):
        self.access_Script("movebound")
        processing.execAlgorithmDialog('r:movebound')


    def access_Script(self, function):
        """
        Entree: function (str) = Function to copy
        Function: Makes a copy of the scripts so that the user can use them and be sure that the scripts are up to date
        """
        try:
            a = '//rscripts'
            b = "processing//rscripts"
            # Access to the folder where R scripts are stored in the module
            folder = os.path.dirname(os.path.abspath(__file__))
            # print(folder) # debugtest
            source_profile = folder + a
            # print(source_profile) # debugtest
            # Access to the folder where they must be copied to make the module work
            new_folder = os.path.dirname(os.path.abspath(folder))
            new_folder = os.path.dirname(os.path.abspath(new_folder))
            new_folder = os.path.dirname(os.path.abspath(new_folder))
            rs = os.path.join(new_folder, b)
            # print(rs) # debugtest

            # Check that the file exists
            if not os.path.exists(rs):
                os.makedirs(rs)

            # Files are copied at the time of use, allowing them to be updated automatically in the event of changes
            for filename in os.listdir(source_profile):
                if str(filename) == "{}.rsx".format(function) or str(filename) == "{}.rsx.help".format(function):
                    # print(str(filename)) # debugtest
                    source_file = os.path.join(source_profile, filename)
                    dest_file = os.path.join(rs, filename)
                    # print(dest_file) # debugtest

                    shutil.copy2(source_file, dest_file)
            # print(os.listdir(rs)) # debugtest
        except:
            iface.messageBar().pushMessage("Erreur:", "Impossible d'accéder aux scripts R. Vérifier que l'extension Processing R Provider est correctement activée", level=2)



    def select_sites(self):
        """
        Function: Selection of the layer from which the origin and destination(s) of the PCLs are to be extracted
        """
        self.site_layer = Utilitaires.layer_finder(self.combo_site)
        # Définition de la source de la list déroulante des entités de la couche sélectionnée
        self.combo_attribute.setLayer(self.site_layer)


    def select_attribute(self):
        """
        Function: Selecting an attribute field for selecting the point of origin of LCPs
        """
        self.attribute = self.combo_attribute.currentField()
        # Changement de champ de référence pour le choix de l'entité
        self.combo_start.setLayer(self.site_layer)
        self.combo_start.setDisplayExpression(self.attribute)


    def select_origin(self):
        """
        Function: Extraction of the LCP point of origin, and creation of a destination layer(s) from the remaining points
        """
        self.attribute = self.combo_attribute.currentField()
        origin = self.combo_start.feature()
        # print(origin) # debugtest

        # Recover the SCR from the input layer to use the same in the output layers
        SCR = self.site_layer.crs().authid().replace("EPSG:", "")
        # print(self.site_layer.crs().authid().replace("EPSG:","").replace(" ","")) # debugtest

        # Creation of layers containing the start point and end point(s)
        origin_layer = Utilitaires.layer_creation(self, "Point", SCR, origin[str(self.attribute)], self.site_layer)
        destination_layer = Utilitaires.layer_creation(self, "Point", SCR, "Destinations", self.site_layer)

        # Access the layer data and add the selected entity
        layer_data = origin_layer.dataProvider()
        layer_data.addFeature(origin)

        # Select all the other points in the layer, then remove the start point to obtain the end point(s)
        self.site_layer.selectAll()
        Fonctions_QGIS.Selectbylocation_simple(self, self.site_layer, origin_layer, 3)
        features = self.site_layer.selectedFeatures()
        # Accès aux données de la couche et ajout des entités restantes
        layer_data = destination_layer.dataProvider()
        layer_data.addFeatures(features)
        # Retrait de la sélection 
        self.site_layer.removeSelection()

        # Possibilité de trier les points en function d'une distance si activée
        # self.t12km = Fonctions_QGIS.Buffer(self, origin_layer, 12000, False, False)
        # self.t100km = Fonctions_QGIS.Buffer(self, origin_layer, 100000, False, False)
        # Division of the entities in the layer according to their distance from the LCP origin
        self.division(destination_layer)


    def select_polygones(self):
        """
        Function: Select a layer of polygons to simplify into points
        """
        self.polygon_layer = Utilitaires.layer_finder(self.combo_polygons)
        self.combo_origin.setEnabled(True)
        self.ok_origin.setEnabled(True)
        self.p_centroide.setEnabled(True)
        self.ok_2.setEnabled(True)


    def select_origin_LCP(self):
        """
        Function: Selection of the layer containing the LCP point of origin
        """
        self.origin_layer = Utilitaires.layer_finder(self.combo_origin)
        self.p_furthest.setEnabled(True)
        self.p_closest.setEnabled(True)
        self.ok_2.setEnabled(True)
    

    def select_type(self):
        """
        Function: Create a point layer from a polygon layer (centroids, nearest and furthest points)
        """
        progress_dialog = QProgressDialog("Chargement en cours...", None, 0, 0)
        progress_dialog.setWindowTitle("Veuillez patienter")
        progress_dialog.setWindowModality(Qt.ApplicationModal)
        progress_dialog.setCancelButton(None)
        progress_dialog.setMinimumDuration(0) 
        progress_dialog.show()

        # print("start") # debugtest
        # Possibilité de trier les points en function d'une distance si activée
        # # Création des tampons correspondant aux domaines proches et à la limite du domaine lointain
        # self.t12km = Fonctions_QGIS.Buffer(self, self.origin_layer, 12000, False, False)
        # self.t100km = Fonctions_QGIS.Buffer(self, self.origin_layer, 100000, False, False)

        # Check the SCRs of the two layers to ensure they are the same for the distance calculation
        SCR1 = self.polygon_layer.crs().authid().replace("EPSG:", "")

        # Check if entities are selected
        if len(self.polygon_layer.selectedFeatures()) > 0:
            selected_poly = True
            polygons = self.polygon_layer.selectedFeatures()
        else:
            selected_poly = False
            polygons = None

        # Creation of the centroid layer if selected
        if self.p_centroide.isChecked():
            # Using the QGIS centroid tool
            Centroides = Fonctions_QGIS.Centroide(self, self.polygon_layer,selected_poly)
            Centroides.setName("Centroides")
            QgsProject.instance().addMapLayer(Centroides)

            # Division of layer entities according to their distance from the LCP origin
            self.division(Centroides)

        # Only trigger these steps if you need to go through all the elements in the layer
        if self.p_furthest.isChecked() or self.p_closest.isChecked():
            SCR2 = self.origin_layer.crs().authid().replace("EPSG:", "")
            # If they are different, reproject according to the SCR of the point of origin
            if SCR1 != SCR2:
                self.polygon_layer = Fonctions_QGIS.Reprojection(self, self.polygon_layer, SCR2)

            # Getting the coordinates of the reference point
            if len(self.origin_layer.selectedFeatures()) == 1: 
                feat = self.origin_layer.selectedFeatures()[0]
            else:
                self.origin_layer.selectAll()
                if len(self.origin_layer.selectedFeatures()) == 1:
                    feat = self.origin_layer.selectedFeatures()[0]
                else:
                    iface.messageBar().pushMessage("Erreur:", "Plus d'un point sélectionné ou sélectionnable dans la couche", level=2)
                    return
                
            xa = feat.geometry().asPoint()[0]
            ya = feat.geometry().asPoint()[1]

            # Creation of a temporary layer that will receive the various entities of the layer to be progressively transformed
            tempo = Utilitaires.layer_creation(self, "Polygon", SCR2, "tempo", self.polygon_layer)
            layer_data = tempo.dataProvider()

            # Creation of lists of minimum and maximum points retained
            liste_min = []
            liste_max = []

            # Browse all the entities in the layer to be transformed
            if polygons == None:
                polygons = self.polygon_layer.getFeatures()
                print(2)
            for feat in polygons:
                # Add the entity to the temporary layer
                layer_data.addFeature(feat)
                # Transforming a polygon into a set of points 
                vertices = Fonctions_QGIS.VerticesExtraction(self, tempo)
                # Search for points closest to and furthest from the entity
                feat_min,feat_max = self.recup_vertices(vertices ,xa, ya)
                # Addition of points identified in the final list
                liste_min.append(feat_min)
                liste_max.append(feat_max)
                # Delete the entity from the temporary layer to replace it
                layer_data.truncate()

            # Layer removed from project as unnecessary
            QgsProject.instance().removeMapLayer(tempo)
        
            # Creation of the nearest points layer if selected
            if self.p_furthest.isChecked():
                # Layer creation
                closest=Utilitaires.layer_creation(self, "Point", SCR2, "Points_+_Proches", vertices)
                # Access to layer entities
                layer_data = closest.dataProvider()
                # Adding identified points to the layer
                layer_data.addFeatures(liste_min)

                # Possibility of sorting points by distance if enabled
                # Layer entities can be divided according to their distance from the LCP origin
                self.division(closest)

            if self.p_closest.isChecked():
                # Layer creation
                furthest=Utilitaires.layer_creation(self, "Point", SCR2, "Points_+_Eloignes", vertices)
                # Access to layer entities
                layer_data = furthest.dataProvider()
                # Adding identified points to the layer
                layer_data.addFeatures(liste_max)

                # Possibility of sorting points by distance if enabled
                # Layer entities can be divided according to their distance from the LCP origin
                self.division(furthest)
        
        progress_dialog.close()

        self.combo_origin.setEnabled(False)
        self.ok_origin.setEnabled(False)
        self.p_furthest.setChecked(False)
        self.p_centroide.setChecked(False)
        self.p_closest.setChecked(False)
        self.p_furthest.setEnabled(False)
        self.p_centroide.setEnabled(False)
        self.p_closest.setEnabled(False)
        self.ok_2.setEnabled(False)


    def recup_vertices(self, vertices, xa, ya):
        """
        Input: vertices (list) = List of vertices to sort
                 xa,ya (int ou float) = Coordinates of the reference point
        Function: Retrieve the nearest and furthest vertices from a point
        Output: Points closest to and furthest from the origin of the LCP (QgsVectorLayer)
        """
        # Creating a value to initialise the search
        dist_min = 0
        dist_max = 0

        # Search all the vertices of the transformed polygon
        for vertex in vertices.getFeatures():
            # Retrieving vertex coordinates
            xb = vertex.geometry().asPoint()[0]
            yb = vertex.geometry().asPoint()[1]
            # Calculation of the distance separating the vertex from the origin of the LCP
            dist=(((xa - xb)**2) + ((ya - yb)**2))**0.5
            # Checking whether it is furthest or closest to the origin of the LCP
            if dist < dist_min or dist_min == 0:
                feat_min = vertex
                dist_min = dist
            if dist > dist_max or dist_max == 0:
                feat_max = vertex
                dist_max = dist
        return feat_min, feat_max


    def division(self, layer):
        """
        Input: layer (QgsVectorLayer): Layer to modify
        Function: Add a field and divide entities in a layer according to their distance from the PCL origin
        (Not maintained, as it is specific to a study being carried out, but can be modified)
        """
        if self.p_furthest.isChecked() or self.p_closest.isChecked():
            # Deletion of columns in the attribute table that were created during the process but are no longer required 
            count = layer.fields().count()
            ind_list = list((count-1, count-2, count-3, count-4, count-5, count-6))
            # Remove multiple fields with a list of indices
            layer.dataProvider().deleteAttributes(ind_list)
            layer.updateFields()

        # # Accès aux données de la couche
        # layer_data = layer.dataProvider()
        # # Création d'un nouveau champ
        # layer_data.addAttributes([QgsField("Classe_Distance", QVariant.String)])
        # layer.updateFields()
        # # Récupération de l'id du champ pour sa complétion
        # field_idx = layer.fields().indexOf("Classe_Distance")

        # # Sélection des points dans le domaine proche (moins de 12km)
        # Fonctions_QGIS.Selectbylocation_simple(self,layer,self.t12km,0)
        # with edit(layer):
        #     for feature in layer.selectedFeatures():
        #         # Changement de la valeur du champ
        #         layer.changeAttributeValue(feature.id(), field_idx, "<12km")
        
        # # Sélection des points dans la zone intermédiaire (entre 12 et 100km)
        # Fonctions_QGIS.Selectbylocation_simple(self,layer,self.t100km,0)
        # Fonctions_QGIS.Selectbylocation_simple(self,layer,self.t12km,3)
        # with edit(layer):
        #     for feature in layer.selectedFeatures():
        #         # Changement de la valeur du champ
        #         layer.changeAttributeValue(feature.id(), field_idx, "12km->100km")

        # # Sélection des points dans le domaine lointain
        # layer.selectAll()
        # Fonctions_QGIS.Selectbylocation_simple(self,layer,self.t100km,3)
        # with edit(layer):
        #     for feature in layer.selectedFeatures():
        #         # Changement de la valeur du champ
        #         layer.changeAttributeValue(feature.id(), field_idx, ">100km")

        # Suppresion de la sélection
        layer.removeSelection()

    def switch(self):
        if self.full.isChecked():
            self.label_specific.setText("Noeuds du réseau:")
            self.label_specific2.setText("")
            self.full.setChecked(False)
            self.combo_star.hide()
            self.combo_star.setCurrentIndex(0) 
            self.combo_star.setEnabled(False)
        if self.star.isChecked():
            self.label_specific.setText("Noeud central: ")
            self.label_specific2.setText("Noeuds à relier: ")
            self.star.setChecked(True)
            self.combo_star.show()
            self.combo_star.setEnabled(True)
            self.combo_star.setCurrentIndex(0) 
            

    def create_lines(self):
        """
        Function = Create a network of straight lines between several points, or between one or more origins and several destinations
        """
        progress_dialog = QProgressDialog("Chargement en cours...", None, 0, 0)
        progress_dialog.setWindowTitle("Veuillez patienter")
        progress_dialog.setWindowModality(Qt.ApplicationModal)
        progress_dialog.setCancelButton(None)
        progress_dialog.setMinimumDuration(0) 
        progress_dialog.show()
        
        nodes = Utilitaires.layer_finder(self.combo_nodes)
        SCR = nodes.crs().authid().replace("EPSG:","")
        new_layer=QgsVectorLayer("{}?crs=epsg:{}".format('LineString',SCR), "Reseau_droites", "memory")
        layer_data = new_layer.dataProvider()

        if self.full.isChecked():
            layer_data.addAttributes([QgsField("Id", QVariant.String),QgsField("IdCouple", QVariant.String),QgsField("IdA", QVariant.Int),QgsField("IdB", QVariant.Int),QgsField("Distance", QVariant.Double)])
            new_layer.setName("Reseau_droites_complet")

        if self.star.isChecked():
            ends = Utilitaires.layer_finder(self.combo_star)
            layer_data.addAttributes([QgsField("Id", QVariant.String),QgsField("Id_Fin", QVariant.Int),QgsField("Distance", QVariant.Double)])
            new_layer.setName("Reseau_droites_etoile")

        new_layer.updateFields()

        # Ajout de la couche à la carte
        QgsProject.instance().addMapLayer(new_layer)

        if self.full.isChecked():
            self.full_network(nodes,new_layer)
                
        if self.star.isChecked():
            self.star_network(nodes,ends,new_layer)
        
        progress_dialog.close()


    def full_network(self, network, new_layer):
        """
        Input = network (QgsVectorLayer): layer of network nodes
                new_layer (QgsVectorLayer): layer created to host the network
        Function = Creation of a complete network between all the nodes
        """
        layer_data = new_layer.dataProvider()
        list = []
        ref = []
        if len(network.selectedFeatures()) > 0:
            l = network.selectedFeatures()
        else:
            l = network.getFeatures()
        for node in l:
            list.append(node)
            ref.append(node)
        m = 0
        while len(list) != 1:
            start = list[0]
            id1 = ref.index(start)+1
            for end in list:
                id2 = ref.index(end)+1
                if end != start:
                    points = []
                    points.append(start.geometry().asPoint())
                    points.append(end.geometry().asPoint())
                    trace = QgsFeature()
                    trace.setGeometry(QgsGeometry.fromPolylineXY(points))
                    dist = trace.geometry().length()
                    layer_data.addFeature(trace)

                    if id1 < id2:
                        ids = str(id1) + "-" + str(id2)
                    else:
                        ids = str(id2) + "-" + str(id1)

                    with edit(new_layer):
                        for feat in new_layer.getFeatures():
                            attribute = feat.fields().names()
                            if feat[attribute[0]]==None:
                                new_layer.changeAttributeValue(feat.id(), 0, m)
                                new_layer.changeAttributeValue(feat.id(), 1, ids)
                                new_layer.changeAttributeValue(feat.id(), 2, id1)
                                new_layer.changeAttributeValue(feat.id(), 3, id2)
                                new_layer.changeAttributeValue(feat.id(), 4, dist/1000)
                    m += 1
            del list[0]


    def star_network(self, network, ends, new_layer):
        """
        Input = network (QgsVectorLayer): layer of destination nodes
                ends (QgsVectorLayer): layer of central nodes
                new_layer (QgsVectorLayer): layer created to host the network
        Function = Creation aof a star shaped network
        """
        layer_data = new_layer.dataProvider()
        listen = []
        if len(ends.selectedFeatures()) > 0:
            l = ends.selectedFeatures()
        else:
            l = ends.getFeatures()
        for node in l:
            listen.append(node)
        list = []
        if len(network.selectedFeatures()) > 0:
            l = network.selectedFeatures()
        else:
            l = network.getFeatures()
        for node in l:
            list.append(node)
        m=0
        for center in listen:
            for end in list:
                points = []
                points.append(center.geometry().asPoint())
                points.append(end.geometry().asPoint())
                trace = QgsFeature()
                trace.setGeometry(QgsGeometry.fromPolylineXY(points))
                dist = trace.geometry().length()
                if dist != 0:
                    layer_data.addFeature(trace)

                    with edit(new_layer):
                            for feat in new_layer.getFeatures():
                                attribute=feat.fields().names()
                                if feat[attribute[0]] == None:
                                    new_layer.changeAttributeValue(feat.id(), 0, m)
                                    new_layer.changeAttributeValue(feat.id(), 1, center.id()+1)
                                    new_layer.changeAttributeValue(feat.id(), 2, dist/1000)
                    m += 1


