# (c) Didier  LECLERC 2024 CHARGE D'ETUDES GEOMATIQUES MTE-MCTRCT/SG/DNUM/MSP/DS/GSG/PMIRG Site de Rouen
# créé sept 2024 

from qgis.PyQt import QtCore, QtGui, QtWidgets
from qgis.PyQt.QtWidgets import ( QMessageBox, QFileDialog, QColorDialog, QProgressDialog, QPushButton, QTreeWidgetItemIterator, QProgressDialog, QApplication ) 
from qgis.PyQt.QtCore    import ( Qt, QFile, QSize )
from qgis.PyQt.QtGui import ( QPalette, QColor, QIcon, QKeySequence, QPixmap, QCursor )

from qgis.core import ( QgsSettings )

from qgis.core import *
from qgis.gui import *
import qgis                              
import os                       
import os.path
import platform
import csv
import requests
import base64
import xml.etree.ElementTree as ET
import re
import math
import psycopg2
from collections import Counter
from datetime import datetime

from qgireferentiels.config import (QGIREFERENTIELS_VERSION)  

#==================================================
dicIcon = {
       'POINT'              : "mIconPointLayer.svg",
       'STRING'             : "mIconLineLayer.svg",
       'POLYGON'            : "mIconPolygonLayer.svg",
       'LINESTRING'         : "mIconLineLayer.svg",          
       'MULTIPOINT'         : "mIconPointLayer.svg",
       'MULTISTRING'        : "mIconLineLayer.svg",          
       'MULTIPOLYGON'       : "mIconPolygonLayer.svg",
       'MULTILINESTRING'    : "mIconLineLayer.svg",          
       'RASTER'             : "mIconRasterLayer.svg",
       'TABLE'              : "mIconTableLayer.svg",
       'GEOMETRYCOLLECTION' : "mIconTableLayer.svg",
       'INDETERMINE'        : "mIconUndefined.svg",    
       'NOGEOMETRY'         : "mIconUndefined.svg"    
         } 
#==================================================

#==================================================
# Données privées
# Retourne True si une clé a été saisie  
def isExisteKeyPrivateGPF(_mValueLoginGPF):
    if _mValueLoginGPF ==  ""  : return False
    if _mValueLoginGPF is None : return False
    return True 
    
#==================================================
# Données privées
# Retourne True si PRIVATE  
def find_private(_libelle):
    _libelle      = _libelle.upper()
    strChaineFind = '/private/'
    strChaineFind = strChaineFind.upper()
    return True if _libelle.find(strChaineFind) != -1 else False

#==================================================
# Recherche de psql sur le disque c:\\ 
def find_psql():

    # Windows
    if os.name == "nt":
        drives = ["C:\\", "D:\\"]  # Liste des lecteurs à scanner
        for root in drives:
            for dirpath, _, filenames in os.walk(root):
                if "psql.exe" in filenames:
                    return os.path.join(dirpath, "psql.exe")

    # Linux / Mac
    else:
        paths = ["/usr/bin", "/usr/local/bin", "/opt/postgresql/bin"]
        for path in paths:
            psql_path = os.path.join(path, "psql")
            if os.path.isfile(psql_path) and os.access(psql_path, os.X_OK):
                return psql_path

    return ""
    
#==================================================
# Charge et transforme le fichier xml en dictionanire python 
def analyseXmlDistribution( self, retXmlOrMessErreur, _zone = None, _format = None, _dateFrom = None, _dateTo = None, _return_date_millesime = None  ) :
        
   if retXmlOrMessErreur == None : return {}

   # Charger et parser l'XML
   root = ET.fromstring(retXmlOrMessErreur)

   # Créer le dictionnaire
   data_dict = {}
   for referentiel in root.findall('referentiel'):
       zone   = referentiel.find('zone').text
       if zone[0:1].upper() == "R"    : zone = zone[0:1] + zone[2:]   # Substitue les R0xx par Rxx  pour Distibution
       if zone.upper()      == "D000" : zone = "FXX"                  # Substitue les D0000 par FXX pour Distibution
       
       format = referentiel.find('format').text

       _cond = True
       if _zone != None :
          _cond = (zone and zone in _zone)
          if _format != None :
             _cond = ( _cond and (format and format in _format) )
       else :   
          if _format != None :
             _cond = (format and format in _format)
       if _cond :
          #print("_dateFrom _dateTo", _dateFrom, _dateTo)
          # Gestion des dates  
          if _dateFrom is not None and _dateTo is not None:
             _dateFrom, _dateTo = _dateFrom[0:4], _dateTo[0:4]
             date = referentiel.find('annee').text
             #print("date _dateFrom _dateTo", date, _dateFrom, _dateTo)
             if date is not None  :
                if ( date >= _dateFrom and date <= _dateTo ) :
                   _cond = True
                else :   
                   _cond = False

       if _cond :  
          nom_fichier = referentiel.find('nom_fichier').text
          details = {child.tag: child.text for child in referentiel if child.tag != 'nom_fichier'}

          # Pour millesime
          details["nom_fichier"] = nom_fichier                                              # J'ajoute la clé dans les valeurs 
          details["nom_fichier_for_millesime"] = nom_fichier.split('.')[0].rsplit('-',1)[0] # enlève par exemple "-ED201.7z.001" 
          details["Millesime"] = True if _return_date_millesime else False  
          
          # Gestion des archives "GI_Assemblege vs GI_brute"
          etat = referentiel.find('Etat').text
          if not self.mArchiveBrut :
             if etat.upper() != "GI_BRUTE" :
                data_dict[nom_fichier] = details
          else :   
             data_dict[nom_fichier] = details
             
   # Gestion des doublons pour trouver le millésime nom_fichier_for_millesime dans et mettre True dans Millésime 
   if _return_date_millesime == True : 
       data_dict = mettre_a_jour_millesime_Distribution(data_dict)          
   data_dict = dict(sorted(data_dict.items(), key=lambda item: item[1]['url']))

   return data_dict 

#==================================================
# For Distribution
# Gestion des doublons pour trouver le millésime nom_fichier_for_millesime et Ediiton et mettre True dans Millésime 
# En lien avec la focntion analyseXmlDistribution() au dessus
def mettre_a_jour_millesime_Distribution(myDico):
    groupes = {}

    # Regroupement par 'nom_fichier_for_millesime'
    for k, v in myDico.items():
        if isinstance(v, dict) and 'nom_fichier_for_millesime' in v:
            cle = v['nom_fichier_for_millesime']
            groupes.setdefault(cle, []).append(v)

    # Mise à jour du champ 'Millesime' basé sur année + édition
    for fichiers in groupes.values():
        # Calculer la (annee, edition) la plus élevée
        max_key = max(
            (
                (int(f.get('annee', 0)), int(f.get('edition', 'ED0').upper().replace('ED', '')))
                for f in fichiers
                if 'annee' in f and 'edition' in f
            ),
            default=(0, 0)
        )

        # Marquer uniquement ceux qui correspondent à cette (annee, edition)
        for f in fichiers:
            cle = (int(f.get('annee', 0)), int(f.get('edition', 'ED0').upper().replace('ED', '')))
            f['Millesime'] = (cle == max_key)

    # Enlève totues les occurences à False
    myDico = {k: v for k, v in myDico.items() if v.get("Millesime") is not False}        
    return myDico

