# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Silextracteur
                                 A QGIS plugin
 Plugin pour l'extraction de formations géologiques à silicites depuis les flux WFS du RT SILEX
 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 time

from qgis import *
from qgis.PyQt import *
from qgis.core import *
from qgis.utils import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtCore import *
from processing.tools import dataobjects

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__), 'Silextracteur_dialog_base.ui'))


class SilextracteurDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(SilextracteurDialog, 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)
        self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint)

        # Connecting the signal of closing the olugin to a function
        self.finished.connect(self.on_finished)

        """Initialisation of some functions and tools"""
        self.preset.setIcon(QtGui.QIcon(os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Logos//Add.png')))
        self.download.setIcon(QtGui.QIcon(os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Logos//Download.png')))
        
        Utilitaires.progression(self.bar_1, self.progres_label, self.tr("Avancement dans la récupération des formations géologiques"), 0, "black;")

        self.layers = QgsProject.instance().mapLayers().values()
        self.attribute = "etage_final"

        self.combo_zone.setFilters(QgsMapLayerProxyModel.PolygonLayer)
        self.select_admin.setFilters(QgsMapLayerProxyModel.PolygonLayer)
        self.combo_cities.setFilters(QgsMapLayerProxyModel.PointLayer)

        self.charge = None

        """Linking buttons and functions"""
        self.preset.clicked.connect(Utilitaires.add_layers)
        self.download.clicked.connect(self.download_default_layers)
        self.ok_zone.clicked.connect(self.select_zone_layer)
        self.combo_feature.fieldChanged.connect(self.select_attribute_zone)
        self.ok_zone_2.clicked.connect(self.select_extraction_zone)
        self.ok_admin.clicked.connect(self.select_admin_type)
        self.ok_zone_3.clicked.connect(self.select_admin_layer)
        self.launch.clicked.connect(self.load_wfs)
        self.ok_wfs.clicked.connect(self.filter_wfs)
        self.choice_2.toggled.connect(self.types_list)
        self.choice_1.toggled.connect(self.types_list)
        self.ok_strati.clicked.connect(self.select_geol)
        self.check_cities.toggled.connect(self.enable_use_cities)
        self.ok_cities.clicked.connect(self.select_cities_layer)
        self.attribute_cities.fieldChanged.connect(self.select_field_cities)
        self.ok_cities_2.clicked.connect(self.select_city)
        self.half_launch.clicked.connect(self.extraction_strati)
        self.final_launch.clicked.connect(self.creation_final_layer)

    """Gestion of the correct loading and unloading the plugin"""
    # Launching the reset of the parameters when the plugin is launched
    def showEvent(self, event: QShowEvent):
        self.recharge()
    
    # Launching the reset of the parameters when the plugin is closed
    def on_finished(self):   
        self.recharge()

    # Reseting the parameters of the plugin when it is shut down for the next uses
    def recharge(self):
        window_bg = QApplication.instance().palette().window().color().name()
        if window_bg.lower() in ["#000000", "#1e1e1e", "#2b2b2b"]:
            self.color = "white"
        else:
            self.color = "black"

        self.charge = False

        self.combo_feature.clear()
        self.combo_feature.clearEditText()
        self.name_zone.clear()
        self.name_zone.clearEditText()
        self.combo_wfs.clear()
        self.list_strati.clear()
        self.layer_name.clear()
        self.combo_feature.setLayer(None)
        self.attribute_cities.setLayer(None)
        self.name_cities.clear()
        self.name_cities.clearEditText()

        self.combo_feature.setEnabled(False)
        self.name_zone.setEnabled(False)
        self.ok_zone_2.setEnabled(False)
        self.combo_admin.setEnabled(False)
        self.ok_admin.setEnabled(False)
        self.select_admin.setEnabled(False)
        self.ok_zone_3.setEnabled(False)
        self.launch.setEnabled(False)
        self.name_admin.setEnabled(False)
        self.combo_wfs.setEnabled(False)
        self.ok_wfs.setEnabled(False)
        self.choice_1.setChecked(True)
        self.choice_1.setEnabled(False)
        self.choice_2.setEnabled(False)
        self.list_strati.setEnabled(False)
        self.ok_strati.setEnabled(False)
        self.combo_cities.setEnabled(False)
        self.ok_cities.setEnabled(False)
        self.attribute_cities.setEnabled(False)
        self.name_cities.setEnabled(False)
        self.ok_cities_2.setEnabled(False)
        self.check_cities.setEnabled(False)
        self.layer_name.setEnabled(False)
        self.half_launch.setEnabled(False)
        self.final_launch.setEnabled(False)

        self.select_admin.hide()
        self.ok_zone_3.hide()
        self.name_admin.hide()
        self.launch.hide()
        self.label_8.hide()
        self.label_15.hide()

        self.setMinimumSize(0, 0)
        new_height = self.minimumSizeHint().height()
        self.resize(self.width(), new_height)

        path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Couches//{}.gpkg')
        if os.path.exists(path.format('Departements')) and os.path.exists(path.format('Communes')):
            self.download.setEnabled(False)
            self.download.hide()
        else:
            self.download.show()
            self.download.setEnabled(True)
        
        Utilitaires.progression(self.bar_1, self.progres_label, self.tr("Avancement dans la récupération des formations géologiques"), 0, self.color)


    @staticmethod
    def tr(message):
        return QCoreApplication.translate("SilextracteurDialogBase", message)


    def enable_use_cities(self):
        """
        Modification of information and access to the module depending on whether the user wishes to use a city or not
        """
        # Verification that users wish to use a town for the selection process
        if self.check_cities.isChecked():
            self.combo_cities.setEnabled(True)
            self.ok_cities.setEnabled(True)
            self.layer_name.setEnabled(False)
            self.half_launch.setEnabled(False)

            message = self.tr("Sélectionner la couche des communes (6/8)")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: {self.color}")

        else:
            self.combo_cities.setEnabled(False)
            self.ok_cities.setEnabled(False)
            self.attribute_cities.setEnabled(False)
            self.name_cities.setEnabled(False)
            self.ok_cities_2.setEnabled(False)
            self.layer_name.setEnabled(True)
            self.half_launch.setEnabled(True)

            if not self.final_launch.isEnabled():
                selected_features = len(self.GDR_SILEX.selectedFeatures())
                message = self.tr("Formation géologique sélectionnée (Nombre d'éléments sélectionnés: {}) (6/6)").format(selected_features)
                self.progres_label.setText(message)
                self.progres_label.setStyleSheet("color: {self.color}")


    def types_list(self):
        """
        Allows to change the field listed in the drop-down list when selecting the stratigraphy
        """
        try:
            if self.choice_1.isChecked():
                self.choice_2.setChecked(False)
                self.attribute = "etage_final"
                self.filter_wfs()

            elif self.choice_2.isChecked():
                self.choice_1.setChecked(False)
                self.attribute = "descr"
                self.filter_wfs()
        except Exception:
            pass


    def download_default_layers(self):
        """
        Recuperation of the default layers if they were not downloaded during the installation
        """
        # Creation of the loading window
        message = QCoreApplication.translate("SilextracteurDialogBase","Veuillez patienter... Le téléchargement des données peut prendre quelques minutes")
        self.progress_dialog = QProgressDialog(message, None, 0, 100)
        self.progress_dialog.setWindowModality(Qt.WindowModality.WindowModal)
        self.progress_dialog.setWindowTitle(message)
        self.progress_dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
        self.progress_dialog.setCancelButton(None)
        self.progress_dialog.setValue(0)
        self.progress_dialog.show()
        QCoreApplication.processEvents()

        # Preparations for the layers downloading
        names = ["Departements", "Communes"]
        path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Couches')

        self.progress_dialog.setLabelText(self.tr("Téléchargement de la couche des départements français..."))
        self.progress_dialog.setValue(25)
        QCoreApplication.processEvents()

        for name in names:
            gpkg_path = path.format(name)
            Utilitaires.recuperation(self, gpkg_path, name)

            if name == "Departements":
                self.progress_dialog.setLabelText(self.tr("Téléchargement de la couche des communes françaises..."))
                self.progress_dialog.setValue(50)
                QCoreApplication.processEvents()
            else:
                self.progress_dialog.setLabelText(self.tr("Vérification de l'installation..."))
                self.progress_dialog.setValue(99)
                QCoreApplication.processEvents()
        
        self.progress_dialog.setLabelText(self.tr("Terminé !"))
        self.progress_dialog.setValue(100)
        QCoreApplication.processEvents()

        self.download.setEnabled(False)
        self.download.hide()

        Utilitaires.add_layers()


    def select_zone_layer(self):
        """
        Enable selection of extraction zones once the layer has been selected
        """
        try:
            self.zone_layer = Utilitaires.layer_finder(self.combo_zone)
            # Define the source of the drop-down list of features in the selected layer
            self.combo_feature.setLayer(self.zone_layer)

            self.combo_feature.setEnabled(True)
            self.name_zone.setEnabled(True)
            self.ok_zone_2.setEnabled(True)

            message = self.tr("Sélectionnez la zone d'extraction recherchée (1/6)")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: {self.color}")
        except Exception:
            message = self.tr("La couche sélectionnée ne contient aucun champ.. Veuillez changer de couche")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: red;")

    
    def select_attribute_zone(self):
        """
        Selecting an attribute field to identify the extraction zone
        """
        self.name_zone.setSourceLayer(self.zone_layer)
        self.attribute_zone = self.combo_feature.currentField()
        # print(str(self.attribute_zone)) # debugtest

        # Change of reference field for entity selection
        self.name_zone.setDisplayExpression(self.attribute_zone)


    def select_extraction_zone(self):
        """
        Select the desired extraction zone
        """
        self.attribute_zone = self.combo_feature.currentField()
        self.zone_name = self.name_zone.currentText()
        try:
            # Selection in the layer using a given expression
            Fonctions_QGIS.Selectbyattribute(self.zone_layer, self.attribute_zone, self.zone_name, 0)
        except Exception:
            message = self.tr("L'entité choisie n'a pas été trouvée dans la couche. Veuillez en choisir une autre")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: red;")
            return

        # Specific case where an extraction was already made, and we need to check if the layer used is still in the project
        if self.charge:
            # Case where the user want to use the default layer of the French departements
            if self.combo_admin.currentIndex() == 0:
                self.select_admin.hide()
                self.ok_zone_3.hide()
                self.name_admin.hide()
                self.launch.hide()
                self.label_8.hide()
                self.label_15.hide()

                # Checking the layer used during last extraction
                layers_names = [layer.name() for layer in QgsProject.instance().mapLayers().values()]
                test = QgsProject.instance().mapLayersByName(self.name_layer_admin)

                # If present, recuperation of the layer and modifying the corresponding signal
                if test != []:
                    self.admin_layer = QgsProject.instance().mapLayersByName(self.name_layer_admin)[0]
                    self.charge = True
                # If not
                else:
                    name1 = self.tr("Departements")
                    name2 = self.tr("2 - Departements")
                    self.admin_layer = None
                    if name2 in layers_names:
                        self.admin_layer = QgsProject.instance().mapLayersByName(name2)[0]
                        self.name_layer_admin = name2
                    elif name1 in layers_names:
                        self.admin_layer = QgsProject.instance().mapLayersByName(name1)[0]
                        self.name_layer_admin = name1
                    else:
                        # Access to the layer in the module folder
                        path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Couches//Departements.gpkg')
                        layer = QgsVectorLayer(path, name1, "ogr")

                        # Defining the layer style
                        style_path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Styles//Departements.qml')
                        layer.loadNamedStyle(style_path)

                        QgsProject.instance().addMapLayer(layer)
                        self.admin_layer = QgsProject.instance().mapLayersByName(name1)[0]
                        self.name_layer_admin = name1
                    self.charge = True
            else:  
                self.charge = False

        if self.charge:
            self.load_wfs()
        else:
            message = self.tr("Zone d'extraction sélectionnée (2/6)")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: {self.color}")
        
        # Zoom on the selected features
        self.zone_layer.triggerRepaint()
        box = self.zone_layer.boundingBoxOfSelected()
        if self.zone_layer.crs() != QgsProject.instance().crs():
            transform = QgsCoordinateTransform(self.zone_layer.crs(), QgsProject.instance().crs(), QgsProject.instance())
            p1 = transform.transform(QgsPointXY(box.xMinimum(), box.yMinimum()))
            p2 = transform.transform(QgsPointXY(box.xMaximum(), box.yMaximum()))
            box = QgsRectangle(p1, p2)
        iface.mapCanvas().setExtent(box)
        iface.mapCanvas().refresh()

        self.combo_admin.setEnabled(True)
        self.ok_admin.setEnabled(True)


    def select_admin_type(self):
        """
        Choice of administrative boundary layer type (French departements or Personalized)
        """
        name1 = self.tr("Departements")
        name2 = self.tr("2 - Departements")
        try:
            QgsProject.instance().mapLayersByName(self.admin_layer.name())
            self.charge = True
        except Exception:
            self.charge = False

        if self.combo_admin.currentIndex() == 0 and not self.charge:
            self.select_admin.hide()
            self.ok_zone_3.hide()
            self.name_admin.hide()
            self.launch.hide()
            self.label_8.hide()
            self.label_15.hide()
            self.setMinimumSize(0, 0)

            if not QgsProject.instance().mapLayersByName(name2):
                if not QgsProject.instance().mapLayersByName(name1):
                    # Access to the layer in the module folder or WFS
                    path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Couches//Departements.gpkg')
                    if os.path.exists(path):
                        layer = QgsVectorLayer(path, name1, "ogr")
                    else:
                        url = "https://services3.arcgis.com/zy1UGXGNCAGm516O/arcgis/rest/services/Departements/FeatureServer/0"

                        uri = QgsDataSourceUri()
                        uri.setParam('url', url)
                        uri.setSrid('EPSG:3857')

                        layer = QgsVectorLayer(uri.uri(), name1, "arcgisfeatureserver")
                    
                    # Defining the layer style
                    style_path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Styles//Departements.qml')
                    layer.loadNamedStyle(style_path)

                    # Adding the layer to the map
                    QgsProject.instance().addMapLayer(layer)
            
            if name1 in {layer.name() for layer in list(QgsProject.instance().mapLayers().values())}:
                self.admin_layer = QgsProject.instance().mapLayersByName(name1)[0]
                self.name_layer_admin = name1
            else:
                self.admin_layer = QgsProject.instance().mapLayersByName(name2)[0]
                self.name_layer_admin = name2

            self.charge = True
            self.column_admin = "nom"

            self.load_wfs()

        elif self.combo_admin.currentIndex() == 0 and self.charge:
            self.select_admin.hide()
            self.ok_zone_3.hide()
            self.name_admin.hide()
            self.launch.hide()
            self.label_8.hide()
            self.label_15.hide()

            if QgsProject.instance().mapLayersByName(name1):
                self.admin_layer = QgsProject.instance().mapLayersByName(name1)[0]

            if QgsProject.instance().mapLayersByName(name2):
                self.admin_layer = QgsProject.instance().mapLayersByName(name2)[0]

            self.column_admin = "nom"
            self.load_wfs()

        elif self.combo_admin.currentIndex() == 1:
            self.select_admin.show()
            self.ok_zone_3.show()
            self.name_admin.show()
            self.launch.show()
            self.label_8.show()
            self.label_15.show()
            self.select_admin.setEnabled(True)
            self.ok_zone_3.setEnabled(True)


    def select_admin_layer(self):
        """
        Selecting the layer containing administrative entities
        """
        try:
            self.admin_layer = Utilitaires.layer_finder(self.select_admin)
            self.name_layer_admin = self.select_admin.currentText()
            # Define the source of the drop-down list of features in the selected layer
            self.name_admin.setLayer(self.admin_layer)

            self.name_admin.setEnabled(True)
            self.launch.setEnabled(True)

            message = self.tr("Sélectionnez la colonne de nom des entités administratives (3/6)")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: {self.color}")
        except Exception:
            message = self.tr("La couche choisie n'a pas été trouvée, ou ne possède pas de champs utilisables")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: red;")


    def load_wfs(self):
        """
        Loading of the RT SILEX WFS flow covering the whole of France, 
        list of administrative entities concerned and recovery of an initial sample of geological formation descriptions
        """
        self.list_names = []
        # Preparation of a layer group to put all the layers in the same place
        name = self.tr("Extractions_Silextracteur")
        self.group = QgsProject.instance().layerTreeRoot().findGroup(name)
        if self.group is None:
            self.group = QgsProject.instance().layerTreeRoot().insertGroup(0, name)
            self.group.setExpanded(True)

        try:
            if self.combo_admin.currentIndex() == 1:
                # Retrieve the column header from the administrative entities layer containing their Name
                self.column_admin = self.name_admin.currentField()
                self.charge = True

                if self.name_admin.currentField() == '':
                    message = self.tr("La couche de limites administratives n'est pas disponible ou aucun champ sélectionné")
                    self.progres_label.setText(message)
                    self.progres_label.setStyleSheet("color: red;")
                    return
        except Exception:
            message = self.tr("La couche de limites administratives ne possède pas le champ sélectionné")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: red;")
            return 
        
        try:
            if "Formations_{}".format(self.zone_name) not in [layer.name() for layer in QgsProject.instance().mapLayers().values()] and self.zone_name not in self.GDR_SILEX.name():
                for layer in QgsProject.instance().mapLayers().values():
                    # Comparaison directe à la source de la couche
                    if layer.source() == self.GDR_SILEX.source():
                        QgsProject.instance().removeMapLayer(layer.id())
                        break
        except Exception:
            pass 

        """Creation of an erosion of the extraction zone to select the administrative entities it covers,
        removing problems of precision/simplification of boundaries
        """
        eroded_layer = Fonctions_QGIS.Buffer(self.zone_layer, -2500, True, True)

        message = self.tr("Chargement des données depuis le WFS... (Cette étape peut prendre quelques instants...)")
        Utilitaires.progression(self.bar_1, self.progres_label, message, 50, "#0049E6")

        progress_dialog = QProgressDialog(self.tr("Chargement des données depuis le WFS... (Cette étape peut prendre quelques instants...)"), None, 0, 0)
        progress_dialog.setWindowModality(Qt.WindowModality.WindowModal)
        progress_dialog.setWindowTitle(self.tr("Veuillez patienter"))
        progress_dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
        progress_dialog.setCancelButton(None)
        progress_dialog.setValue(0)
        progress_dialog.show()
        QCoreApplication.processEvents()

        # Deselection of entities to facilitate searches and avoid duplication
        self.admin_layer.removeSelection()

        """Succession of selection by localisation to get the targeted geological formations"""
        # Selection by location of administrative entities covered by erosion
        Fonctions_QGIS.Selectbylocation_simple(self.admin_layer, eroded_layer, 0)

        if any("Formations_" in layer.name() for layer in QgsProject.instance().mapLayers().values()):
            for layer in QgsProject.instance().mapLayers().values():
                if "Formations_" in layer.name():
                    self.GDR_SILEX = layer
                    break

        if self.zone_name != self.name_zone.currentText() or not any("Formations_" in layer.name() for layer in QgsProject.instance().mapLayers().values()):
            # Selection by location of geological formations intersecting the selected administrative units
            self.GDR_SILEX = Utilitaires.prepare_extraction(self, self.zone_layer, self.zone_name)
            QgsProject.instance().addMapLayer(self.GDR_SILEX, False)

        # Adding the layer to the group
        if self.group.findLayer(self.GDR_SILEX.id()) is None:
            self.group.addLayer(self.GDR_SILEX)


        self.GDR_SILEX.selectAll()
        
        """Completion of the lists for the selections"""
        if len(self.GDR_SILEX.selectedFeatures()) != 0:
            # Creation of a list of administrative entities
            self.combo_wfs.clear()
            self.list_depart = []

            # Looping selected elements in the layer
            for feature in self.admin_layer.selectedFeatures():
                # print(feature['{}'.format(self.column_admin)]) # debugtest
                name = feature['{}'.format(self.column_admin)]
                # print(name) # debugtest
                self.list_depart.append(name)

            # Sorting the list
            self.list_depart.sort()
            # Adding administrative entities to the drop-down list
            self.combo_wfs.addItems(self.list_depart)

            # Creating a list of descriptions
            self.list_strati.clear()
            descr_list = []

            # Resetting the drop-down list for new selections
            self.list_strati.clear()
            self.list_strati.clearEditText()

            # Looping selected elements in the layer
            for feature in self.GDR_SILEX.selectedFeatures():
                descr_list.append(feature[self.attribute])

            # Retrieving unique values
            unique = set(descr_list)
            descr_list = list(unique)

            # Sorting the list
            descr_list.sort()

            # Adding descriptions to the drop-down list
            self.list_strati.addItems(descr_list)

            self.combo_wfs.setEnabled(True)
            self.ok_wfs.setEnabled(True)
            self.list_strati.setEnabled(True)
            self.ok_strati.setEnabled(True)
            self.layer_name.setEnabled(False)
            self.half_launch.setEnabled(False)
            self.final_launch.setEnabled(False)
            self.choice_1.setEnabled(True)
            self.choice_2.setEnabled(True)

            self.bar_1.reset()
            selected_features = len(self.GDR_SILEX.selectedFeatures())
            message = self.tr("Préparations terminées (Nombre d'éléments sélectionnés: {}) (4/6)").format(selected_features)
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: #006400")

        else:
            self.bar_1.reset()
            message = self.tr("Aucune formation sélectionnable")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: red;")
        
        progress_dialog.close()


    def filter_wfs(self):
        """
        Choice of administrative entities and selection of related geological layers
        """
        message = self.tr("Récupération des formations géologiques de l'entité administrative...")
        Utilitaires.progression(self.bar_1, self.progres_label, message, 50, self.color)

        admin = self.combo_wfs.currentText()
        # print("A: " + admin) # debugtest

        """Succession of selection by localisation to get the targeted geological formations"""
        # Selection by administrative entity attribute selected in the administrative entities layer 
        Fonctions_QGIS.Selectbyattribute(self.admin_layer, self.column_admin,admin, 0)
        # print("B: "+str(len(self.admin_layer.selectedFeatures()))) # debugtest

        # Selection by location of geological formations covered by the selected administrative unit
        Fonctions_QGIS.Selectbylocation(self.GDR_SILEX, self.admin_layer, True, 0)
        
        """Creation of a list of descriptions"""
        descr_list = []
        # Resetting the drop-down list for new selections
        self.list_strati.clear()
        self.list_strati.clearEditText()
        # print("C: "+str(len(self.GDR_SILEX.selectedFeatures()))) # debugtest

        # Looping selected elements in the layer
        for feature in self.GDR_SILEX.selectedFeatures():
            # Creation of a list to contain the Name of the administrative entity and its version in upper case
            list_admin = []
            d = admin.upper()
            list_admin.append(d)
            # print(list_admin) # debugtest
            # print(str(feature["nom_dept"])) # debugtest
            # print(str(feature["nom_dept"]) == list_admin) # debugtest

            try:
                # Only add descriptions of geological formations if they are linked to the administrative unit selected once.
                if feature[self.attribute] not in descr_list and feature["nom_dept"].upper() in list_admin:
                    descr_list.append(feature[self.attribute])
            except Exception:
                pass

        # Sorting the list
        self.list_strati.clear()
        descr_list.sort()

        # Adding descriptions to the drop-down list
        self.list_strati.addItems(descr_list)

        # Deselection of entities to facilitate searches and avoid duplication
        self.admin_layer.removeSelection()

        self.bar_1.reset()
        selected_features = len(self.GDR_SILEX.selectedFeatures())
        message = self.tr("Sélectionnez la formation géologique recherchée (Nombre d'éléments sélectionnés: {}) (5/6)").format(selected_features)
        self.progres_label.setText(message)
        self.progres_label.setStyleSheet("color: {self.color}")
    

    def select_geol(self):
        """
        Selection of geological formations based on their description
        """
        self.strati = self.list_strati.currentText()
        # print("A: "+str(len(self.GDR_SILEX.selectedFeatures()))) # debugtest

        if len(self.GDR_SILEX.selectedFeatures())!=0:
            # Selection of geological formations by attribute from the description selected in the existing selection
            Fonctions_QGIS.Selectbyattribute(self.GDR_SILEX, self.attribute, self.strati, 3)
        else: # If a second type of geological formation is loaded
            admin = self.combo_wfs.currentText()

            # Selection by attribute of the administrative entity concerned in the administrative entities layer
            Fonctions_QGIS.Selectbyattribute(self.admin_layer, self.column_admin, admin, 0)
            # print("B: "+str(len(self.admin_layer.selectedFeatures()))) # debugtest

            # Selection by location of geological formations from selected administrative units
            Fonctions_QGIS.Selectbylocation(self.GDR_SILEX, self.admin_layer, True, 0)
            # print("C: "+str(len(self.GDR_SILEX.selectedFeatures()))) # debugtest
            
            # Attribute-based selection of geological formations from the description selected in the existence selection
            Fonctions_QGIS.Selectbyattribute(self.GDR_SILEX, self.attribute, self.strati, 3)
            # print("D: "+str(len(self.GDR_SILEX.selectedFeatures()))) # debugtest

            # Deselection of entities to facilitate searches and avoid duplication
            self.admin_layer.removeSelection()

        # Zoom on the selected features
        self.GDR_SILEX.updateExtents()
        box = self.GDR_SILEX.boundingBoxOfSelected()
            
        if self.GDR_SILEX.crs() != QgsProject.instance().crs():
            transform = QgsCoordinateTransform(self.GDR_SILEX.crs(), QgsProject.instance().crs(), QgsProject.instance())
            p1 = transform.transform(QgsPointXY(box.xMinimum(), box.yMinimum()))
            p2 = transform.transform(QgsPointXY(box.xMaximum(), box.yMaximum()))
            box = QgsRectangle(p1, p2)

        if box.xMinimum() != 0 and box.xMaximum() != 0:
            iface.mapCanvas().setExtent(box)
            iface.mapCanvas().refresh()
        
        self.layer_name.setEnabled(True)
        self.half_launch.setEnabled(True)
        self.check_cities.setEnabled(True)

        selected_features = len(self.GDR_SILEX.selectedFeatures())
        message = self.tr("Formation géologique sélectionnée (Nombre d'éléments sélectionnés: {}) (6/6)").format(selected_features)
        self.progres_label.setText(message)
        self.progres_label.setStyleSheet("color: {self.color}")


    def select_cities_layer(self):
        """
        Selection of the layer containing city names
        """
        try:
            self.cities_layer = Utilitaires.layer_finder(self.combo_cities)
            # Define the source of the drop-down list of features in the selected layer
            self.attribute_cities.setLayer(self.cities_layer)
            self.name_cities.setSourceLayer(self.cities_layer)

            self.name_cities.setEnabled(True)
            self.attribute_cities.setEnabled(True)
            self.ok_cities_2.setEnabled(True)

            message = self.tr("Sélectionnez la ville recherchée (7/8)")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: {self.color}")
        except Exception:
            message = self.tr("La couche choisie n'a pas été trouvée, ou ne possède pas de champs utilisables")
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: red;")


    def select_field_cities(self):
        """
        Choice of identification field for towns and municipalities
        """
        self.cities_attribute = self.attribute_cities.currentField()

        self.name_cities.setAllowNull(False)
        self.name_cities.setFilterExpression(None)

        self.name_cities.setDisplayExpression(f'"{self.cities_attribute}"')
        self.name_cities.setIdentifierFields([self.cities_attribute])

        model = self.name_cities.model()
        if model:
            for row in range(min(100, model.rowCount())):
                model.data(model.index(row, 0))


    def select_city(self):
        """
        Select the city you are looking for in the city layer
        """
        self.cities_attribute = self.attribute_cities.currentField()
        # print(self.cities_attribute) debugtest
        city_name = self.name_cities.currentText()
        # print(city_name) # debugtest

        # Sélection dans la couche grâce à une expression donnée
        self.Ville = self.cities_layer.selectByExpression(" \"{}\" = '{}' ".format(self.cities_attribute, city_name))

        # Zoom on the selected features
        self.cities_layer.triggerRepaint()
        box = self.cities_layer.boundingBoxOfSelected()
        if self.cities_layer.crs() != QgsProject.instance().crs():
            transform = QgsCoordinateTransform(self.cities_layer.crs(), QgsProject.instance().crs(), QgsProject.instance())
            p1 = transform.transform(QgsPointXY(box.xMinimum(), box.yMinimum()))
            p2 = transform.transform(QgsPointXY(box.xMaximum(), box.yMaximum()))
            box = QgsRectangle(p1, p2)

        if box.xMinimum() != 0 and box.xMaximum() != 0:
            iface.mapCanvas().setExtent(box)
            iface.mapCanvas().refresh()
        
        self.layer_name.setEnabled(True)
        self.half_launch.setEnabled(True)

        message = self.tr("Ville sélectionnée (8/8)")
        self.progres_label.setText(message)
        self.progres_label.setStyleSheet("color: {self.color}")
        

    def extraction_strati(self):
        """
        Selection of geological formations according to whether or not a town is used
        """
        """Check whether the selection will be made in a particular city"""
        # Case 1: Using city centroid to extract targeted geological formations
        if self.check_cities.isChecked():
            if len(self.cities_layer.selectedFeatures()) == 0:
                self.select_city()

            # Creation of a buffer zone around the town, corresponding to the distance within which the geological formations are most likely to be found
            self.Buffer = Fonctions_QGIS.Buffer(self.cities_layer, 10000, True, True)

            # Creation of an additional zone to prevent courses that are too far away from each other from being selected
            min = Fonctions_QGIS.Buffer(self.cities_layer, 12000, True, True)
            max = Fonctions_QGIS.Buffer(self.cities_layer, 30000, True, True)
            self.limite = Fonctions_QGIS.Difference(min, max)

            features = self.GDR_SILEX.selectedFeatures()
            # print("A nb select = "+str(len(features))) # debugtest

            if len(features) != 0:
                # Selection by location of geological formations from the buffer
                Fonctions_QGIS.Selectbylocation_simple(self.GDR_SILEX, self.Buffer, 2)

            features = self.GDR_SILEX.selectedFeatures()
            # print("B nb select = "+str(len(features))) # debugtest

            if len(features) != 0:
                # Selection by attributes of geological formations with the description sought in the selection made
                Fonctions_QGIS.Selectbyattribute(self.GDR_SILEX, self.attribute, self.strati, 3)

                # Récupération des éléments sélectionnés dans la couche
                features = self.GDR_SILEX.selectedFeatures()
                # print("C nb select = "+str(len(features))) # debugtest

            # Deselection of entities to facilitate searches and avoid duplication
            self.cities_layer.removeSelection()
            self.zone_layer.removeSelection()

            message = self.tr("Début de la sélection des entités")
            Utilitaires.progression(self.bar_1, self.progres_label, message, 50, self.color)

        # Case 2: Directly extract targeted geological formations
        else:
            # print("A "+str(len(self.zone_layer.selectedFeatures()))) # debugtest
            if len(self.zone_layer.selectedFeatures()) != 0:
                # print("B "+str(len(self.GDR_SILEX.selectedFeatures()))) # debugtest
                buffer = Fonctions_QGIS.Buffer(self.zone_layer, 12000, True)
                
                # Selection by location of geological formations from the extraction zone selected in the existing selection
                Fonctions_QGIS.Selectbylocation_simple(self.GDR_SILEX, buffer, 0)
                # print("C "+str(len(self.GDR_SILEX.selectedFeatures()))) # debugtest

                if len(self.GDR_SILEX.selectedFeatures()) != 0:
                    # Attribute-based selection of geological formations from the description selected in the existence selection
                    Fonctions_QGIS.Selectbyattribute(self.GDR_SILEX, self.attribute, self.strati, 3)
            
            selected_features = len(self.GDR_SILEX.selectedFeatures())
            message = self.tr("Formation géologique sélectionnée (Nombre d'éléments sélectionnés: {}) (6/6)").format(selected_features)
            self.progres_label.setText(message)
            self.progres_label.setStyleSheet("color: {self.color}")

            # Retrieving items selected in the layer
            features = self.GDR_SILEX.selectedFeatures()
            # print("b_nb select = "+str(len(features))) # debugtest

            # Deselection of entities to facilitate searches and avoid duplication
            self.zone_layer.removeSelection()

            selected_features = len(self.GDR_SILEX.selectedFeatures())
            message = self.tr("Début de la sélection des entités (Nombre d'éléments sélectionnés: {})").format(selected_features)
            Utilitaires.progression(self.bar_1, self.progres_label, message, 50, self.color)


        progress_dialog = QProgressDialog(self.tr("Extraction des données en cours..."), None, 0, 0)
        progress_dialog.setWindowModality(Qt.WindowModality.WindowModal)
        progress_dialog.setWindowTitle(self.tr("Veuillez patienter"))
        progress_dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
        progress_dialog.setCancelButton(None)
        progress_dialog.setValue(0)
        progress_dialog.show()
        QCoreApplication.processEvents()


        """Preparation of the layer for the extracted data"""
        name = self.layer_name.text()
        if name == "":
            name = "Extraction"

        # Check that one of the layers is not already loaded
        if not QgsProject.instance().mapLayersByName(name):
            # Creation of the intermediate layer containing the geological formations selected before aggregation
            geological_formation = Utilitaires.layer_creation("Polygon", "3857", name, self.GDR_SILEX)
            QgsProject.instance().addMapLayer(geological_formation, False)

            # Adding the layer to the group
            if self.group.findLayer(geological_formation.id()) is None:
                self.group.addLayer(geological_formation)

            # Defining the layer style
            style_path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Styles//Legende_CODE_HEXA_ETAGE_FINAL_vEV.qml')
            if os.path.exists(style_path):
                geological_formation.loadNamedStyle(style_path)
            else:
                pass

        # Select the layer by name
        geological_formation = next((child.layer() for child in self.group.children() if isinstance(child, QgsLayerTreeLayer) and child.layer().name() == name), None)

        # Addition of selected features to the intermediate layer
        layer_data = geological_formation.dataProvider()
        layer_data.addFeatures(features)

        try:
            # Check that you want to use a town for the selection process
            if self.check_cities.isChecked():
                # Using the city-specific selection function
                self.selection_with_city(geological_formation, layer_data)
                self.admin_layer.removeSelection()
            else:
                # Use of the selection function specific to extraction zones
                self.selection_with_zone(geological_formation)  
                self.admin_layer.removeSelection()

            if name == "Extraction":
                for feat in geological_formation.getFeatures():
                    formation = feat["etage_final"]
                    break
                if self.check_cities.isChecked():
                    ville = self.name_cities.currentText()
                    name = formation + "_" + ville
                else:
                    zone = self.name_zone.currentText()
                    name = formation + "_" + zone
                geological_formation.setName(name)
                self.name = name
                geological_formation.removeSelection()
            else:
                self.name = self.layer_name.text()
            
            if name not in self.list_names:
                self.list_names.append(self.name)

            message = self.tr("Fin de la sélection des couches géologiques. Terminer la sélection ou lancer une nouvelle recherche")
            iface.messageBar().pushMessage(self.tr("Succès:"), self.tr("L'extraction a été réalisée avec succès"), level=Qgis.MessageLevel.Success)
            Utilitaires.progression(self.bar_1, self.progres_label, message, 100, "#006400")
        
        except Exception:
            QgsProject.instance().removeMapLayer(geological_formation)
            message = self.tr("Une erreur c'est produite: Aucune formation n'a été extraite")
            iface.messageBar().pushMessage(self.tr("Erreur:"), self.tr("Aucune formation récupérable"), level=Qgis.MessageLevel.Critical)
            Utilitaires.progression(self.bar_1, self.progres_label, message, 0, "red;")

        # Deselection of entities to facilitate searches and avoid duplication
        self.GDR_SILEX.removeSelection()

        progress_dialog.close()

        self.layer_name.setEnabled(False)
        self.half_launch.setEnabled(False)
        self.final_launch.setEnabled(True)
        self.check_cities.setChecked(False)


    def verification_loop(self, layer, WFS, attribute, strati):
        """
        Perform an entity selection round of a WFS flow for the layer currently being completed

        :param layer: Layer of geological formations being completed
        :type layer: QgsVectorLayer

        :param WFS: WFS flow where entities will be selected
        :type WFS: wfs

        :param attribute: Name of reference field for selection
        :type attribute: str

        :param strati: Value for selection
        :type strati: str

        :returns: Translated string
        :rtypes: str
        """
        # print("0") # debugtest
        # Creating a buffer around geological formations that are already isolated
        buffered_layer = Fonctions_QGIS.Buffer(layer, 1000, False, True)

        # print("1") # debugtest
        # Selection by location of geological formations intersecting the buffer in the existing selection
        Fonctions_QGIS.Selectbylocation_simple(WFS, buffered_layer, 0)

        # print("2") # debugtest
        # Attribute-based selection of geological formations based on the reference value in the existing selection
        Fonctions_QGIS.Selectbyattribute(WFS, attribute, strati, 3)

        # print("3") # debugtest
        # Erosion of already isolated geological formations
        buffered_layer = Fonctions_QGIS.Buffer(layer, -1, False, True)

        # Verification that users wish to use a town for the selection process
        if self.check_cities.isChecked():
            # Locate geological formations intersecting the boundary zone to deselect them
            Fonctions_QGIS.Selectbylocation_simple(WFS, self.limite, 3)

        # Location-based selection of geological formations intersecting erosion to deselect already isolated geological formations
        Fonctions_QGIS.Selectbylocation_simple(WFS, buffered_layer, 3)
        # print("4") # debugtest


    def selection_with_zone(self, geological_formation):
        """
        Selection of the additional geological formation from the extraction zone

        :param geological_formation: Intermediate layer containing selected geological formations prior to aggregation 
        :type strati: str
        """
        # Retrieving items selected in the layer
        features = self.GDR_SILEX.selectedFeatures()
        # print("nb select = "+str(len(features))) # debugtest

        if len(features) != 0:
            # Using the verification loop to select additional training courses
            self.verification_loop(geological_formation, self.GDR_SILEX, self.attribute, self.strati)
        

    def selection_with_city(self, geological_formation, layer_data):
        """
        Selection of complementary geological formations from the city's buffer zone

        :param geological_formation: Intermediate layer containing selected geological formations prior to aggregation 
        :type geological_formation: str

        :param layer_data: Access to data in the layer being modified
        :type layer_data: data provider of a layer
        """
        repeater = True

        # Retrieving items selected in the layer
        features = self.GDR_SILEX.selectedFeatures()
        # print("AA nb select = "+str(len(features))) # debugtest

        if len(features) != 0:
            # Using the verification loop to select additional training courses
            self.verification_loop(geological_formation, self.GDR_SILEX, self.attribute, self.strati)

        # Selection as long as additional courses in the zone can be selected
        while repeater:
            # Retrieving items selected in the layer
            features=self.GDR_SILEX.selectedFeatures()
            # print("T nb select = "+str(len(features))) # debugtest

            if len(features)>1:
                # Add selected entities to the layer being completed 
                layer_data.addFeatures(features)

                # Using the verification loop to select additional training courses
                self.verification_loop(geological_formation, self.GDR_SILEX, self.attribute, self.strati)
            else:
                repeater = False            
        selected_features = len(self.GDR_SILEX.selectedFeatures())
        message = self.tr("Nombre de formations sélectionnées (Nombre d'éléments sélectionnés: {})").format(selected_features)
        Utilitaires.progression(self.bar_1, self.progres_label, message, 80, self.color)
        
                    
    def creation_final_layer(self):
        """
        Creation of the final layer once the entities of each type of silicites have been aggregated
        """
        self.bar_1.reset()
        message = self.tr("Regroupement des polygones dans la couche finale...")
        Utilitaires.progression(self.bar_1, self.progres_label, message, 50, self.color)

        progress_dialog = QProgressDialog(self.tr("Regroupement des polygones dans la couche finale..."), None, 0, 0)
        progress_dialog.setWindowModality(Qt.WindowModality.WindowModal)
        progress_dialog.setWindowTitle(self.tr("Veuillez patienter"))
        progress_dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
        progress_dialog.setCancelButton(None)
        progress_dialog.setValue(0)
        progress_dialog.show()
        QCoreApplication.processEvents()

        missing = 0

        for layer_name in self.list_names:
            # Select the layer by name
            layer = QgsProject.instance().mapLayersByName(layer_name)

            try:
                # Aggregation of intermediate layer entities to facilitate selections
                fusion = Fonctions_QGIS.Aggregation(layer[0])

                # Addition of an extra field to retain the name of the type of silica/geological stage
                name = self.tr("Nom_extraction")
                fusion.dataProvider().addAttributes([QgsField(name, QVariant.String)])
                fusion.updateFields()

                # Extra field index retrieval
                field_idx = fusion.fields().indexOf(name)
                with edit(fusion):
                    for feature in fusion.getFeatures():
                        # Changing the field value
                        fusion.changeAttributeValue(feature.id(), field_idx, layer_name)

                # Check that one of the layers is not already loaded
                name = self.tr("Couches_Geologiques")
                if QgsProject.instance().mapLayersByName(name):
                    # Select the layer by name
                    new_layer = QgsProject.instance().mapLayersByName(name)[0]
                else:
                    # Create the layer if it doesn't exist
                    new_layer = Utilitaires.layer_creation("Polygon", "3857", name, fusion)
                    QgsProject.instance().addMapLayer(new_layer, False)

                    # Defining the layer style
                    style_path = os.path.dirname(os.path.abspath(__file__)).replace('Silextracteur','Styles//Legende_CODE_HEXA_ETAGE_FINAL_vEV.qml')
                    if os.path.exists(style_path):
                        new_layer.loadNamedStyle(style_path)
                    else:
                        pass

                    # Adding the layer to the group
                    if self.group.findLayer(new_layer.id()) is None:
                        self.group.addLayer(new_layer)
                
                # Retrieving entities from the aggregated layer
                features=fusion.getFeatures()

                # Add the selected entities to the final layer
                new_layer.dataProvider().addFeatures(features)
            
            except Exception:
                iface.messageBar().pushMessage(self.tr("Erreur:"), self.tr("La couche {} n'existe plus ou son nom a été modifié. Impossible de l'intégrer à la couche finale.").format(layer_name), level=Qgis.MessageLevel.Critical)
                missing += 1

        # Deletion of the extraction after the integration
        for layer in QgsProject.instance().mapLayers().values():
            # Comparaison directe à la source de la couche
            if layer.source() == self.GDR_SILEX.source():
                QgsProject.instance().removeMapLayer(layer.id())
                break

        if missing == 0:
            iface.messageBar().pushMessage(self.tr("Succès:"), self.tr("L'ajout à la couche finale a été réalisé avec succès"), level=Qgis.MessageLevel.Success)
        else:
            iface.messageBar().pushMessage(self.tr("Attention:"), self.tr("Certaines des couches n'ont pu être intégrées à la couche finale car leur nom a été modifié. Consultez les messages d'erreur pour identifier lesquelles"), level=Qgis.MessageLevel.Warning)

        progress_dialog.close()

        """Resetting the plugin after use"""
        self.list_names = []
        message = self.tr("Avancement dans la récupération des formations géologiques")
        self.progres_label.setText(message)
        self.progres_label.setStyleSheet("color: {self.color}")
        self.bar_1.reset()

        self.combo_feature.clear()
        self.combo_feature.clearEditText()
        self.name_zone.clear()
        self.name_zone.clearEditText()
        self.combo_wfs.clear()
        self.list_strati.clear()
        self.layer_name.clear()
        self.combo_feature.setLayer(None)
        self.attribute_cities.setLayer(None)
        self.name_cities.clear()
        self.name_cities.clearEditText()

        self.combo_feature.setEnabled(False)
        self.name_zone.setEnabled(False)
        self.ok_zone_2.setEnabled(False)
        self.combo_admin.setEnabled(False)
        self.ok_admin.setEnabled(False)
        self.select_admin.setEnabled(False)
        self.ok_zone_3.setEnabled(False)
        self.launch.setEnabled(False)
        self.name_admin.setEnabled(False)
        self.combo_wfs.setEnabled(False)
        self.ok_wfs.setEnabled(False)
        self.choice_1.setChecked(True)
        self.choice_1.setEnabled(False)
        self.choice_2.setEnabled(False)
        self.list_strati.setEnabled(False)
        self.ok_strati.setEnabled(False)
        self.combo_cities.setEnabled(False)
        self.ok_cities.setEnabled(False)
        self.attribute_cities.setEnabled(False)
        self.name_cities.setEnabled(False)
        self.ok_cities_2.setEnabled(False)
        self.check_cities.setEnabled(False)
        self.layer_name.setEnabled(False)
        self.half_launch.setEnabled(False)
        self.final_launch.setEnabled(False)

        