# -*- coding: utf-8 -*-
###########################################################################
#
# Eole NG - 2011
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
# eole@ac-dijon.fr
#
###########################################################################
"""
 librairie pour le parsing des fichiers de données Sconet/STS-Web

 - parse_sco_eleves : parsing des fichiers XML issus de Sconet
 - parse_sts_profs  : parsing des fichiers XML issus de STS-Web

"""

from scribe.eoletools import replace_cars, formate_civilite, \
not_empty, is_empty, formate_date, replace_more_cars
from scribe.importation import log
from scribe.importation.config import DEBUG_NUM
from scribe.storage import Eleve, \
Responsable, Adresse, JointureGroupeEnseignant, JointureGroupeEleve, \
Classe, Niveau, Enseignant, Administratif, \
EnsClasse, JointureClasseEnseignant, \
Matiere, JointureMatiereEnseignant, JointureResponsableEleve, \
Groupe, Service
from scribe.parsing.tools import parse_xml
from scribe.parsing.nomenclature import REGIMES

##########################################
# Extraction Sconet Elèves & Responsables
# Fichiers :
# - ElevesSansAdresses.xml
# - ResponsablesAvecAdresses.xml
##########################################

ERRMEF = "Le niveau portant le code MEF %s n'est pas défini dans la nomenclature"

def parse_sco_divisions(store, structure_file, nomenclature_file):
    """
    Liaison Division (classe) - MEF (niveau)
    d'après : Structures.xml et Nomenclatures.xml
    """
    num = 0
    log.infolog("Lecture des classes et des niveaux...", title=True)
    niveaux = {}
    context, _ = parse_xml(nomenclature_file, 'MEF')
    for _, tnom in context:
        mef = tnom.attrib['CODE_MEF']
        niveaux[mef] = {}
        clean_formation = replace_cars(tnom.find('FORMATION').text)
        niveaux[mef]['label'] = str(clean_formation)
        if tnom.find('MEF_RATTACHEMENT') is not None:
            niveaux[mef]['real'] = tnom.find('MEF_RATTACHEMENT').text
    divisions = {}
    context, _ = parse_xml(structure_file, 'DIVISION')
    for _, tstr in context:
        div = tstr.attrib['CODE_STRUCTURE']
        if div.lower() == 'inactifs':
            # élèves inactifs (cf. #913)
            continue
        tmef = tstr.findall('MEFS_APPARTENANCE/MEF_APPARTENANCE/CODE_MEF')
        if len(tmef) == 1:
            try:
                divisions[div] = niveaux[tmef[0].text]['label']
            except KeyError:
                msg = ERRMEF % tmef[0].text
                log.errorlog(msg)
                raise Exception(msg)
        else:
            # si plusieurs mef, on prend celle de rattachement
            errmef = set()
            for txtmef in tmef:
                try:
                    mef = niveaux[txtmef.text]['real']
                except KeyError:
                    log.errorlog(ERRMEF % tmef[0].text)
                    errmef.add(tmef[0].text)
                    continue
                try:
                    divisions[div] = niveaux[mef]['label']
                    break
                except KeyError:
                    log.errorlog(ERRMEF % mef)
                    errmef.add(mef)
                    continue
            if div not in divisions:
                if errmef:
                    raise Exception(ERRMEF % list(errmef))
                else:
                    raise Exception("La division %s n'est associée à aucune MEF" % div)
    for classe, niveau in list(divisions.items()):
        my_niveau = store.findOrCreate(Niveau, nom=niveau)
        clean_classe = str(replace_cars(classe))
        store.findOrCreate(Classe, nom=clean_classe,
                             niveau=my_niveau)
        num += 1
    log.infolog("TOTAL : %d classes" % num)
    store.flush()

def parse_sco_groupes(store, structure_file):
    """
    Recherche des Groupes (Options) et de leurs libellés
    """
    num = 0
    log.infolog("Lecture des groupes (options)...", title=True)
    context, _ = parse_xml(structure_file, 'GROUPE')
    for _, tstr in context:
        grp = str(replace_cars(tstr.attrib['CODE_STRUCTURE']))
        desc = str(replace_cars(tstr.find('LIBELLE_LONG').text))
        store.add(Groupe(nom=grp, description=desc))
        num += 1
    log.infolog("TOTAL : %d groupes" % num)
    store.flush()