#==================================================
# mapping Iso TreeDistribution - TreePanier
def mappingIsoTreeDistributionTreePanier(self, _mTreeCouchesEntrepot, _mTreeCouchesPanier, videOrDeletePanier = None) :

    # Treeview panier
    iterator = QTreeWidgetItemIterator(_mTreeCouchesPanier)
    mDicEntrepotRessource = []
    
    while iterator.value():
       item = iterator.value()
       # Liste des cases à cocher à NE PAS prendre en compte
       _listItemChekcBoxOK    = ['ressource','ressourcegidistribution']

       if ( item.data(1, QtCore.Qt.ItemDataRole.DisplayRole).lower() in _listItemChekcBoxOK or item.data(2, QtCore.Qt.ItemDataRole.DisplayRole).lower() in _listItemChekcBoxOK ) :  
          mDicEntrepotRessource.append( item.data(0, QtCore.Qt.ItemDataRole.DisplayRole) )
       iterator += 1

    if videOrDeletePanier is None :    
       # Treeview réferentiels
       iterator = QTreeWidgetItemIterator(_mTreeCouchesEntrepot)
       while iterator.value():
          item = iterator.value()
          if item.text(0) in mDicEntrepotRessource : 
             item.setFlags(item.flags() & ~QtCore.Qt.ItemFlag.ItemIsEnabled)
          iterator += 1

    elif videOrDeletePanier == "videOrDeletePanier" :   # Cas où on supprime un item dans le panier
       if len(mDicEntrepotRessource) == 0 :    
          # Treeview réferentiels
          iterator = QTreeWidgetItemIterator(_mTreeCouchesEntrepot)
          while iterator.value():
             item = iterator.value()
             state = item.checkState(0)
             item.setCheckState(0, Qt.CheckState.Unchecked)
             item.setFlags(item.flags() | QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable)
             iterator += 1
       else :    
          # Treeview réferentiels
          iterator = QTreeWidgetItemIterator(_mTreeCouchesEntrepot)
          while iterator.value():
             item = iterator.value()
             state = item.checkState(0)

             if item.text(0) not in mDicEntrepotRessource : 
                item.setCheckState(0, Qt.CheckState.Unchecked)
                item.setFlags(item.flags() | QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable)
             iterator += 1

    return

#==================================================
# Maj libellé du texte du nombre d'objets sélectionnés
def updateLibelleQCheckBoxSelectObjets(_selfQgeoDistribution, _mTreeCouchesEntrepot, _mTreeCouchesPanier) :
    countRessourceSelectionneeEntrepot = 0
    countLotSelectionneePanier         = 0
    countRessourceSelectionneePanier   = 0

    # Treeview réferentiels
    iterator = QTreeWidgetItemIterator(_mTreeCouchesEntrepot)
    while iterator.value():
       item = iterator.value()
       state = item.checkState(0)  
       if state == Qt.CheckState.Checked: 
          countRessourceSelectionneeEntrepot += 1
       iterator += 1
    
    # Treeview panier
    iterator = QTreeWidgetItemIterator(_mTreeCouchesPanier)
    while iterator.value():
       item = iterator.value()
       
       # Liste des cases à cocher à NE PAS prendre en compte
       _listItemChekcBoxOK    = ['ressource','ressourcegidistribution']
       _listItemLotChekcBoxOK = ['ressourcepanier','ressourcegidistributionpanier']

       if ( item.data(1, QtCore.Qt.ItemDataRole.DisplayRole).lower() in _listItemChekcBoxOK or item.data(2, QtCore.Qt.ItemDataRole.DisplayRole).lower() in _listItemChekcBoxOK ) :  
          countLotSelectionneePanier += 1

       if ( item.data(1, QtCore.Qt.ItemDataRole.DisplayRole).lower() in _listItemLotChekcBoxOK or item.data(2, QtCore.Qt.ItemDataRole.DisplayRole).lower() in _listItemLotChekcBoxOK ) :  
          countRessourceSelectionneePanier += 1
       iterator += 1

    if countRessourceSelectionneeEntrepot == 0 and countLotSelectionneePanier == 0 :
       _flagEnabledAddBasket = False
       _flagEnabledOther     = False
    else :    
       if countRessourceSelectionneeEntrepot == 0 and countLotSelectionneePanier != 0 :
          _flagEnabledAddBasket = False
          _flagEnabledOther     = True
       elif countRessourceSelectionneeEntrepot != 0 and countLotSelectionneePanier == 0 :
          _flagEnabledAddBasket = True
          _flagEnabledOther     = False
       else :   
          _flagEnabledAddBasket = True
          _flagEnabledOther     = True
    
    _selfQgeoDistribution.buttonAddBasket.setEnabled(_flagEnabledAddBasket)
    _selfQgeoDistribution.buttonEmptyBasket.setEnabled(_flagEnabledOther)
    _selfQgeoDistribution.buttonDownloadBasket.setEnabled(_flagEnabledOther)
    _selfQgeoDistribution.buttonGenerateBasket.setEnabled(_flagEnabledOther)
    _selfQgeoDistribution.treeActionSaveBasket.setEnabled(_flagEnabledOther)

    if countRessourceSelectionneePanier == 0 :
       _selfQgeoDistribution.mLabelNbOpenBasket.setText( "" )                 
       _selfQgeoDistribution.mLabelNbBasket.setText( " (-) " + QtWidgets.QApplication.translate("qgidistribution_ui", "ressource(s)", None) )
       _selfQgeoDistribution.mTreeListePanier.imageBasket = QPixmap(os.path.abspath(os.path.dirname(__file__) + "/icons/general/basket.svg"))
    else :     
       _selfQgeoDistribution.mLabelNbOpenBasket.setText( "(" + str(countLotSelectionneePanier) + ")" )                 
       _selfQgeoDistribution.mLabelNbBasket.setText( "" + str(countRessourceSelectionneePanier) + " " + QtWidgets.QApplication.translate("qgidistribution_ui", "ressource(s)", None) + " dans " + str(countLotSelectionneePanier) + " lot(s)" )
       _selfQgeoDistribution.mTreeListePanier.imageBasket = QPixmap(os.path.abspath(os.path.dirname(__file__) + "/icons/general/basket_gi_gpf.svg"))
    return  
    
#==================================================
# Retourne le nombre d'occurences en égalité avec le nom du référentiel pour télécharger l'enregistrement du LOT 
def returnNombreOccurence( nameReferentiel, mDic, GPF = None ) :
    if GPF == None : 
       base_keys = [ key.rsplit('.', 1)[0] for key in mDic.keys()]
    elif GPF == "GPF" :    
       base_keys = [ str(os.path.basename(key.rsplit('.', 1)[0])) for key in mDic.keys()]

    counts = Counter(base_keys)
    return  counts.get(nameReferentiel, 0)

