# Dépendances
from scrapy import signals

# Python a une fonctionnalité regex native (re.py) mais elle a quelques limitations qui dans le cas présent rendent les regex inopérants
# On importe donc regex.py (pip install regex) qui étend les capacités de re.py (notamment la tolérance aux erreurs)
import regex


class SplitAndSort:
    
        @classmethod

        # Une fonction qui sépare les adresses selon les noms de rue
        def splitStreet(self, value):
            # Le regex qui évalue là où il faut séparer la chaîne de caractère 
            # (présence de "et / - + ainsi que" )
            # (sans que ce séparateur ne soit placé près d'un groupe de numéros de la même rue)
            expr = r"((?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))(–|\bet)|(?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))\/|(ainsi\sque)|((?<!(\d))\s-\s(?!(bt)|(bis)|(ter)))|(?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))\+)"
            # Remplacement de chaque séparateur par un pipe ("|")
            subst = "|"
            repl = regex.sub(expr, subst, value, 0, regex.MULTILINE | regex.IGNORECASE)
            # Formattage (supression des espaces insécables et des ":")
            filtered = regex.sub('\\xa0|:', '', repl, 0, regex.MULTILINE | regex.IGNORECASE)
            # Séparation en liste
            splitted = regex.split('\|', filtered, regex.MULTILINE | regex.IGNORECASE)
            # Suppression des espaces en début et fin de chaîne
            stripped = [x.strip() for x in splitted]
            # Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
            result = list(filter(None, stripped))
            return result


        # Une fonction qui valide tout ce qui est un couple n°/nom de rue
        def isWorth(self, value):
            # Le regex qui évalue ce qu'il faut garder
            expr = r"(?=.*[a-z])(?=.*\d+).*"
            # Un regex contre lequel comparer avant pour supprimer les exceptions 
            # (cas d'usage : "jardin public du 19 mars 1962" est reconnu comme un couple n°/nom de rue)
            # ce n'est pas très sexy, il vaudrait mieux un regex plus strict,
            # mais il serait trop complexe pour que ça en vaille la peine pour de rares exceptions
            # (possibilité d'en ajouter d'autres ici par la suite si besoin)
            xcp = r"(jardin public)|(Bâtiment 12)"
            # Le résultat (booléen) si le regex match ou non, après comparaison avec les exceptions
            if bool(regex.findall(xcp, value, regex.MULTILINE | regex.IGNORECASE)) is False :
                result =bool(regex.findall(expr, value, regex.MULTILINE | regex.IGNORECASE))
            else :
                result = False
            return result


        # Une fonction qui enlève tous les trucs peu utiles ou encombrants des adresses 
        # ("immeuble", infos en parenthèses, infos sur l'arrêté, etc)
        def removeClutter(self,value):
            # Le regex qui évalue ce qu'il faut retirer
            expr = r"\(.*\)|Immeuble|Chapelle,|Arrêté.*|\((.*){0,3}|​"
            # Le résultat après retrait
            result = regex.sub(expr, '', value, regex.IGNORECASE | regex.MULTILINE)
            stripped = result.strip()
            return stripped

        # Une fonction qui sépare les numéros des adresses
        def separateNbr(self,value):
            # Le regex qui évalue ce qui est un numéro ou un groupe de numéros
            expr = r"(((\d).*(\da\b))(?=\s?\S?.{8,})|((\d).*(\db\b))(?=\s?\S?.{8,})|((\d).*(\dt\b))(?=\s?\S?.{8,})|((\d).*(\dbis\b))(?=\s?\S?.{8,})|((\d).*(\dter\b))(?=\s?\S?.{8,})|((\d).*(\d))(?=\s?\S?.{8,})|(\da\b)(?=\s?\S?.{8,})|(\db\b)(?=\s?\S?.{8,})|(\dbis\b)(?=\s?\S?.{8,})|(\dt\b)(?=\s?\S?.{8,})|(\dter\b)(?=\s?\S?.{8,})|(\d)(?=\s?\S?.{8,}))"
            # On extrait le groupe numérique et on le place dans la variable "nbr"
            sep = regex.search(expr, value, regex.IGNORECASE | regex.MULTILINE)
            nbr = sep[0]
            # On supprime le texte de la variable "nbr" de l'adresse (et on enlève les espaces en trop)
            invexpr = regex.compile(nbr)
            name = regex.sub(invexpr, '', value, regex.IGNORECASE | regex.MULTILINE)
            name = name.strip()
            # On retourne une liste avec le groupe numérique d'un côté, le nom de rue de l'autre
            return (nbr,name)



        # Une fonction qui sépare les numéros (hors rangées)
        def splitNumber(self, value):
            # Le regex qui évalue là où il faut séparer les numéros
            expr = r"((?<=\d)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\da)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\db)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dt)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dbis)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dter)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))"
            # Remplacement de chaque séparateur par un pipe ("|")
            repl = regex.sub(expr, "|", value, regex.MULTILINE | regex.IGNORECASE)
            # Séparation en liste
            splitted = regex.split('\|', repl, regex.MULTILINE | regex.IGNORECASE)
            # Suppression des espaces en début et fin de chaîne
            stripped = [x.strip() for x in splitted]
            # Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
            value = list(filter(None, stripped))
            return value

        #Une fonction qui interpole les numéros manquants à partir des rangées
        def splitRanges(self,value):
            # Le regex qui évalue là où il faut séparer les numéros
            expr = r"((?<=\d)\s?(au|à)\s?(?=\d))|((?<=\da)\s?(au|à)\s?(?=\d))|((?<=\db)\s?(au|à)\s?(?=\d))|((?<=\dt)\s?(au|à)\s?(?=\d))|((?<=\dbis)\s?(au|à)\s?(?=\d))|((?<=\dter)\s?(au|à)\s?(?=\d))"
            # Remplacement de chaque séparateur par un pipe ("|")
            repl = regex.sub(expr, "|", value, regex.MULTILINE | regex.IGNORECASE)
            # Séparation en liste
            splitted = regex.split('\|', repl, regex.MULTILINE | regex.IGNORECASE)
            # Suppression des espaces en début et fin de chaîne
            stripped = [x.strip() for x in splitted]
            # Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
            listed = list(filter(None, stripped))
            # Si c'est effectivement une rangée :
            if len(listed) > 1 :
                # On initie une liste vide et un index correspondant au premier numéro de la rangée
                rawList = []
                index = int(listed[0])
                # Tant que l'index n'est pas supérieur au dernier numéro de la rangée...
                while index <= int(listed[1]):
                    # Inscrire l'index dans la liste
                    rawList.append(index)
                    # augmenter l'index d'un numéro
                    index += 1
                    # Un petit check pour supprimer les numéros pairs ou impairs selon la parité du premier numéro.
                    if int(listed[0])%2 == 0 :
                        for i in rawList :
                            if i%2 != 0 :
                                rawList.remove(i)
                    if int(listed[0])%2 != 0 :
                        for i in rawList :
                            if i%2 == 0 :
                                rawList.remove(i)
                return rawList
            else :
                return value

        def removeAbrog(self,value):
            expr = r"(?:((main\s?-?levée)|(abrog))){i<=2,d<=2,e<=3}"
            part = r"(?:(partiel)){i<=2,d<=2,e<=3}"
            if bool(regex.findall(expr, value, regex.MULTILINE | regex.IGNORECASE)):
                if bool(regex.findall(part, value, regex.MULTILINE | regex.IGNORECASE)):
                    return 1 # partiellement abrogé
                else :
                    return 2 # abrogé
            else :
                return 0

        def typeOf(self,value):
            # mise en sécurité
            peril = r"(?:((?<!périmètre)(sécurité|péril))){i<=2,d<=2,e<=2}"
            # périmètre de sécurité
            perimetre = r"(?:(périmètre)){i<=2,d<=2,e<=3}"
            # imminent
            urgent = r"(?:(urgent|imminent|grave)){i<=2,d<=2,e<=3}"
            # interdiction d'occupation 
            interdi = r"(?:((?<=(occup)).*(interdi)|(?<=(util)).*(interdi)|(interdi).*(?=(occup))|(interdi).*(?=(util)))){i<=2,d<=2,e<=3}"
            # déconstruction
            deconstr = r"(?:(déconstr)){i<=2,d<=2,e<=3}"
            # astreinte administrative 
            astr = r"(?:(astreinte)){i<=2,d<=2,e<=3}"
            if bool(regex.findall(perimetre, value, regex.MULTILINE | regex.IGNORECASE)):
                return "Périmètre de sécurité"
            elif bool(regex.findall(interdi, value, regex.MULTILINE | regex.IGNORECASE)):
                return "Interdiction d'occupation"
            elif bool(regex.findall(deconstr, value, regex.MULTILINE | regex.IGNORECASE)):
                return "Déconstruction"
            elif bool(regex.findall(astr, value, regex.MULTILINE | regex.IGNORECASE)):
                return "Astreinte administrative"
            elif bool(regex.findall(peril, value, regex.MULTILINE | regex.IGNORECASE)):
                if bool(regex.findall(urgent, value, regex.MULTILINE | regex.IGNORECASE)):
                    return "Péril imminent"
                else :
                    return "Péril"
            else :
                return ""

        # La fonction principale de traitement du résultat de la requête de scrapy avant yield
        def process_spider_output(self, response, result, spider):
            # Pour chaque résultat individuel dans le résultat de la requête :
            for r in result:
                # Extraire le texte brut de l'adresse et le stocker dans la variable "adresses"
                adresses = r.pop('adrs')
                # Extraire le dernier arrêté et le stocker dans la variable "dernA"
                dernA = r.pop('dernierA')
                As = r.pop('As')
                raw = r.pop('raw')
                # Ne prendre que le premier index de la liste 
                # il n'y en a qu'un de toute manière, c'est pour extraire le texte de l'objet car regex.py ne sait pas
                # traiter les objets tuple et scrapy retourne un objet tuple, pas seulement une chaîne de caractères
                adresses = adresses[0]
                dernA = dernA[0]
                # Si l'adresse est non-nulle :
                # (pour éviter que regex.py ne plante à cause d'un objet de type None au lieu d'une chaîne de caractères)
                # (ça supprime par la même occasion les arrêtés sans adresse, à date il n'y en a qu'un, sur un passage privé)
                if dernA :
                    abrog = self.removeAbrog(dernA)
                    if abrog == 1:
                        typeof = "(Mainlevée partielle) "+ self.typeOf(dernA)
                    elif abrog == 2:
                        typeof = "Mainlevée" 
                    else :
                        typeof = self.typeOf(dernA)

                if adresses :
                    # On apelle la fonction splitStreet pour séparer les rues dans la variable "adresses" et les inscrire dans la liste "indiv"
                    indiv = self.splitStreet(adresses)
                    # Pour chaque adresse de la liste d'adresses "indiv" précemment obtenue :
                    for i in indiv:
                        # Si l'adresse est utile
                        if self.isWorth(i) :
                            iClean = self.removeClutter(i)
                            iSep = self.separateNbr(iClean)
                            iNbrs = self.splitNumber(iSep[0])
                            for r in iNbrs:
                                x = self.splitRanges(r)
                                print(x)
                                if isinstance(x, list):
                                    for n in x:
                                        yield {'N°':n,'Nom de rue':iSep[1],'Statut':typeof,'Dernier arrêté (hors modificatif)':dernA,'Arrêtés':As,'Données brutes':raw,"QGIS-RAW":str(n)+" "+iSep[1],"QGIS-City":"Marseille","QGIS-Country":"France"}
                                else :
                                    yield {'N°':r,'Nom de rue':iSep[1],'Statut':typeof,'Dernier arrêté (hors modificatif)':dernA,'Arrêtés':As,'Données brutes':raw,"QGIS-RAW":str(r)+" "+iSep[1],"QGIS-City":"Marseille","QGIS-Country":"France"}