def parse_sco_eleves(store, eleve_file):
    """
    parsing des élèves depuis Sconet
    """
    num = 0
    log.infolog("Lecture des élèves...", title=True)
    context, eleve_file = parse_xml(eleve_file, 'ELEVE')
    # nb : la date de naissance est au format jj/mm/aaaa
    mapping = {'nom':'NOM_DE_FAMILLE',
              'deprecated_nom':'NOM',
              'prenom':'PRENOM',
              'date':'DATE_NAISS',
              'civilite':'CODE_SEXE',
              'numero':'ELENOET',
              'ine':'ID_NATIONAL',
              'prenom2':'PRENOM2',
              'regime':'CODE_REGIME',
    }

    # parcours des élèves
    for _, televe in context:
        eleid = televe.attrib['ELEVE_ID']
        sortie = televe.find('DATE_SORTIE')
        if sortie != None and sortie.text != '':
            continue
        # attributs non initialisés
        eleve = {'int_id':str(eleid)}
        for cle, balise in list(mapping.items()):
            try:
                clean_text = replace_more_cars(televe.find(balise).text)
                eleve[cle] = str(clean_text)
            except:
                pass
        if eleve.get('nom') is None and 'deprecated_nom' in eleve:
            # ancien format Sconet #16876
            eleve['nom'] = eleve.get('deprecated_nom')
            eleve.pop('deprecated_nom')
        if not eleve.get('civilite', ''):
            log.infolog("Attribution arbitraire d'une civilité à l'élève : %s %s" % (
                         str(eleve['prenom']), str(eleve['nom'])))
            eleve['civilite'] = '1'
        if eleve.get('regime', ''):
            # mapping du régime de l'élève selon la nomenclature Sconet
            if eleve['regime'].rstrip() in REGIMES:
                eleve['regime'] = str(REGIMES[eleve['regime'].rstrip()])
        try:
            store.add(Eleve(**eleve))
            num += 1
            if num % DEBUG_NUM == 0:
                log.debuglog("%d élèves lus..." % num)
        except TypeError as msg:
            log.infolog("Erreur sur l'élève %s : %s" % (eleid, msg))
            continue
    log.infolog("TOTAL : %d élèves" % num)

    # affectation des élèves
    context, _ = parse_xml(eleve_file, 'STRUCTURES_ELEVE', check=False)
    num = 0
    for _, tstruct in context:
        eleid = tstruct.attrib['ELEVE_ID']
        eleve = store.query(Eleve).filter_by(int_id=str(eleid)).first()
        if eleve is None:
            continue
        for tstr in tstruct.getiterator('STRUCTURE'):
            type_struct = str(tstr.find('TYPE_STRUCTURE').text)
            if type_struct == 'D':
                # c'est une classe
                nom_classe = replace_cars(tstr.find('CODE_STRUCTURE').text)
                if nom_classe.lower() == 'inactifs':
                    # élèves inactifs (cf. #913)
                    continue
                classe = store.query(Classe).filter_by(nom=str(nom_classe)).first()
                eleve.classe = classe
                eleve.niveau = classe.niveau
                num += 1
            elif type_struct == 'G':
                # c'est un groupe (-> option)
                nom_groupe = replace_cars(tstr.find('CODE_STRUCTURE').text)
                groupe = store.query(Groupe).filter_by(nom=str(nom_groupe)).first()
                if groupe is None:
                    log.infolog("Groupe inconnu : %s" % nom_groupe)
                    continue
                store.add(JointureGroupeEleve(groupe=groupe, eleve=eleve))
                num += 1
            else:
                log.infolog("Type de structure inconnu :", type_struct)
            if num % DEBUG_NUM == 0:
                log.debuglog("%d affectations lues..." % num)
    log.infolog("TOTAL : %d affectations d'élèves" % num)
    store.flush()