#==================================================
# Lit le fichier xml de Distribution sur le serveur 
def get_and_read_xml(self, url, username, password, _HTTP_PROXY_GPF, _HTTPS_PROXY_GPF, _HTTP_PROXY_GI_DISTRIBUTION, _HTTPS_PROXY_GI_DISTRIBUTION):
    try:
        os.environ["HTTP_PROXY"]  = _HTTP_PROXY_GI_DISTRIBUTION
        os.environ["HTTPS_PROXY"] = _HTTPS_PROXY_GI_DISTRIBUTION
        os.environ["http_proxy"]  = _HTTP_PROXY_GI_DISTRIBUTION
        os.environ["https_proxy"] = _HTTPS_PROXY_GI_DISTRIBUTION
        
        # Récupérer le fichier XML depuis le serveur
        response = requests.get(url, auth=(username, password))
        response.raise_for_status()

        # Retourner le contenu du fichier XML comme chaîne de caractères
        os.environ["HTTP_PROXY"]  = _HTTP_PROXY_GPF
        os.environ["HTTPS_PROXY"] = _HTTPS_PROXY_GPF
        os.environ["http_proxy"]  = _HTTP_PROXY_GPF
        os.environ["https_proxy"] = _HTTPS_PROXY_GPF
        return response.content

    except requests.exceptions.RequestException as e:
        if self.quellePlateforme != "GPF" :   # Cas où on a choisit uniquement la GPF dans la liste déroualnte et donc, on n'affiche pas le message 
           zTitre = QtWidgets.QApplication.translate("qgidistribution_ui", "PLUGIN QGIREFERENTIELS : Attention" , None)
           zMess1  = QtWidgets.QApplication.translate("qgidistribution_ui", "A connection problem occurred on Géo-IDE Distribution." , None)           
           zMess1 += "\n\n" + QtWidgets.QApplication.translate("qgidistribution_ui", "An exception was thrown:" , None)           
           zMess1 += "\n" + f"{e}"

           QMessageBox.warning(self, zTitre, zMess1)
           print(f"Erreur lors de la requête : {e}")
        return None

#==================================================
# Obtenir 
def analyseXml( retXmlOrMessErreur, page_size=10, page_number=1) :
    if isinstance(retXmlOrMessErreur, dict):
        raise TypeError("xml_GetCapabilities should be a string, not a dictionary")

    # Définir l'espace de noms Atom pour extraire les balises
    namespaces = {'atom': 'http://www.w3.org/2005/Atom', 
                  'georss': 'http://www.georss.org/georss', 
                  'gpf_dl': 'https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd'}
    
    # Parse le XML à partir de la chaîne
    root = ET.fromstring(retXmlOrMessErreur)
    
    # Dictionnaire de résultat
    result = {}

    # Parse le XML à partir de la chaîne
    root = ET.fromstring(retXmlOrMessErreur)
    
    # Dictionnaire de résultat
    result = {}
    
    # Extraction des informations sur la pagination dans l'élément `feed`
    feed = root.find('.')
    page_size_xml = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}pagesize", page_size))
    nb_occurence  = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}totalentries", 0))
    #nb_page       = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}pagecount", (nb_occurence + page_size - 1) // page_size))
    _param50      = 50
    nb_page       = math.ceil( nb_occurence / _param50 )
         
    # Parcours de chaque entry dans le XML
    for entry in root.findall('atom:entry', namespaces):
        # Extraction de l'id, du title et du content
        item_id = entry.find('atom:id', namespaces).text
        title   = entry.find('atom:title', namespaces).text
        content = entry.find('atom:content', namespaces).text
        
        # Ajout au dictionnaire avec l'id comme clé et un tuple (title, content) comme valeur
        result[item_id] = (title, content)
        sorted_result   = dict(sorted(result.items(), key=lambda item: item[1][0]))
        result          = sorted_result
    return result, page_size_xml, nb_occurence, nb_page
               
#==================================================
# Obtenir 
def analyseXmResource( self, retXmlOrMessErreur, page_size=10, page_number=1, _return_date_millesime = None) :
    if isinstance(retXmlOrMessErreur, dict):
        raise TypeError("xml_GetCapabilities should be a string, not a dictionary")

    # Définir l'espace de noms Atom pour extraire les balises
    namespaces = {'atom': 'http://www.w3.org/2005/Atom', 'georss': 'http://www.georss.org/georss', 'gpf_dl': 'https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd'}
    
    # Parse le XML à partir de la chaîne
    try:
       # Nettoyage du contenu XML (en cas de BOM ou d'espaces)
       cleaned_xml = retXmlOrMessErreur.lstrip('\ufeff').strip()

       # Parsing XML
       root = ET.fromstring(cleaned_xml)

    except ET.ParseError as e:
       # Affichage d'une boîte de dialogue d'erreur XML
       QMessageBox.critical(self, "Erreur XML", 
           f"Le fichier XML est invalide :\n{str(e)}\n\n"
           f"Début du contenu reçu :\n{cleaned_xml[:200]}")
       root = None

    except Exception as e:
       # Affichage d'une erreur inattendue
       QMessageBox.critical(self, "Erreur Inattendue",
           f"Une erreur est survenue lors du traitement XML :\n{str(e)}")
       root = None
    
    if root == None : 
       QApplication.setOverrideCursor( QCursor( Qt.CursorShape.ArrowCursor ) )
       QApplication.setOverrideCursor( QCursor( Qt.CursorShape.ArrowCursor ) )
       return None, None, None, None
    #root = ET.fromstring(retXmlOrMessErreur)
    
    # Dictionnaire de résultat
    result = {}
    
    # Extraction des informations sur la pagination dans l'élément `feed`
    feed = root.find('.')
    page_size_xml = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}pagesize", page_size))
    nb_occurence  = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}totalentries", 0))
    #nb_page       = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}pagecount", (nb_occurence + page_size - 1) // page_size))
    _param50      = 50
    nb_page       = math.ceil( nb_occurence / _param50 )
         
    # Parcours de chaque entry dans le XML
    for entry in root.findall('atom:entry', namespaces):
        # Extraction de l'id, du title et du content
        item_id = entry.find('atom:id', namespaces).text
        title   = entry.find('atom:title', namespaces).text
        """
        # Vérification et extraction du contenu de la balise <georss:polygon>
        for georss_tag in dicIcon.keys() :
            georss_tag = georss_tag.lower() 
            georss_value =  None
            georss_element = entry.find(f'georss:{georss_tag}', namespaces)
            if georss_element is not None:
                # Extraire le type de géométrie (i.e., 'polygon', 'line', 'point')
                georss_value = georss_tag  # Le nom de la balise sans espace de noms
                break
        polygon_value = georss_value
        """
        # Extraction de la valeur de <gpf_dl:format> pour obtenir "SHP" s'il est présent
        format_value = None
        format_element = entry.find('gpf_dl:format', namespaces)
        if format_element is not None:
           format_value = format_element.get('term')  # Récupérer l'attribut "term" de la balise format
           
        #Ajout Flag Millesime
        _val_millesime = (True if _return_date_millesime else False)
        
        # Ajout au dictionnaire avec l'id comme clé et un tuple (title, content) comme valeur
        result[item_id] = ( title, format_value, _val_millesime )
    # Gestion des doublons pour trouver le millésime nom_fichier_for_millesime dans et mettre True dans Millésime
    if _return_date_millesime == True : 
       result = mettre_a_jour_millesime_GPF(result)          
    return result, page_size_xml, nb_occurence, nb_page

