## Script Banbou pour le prétraitement des données des dossiers de recollement
## de fibre optique. 

import os, shutil, datetime
import openpyxl as xls
#from openpyxl.styles import Alignment, PatternFill






# DEFINITIONS DE FONCTIONS

def formatter(chaine):
    """Formate selon nomenclature.

    Formate la chaine de caractères passée en paramètre :
    Enlève tous les accents français. Enlève la cédille du C.
    Remplace les espaces ' ' et les traits d'union '-' par
    des tirets bas '_'.
    Ne traite pas pour le moment le AE et OE ligaturé.
    Si plusieurs '_' se suivent, les réduire à un seul.
    """
    #TODO : gérer les accents sur majuscules
    #TODO : amélioration ou exercice,  utiliser la méthode str.translate() et maketrans
    resultat = ""
    precedent = None
    for c in chaine:
        match c:
            case "à" | "â" | "ä":
                resultat+= "a"
            case "é" | "è" | "ê" | "ë":
                resultat+= "e"
            case "î" | "ï":
                resultat+= "i"
            case "ô" | "ö":
                resultat+= "o"
            case "ù" | "û" | "ü":
                resultat+= "u"
            case "ÿ":
                resultat+= "y"
            case "ç":
                resultat+= "c"
            case " " | "-" | "_":
                if('_' not in precedent ):
                    resultat+= "_"
                c = "_"
            case _:
                resultat+= c
        precedent = c
    return resultat



def creer_liste(dossier):
    """
        Construit une liste avec les fichiers de 'dossier'

        Parcourt le dossier et ses sous-dossier, ajoute TOUS les fichiers
        dans une liste de _Fichier.
        Chaque _Fichier ajouté a ses attributs mis a jour.
        Retourne la liste.
        :param user_input: nom complet du dossier
        :return: liste d'élements de type _Fichier
    """
    liste = []

    for dossier_courant, list_sousdossiers, list_fichiers in os.walk(dossier):
        for  fichier_courant in list_fichiers:  
            print(f"\nfichier courant : {fichier_courant}")
            ce_Fichier = _Fichier.lire(dossier_courant, fichier_courant)
            liste.append(ce_Fichier)
            
    return liste


# TODO surcharger la fonction print native pour cet affichage
def afficher(liste):
    """
        Affiche le nom des fichiers de la liste des _Fichiers
    """
    for courant in liste:
        print("\n" + courant.nom + "." + courant.extension)

        