def parse_sco_responsables(store, responsable_file):
    """
    parsing des responsables depuis Sconet
    """
    num = 0
    log.infolog("Lecture des responsables...", title=True)
    context, responsable_file = parse_xml(responsable_file, 'PERSONNE')
    # liste des responsables
    for _, tresp in context:
        rid = tresp.attrib['PERSONNE_ID']
        responsable = {'int_id':str(rid)}
        mapping = {'nom':'NOM_DE_FAMILLE',
              'deprecated_nom':'NOM',
              'prenom':'PRENOM',
              'mail':'MEL',
              'telephone':'TEL_PERSONNEL',
              'civilite':'LC_CIVILITE',
              'id_adresse':'ADRESSE_ID',
              'tel_portable':'TEL_PORTABLE',
              'tel_pro':'TEL_PROFESSIONNEL',
        }
        for cle, balise in list(mapping.items()):
            try:
                clean_text = replace_more_cars(tresp.find(balise).text)
                responsable[cle] = str(clean_text)
            except:
                pass
        if responsable.get('nom') is None and 'deprecated_nom' in responsable:
            # ancien format Sconet #16876
            responsable['nom'] = responsable.get('deprecated_nom')
            responsable.pop('deprecated_nom')
        if 'civilite' in responsable:
            responsable['civilite'] = str(formate_civilite(str(responsable['civilite'])))
        try:
            store.add(Responsable(**responsable))
            num += 1
            if num % DEBUG_NUM == 0:
                log.debuglog("%d responsables lus..." % num)
        except TypeError as msg:
            log.infolog("Erreur sur le responsable %s : %s" % (rid, msg))
    log.infolog("TOTAL : %d responsables élèves" % num)

    # adresses des responsables
    num = 0
    context, _ = parse_xml(responsable_file, 'ADRESSE', check=False)
    for _, taddr in context:
        aid = taddr.attrib['ADRESSE_ID']
        adresse = {'int_id':str(aid)}
        mapping = {'code_postal':'CODE_POSTAL',
                   'ville':'LIBELLE_POSTAL',
                   'pays':'LL_PAYS'}
        for cle, balise in list(mapping.items()):
            try:
                clean_text = replace_cars(taddr.find(balise).text)
                adresse[cle] = str(clean_text)
            except:
                pass
        adr = Adresse(**adresse)
        store.add(adr)
        txt_addr = []
        for balise in ['LIGNE1_ADRESSE', 'LIGNE2_ADRESSE',
                'LIGNE3_ADRESSE', 'LIGNE4_ADRESSE']:
            try:
                clean_text = replace_cars(taddr.find(balise).text)
                if clean_text != '':
                    txt_addr.append(str(clean_text))
            except:
                pass
            if txt_addr != []:
                # FIXME "\n" ?
                adr.adresse = str("\n".join(txt_addr))
        resp = store.query(Responsable).filter_by(id_adresse=str(aid)).first()
        if resp is None:
            # l'adresse n'est à personne
            continue
        resp.adresse = adr
        num += 1
        if num % DEBUG_NUM == 0:
            log.debuglog("%d adresse lues..." % num)
    log.infolog("TOTAL : %d adresses de responsables" % num)

    # jointures responsable/élève
    num = 0
    context, _ = parse_xml(responsable_file, 'RESPONSABLE_ELEVE', check=False)
    for _, tjoint in context:
        niv_resp = tjoint.find('NIVEAU_RESPONSABILITE')
        if niv_resp is not None:
            if niv_resp.text in ['2', '3']:
                # on garde uniquement les représentants légaux
                continue
            resp_field = 'NIVEAU_RESPONSABILITE'
        else:
            # SIECLE <= 18.1.1.2.0
            resp_legal = tjoint.find('RESP_LEGAL').text
            if resp_legal == '0':
                # on ne garde que les responsables 1 et 2 (ref #1163)
                continue
            resp_field = 'RESP_LEGAL'
        eleve_id = tjoint.find('ELEVE_ID').text
        responsable_id = tjoint.find('PERSONNE_ID').text
        eleve = store.query(Eleve).filter_by(int_id=str(eleve_id)).first()
        if eleve is None:
            continue
        responsable = store.query(Responsable).filter_by(int_id=str(responsable_id)).first()
        if responsable is None:
            log.infolog("responsable n°%s non trouvé" % responsable_id)
            continue
        jointure = dict(eleve=eleve, responsable=responsable)
        mapping = {'resp_legal':resp_field,
                   'resp_financier':'RESP_FINANCIER',
                   'resp_paiement':'PERS_PAIEMENT',
                   'pers_contact':'PERS_CONTACT',
                   'code_parente':'CODE_PARENTE',}
        for cle, balise in list(mapping.items()):
            try:
                clean_text = replace_cars(tjoint.find(balise).text)
                jointure[cle] = str(clean_text)
            except:
                pass
        store.add(JointureResponsableEleve(**jointure))
        num += 1
        if num % DEBUG_NUM == 0:
            log.debuglog("%d liens responsable/élève lus..." % num)
    log.infolog("TOTAL : %d liens responsable/élève" % num)
    store.flush()