#==================================================
# For GPF
# Gestion des doublons pour trouver le millésime nom_fichier_for_millesime et Ediiton et mettre True dans Millésime 
# En lien avec la focntion analyseXmlDistribution() au dessus
def mettre_a_jour_millesime_GPF(dico):
    groupes = {}

    # Étape 1 : regrouper les fichiers par nom de base (sans date)
    for url, (nom_complet, type_fichier, flag) in dico.items():
        match = re.match(r'(.+)_\d{4}-\d{2}-\d{2}$', nom_complet)
        if match:
            base_nom = match.group(1)
            groupes.setdefault(base_nom, []).append((url, nom_complet, type_fichier))

    # Étape 2 : pour chaque groupe, trouver celui avec la date la plus récente
    for base_nom, fichiers in groupes.items():
        fichiers_par_date = []

        for url, nom_complet, type_fichier in fichiers:
            match = re.search(r'_(\d{4}-\d{2}-\d{2})$', nom_complet)
            if match:
                date = datetime.strptime(match.group(1), "%Y-%m-%d")
                fichiers_par_date.append((date, url, nom_complet, type_fichier))

        if fichiers_par_date:
            fichiers_par_date.sort()
            _, url_plus_recent, _, _ = fichiers_par_date[-1]

            # Mise à jour du dico
            for _, url, nom_complet, type_fichier in fichiers_par_date:
                dico[url] = (nom_complet, type_fichier, url == url_plus_recent)

    # Enlève totues les occurences à False
    dico = {k: v for k, v in dico.items() if v[2] is not False}        
    return dico
    