def controller_projection(id_point, point_x, point_y):
    """
        regarde si les coordonnées d'un point sont bien dans une des projections
        autorisées

        Contrainte :
        - Pour le moment, ne peut reconnaitre que des projections
        de points situés en France métropolitaine (Lambert93 et les 9 zones CC).
        - Ce sont toutes des projections coniques depuis le pole nord --> la longitude (axe Ouest Est) n'est pas déformé par ces projections et donc reste valide.
        Mais je vais aussi contraindre la longitude à la France métropolitaine, pour pas qu'un point situé en Russie par exemple apparaisse comme valide.
        
        param user_input: un point de type (int, float, float) TODO: avoir si conversion nécessaire ou faire en chaine de carac
        return: Une chaine nommant la projection trouvé sinon "Mauvaise projection"
    """
    # Valeurs constantes définie par IGN
    projections_conformes = [
        {
            "nom" : "GPS",
            "E0" : 0,
            "N0" : 0,
            "fenetreE0" : 180,
            "fenetreN0" : 90,
            "EPSG" : 4326 # Correspond à la projection des GPS
        },
        {
            "nom" : "Lambert93",
            "E0" : 700000,
            "N0" : 6600000,
            "fenetreE0" : 600000,
            "fenetreN0" : 600000,
            "EPSG" : 2154 # Attention la plus recente est 9794
        },
        {
            "nom" : "CC42",
            "E0" : 1700000,
            "N0" : 1200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3942
        },
        {
            "nom" : "CC43",
            "E0" : 1700000,
            "N0" : 2200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3943
        },
        {
            "nom" : "CC44",
            "E0" : 1700000,
            "N0" : 3200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3944
        },
        {
            "nom" : "CC45",
            "E0" : 1700000,
            "N0" : 4200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3945
        },
        {
            "nom" : "CC46",
            "E0" : 1700000,
            "N0" : 5200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3946
        },
        {
            "nom" : "CC47",
            "E0" : 1700000,
            "N0" : 6200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3947
        },
        {
            "nom" : "CC48",
            "E0" : 1700000,
            "N0" : 7200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3948
        },
        {
            "nom" : "CC49",
            "E0" : 1700000,
            "N0" : 8200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3949
        },
        {
            "nom" : "CC50",
            "E0" : 1700000,
            "N0" : 9200000,
            "fenetreE0" : 600000,
            "fenetreN0" : 111000,
            "EPSG" : 3950
        }
        # TODO : ajouter les projections DOMTOMs
    ]

    # projection à retourner
    projection = "Mauvaise projection"

    # NOTE : Une partie des ensembles de coordonnées de CC47 et de Lambert93 s'intersectionne : 
    # Lorsqu'un point de coordonnées (X, Y) se situe dans la plage 1100000, 1300000 pour X et
    # la plage 3089000, 3311000 pour Y, alors ce point est à la fois valide en CC47 et en Lambert93
    # Avant de déterminer la projection, je vais donc vérifier si le point à controler n'est pas dans cette plage
    # Et notifier si necessaire
    # TODO : exporter la notif
    if 6089000  <  point_y  <  6311000:
        if 1100000  <  point_x  <  1300000:
            print("\nAvertissement : ces coordonnées peuvent être interpréter correctement comme du CC47 et du Lambert93")
            print("Normalement vous devrez projeter la Table de Point en CC47")


    # définie la projection conique Nord en regardant dans quelle intervalle la valeur se situe
    for elem in projections_conformes:
        borne_basse = elem["N0"] - elem["fenetreN0"]
        borne_haute = elem["N0"] + elem["fenetreN0"]
        if  borne_basse  <  point_y  <  borne_haute :
            projection = elem["nom"]

    print(f"\nProj. trouvé : {projection}")

    # Controle la longitude
    # TODO: refaire cette partie avec des variables et non des entiers literraux       
    longitude_correcte = False
    match projection:
        case "Lambert93":
            if 100000  <  point_x  < 1300000 :
                longitude_correcte = True
        case "CC42" | "CC43" | "CC44" | "CC45" | "CC46" | "CC47" | "CC48" | "CC49" | "CC50":
            if 1100000  <  point_x  < 2300000 :
                longitude_correcte = True
        case "GPS":
            if -180 < point_x < 180:
                longitude_correcte = True
        case _:
            pass
    
    if not longitude_correcte :
        projection = "Mauvaise projection"
        print(f"\nAvertissement : Longitude du point id {id_point} pas en métropole.")
    
    return projection