##########################################
# Extraction STS Professeurs
# Fixhier :
# - sts_emp_$RNE$_$ANNEE$.xml
##########################################
def _parse_service(tree):
    """
    lecture des services d'une classe ou d'une option
    """
    tservs = tree.find('SERVICES')
    if tservs is None:
        return []
    machin = []
    for tserv in tservs.getiterator('SERVICE'):
        type_cours = tserv.attrib['CODE_MOD_COURS']
        #matiere = t.ok_groupe(code_matieres[tserv.attrib['CODE_MATIERE']], 'm')
        matiere = tserv.attrib['CODE_MATIERE']
        tens = tserv.find('ENSEIGNANTS')
        for ten in tens.getiterator('ENSEIGNANT'):
            # id enseignant
            #print "le prof", ten.attrib['ID'], "enseigne", matiere, "c'est", type_cours
            machin.append([ten.attrib['ID'], matiere, type_cours])
    return machin

def _parse_division_appartenance(tree):
    """
    lecture des divisions affectées à une option
    """
    tdivs = tree.find('DIVISIONS_APPARTENANCE')
    if tdivs is None:
        return []
    div = []
    for tdiv in tdivs.getiterator('DIVISION_APPARTENANCE'):
        div.append(tdiv.attrib['CODE'])
    return div