#==================================================
# Obtenir 
def analyseXmSubResource( retXmlOrMessErreur, page_size=10, page_number=1) :
    if isinstance(retXmlOrMessErreur, dict):
        raise TypeError("xml_GetCapabilities should be a string, not a dictionary")

    # Définir l'espace de noms Atom pour extraire les balises
    namespaces = {'atom': 'http://www.w3.org/2005/Atom', 'georss': 'http://www.georss.org/georss', 'gpf_dl': 'https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd'}
    
    # Parse le XML à partir de la chaîne
    root = ET.fromstring(retXmlOrMessErreur)
    
    # Dictionnaire de résultat
    result = {}

    page_size_xml = 10
    nb_occurence  = 10
    nb_page       = 1
    
    # Extraction des informations sur la pagination dans l'élément `feed`
    feed = root.find('.')
    page_size_xml = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}pagesize", page_size))
    nb_occurence = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}totalentries", 0))
    nb_page    = int(feed.get("{https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd}pagecount", (nb_occurence + page_size - 1) // page_size))
    
    # Parcours de chaque entry dans le XML
    for entry in root.findall('atom:entry', namespaces):
        # Extraction de l'id, du title et du content
        item_id = entry.find('atom:id', namespaces)
        title   = entry.find('atom:title', namespaces)
        
        if not (item_id is None or title is None) :
           item_id = entry.find('atom:id', namespaces).text
           title   = entry.find('atom:title', namespaces).text
           # Ajout au dictionnaire avec l'id comme clé et un tuple (title, content) comme valeur
           result[item_id] = title
    
    return result, page_size_xml, nb_occurence, nb_page
    
#==================================================
# Obtenir 
def analyseXmDownload( retXmlOrMessErreur) :
    if isinstance(retXmlOrMessErreur, dict):
        raise TypeError("xml_GetCapabilities should be a string, not a dictionary")

    # Définir l'espace de noms Atom pour extraire les balises
    namespaces = {'atom': 'http://www.w3.org/2005/Atom', 'georss': 'http://www.georss.org/georss', 'gpf_dl': 'https://data.geopf.fr/annexes/ressources/xsd/gpf_dl.xsd'}
    
    # Parse le XML à partir de la chaîne
    root = ET.fromstring(retXmlOrMessErreur)
    
    # Dictionnaire de résultat
    result = {}
    
    # Parcours de chaque entry dans le XML
    for entry in root.findall('atom:entry', namespaces):
        # Extraction de l'id, du title et du content
        item_id = entry.find('atom:id', namespaces).text
        
        # Ajout au dictionnaire avec l'id comme clé et un tuple id comme valeur
        result[item_id] = item_id
    
    return result

#==================================================
# Obtenir des informations sur le système d'exploitation
def getOsInfo() : return ( platform.system(), platform.release() ) 

#==================================================
#==================================================
def returnVersion() : return QGIREFERENTIELS_VERSION

#==========================         
def myIconGeom(_geom) :
    dicIcon = {
       'POINT'              : "mIconPointLayer.svg",
       'STRING'             : "mIconLineLayer.svg",
       'POLYGON'            : "mIconPolygonLayer.svg",
       'LINESTRING'         : "mIconLineLayer.svg",          
       'MULTIPOINT'         : "mIconPointLayer.svg",
       'MULTISTRING'        : "mIconLineLayer.svg",          
       'MULTIPOLYGON'       : "mIconPolygonLayer.svg",
       'MULTILINESTRING'    : "mIconLineLayer.svg",          
       'RASTER'             : "mIconRasterLayer.svg",
       'TABLE'              : "mIconTableLayer.svg",
       'GEOMETRYCOLLECTION' : "mIconTableLayer.svg",
       'INDETERMINE'        : "mIconUndefined.svg",    
       'NOGEOMETRY'         : "mIconUndefined.svg"    
    } 
    _imageSvg =  dicIcon[_geom] if _geom in dicIcon else "mIconUndefined.svg"    
    return os.path.dirname(__file__) + "/icons/geom/" + _imageSvg

#==========================         
def myIconFormat(_format) :
    dicIcon = {
       'SHAPEFILE'    : "shp.svg",
       'SHP'          : "shp.svg",
       'GEOPACKAGE'   : "gpkg.svg",
       'GPKG'         : "gpkg.svg",
       'JPG'          : "jpg.svg",
       'JPEG'         : "jpg.svg",
       'JP2'          : "jpg.svg",
       'TAB'          : "tab.svg",          
       'TIFF'         : "tiff.svg",
       'TIF'          : "tiff.svg",
       'ASC'          : "asc.svg",
       'LAZ'          : "laz.svg",
       'SQL'          : "sql.svg",
       'SQLITE'       : "sqlite.svg",
       'GDB'          : "gdb.svg",
       'HYB'          : "hyb.svg",
       'GML'          : "gml.svg",
       'INDETERMINE'  : "mIconUndefined.svg"    
    } 
    """
    dicIcon = {
       'SHAPEFILE'    : "qgis_shp_icon.svg",
       'SHP'          : "qgis_shp_icon.svg",
       'GEOPACKAGE'   : "gpkg.svg",
       'GPKG'         : "gpkg.svg",
       'JPG'          : "qgis_jp2_icon.svg",
       'JPEG'         : "qgis_jp2_icon.svg",
       'JP2'          : "qgis_jp2_icon.svg",
       'TAB'          : "tab.svg",          
       'TIFF'         : "qgis_tif_icon.svg",
       'TIF'          : "qgis_tif_icon.svg",
       'ASC'          : "qgis_asc_icon.svg",
       'LAZ'          : "laz.svg",
       'SQL'          : "sql.svg",
       'SQLITE'       : "sqlite.svg",
       'INDETERMINE'  : "mIconUndefined.svg"    
    } 
    """

    if _format is None :
       _imageSvg =  "mIconUndefined.svg"    
    else :
       _format =  "JPG" if re.search("JP", _format) else _format    
       _imageSvg =  dicIcon[_format] if _format in dicIcon else "mIconUndefined.svg"    
    return os.path.dirname(__file__) + "/icons/format/" + _imageSvg
#==================================================
#Lecture du fichier ini pour dimensions Dialog
#==================================================
def returnAndSaveDialogParam(self, mAction, templateWidth = None, templateHeight = None) :
    mDicAutre          = {}
    mDicAutreColor     = {}
    mDicAutrePolice    = {}
    mDicAutreExtension = {}
    mSettings = QgsSettings()
    mSettings.beginGroup("QGIREFERENTIELS")
    mSettings.beginGroup("Generale")
    
    if mAction == "Load" :
       #Ajouter si autre param
       valueDefautL = 810
       valueDefautH = 640
       valueDefautDisplayMessage = "dialogBox"
       valueDefautDurationBarInfo = 10
       valueProfondeur            = 2
       valueOpenPanelValue        = [self.Dialog.width()/2, self.Dialog.width()/2, 0]  #temporaire
       valueOpenPanelValue        = [self.Dialog.width()/3, self.Dialog.width()/3, self.Dialog.width()/3]
       openPanelValueSplitterfiltre = [self.Dialog.height()/2, self.Dialog.height()/2, self.Dialog.height()/2, self.Dialog.height()/2] 
       valueZoneDownLotDirect     = 'false'
       valuePathReferentiel       =   os.path.dirname(__file__) 
       valuePathReferentielList   = [ os.path.dirname(__file__) ] 
       valueOrganisation          = ""
       valueZoneDateDerniere    = 'false'
       valueZoneRadioDate       = 'true'

       valueLoginGI                    = ""
       valueMdpGI                      = ""
       valueSelectQComboBox_GI_Proxy   = 0
       valueLabel_GI_Proxy             = ""
       valueSelectArchiveBrute         = 'false'

       valueLoginGPF                   = ""
       valueMdpGPF                     = ""
       valueSelectQComboBox_GPF_Proxy  = 1
       valueLabel_GPF_Proxy            = ""
       valueSelectPagination           = 'false'
       valueRadioPath                  = 1
       valueRadioPathZone              = ""
       valueFavoriFilter               = "Aucun"
                                                                                                                 
       mDicAutre["dialogLargeur"]    = valueDefautL
       mDicAutre["dialogHauteur"]    = valueDefautH
       mDicAutre["displayMessage"]   = valueDefautDisplayMessage
       mDicAutre["durationBarInfo"]  = valueDefautDurationBarInfo
       mDicAutre["profondeur"]       = valueProfondeur
       mDicAutre["OpenPanelValue"]   = valueOpenPanelValue
       mDicAutre["OpenPanelValueSplitterfiltre"]   = openPanelValueSplitterfiltre
       mDicAutre["zoneDownLotDirect"]    = valueZoneDownLotDirect      
       mDicAutre["valuePathReferentiel"] = valuePathReferentiel      
       mDicAutre["valuePathReferentielList"] = valuePathReferentielList      
       mDicAutre["valueOrganisation"]    = valueOrganisation    
       mDicAutre["zoneDateDerniere"]     = valueZoneDateDerniere    
       mDicAutre["zoneRadioDate"]        = valueZoneRadioDate    

       mDicAutre["valueLoginGI"]              = valueLoginGI      
       mDicAutre["valueMdpGI"]                = valueMdpGI      
       mDicAutre["selectQComboBox_GI_Proxy"]  = valueSelectQComboBox_GI_Proxy      
       mDicAutre["label_GI_Proxy"]            = valueLabel_GI_Proxy      
       mDicAutre["selectArchiveBrute"]        = valueSelectArchiveBrute      

       mDicAutre["valueLoginGPF"]             = valueLoginGPF      
       mDicAutre["valueMdpGPF"]               = valueMdpGPF      
       mDicAutre["selectQComboBox_GPF_Proxy"] = valueSelectQComboBox_GPF_Proxy      
       mDicAutre["label_GPF_Proxy"]           = valueLabel_GPF_Proxy      
       mDicAutre["selectPagination"]          = valueSelectPagination      

       mDicAutre["valueRadioPath"]            = valueRadioPath      
       mDicAutre["valueRadioPathZone"]        = valueRadioPathZone      

       mDicAutre["valueFavoriFilter"]         = valueFavoriFilter      
       #---- for Tooltip
       for key, value in mDicAutre.items():
           if not mSettings.contains(key) :
              mSettings.setValue(key, value)
           else :
              mDicAutre[key] = mSettings.value(key)
       #--                  
       #--                  
       mSettings.endGroup()
       mSettings.beginGroup("BlocsColor")
       #Ajouter si autre param
       mDicAutreColor["defaut"]                      = "#a38e63"
       mDicAutreColor["QGroupBox"]                   = "#a38e63"
       mDicAutreColor["QTabWidget"]                  = "#a38e63"
       mDicAutreColor["QLabelBackGround"]            = "#e3e3fd"
       mDicAutreColor["QGroupBox"]                   = "#a38e63"
       mDicAutreColor["QLabelBackGround"]            = "#e3e3fd"
       mDicAutreColor["ColorQSPlitter"]              = "#e5e5e5"
       

       for key, value in mDicAutreColor.items():
           if not mSettings.contains(key) :
              mSettings.setValue(key, value)
           else :
              mDicAutreColor[key] = mSettings.value(key)           
       #----
       mDicAutre = {**mDicAutre, **mDicAutreColor}          
       #--   
       #--                  
       mSettings.endGroup()
       mSettings.beginGroup("BlocsPolice")
       #Ajouter si autre param
       mDicAutrePolice["QGroupBoxEpaisseur"] = 1
       mDicAutrePolice["QGroupBoxLine"]    = "dashed"
       mDicAutrePolice["QGroupBoxPolice"]  = "Marianne"
       mDicAutrePolice["QTabWidgetPolice"] = "Marianne"

       for key, value in mDicAutrePolice.items():
           if not mSettings.contains(key) :
              mSettings.setValue(key, value)
           else :
              mDicAutrePolice[key] = mSettings.value(key)           
       #----
       mDicAutre = {**mDicAutre, **mDicAutrePolice}          
       #--                  
       #--                  
       mSettings.endGroup()

    elif mAction == "SavePersonnalisation" :
       mSettings.endGroup()
       mSettings.beginGroup("BlocsColor")
       #Gestion des couleurs des images
       mChild_premier = [mObj for mObj in self.groupBox_personnalisation.children()] 
       mLettre, mColorFirst, mDicSaveColor = "", None, {}
       for mObj in mChild_premier :
           for i in range(5) :
               if mObj.objectName() == "img_" + str(i) :
                  mLettre      = str(self.dicListLettre[i])
                  mColor       = mObj.palette().color(QPalette.ColorRole.Window)
                  mColorFirst  = mColor.name()
                  mDicSaveColor[mLettre] = mColorFirst
                  mDicAutreColor[mLettre] = mColorFirst

       #Ajouter si autre param
       for key, value in mDicAutreColor.items():
           mSettings.setValue(key, value)
       mSettings.endGroup()
       mSettings.beginGroup("BlocsPolice")
       mDicAutrePolice["QGroupBoxPolice"]      = self.zFontQGroupBox
       mDicAutrePolice["QTabWidgetPolice"]     = self.zFontQGroupBox
       for key, value in mDicAutrePolice.items():
           mSettings.setValue(key, value)
       #----
       mDicAutre = {**mDicAutreColor, **mDicAutrePolice}          
       #--  
       #--  
       mSettings.endGroup()
       mSettings.beginGroup("Generale")
       mDicAutre = {}
       mDicAutre["valuePathReferentiel"]     = self.mZoneQComboValuePathReferentiel.currentText()      
       mDicAutre["valuePathReferentielList"] = [ self.mZoneQComboValuePathReferentiel.itemText(ii) for ii in range(self.mZoneQComboValuePathReferentiel.count()) ]      
       mDicAutre["valueOrganisation"]    = self.mZoneValueOrganisation.text()
       self.mPaging                      = True   if self.mZonePaging.isChecked()      else False
       self.mArchiveBrut                 = True   if self.mZonePaging.isChecked()      else False
       mDicAutre["zoneDownLotDirect"]    = "true" if self.mZoneDownLotDirect.isChecked()     else "false"
       self.mDownLotDirect               = True   if self.mZoneDownLotDirect.isChecked()     else False
       
       mDicAutre["valueLoginGI"]              = self.mZoneValueloginGI.text()      
       mDicAutre["valueMdpGI"]                = self.mZoneValuemdpGI.text()     
       mDicAutre["selectQComboBox_GI_Proxy"]  = self.listQComboBoxProxy.index( self.mZoneChoiceProxyGIDistribution.currentText() )   
       mDicAutre["label_GI_Proxy"]            = self.mZoneLabelProxyGIDistribution.text()      
       mDicAutre["selectArchiveBrute"]        = "true" if self.mZoneArchiveBrut.isChecked() else "false"

       mDicAutre["valueLoginGPF"]             = self.mZoneValueloginGPF.text()      
       mDicAutre["valueMdpGPF"]               = self.mZoneValuemdpGPF.text()     
       mDicAutre["selectQComboBox_GPF_Proxy"] = self.listQComboBoxProxy.index( self.mZoneChoiceProxygpf.currentText() )   
       mDicAutre["label_GPF_Proxy"]           = self.mZoneLabelProxygpf.text()      
       mDicAutre["selectPagination"]          = "true" if self.mZonePaging.isChecked()      else "false"

       mDicAutre["valueRadioPath"]            = 1 if self.createRadioButton.pathDefault.isChecked() else 2      
       mDicAutre["valueRadioPathZone"]        = self.createRadioButton.mZonepathForce.text()      

       for key, value in mDicAutre.items():
           mSettings.setValue(key, value)

       mSettings.endGroup()
       #--         
    elif mAction == "Save" :
       mDicAutre["dialogLargeur"]      = self.Dialog.width()
       mDicAutre["dialogHauteur"]      = self.Dialog.height()
       mDicAutre["OpenPanelValue"]     = self.splitter.sizes()
       mDicAutre["openPanelValueSplitterfiltre"] = self.splitterfiltre.sizes()
       mDicAutre["zoneDateDerniere"]      = True   if self.createRadioButtonDate.mZoneChoiceDateDer.isChecked()      else False    

       for key, value in mDicAutre.items():
           mSettings.setValue(key, value)

       mSettings.endGroup()
       #--         
    mSettings.endGroup()
    mSettings.endGroup()    
    return mDicAutre


#==================================================
def resizeIhm(self, l_Dialog, h_Dialog) :
    #----
    #Réinit les dimensions de l'IHM
    returnAndSaveDialogParam(self, "Save")
    self.mDic_LH = returnAndSaveDialogParam(self, "Load")
    self.Dialog.lScreenDialog, self.Dialog.hScreenDialog = int(self.mDic_LH["dialogLargeur"]), int(self.mDic_LH["dialogHauteur"])
    #----
    self.tabWidget.setGeometry(QtCore.QRect(10,100,int(self.width()) - 20, int(self.height()) - int(self.deltaHauteurTabWidget)))
    self.groupBox_tab_widget_distribution.setGeometry(QtCore.QRect(10,10,int(self.tabWidget.width()) - 20, int(self.tabWidget.height()) - 40))
    self.groupBox_tab_widget_Perso.setGeometry(QtCore.QRect(10,10,int(self.tabWidget.width()) - 20, int(self.tabWidget.height()) - 40))
    self.groupBox_bandeau.setGeometry(QtCore.QRect(10,10,int(self.width()) - 20, 90))
    #----
    return  

#==========================         
def genereLabelWithDict(dicParamLabel ) :
    for k, v in dicParamLabel.items() :
        if v != "" :
           if k == "typeWidget"    : _label = v
           if k == "nameWidget"    : _label.setObjectName(v)
           if k == "toolTipWidget" : _label.setToolTip(v)
           if k == "aligneWidget"  : _label.setAlignment(v)
           if k == "textWidget"    : _label.setText(v)
           if k == "styleSheet"    : _label.setStyleSheet(v)
           if k == "wordWrap"      : _label.setWordWrap(v)
    return _label
    
#==========================         
def genereButtonWithDict( dicParamButton ) :
    for k, v in dicParamButton.items() :
        if k == "typeWidget"    : _buttonToolBar = v
        if k == "qSizePolicy"   : _buttonToolBar.setSizePolicy(v, v)
        if k == "iconWidget"    : _buttonToolBar.setIcon(QIcon(v))
        if k == "nameWidget"    : _buttonToolBar.setObjectName(v)
        if k == "toolTipWidget" : _buttonToolBar.setToolTip(v)
        if k == "actionWidget"  : _buttonToolBar.clicked.connect(v)
        if k == "autoRaise"     : _buttonToolBar.setAutoRaise(v)
        if k == "checkable"     : _buttonToolBar.setCheckable(v)
        #-- Icon Blank
        if k == "redimIcon"     :
           h = _buttonToolBar.iconSize().height()
           _buttonToolBar.setIconSize(QSize(1, h))
        #-- Text
        if k == "textWidget"    : _buttonToolBar.setText(v)
        #-- StyleSheet
        if k == "styleSheet"    : _buttonToolBar.setStyleSheet(v)
        #-- Raccourci
        if k == "shorCutWidget" : _buttonToolBar.setShortcut(QKeySequence(v))
    return _buttonToolBar

#==========================         
def genereButtonActionColor(self, mLayout, mButton, mImage, mReset, mButtonName, mImageName, mResetName, compt):
   i = compt
   line, col = compt, 3
   if compt in (0, 1, 2, 3) : 
      if compt == 0 :
         line, col = compt, 0
      if compt == 1 :
         line, col = compt + 2 , 0
      if compt == 2 :
         line, col = compt + 2 , 0
      if compt == 3 :
         line, col = compt + 2 , 0

      mButton.setObjectName(mButtonName)
      mButton.setText(self.dicListLettreLabel[i])
      mLayout.addWidget(mButton, line, col, Qt.AlignmentFlag.AlignTop)
      #
      mImage.setObjectName(mImageName)
      if self.dicListLettre[i] in self.mDic_LH :
         varColor = str( self.mDic_LH[self.dicListLettre[i]] ) 
         zStyleBackground = "QLabel { background-color : "  + varColor + "; }"
         mImage.setStyleSheet(zStyleBackground)
      mLayout.addWidget(mImage, line, col + 1, Qt.AlignmentFlag.AlignTop)
      #
      mReset.setObjectName(mResetName)
      mReset.setText(QtWidgets.QApplication.translate("qgidistribution_ui", "Reset")) 
      mLayout.addWidget(mReset, line, col + 2, Qt.AlignmentFlag.AlignTop)
      #
      mButton.clicked.connect(lambda : functionColor(self, mImage, i))
      mReset.clicked.connect(lambda : functionResetColor(self, mImage, i, mButtonName))
   return 

#==========================         
def functionColor_ok(self, mImage, i):
   # Supposons que mImage soit un QLabel ou un widget similaire
   mColor = mImage.palette().color(QPalette.ColorRole.Window)
   mColorInit = QColor(mColor.name())
   zMess = "{} {}"

   # Création d'une instance de QColorDialog
   color_dialog = QColorDialog(self)
   color_dialog.setWindowTitle(zMess)
   color_dialog.setCurrentColor(mColorInit)

   # Activer le canal alpha si nécessaire
   color_dialog.setOption(QColorDialog.ColorDialogOption.DontUseNativeDialog, True)

   # Afficher la boîte de dialogue et récupérer la couleur sélectionnée
   if color_dialog.exec():
       zColor = color_dialog.selectedColor()
       if zColor.isValid():
           zStyleBackground = "QLabel {{ background-color : {} }}".format(zColor.name())
           mImage.setStyleSheet(zStyleBackground)


#==========================         
def functionColor(self, mImage, i):
   mColor = mImage.palette().color(QPalette.ColorRole.Window)
   mColorInit = QColor(mColor.name())
   zMess = "%s %s" %(QtWidgets.QApplication.translate("qgidistribution_ui", "Choose a color : ", None), str(self.dicListLettreLabel[i]) )
   zColor = QColorDialog.getColor(mColorInit, self, zMess)
   if zColor.isValid():
      zStyleBackground = "QLabel { background-color : " + zColor.name() + " }"
      mImage.setStyleSheet(zStyleBackground)
   return 
  
#==========================         
#==========================         
def functionResetColor(self, mImage, i, mButtonName):
   listBlocsKey = [
           "QTabWidget",
           "ColorTarget",    
           "ColorQSPlitter",    
           "ColorEntrepotPatrimoine",
           "ColorEntrepotCarto"
           ]
   listBlocsValue = [
           "#a38e63",
           "#000000",
           "#e5e5e5",
           "#ddddbe",
           "#d8deff"
           ] 
   mDicDashBoard = dict(zip(listBlocsKey, listBlocsValue))

   if self.dicListLettre[i] in self.mDic_LH :
      varColor = str( mDicDashBoard[self.dicListLettre[i]] )
      zStyleBackground = "QLabel { background-color : "  + varColor + "; }"
      mImage.setStyleSheet(zStyleBackground)
   return   
   
#==================================================
def functionSavePerso(self):
    zTitre = QtWidgets.QApplication.translate("qgidistribution_ui", "PLUGIN QGIREFERENTIELS : Warning" , None)
    if self.listQComboBoxProxy.index( self.mZoneChoiceProxyGIDistribution.currentText() ) not in (0, 1) and  self.mZoneLabelProxyGIDistribution.text() == "" : 
       if self.listQComboBoxProxy.index( self.mZoneChoiceProxygpf.currentText() ) not in (0, 1) and  self.mZoneLabelProxygpf.text() == "" : 
          zMess1 = QtWidgets.QApplication.translate("qgidistribution_ui", "You must enter a proxy for Géo-IDE Distribution and for the Géoplatforme." , None)
       else :    
          zMess1 = QtWidgets.QApplication.translate("qgidistribution_ui", "You must enter a proxy for Géo-IDE Distribution." , None)
       displayMess(self, (2 if self.displayMessage else 1), zTitre, zMess1 , Qgis.Warning, self.durationBarInfo)
    elif self.listQComboBoxProxy.index( self.mZoneChoiceProxygpf.currentText() ) not in (0, 1) and  self.mZoneLabelProxygpf.text() == "": 
          zMess1 = QtWidgets.QApplication.translate("qgidistribution_ui", "You must enter a proxy for the Geoplatforme." , None)
          displayMess(self, (2 if self.displayMessage else 1), zTitre, zMess1 , Qgis.Warning, self.durationBarInfo)
    elif self.createRadioButton.pathForce.isChecked() and  self.createRadioButton.mZonepathForce.text() == "" : 
          zMess1 = QtWidgets.QApplication.translate("qgidistribution_ui", "You must enter a path for the destination of deferred downloads." , None)
          displayMess(self, (2 if self.displayMessage else 1), zTitre, zMess1 , Qgis.Warning, self.durationBarInfo)
    else : 
       returnAndSaveDialogParam(self, "SavePersonnalisation")
    return

#==================================================
def functionOpenPathReferentiel(self, _valuePathReferentiel):
    #boite de dialogue Fichiers               Dossier pour le téléchargement des référentiels
    InitDir  = _valuePathReferentiel
    _text    = QtWidgets.QApplication.translate("qgientrepot_ui", "File for downloading repositories", None)
    #TypeList = "Fichier de journalisation des actions effectuées dans QGIEntrepôt (*.log)"
    inputDir = QFileDialog.getExistingDirectory(self, QtWidgets.QApplication.translate("qgientrepot_ui", "Select a folder", None), InitDir, QFileDialog.Option.ShowDirsOnly|QFileDialog.Option.DontResolveSymlinks)
    #fileName = QFileDialog.getSaveFileName(self, _text, InitDir, TypeList)[0]
    inputDir = str(inputDir).strip()    
    if inputDir == "" : return
    # Insertion du nouvel item dans la liste QComBox
    functionInsertPathReferentiel(self, self.mZoneQComboValuePathReferentiel, inputDir)
    self.mZoneQComboValuePathReferentiel.setCurrentText(inputDir)
    return

#==================================================
def functionInsertPathReferentiel(self, _comboBox, _new_value):
    items = [_comboBox.itemText(i) for i in range(_comboBox.count())]
    if _new_value not in items : items.append(_new_value)
    items.sort()
    _comboBox.clear()
    _comboBox.addItems(items)
    return

#==================================================
def functionDeletePathReferentiel(self, _del_value):
    _comboBox = self.mZoneQComboValuePathReferentiel
    if _comboBox.count() < 2 : return
    items = [ _comboBox.itemText(i) for i in range(_comboBox.count()) if _comboBox.itemText(i) != _del_value ]
    items.sort()
    _comboBox.clear()
    _comboBox.addItems(items)
    return
                    
#==================================================
#Création GIF et Déplacement 
def addDeplaceGif(**kwargs) :
    if kwargs['_action'] == "ADD" :
       _movie = QtGui.QMovie(kwargs['_gif'])
       kwargs['_objet'].setMovie(_movie)
       _movie.start()
    kwargs['_objet'].setGeometry(QtCore.QRect(int(kwargs['_x']), int(kwargs['_y']), int(kwargs['_l']), int(kwargs['_h'])))
    return

#==================================================
#Execute Pdf 
#==================================================
def execPdf(nameciblePdf):
    paramGlob = nameciblePdf            
    os.startfile(paramGlob)

    return            
#==================================================
def getThemeIcon(theName):
    myPath = CorrigePath(os.path.dirname(__file__))
    myDefPathIcons = myPath + "/icons/logo/"
    myDefPath = myPath.replace("\\","/")+ theName
    myDefPathIcons = myDefPathIcons.replace("\\","/")+ theName
    myCurThemePath = QgsApplication.activeThemePath() + "/plugins/" + theName
    myDefThemePath = QgsApplication.defaultThemePath() + "/plugins/" + theName
    myQrcPath = "python/plugins/qgireferentiels/" + theName
    if QFile.exists(myDefPath): return myDefPath
    elif QFile.exists(myDefPathIcons): return myDefPathIcons
    elif QFile.exists(myCurThemePath): return myCurThemePath
    elif QFile.exists(myDefThemePath): return myDefThemePath
    elif QFile.exists(myQrcPath): return myQrcPath
    else: return theName

#==================================================
def returnIcon( iconAdress, _taille = 15) :
    iconSource = iconAdress
    iconSource = iconSource.replace("\\","/")
    icon = QtGui.QIcon()
    icon.addPixmap(QtGui.QPixmap(iconSource), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
    icon.actualSize(QSize(_taille, _taille))
    return icon 

#===============================              
def design_Items(_item, _police, _color) :
    #gras = 1, italic = 2 gras italic = 3
    _item.setFont(0, defineFont(_police))
    #color text
    mColor = QtGui.QBrush(QtGui.QColor(_color))
    _item.setForeground(0, mColor)
    return

#===============================              
def design_QAction(_item, _police, _color) :
    #gras = 1, italic = 2 gras italic = 3
    _item.setFont(0, defineFont(_police))
    #color text
    mColor = QtGui.QBrush(QtGui.QColor(_color))
    _item.setForeground(0, mColor)
    return
        
#==========================         
def defineFont( param ) : #gras = 1, italic = 2 gras italic = 3
    _font = QtGui.QFont()
    if param == 1 : 
       _font.setBold(True)
    elif param == 2 : 
       _font.setItalic(True)
    elif param == 3 : 
       _font.setBold(True)
       _font.setItalic(True)
    return _font  
    
#==================================================
def zMyFrenchMonth(zNumberMonth):
    aMyFrenchMonth = {1:"Janvier", 2:"Février", 3:"Mars",4:"Avril",5:"Mai",6:"Juin",7:"Juillet",8:"Août",9:"Septembre",10:"Octobre",11:"Novembre",12:"Décembre"}
    return aMyFrenchMonth[zNumberMonth]
    
#==========================         
def functionFont(self):
    self.zFontQGroupBox = self.fontQGroupBox.currentFont().family()
    return 

#==================================================
def execPdf(nameciblePdf):
    paramGlob = nameciblePdf            
    os.startfile(paramGlob)
    return  

#==================================================
def CorrigePath(nPath):
    nPath = str(nPath)
    a = len(nPath)
    subC = "/"
    b = nPath.rfind(subC, 0, a)
    if a != b : return (nPath + "/")
    else: return nPath

#==================================================
def createFolder(mFolder):
    try:
       os.makedirs(mFolder)
    except OSError:
       pass
    return mFolder
    
#==================================================
#==================================================
def displayMess(mDialog, type,zTitre,zMess, level=Qgis.Critical, duration = 10):
    #type 1 = MessageBar
    #type 2 = QMessageBox
    try : 
       if type == 1 :
          mDialog.barInfo.clearWidgets()
          mDialog.barInfo.pushMessage(zTitre, zMess, level=level, duration = duration)
       else :
          QMessageBox.information(mDialog,zTitre,zMess)
    except : 
          QMessageBox.information(mDialog,"Attention", "Un erreur est survenue dans l'affichage")
    return  

#==================================================
def debugMess(type,zTitre,zMess, level=Qgis.Critical):
    #type 1 = MessageBar
    #type 2 = QMessageBox
    if type == 1 :
       qgis.utils.iface.messageBar().pushMessage(zTitre, zMess, level=level)
    else :
       QMessageBox.information(None,zTitre,zMess)
    return  

#=======================
# Journalisation
class ManagerLog():
    def __init__(self, log_file_path):
       self.log_file_path = log_file_path
       # Gestion du fichier de log des actions
       if not os.path.exists(log_file_path):
          with open(log_file_path, "w", newline='', encoding="utf-8") as csvfile :
              writer = csv.writer(csvfile, delimiter=';')
       else :
          with open(log_file_path, "r", newline='', encoding="utf-8") as csvfile :
              writer = csv.writer(csvfile, delimiter=';')
       
    def writeManagerLog(self, _content) :
        _date  = datetime.now().strftime("%Y-%m-%d")
        _heure = datetime.now().strftime("%H:%M:%S")
        _machine = platform.uname()
        
        _myMess = f"{_date}; {_heure}; "
        _myMess += "; ".join(_content)
        _myMess += f"; {_machine}"

        # Écrire les valeurs dans le fichier CSV
        with open(self.log_file_path, "a", newline='') as csvfile:
           writer = csv.writer(csvfile, delimiter=';')
           writer.writerow(_myMess.split(';'))

#=======================
# Journalisation
class ManagerLogPostgreSQL():
   def __init__(self, _dbname, _user, _password, _host, _port, _schema, _table, _mValueOrganisation) :
       self.host     = _host
       self.port     = _port
       self.dbname   = _dbname
       self.user     = _user
       self.password = _password
       self.schema   = _schema
       self.table    = _table
       self._mValueOrganisation = _mValueOrganisation
              
   def writeManagerLogPostgreSQL(self, _content) :
       _schema = self.schema
       _table  = self.table
       cursor  = None
       
       try:
          conn = psycopg2.connect(
              dbname   = self.dbname,
              user     = self.user,
              password = self.password,
              host     = self.host,
              port     = self.port
          )
          
          # Pointeur
          cursor = conn.cursor()             
          
          _date_   = datetime.now().strftime("%Y-%m-%d")
          _date    = datetime.strptime(_date_, "%Y-%m-%d").date()
          _heure_  = datetime.now().strftime("%H:%M:%S")
          _heure   = datetime.strptime(_heure_, "%H:%M:%S").time()
          _machine = platform.uname()
          _organisation = self._mValueOrganisation
        
          # Create requête
          query = f"""
             INSERT INTO {_schema}.{_table} 
             (date, heure, organisation, plateforme, action, id_telechargement, lot, ressource, taille, temps, vitesse, machine)
             VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
          """
          # Définir les valeurs
          values = (_date, _heure, _organisation, _content[0], _content[1], _content[2], _content[3], _content[4], float(_content[5]) if _content[5] else 0.0, datetime.strptime(_content[6], "%H:%M:%S").time(), float(_content[7]) if _content[7] else 0.0, _machine)
          # Exécuter la requête
          cursor.execute(query, values)
          conn.commit()

       except Exception as e:
          print(f"Une erreur s'est produite : {e}")

       finally:
          # Fermer la connexion et le curseur
          if cursor is not None :
             if cursor:
                 cursor.close()
             if conn:
                 conn.close()
           
#==================================================
class ManagerPatienter() :
   #------
   def __init__(self, _title, _mess) :
       _pathIcons = os.path.dirname(__file__) + "/icons/logo"
       iconSource          = _pathIcons + "/distribution.svg"
       icon = QtGui.QIcon()
       icon.addPixmap(QtGui.QPixmap(iconSource), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
       #
       self.prgr_dialog = QProgressDialog(_mess, "",  0, 100)
       self.prgr_dialog.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
       self.prgr_dialog.setWindowIcon(icon)
       self.prgr_dialog.setFixedSize(500, 300)
       self.prgr_dialog.setMinimumDuration(0)
       self.prgr_dialog.mCancelButton = self.prgr_dialog.findChild(QPushButton)
       self.prgr_dialog.mCancelButton.setVisible(True)
       self.prgr_dialog.setCancelButtonText("Annuler")
       self.prgr_dialog.setWindowFlag(QtCore.Qt.WindowType.WindowContextHelpButtonHint, False)
       self.prgr_dialog.setWindowFlag(QtCore.Qt.WindowType.WindowCloseButtonHint, False)
       self.prgr_dialog.setWindowTitle(_title)
       self.prgr_dialog.setAutoReset(False)
       self.prgr_dialog.setAutoClose(False)
       self.prgr_dialog.show()

   #------
   def startDownload(self, value, message=None) :
       self.prgr_dialog.setValue(int(value))
       if message : self.prgr_dialog.setLabelText(message)
       QtCore.QCoreApplication.processEvents()  # Permet de rafraîchir l'UI      

#==================================================
# FIN
#==================================================