def remplir_VISA(modele, projet):
    """
        Complète le Visa du projet à partir d'un fichier modele.

        Controle le nb de fichiers de chaque catégorie et notifie.
        Parcours la liste de notifications du projet et ajoute chaque
        notification dans la feuille concerné du classeur
        A défaut, une notification de la liste qui n'a pas de catégorie
        avec sa propre feuille sera ajoutée à la première feuille (FRONT)

        Enregistre le Visa dans le repertoire de Travail
        Contraintes : 
         - le modele du VISA soit être NÉCESSAIREMENT dans le même
           dossier que le script. TODO : traité l'exception si fichier non trouvé
         - le modele du VISA NE doit PAS avoir eu de modification notable 
           dans son design ( emplacements des cellules utilisées pour 
           notifier, nom des feuilles, etc) 
    """

    # Masques Binaires représentants les controles de chaques catégorie traitées
    # les bits sont allumés à chaque position où un test existe
    FAIL_CSV = 7            # Explication : il existe pour le moment 3 tests pour les CSVs donc :   "0000 0000 0000 0111"
    FAIL_DWG = 16           #               un seul test pour les DWGs                          :   "0000 0000 0001 0000"
    FAIL_PDF = 256          #               un seul test pour les PDFs                          :   "0000 0001 0000 0000"
    FAIL_INFO = 4096        #               un seul test pour les fiches infos                  :   "0001 0000 0000 0000"







    # creation d'un workbook à partir d'un modele
    classeur = xls.load_workbook(modele)
    

    ## definition personnalisé vers chaque feuille du classeur
    FRONT = classeur[classeur.sheetnames[0]]
    PDF = classeur[classeur.sheetnames[1]]
    CSV = classeur[classeur.sheetnames[2]]
    DWG = classeur[classeur.sheetnames[3]]



    # feuille = classeur.active

    # cell = feuille["A2"]
    # cell.value = "Dossier : "+ racine.name

    ## NOTE: cell est la cellule courante dans la feuille
    ## Ainsi cell = 'A1'

    # Nom dossier
    # Date de reception
    # Date Visa
    FRONT['C1'].value = f'Analyse : {projet.nom}\nDate de réception :\nDate visa : {projet.date}'



    # TODO: factoriser les instructions ci dessous --> faire une fonction

    # FRONT DWG
    # Je ne peux pas controler automatiquement la projection du DWG (c'est un binaire illisible hors de AutoCAD)
    # J'ajoute automatiquement la mention comme dans le script précedent.
    FRONT['C3'].value = f"{projet.nb_dwgs} fichier(s) DWG présent(s).\nProjection de tous les fichiers DWG : RFG93-CC43 (EPSG:3943)."

    # Controle le nb de fichiers DWG
    if projet.nb_dwgs == 0:
        projet.controles |= 2**4      #  allume le bit 5ième bit
        FRONT['C3'].value = "Pas de fichier DWG présent."
    
    # Regarde si des fails ont été détectés sur les DWGs
    fail = projet.controles & FAIL_DWG    # operation logique avec le masque
    # notifie en consequence
    if fail :
        FRONT['B3'].value = "FAIL"
        FRONT['B3'].fill = xls.styles.PatternFill("solid", start_color="FF4343")
    else :
        FRONT['B3'].value = "OK"
        FRONT['B3'].fill = xls.styles.PatternFill("solid", start_color="52BE80")   # ancien vert "68D000"



    # FRONT PDF
    FRONT['C4'].value = f"{projet.nb_pdfs} fichier(s) PDF présent(s)."

    # Controle le nb de fichiers PDF
    if projet.nb_pdfs == 0:
        projet.controles |= 2**8      #  allume le 9ième bit 
        FRONT['C4'] = "Pas de fichier PDF présent."
    
    # Regarde si des fails ont été détectés sur les PDFs
    fail = projet.controles & FAIL_PDF
    # notifie en consequence
    if fail :
        FRONT['B4'].value = "FAIL"
        FRONT['B4'].fill = xls.styles.PatternFill("solid", start_color="FF4343")
    else :
        FRONT['B4'].value = "OK"
        FRONT['B4'].fill = xls.styles.PatternFill("solid", start_color="52BE80") 



    # FRONT CSV
    FRONT['C5'].value = f"{projet.nb_points} point(s) en RFG93-CC43 (EPSG:3943)."

    # Controle le nb de fichiers CSV
    if projet.nb_csvs == 0:
        projet.controles |= 2**0  # allume le 1er bit
        FRONT['C5'].value = "Pas de fichier CSV présent."
    
    # Regarde si des fails ont été détectés sur les CSVs
    fail = projet.controles & FAIL_CSV
    # notifie en consequence
    if fail : 
        FRONT['B5'].value = "FAIL"
        FRONT['B5'].fill = xls.styles.PatternFill("solid", start_color="FF4343")
    else :
        FRONT['B5'].value = "OK"
        FRONT['B5'].fill = xls.styles.PatternFill("solid", start_color="52BE80")



    # FRONT FICHE INFO TOPOLOGIE
    FRONT['C6'].value = f"{projet.nb_releves} fiche(s) Info. présente(s)."

    # Controle le nb de fichiers Fiche Topologique
    if projet.nb_releves == 0:
        projet.controles |= 2**12  # allume le 13ième bit
        FRONT['C6'].value = "Pas de fiche d'Info. Topologie présente."
    
    # Regarde si des fails ont été détectés sur les Fiches Topo
    fail = projet.controles & FAIL_INFO
    # notifie en consequence
    if fail : 
        FRONT['B6'].value = "FAIL"
        FRONT['B6'].fill = xls.styles.PatternFill("solid", start_color="FF4343")
    else :
        FRONT['B6'].value = "OK"
        FRONT['B6'].fill = xls.styles.PatternFill("solid", start_color="52BE80")




    # Notifier dans les autres Feuilles

    # lignes en cours à remplir pour chaque feuille
    # initialisé aux lignes où on va commencer à notifier
    A = 2        # "B2"    # Pour la feuille PDF
    B = 2        # "B2"    # Pour la feuille CSV
    C = 2        # "B2"    # Pour la feuille DWG
    D = 11       # "C11"      # Pour la feuille FRONT

    cell = ""   # cellule courante qui va être notifier

    for notif in projet.notifs :
        match notif.categorie :
            case "PDF" :
                cell = PDF["B"+str(A)]
                A += 1
            case "CSV" :
                cell = CSV["B"+str(B)]
                B += 1
            case "DWG" :
                cell = DWG["B"+ str(C)]
                C += 1
            case _:
                cell = FRONT["C"+str(D)]
                D += 1
            
        cell.value = notif.texte


    # Ici le VISA doit être correctement rempli
    # on sauvegarde dans un fichier le classeur
    classeur.save(f"Travail\\{projet.nom}_VISA.xlsx")