def parse_sts_profs(store, sts_file):
    """
    parsing des professeurs depuis sts
    """
    num = 0
    log.infolog("Lecture des personnels", title=True)
    context, sts_file = parse_xml(sts_file, 'MATIERE')
    code_matieres = {}
    # lien code/libellé pour les matières
    for _, tcmat in context:
        code = tcmat.attrib['CODE']
        mat = tcmat.find('CODE_GESTION').text
        lib = tcmat.find('LIBELLE_EDITION').text
        code_matieres[code] = (mat, lib)

    # liens prof-classes, prof-matieres, prof-options
    classes = {}
    matieres = {}
    options = {}

    # -- parcours des classes -- #
    context, _ = parse_xml(sts_file, 'DIVISION', check=False)
    for _, tmat in context:
        code_classe = tmat.attrib['CODE']
        nom = str(replace_cars(code_classe))
        my_classe = store.findOrCreate(EnsClasse, nom=nom)
        for prof, mat, _ in _parse_service(tmat):
            classes.setdefault(prof, []).append(my_classe)
            if mat in code_matieres:
                matiere = code_matieres[mat]
                nom = str(replace_cars(matiere[0]))
                desc = str(replace_cars(matiere[1]))
                my_mat = store.findOrCreate(Matiere, nom=nom, description=desc)
                matieres.setdefault(prof, []).append(my_mat)
            else:
                log.infolog("matière %s inconnue" % mat)

    # -- parcours des groupes (options) -- #
    context, _ = parse_xml(sts_file, 'GROUPE', check=False)
    for _, tgrp in context:
        code_groupe = tgrp.attrib['CODE']
        nom = str(replace_cars(code_groupe))
        desc = str(replace_cars(tgrp.find('LIBELLE_LONG').text))
        my_groupe = store.findOrCreate(Groupe, nom=nom, description=desc)
        my_classes = []
        for div in _parse_division_appartenance(tgrp):
            nom = str(replace_cars(div))
            my_classes.append(store.findOrCreate(EnsClasse, nom=nom))
        for prof, mat, _ in _parse_service(tgrp):
            options.setdefault(prof, []).append(my_groupe)
            for my_classe in my_classes:
                # on ajoute les classes touchées par l'option au prof
                classes.setdefault(prof, []).append(my_classe)
            if mat in code_matieres:
                matiere = code_matieres[mat]
                nom = str(replace_cars(matiere[0]))
                desc = str(replace_cars(matiere[1]))
                my_mat = store.findOrCreate(Matiere, nom=nom, description=desc)
                matieres.setdefault(prof, []).append(my_mat)
            else:
                log.infolog("matière %s inconnue" % mat)

    mapping = {'NOM_USAGE':'nom',
               'PRENOM':'prenom',
               'DATE_NAISSANCE':'date',
               'CIVILITE':'civilite',
               'NOM_PATRONYMIQUE':'nom_patronymique',
    }
    for personnel in ['INDIVIDU', 'SUPPLEANT']:
        context, _ = parse_xml(sts_file, personnel, check=False)
        for _, ind in context:
            #<INDIVIDU ID='2453' TYPE='epp'>
            #<SUPPLEANT ID="46671" TYPE="eppsup">
            profid = ind.attrib['ID']
            professeur = {'int_id':str(profid)}
            fonction = ind.find('FONCTION')
            if fonction is None:
                log.infolog("(pas de fonction pour le personnel %s)" % profid)
                continue
            for balise, cle in list(mapping.items()):
                value = ind.find(balise)
                if value is None:
                    professeur[cle] = ''
                else:
                    clean_text = replace_more_cars(value.text)
                    professeur[cle] = str(clean_text)
            if not_empty(professeur, 'date'):
                my_date = formate_date(str(professeur['date']).replace('-', ''))
                professeur['date'] = str(my_date)
            else:
                # date de naissance arbitraire #1730
                professeur['date'] = '01/01/0001'
            if is_empty(professeur, 'civilite'):
                # civilité arbitraire #2599
                log.infolog("Attribution arbitraire d'une civilité au personnel : %s %s" % (
                             str(professeur['prenom']), str(professeur['nom'])))
                professeur['civilite'] = '1'
            if fonction.text == 'ENS':
                # c'est un enseignant !
                # on regarde si il est prof principal
                principal = []
                tprincs = ind.findall('PROFS_PRINC/PROF_PRINC')
                for tprinc in tprincs:
                    nom = str(replace_cars(tprinc.find('CODE_STRUCTURE').text))
                    my_classe = store.findOrCreate(EnsClasse, nom=nom)
                    principal.append(my_classe)
                try:
                    prof = Enseignant(**professeur)
                    store.add(prof)
                    num += 1
                except Exception as msg:
                    log.infolog("Erreur sur l'enseignant %s : %s" % (profid, msg))
                # affectation des classes
                if profid in classes:
                    for classe in classes[profid]:
                        if classe in principal:
                            is_principal = True
                        else:
                            is_principal = False
                        store.add(JointureClasseEnseignant(classe=classe,
                                enseignant=prof, profprincipal=is_principal))

                # affectation des options
                if profid in options:
                    for groupe in options[profid]:
                        store.add(JointureGroupeEnseignant(groupe=groupe, enseignant=prof))

                # affectation des matieres
                if profid in matieres:
                    for mat in matieres[profid]:
                        store.add(JointureMatiereEnseignant(matiere=mat, enseignant=prof))
            else:
                # c'est un administratif
                try:
                    admin = Administratif(**professeur)
                    store.add(admin)
                    nom = str(replace_cars(fonction.text))
                    groupe = store.findOrCreate(Service, nom=nom)
                    admin.groupe = groupe
                    num += 1
                except Exception as msg:
                    import traceback
                    traceback.print_exc()
                    log.infolog("Erreur sur le personnel %s : %s" % (profid, msg))
        if num % DEBUG_NUM == 0:
            log.debuglog("%d personnels lus..." % num)
    log.infolog("TOTAL : %d personnels" % num)
    store.flush()