# REPRÉSENTATION DES DONNÉES

class _Fichier:


    def __init__(self,
                 nom_original="Pas de nom original",
                 chemin="Pas de chemin",
                 extension="Pas d'extension",
                 nom= "Pas de nom",
                 implication="Non-conforme",
                 taille=0):
        self.nom_original = nom_original    # - son nom original
        self.chemin = chemin                # - son chemin absolue (dossier+fichier)
        self.extension = extension          # - son extension (ecrit en minuscule)
        self.nom = nom                      # - son nom formaté
        self.implication = implication      # - son implication dans le projet
        self.taille = taille                # taille en octets


    def afficher(self):
        """
            Affiche dans la sortie standard les éléments du fichier
        """
        print("")
        print("nom :".ljust(16) + self.nom)
        print("nom orig :".ljust(16) + self.nom_original)
        print("ext :".ljust(16) + self.extension)
        print("chemin :".ljust(16) + self.chemin)
        print("implication :".ljust(16) + self.implication)
        print("taille :".ljust(16) + str(self.taille) )


    def lire(dossier, fichier):
        """
            Lit le nom du fichier et du dossier
            
            Construit un élement _Fichier et met à jour tous ses attributs
            :param user_input: chemin absolue du dossier, nom du fichier
            :return: un element _Fichier 
        """
        # initialiser un _Fichier
        ce_Fichier = _Fichier()
        # lire son chemin
        ce_Fichier.chemin = dossier + "\\" + fichier
        # déterminer son nom original et son extension
        ce_Fichier.nom_original, ce_Fichier.extension = fichier.split(".", maxsplit=1) # maxsplit permet de spliter qu'une fois au cas ou on a plusieurs . dans le nom de fichier)
        # lettrer les extensions en minuscule
        ce_Fichier.extension = ce_Fichier.extension.casefold()
        # formatter et écrire le nom
        ce_Fichier.nom = formatter(ce_Fichier.nom_original)
        # déterminer son implication
        ce_Fichier.impliquer()
        # calculer sa taille
        ce_Fichier.taille = os.path.getsize(ce_Fichier.chemin)

        return ce_Fichier


    def impliquer(self):
        """Définir l'implication d'un fichier

        Les fichiers nécessaires seront copiés dans le répertoire "Travail"
        L'implication est définie en fonction de l'extension du fichier.
        Les fichiers nécessaires sont les DWGs, les CSVs pour les datas.
        Pour les shémas et relevés de topo :
         - PDFs, DOCs, ODTs (Doc LibreOffice), 
        """
        match self.extension:
            case "pdf" | "dwg" | "csv" | "doc" | "docs" | "odt" | "PDF" | "DWG" | "CSV" | "DOC" | "ODT" | "DOCS":
                self.implication = "Necessaire"
            case _:
                self.implication = "A-ignorer"





class _Projet:


    def __init__(self,
                 nom="Pas de nom",
                 date="Pas de date",
                 racine="Pas de chemin",
                 fichiers=[],
                 nb_fichiers=0,
                 taille=0,
                 rapport="Pas de fichier",
                 nb_shemas=0,
                 nb_releves=0,
                 nb_csvs=0,
                 points=[],
                 nb_points=0,
                 nb_dwgs=0,
                 nb_pdfs=0,
                 notifs=[],
                 controles=0):
        self.nom = nom                  # nom du projet
        self.date = date                # date du traitement
        self.racine = racine            # chemin racine du projet
        self.fichiers = fichiers        # liste de _Fichier
        self.nb_fichiers = nb_fichiers  # nb de fichiers dans "Travail"
        self.taille = taille            # taille des fichiers dans "Travail"
        self.rapport = rapport          # chemin vers le visa
        self.nb_shemas = nb_shemas      # nb de plans de la mise en place
        self.nb_releves = nb_releves    # nb de rapports de relevés topo
        self.nb_csvs = nb_csvs            # nb de fichiers CSV
        self.points = points            # liste des points du CSV
        self.nb_points = nb_points      # nb de points des CSVs     TODO: REDONDANT avec len(points)
        self.nb_dwgs = nb_dwgs          # nb de fichiers DWG
        self.nb_pdfs = nb_pdfs          # nb de fichiers PDF
        self.notifs = notifs            # liste contenant les notifs du VISA
        self.controles = controles      # variable drapeau contenant toutes les validations de controles (voir Document pour plus d'info sur sa représentation)


    def enraciner(self):
        """
            récupère le repertoire de travail (working directory) courant
            met à jour l'attribut 'racine'
        """
        self.racine = os.getcwd()
        print("\nRacine : ".ljust(16), f"{self.racine}\n")


    def calculer_taille(self):
        """
            calcule la taille des fichiers necessaires d'un liste
            d'élements _Fichier.
            Met à jour l'attribut 'taille' dans le projet
        """
        taille = 0.0
        for courant in self.fichiers:
            if courant.implication in "Necessaire":
                taille += courant.taille
        self.taille = taille
        # Affichage adapté à la bonne unité
        if taille < 1024 :
            unite = "octets"
        elif taille < 1024**2 :
            unite = "Ko"
            taille /= 1024
        else :
            unite = "Mo"
            taille /= 1024**2
        print(f"\nTaille totale : {taille:.2f} {unite}.\n")

    def dater(self):
        """
            recupère la date du jour
            met à jour l'attribut 'date' du projet
        """
        self.date = datetime.datetime.today().strftime('%Y%m%d')
    

    def nommer(self):
        """
            Met à jour l'attribut 'nom' en composant un nom.

            Le nom est constitué du nom du dossier racine et de la date
            courante formatté.

        """
        self.nom = f"{os.path.basename(os.getcwd())}_{self.date}"



    def preparer_dossier_travail(self):
        """
            Créer un dossier "Travail" dans la racine du working directory et
            le peuple des fichiers nécessaires

            Met à jour le nombre de fichiers CSVs, DWGs
            Préfixe et suffixe les fichiers le nécessitant (le CSV et le DWG)
        """
        travail = "Travail"

        # création du dossier "Travail et dossier"
        chemin = f"{self.racine}\\{travail}"
        print(f"\nCHEMIN FABRIQUE : {chemin}")
        try:
            os.mkdir(chemin)
            print (f'\nDossier "{travail}" créé.')
        except FileExistsError as erreur:
            print(f'\nAvertissement: Le dossier "{travail}" existe déja.')
        except OSError as err:
            print(f"\nFichier non trouvé. Surement un pb de chemin en amont.")
        
        # peuplement du dossier Travail avec les fichiers necessaires
        for fichier in self.fichiers:
            source = fichier.chemin
            print(f"\nsource : {source}")

            # lors du peuplement préfixer et suffixer les noms des fichiers concernés comme suit : "Plan_nomfichierdwg.dwg" et "Point_nomfichiercsv_IN.csv"
            # Confirmation par Audrey qu'il n'y a nécessairement qu'un fichier CSV par projet --> Le script s'occupe de le renommer.
            # Mais parfois il peut y avoir plusieurs DWGs dont un seul est utile --> PASS -- Je laisse l'opérateur choisir lequel utiliser et le renommer manuellement.
            match fichier.extension:
                case "csv":
                    dest = self.racine + "\\" + travail + "\\" + "Point_" + fichier.nom + "_IN" + "." + fichier.extension
                    self.nb_csvs += 1
                case "dwg":
                    dest = self.racine + "\\" + travail + "\\" + "Plan_" + fichier.nom + "." + fichier.extension
                    self.nb_dwgs += 1
                
                case "pdf":
                    dest = self.racine + "\\" + travail + "\\" + fichier.nom + "." + fichier.extension
                    self.nb_pdfs += 1 # TODO: refaire le comptage auillleurs !!!!

                case _:
                    dest = self.racine + "\\" + travail + "\\" + fichier.nom + "." + fichier.extension

            print(f"dest : {dest}")
            if fichier.implication in "Necessaire":
                try:
                    shutil.copyfile( source , dest)
                    print("\n" + fichier.nom.ljust(40,".") + "copié")
                except shutil.SameFileError as err :
                    print(f"\nLe fichier {fichier.nom + fichier.extension} existe déjà.")
        #TODO : 
        # Confirmation par Audrey qu'il n'y a necessairement qu'un fichier CSV par projet --> On peut le renommer.
        # Mais parfois il peut y avoir plusieurs DWGs dont un seul est utile --> PASS -- Je laisse l'opérateur choisir lequel utiliser et le renommer manuellement. EDIT : Nathalie prefere que tous les fichier DWG soient préfixé avec Plan_
        #TODO : verifier aussi la longueur des noms de fichiers. Notifier si nécessaire



    def formatter_vers_ArcGIS(self, fichier_entree, fichier_sortie):
        """
            Lit et formatte un fichier CSV pour son importation dans ArcGIS.

            Lit la 1ère ligne et s'assure de la présence des titres de colonnes,
            formatte cette 1ère ligne.
            Supprime les lignes vides.
            Change le séparateur décimal ',' en '.' (necessaire car sinon je ne peux convertir les strings en float)
            Notifie si nécessaire les points de controles qui ne passent pas.
            Met à jour l'attribut nb_points
            Contraintes :
            - Le fichier CSV NÉCESSITE des ';' comme séparateur d'élements. (Normalement c'est toujours le cas de toutes façon)
            - Le fichier doit avoir exactement 5 colonnes. TODO : vois si plus de souplesse avec le catchage de paramètres restants
            param user_input: nom complet d'un fichier csv
            NOTE: Ne pas formatter le fichier original, mais celui qui est deja copié dans le dossier Travail
        """
        # titres des colonnes correctement formattés.
        titres = "id_point;TYPE;X;Y;Z\n"

        sortie = [titres] # le fichier de sortie ( representé comme une liste de lignes ) contient un premiere ligne de titre


        df = open(fichier_entree, "r")
        ligne = df.readline()

        # analyser la premiere ligne et formatter cette ligne    
        # Si le premier mot est un nombre Alors il manque les titres, dans
        # ce cas insérer une ligne.
        mots = ligne.split(sep=";", maxsplit=1)
        print(f"Mots[0] = {mots[0]}")


        if mots[0].isnumeric():
            #insérer ligne
            sortie.append(ligne)
            self.nb_points += 1

        # la tête de lecture du descripteur de fichier ne reset pas sa position
        # donc on peut continuer le parcours des lignes directement
        for ligne_courante in df :

            courant = self.lire_ligne(ligne_courante)
            sortie.append(courant)
            # compter ce point
            if courant not in "":
                self.nb_points += 1
    
        df.close()

        # je reouvre le descripteur en mode w only pour ecrire le fichier
        df = open(fichier_sortie, "w")
        # écriture
        for ligne in sortie:
            print(f"{ligne}")
            df.write(ligne) # NOTE: write()
        
        df.close()
    

    def lire_ligne(self, ligne):
        """
            Analyse une chaine de caractère (concrètement, une ligne du CSV)/

            remplace les séparateurs décimaux ',' par '.'
            Notifie si nécessaire les points de controles qui ne passent pas.
            Met à jour l'attribut nb_points
            Met à jour la variable de controles
            Contraintes :
            - Le fichier CSV NÉCESSITE des ';' comme séparateur d'élements.
            :return: une chaine de carac formatté pour ArcGIS
        """
        print(f'ligne : "{ligne}"')
        # enlever les espaces
        ligne = ligne.replace(" ", "")
        ligne = ligne.replace(",", ".")
        # si la ligne n'est pas vides alors
        # TODO : changer le match en  if ligne not in ["", ";;;;"]:
        match ligne:
            case "" | ";;;;" |";;;;\n"| ";;;" | ";;;\n" | "\n":
                ligne = ""
            case _:
                # controler la projection de ce point
                id_point, type_, point_x, point_y, point_z, *autres = ligne.split(sep=";")
                print(f"Typage point X : {type(point_x)}")

                #PRINT de controle
                print(f"ID:{id_point}, TYPE:{type_}, X:{point_x}, Y:{point_y}, Z:{point_z}, *autres={autres}")

                projection = controller_projection(id_point, float(point_x), float(point_y))
                # notifier si pas bonne projection
                if projection in "Mauvaise projection":
                    incident = _Notification("CSV", f"Point ID {id_point} : Mauvaise projection")
                    self.notifs.append(incident)
                    self.controles |= 2**2

        return ligne
        # TODO: afficher dans la sortie standard le point


    def controler_longueur_noms(self):
        """
        controle la longueur des noms des fichiers du projet.
        
        Notifie si necessaire les noms trop longs
        """
        for fichier in self.fichiers:
            match fichier.extension:
                case "dwg":
                    if len(fichier.nom) > 30 :
                        self.notifier("DWG", f'"{fichier.nom}" : Nom trop long.')
                case "csv" :
                    if len(fichier.nom) > 45 :
                        self.notifier("CSV", f'"{fichier.nom}" : Nom trop long.')
                case "pdf" :
                    if len(fichier.nom) > 45 :
                        self.notifier("PDF", f'"{fichier.nom}" : Nom trop long.')
                case _:
                    if len(fichier.nom) > 45 :
                        self.notifier("FRONT", f'"{fichier.nom}" : Nom trop long.')
    
    def notifier(self, categorie, texte):
        """
            Ajoute une notification au _Projet
        """
        incident = _Notification( categorie, texte)
        self.notifs.append(incident)




# structure représetant des messages à mentionner dans le rapport VISA
# chaque notification possède un texte et sa catégorie concernée.
class _Notification:
    def __init__(self,
                 categorie="Pas de catégorie",
                 texte="Pas de texte"):
        self.categorie = categorie  # vu comme une énumération de l'ensemble {"CSV", "DWG", "PDF", "FRONT"} seule ces valeurs sont donc possible et traité par le script
        self.texte = texte




# # -------------------------------------------------------------------------- #
# # -------------------------------------------------------------------------- #
# # MAIN

racine = os.getcwd()
print("\nRépertoire courant : ".center(18), racine)

# --recupérer le chemin du dossier à traiter
pas_de_dossier = True

for a in os.scandir():
    print("\ncourant scandir() = ", a.name.ljust(25), "  ", a.is_dir())
    if a.is_dir():
        #il y a un dossier
        pas_de_dossier = False
        print(f"\nDossier à Traiter trouvé : {a.name}")
        racine = a
        
if pas_de_dossier:
    print("\nPas de dossier trouvé...\nFin de programme.\n")
    os.system("pause")
    exit()

# Création entité projet et maj de ses attributs
projet = _Projet()

projet.dater()

projet.nommer()
# controle de la longueur du nom du projet
if len(projet.nom) >=46 :
    print(f"\n{projet.nom} est un nom de dossier trop long (+ de 46 caractères). Veuillez raccourcir son nom\nFin de programme.\n")
    os.system("pause")
    exit()

projet.enraciner()

## projet.rapport = f"{racine}\\Visa_lot_2.xlsx"

projet.fichiers = creer_liste(projet.racine)

projet.calculer_taille()

projet.preparer_dossier_travail()


# il faut UN SEUL fichier CSV
if projet.nb_csvs > 1:
    print("Avertissement : Il y a plusieurs fichiers CSV.\n Veuillez ne garder qu'UN SEUL fichier CSV dans le dossier original.\nFin de programme.\n")
    os.system("pause")
    exit()


fichier_csv = ""

for fichier in projet.fichiers:
    if fichier.extension in "csv":
        entree = projet.racine + "\\Travail\\Point_" + fichier.nom + "_IN" + ".csv"
        sortie = projet.racine + "\\Travail\\Point_" + fichier.nom + "_IN" + ".csv"
        projet.formatter_vers_ArcGIS(fichier.chemin, sortie)



projet.controler_longueur_noms()


modele = projet.racine + "\\Visa_banbou03.xlsx"
remplir_VISA(modele, projet)


# exit()