# -*- coding: UTF-8 -*-
###########################################################################
# Eole NG - 2007
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  cf /root/LicenceEole.txt
# eole@ac-dijon.fr
#
# etabs_rpc.py
#
# fonctions xmlrpc pour la gestion des etablissement sous Zephir
#
###########################################################################
from zephir.backend import config
from zephir.backend.config import u, log
from zephir.backend.db_utils import *
from zephir.backend.xmlrpceole import XMLRPCEole as XMLRPC
from twisted.internet import defer

# import relatifs aux tables
import psycopg2 as PgSQL

import sys,os,shutil

class RPCEtabs(XMLRPC):
    """serveur XMLRPC zephir pour la gestion des établissements
    """

    def __init__(self,parent):
        # connexion à la base de données zephir
        self.dbpool = db_connect()
        self.dbpool.noisy = 0
        XMLRPC.__init__(self)
        self.parent = parent


    def _got_etabs(self, etabs, cred_user):
        """formate la sortie de la table etablissements
        """
        l=[]
        for etab in etabs:
            try:
                self.parent.s_pool.check_etab_credential(cred_user, etab[0])
            except:
                continue
            l.append({'rne':etab[0],
                    'libelle':etab[1],
                    'adresse':etab[2],
                    'ville':etab[9],
                    'cp':etab[10],
                    'tel':etab[3],
                    'fax':etab[4],
                    'mail':etab[5],
                    'responsable':etab[6],
                    'remarques':etab[7],
                    'type':etab[8]
                    })
        return 1,u(l)

    def _got_libelle_ville_etabs(self,etabs):
        """formate la sortie de la table etablissements
        """
        l1=[]
        l2=[]
        for etab in etabs:
            l1.append(etab[0])
            if etab[1] not in l2:
                l2.append(etab[1])
        return 1,u([l1,l2])

    def _got_types(self,types_etab):
        """formate la sortie des types d'établissement
        """
        d={}
        for t in types_etab:
            d[str(t[0])]=u(t[1])
        return 1,[d]

    def xmlrpc_get_etab(self,cred_user, rne=None):
        """Récupération des données d'un établissement (ou de tous)
        """
        if rne:
            query = """select * from etablissements where rne ilike %s"""
            return self.dbpool.runQuery(query, (rne,)).addCallbacks(self._got_etabs,db_client_failed,callbackArgs=[cred_user])
        else:
            # si pas de rne demandé, on renvoie tous les établissements connus
            query = """select * from etablissements"""
            return self.dbpool.runQuery(query).addCallbacks(self._got_etabs,db_client_failed,callbackArgs=[cred_user])

    def xmlrpc_get_libelle_etab(self,cred_user):
        """Récupération des données d'un établissement (ou de tous)
        """
        query = """select libelle, ville from etablissements order by ville"""
        return self.dbpool.runQuery(query).addCallbacks(self._got_libelle_ville_etabs,db_client_failed)

    def _load_types(self,cx):
        """lit les types établissement et renvoie un dictionnaire
        """
        cursor=cx.cursor()
        # on lit la base
        cursor.execute("""select id,libelle from types_etab""")
        result = cursor.fetchall()
        cursor.close()
        num_types = {}
        # correspondance libelle -> n°
        for row in result:
            num_types[row[1].upper()]=row[0]

        return num_types


    def xmlrpc_import_etab(self,cred_user,data):
        """importe des établissements depuis un fichier csv"""
        # recherche des types d'établissement
        # from nomenclature import nomenclature_ramses
        # correspondances des types etab
        query = """select rne from etablissements"""
        return self.dbpool.runQuery(query).addCallbacks(self._import_etab,db_client_failed,callbackArgs=[cred_user,data])

    def _import_etab(self,liste_rne,cred_user,data):
        # mise à plat de la liste des rnes existants
        existants = [rne[0] for rne in liste_rne]
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        num_types = self._load_types(cx)
        cx.close()
        # on commence par vérifier les types d'établissement
        missing=[]
        for etab in data:
            libelle = etab[3]
            if libelle.strip() == "":
                libelle = "INDEFINI"
                etab[3] = libelle
            if libelle.upper() not in num_types.keys():
                if libelle.upper() not in missing:
                    missing.append(libelle.upper())

        if missing != []:
            dict_miss = []
            for miss in missing:
                dict_miss.append({'miss':miss})
            cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
            cursor=cx.cursor()
            # on ajoute les types non existants
            cursor.executemany("insert into types_etab (libelle) values (%(miss)s)", dict_miss)
            cursor.close()
            cx.commit()
            # on relit la base
            num_types = self._load_types(cx)
            cx.close()

        d_list = []
        processed = []
        # on ajoute/met à jour les etablissements
        for etab in data:
            rne = etab[0]
            # type_etab = nomenclature_ramses[etab[1] + '_' + etab[2]]
            libelle = etab[3] + " " + etab[4]
            cp = etab[5]
            adresse = " "
            ville = etab[6]
            telephone = etab[9]
            fax = etab[8]
            mail = etab[7]
            responsable = "inconnu"
            remarques = ""
            # on récupère son n° de type
            if etab[3].upper() in num_types:
                type_etab = num_types[etab[3].upper()]
                if rne in existants:
                    # mise à jour d'un établissement existant
                    update_infos = {'libelle':libelle,
                                     'adresse':adresse,
                                     'ville':ville,
                                     'cp':cp,
                                     'tel':telephone,
                                     'fax':fax,
                                     'mail':mail,
                                     'responsable':responsable,
                                     'remarques':remarques,
                                     'type':type_etab}
                    d_list.append(defer.maybeDeferred(self.xmlrpc_edit_etab, cred_user, rne, update_infos))
                    processed.append((rne, libelle))
                else:
                    d_list.append(defer.maybeDeferred(self.xmlrpc_add_etab, cred_user,rne,libelle,adresse,ville,cp,telephone,fax,mail,responsable,remarques,type_etab))
                    processed.append((rne, libelle))
        d = defer.DeferredList(d_list, consumeErrors=True)
        return d.addBoth(self._callb_import, processed, existants)

    def _callb_import(self, result, processed, existants):
        # log de la création des modules
        errors = []
        nouveaux = []
        for index, etab_data in enumerate(processed):
            rne, libelle = etab_data
            if rne in existants:
                label_action = "modification"
            else:
                label_action = "ajout"
            if result[index][0] == False:
                # traceback pendant le traitement : log backend + message
                errors.append("%s (%s) : voir logs du backend Zéphir" % (rne, label_action))
            elif result[index][1][0] == 0:
                # erreur renvoyée par les fonctions add/edit le message utile est en dernière position
                errors.append("%s (%s) : %s" % (rne, label_action, str(result[index][1][-1])))
            else:
                if rne not in existants:
                    nouveaux.append((rne, libelle))
                else:
                    # on le supprime de la liste des rnes pour conserver
                    # les établissements existants non référencés dans le fichier
                    existants.remove(rne)
        return 1, u((existants, nouveaux, errors))

    def xmlrpc_add_etab(self,cred_user,rne,libelle,adresse,ville,cp,telephone,fax,mail,responsable,remarques,type_etab):
        """ajoute un établissement"""
        # on vérifie que les données obligatoires sont remplies
        if (rne and libelle and type_etab):
            # objet etab
            query = """insert into etablissements values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"""
            params = (rne, libelle, adresse, telephone, fax, mail, responsable, remarques, type_etab, ville.upper(), cp)
            # on effectue l'insertion (l'existence est à tester dans l'application cliente)
            return self.dbpool.runOperation(query, params).addCallbacks(self._add_etab, db_client_failed,callbackArgs=[rne, cred_user])
        else:
            # des attributs manquent
            return 0,u('arguments manquants')

    def _add_etab(self,resultat, rne, cred_user):
        """met en place l'arborescence zephir de l'établissement """
        # si nécessaire, on donne les droits d'accès à l'établissement pour la personne qui l'a créé
        try:
            self.parent.s_pool.check_etab_credential(cred_user, rne)
        except:
            log.msg("acces non permis , ajout d'une restriction")
            self.parent.s_pool.add_restriction(cred_user,'rne',rne)
        # création de l'arborescence de l'établissement
        etab_dir = os.path.abspath(config.PATH_ZEPHIR)+'/conf/'+rne
        try:
            os.makedirs(etab_dir)
        except:
            return 0,u("erreur de création du répertoire de l'établissement")
        else:
            return 1,u(rne)

    def xmlrpc_del_etab(self,cred_user,rne):
        """supprime un établissement"""
        self.parent.s_pool.check_etab_credential(cred_user, rne)
        # on vérifie qu'il ne reste pas de serveurs
        query = """select id from serveurs where rne=%s"""
        return self.dbpool.runQuery(query, (rne,)).addCallbacks(self._del_etab, db_client_failed,callbackArgs=[rne])

    def _del_etab(self,data,rne):
        """supprime l'établissement dans la base de données"""
        if len(data) > 0:
            # on n'autorise pas la supression si il reste des serveurs
            return 0, u("Suppression impossible, il existe des serveurs rattachés à cet établissement")

        # on supprime la configuration uucp de tous les serveurs correspondants
        try:
            file_conf = open('/etc/uucp/config_zephir','r')
            conf_uucp = file_conf.readlines()
            file_conf.close()
        except:
            return 0, u("""erreur de lecture de /etc/uucp/config_zephir""")
        try:
            for sysfile in os.listdir('/etc/uucp/serveurs'):
                if sysfile.startswith(rne+'-'):
                    # on supprime le fichier de définition  du serveur
                    os.unlink('/etc/uucp/serveurs'+os.sep+sysfile)
                    # on supprime la référence à ce fichier dans /etc/uucp/config
                    conf_uucp.remove('sysfile /etc/uucp/serveurs/'+sysfile+'\n')
        except:
            # si pas encore de serveurs -> pas de répertoire
            pass

        # supression des logins/mots de passes correpondants
        try:
            # lecture des infos de login
            file_pwd = open('/etc/uucp/passwd_zephir','r')
            pwd_uucp = file_pwd.readlines()
            file_pwd.close()
            # supression des lignes pour cet étab
            for line in pwd_uucp:
                if line.startswith(rne+'-'):
                    pwd_uucp.remove(line)
            # sauvegarde de la liste
            file_pwd = open('/etc/uucp/passwd_zephir','w')
            file_pwd.writelines(pwd_uucp)
            file_pwd.close()
        except:
            return 0, u("""erreur de mise à jour du fichier des mots de passe""")

        # mise à jour du fichier /etc/uucp/config_zephir
        try:
            file_conf = open('/etc/uucp/config_zephir','w')
            file_conf.writelines(conf_uucp)
            file_conf.close()
        except:
            return 0, u("""erreur d'écriture dans /etc/uucp/config_zephir""")

        query = """delete from etablissements where rne=%s"""
        return self.dbpool.runOperation(query, (rne,)).addCallbacks(self._del_etab2, db_client_failed,callbackArgs=[rne])

    def _del_etab2(self,resultat,rne):
        """supprime l'arborescence de l'établissement"""
        etab_dir = os.path.abspath(config.PATH_ZEPHIR)+'/conf/'+rne
        # supression de ce répertoire ?
        try:
            shutil.rmtree(etab_dir)
        except:
            return 0,u("""erreur de supression du répertoire de l'établissement""")
        else:
            log.msg("établissement %s supprimé" % rne)
            return 1,u('ok')

    # modification d'un etablissement
    def xmlrpc_edit_etab(self,cred_user,rne,dico_modifs):
        """modification d'un établissement
        cette fonction prend en compte un dictionnaire qui indique les
        champs à modifier et leur nouvelle valeur. l'application cliente
        doit s'assurer que ces champs existent dans la base
        ex: zephir.etabs.edit_etab('R620001X',{'libelle':'bla bla','type':1})"""
        self.parent.s_pool.check_etab_credential(cred_user, rne)
        # on vérifie que l'identifiant n'est pas modifié
        if dico_modifs == {}:
            return 1,u('ok')
        if 'rne' in dico_modifs.keys():
            return 0,u('modification du rne interdit')
        # construction de la requête SQL de modification
        query=["update etablissements set "]
        params = []
        for cle in dico_modifs.keys():
            query.append(str(cle))
            query.append("=%s")
            params.append(str(dico_modifs[cle]))
            query.append(", ")
        string_fin=""" where rne=%s"""
        params.append(rne)
        query="".join(query)[:-2]
        query += string_fin
        return self.dbpool.runOperation(query, params).addCallbacks(lambda x:(1,'ok'), db_client_failed)

    def xmlrpc_rech_etab(self,cred_user,d):
        """Recherche multi-critères d'un établissement
        select * from etablissements where nom_champ like '%%libelle%%'
        { rne, libelle, cp, type, ville }
        """
        # construction de la requête SQL de recherche
        query=["select * from etablissements where "]
        params = []
        for nom_champ in d.keys():
            if d[nom_champ] != "":
                if nom_champ == 'type':
                    query.append(str(nom_champ) + " = %s and ")
                    params.append(int(d[nom_champ]))
                else:
                    query.append(str(nom_champ) + " ilike %s and ")
                    params.append(str(d[nom_champ]))

        query = "".join(query)[:-4]
        query += "order by RNE"
        return self.dbpool.runQuery(query, params).addCallbacks(self._got_etabs, db_client_failed, callbackArgs=[cred_user])

    def xmlrpc_get_types(self,cred_user):
        """Récupération des types d'établissement existants
        """
        query = """select * from types_etab order by libelle"""
        return self.dbpool.runQuery(query).addCallbacks(self._got_types,db_client_failed)


    def xmlrpc_add_type(self,cred_user,libelle):
        """ajoute un type d'établissement dans la base de données"""
        # on vérifie que les données obligatoires sont remplies
        if (libelle):
            query = """insert into types_etab (libelle) values (%s)"""
            # on effectue l'insertion (l'existence est à tester dans l'application cliente)
            return self.dbpool.runOperation(query, (libelle,)).addCallbacks(lambda x:(1,'ok'), db_client_failed)
        else:
            # des attributs manquent
            return 0,u('libellé manquant')

    def xmlrpc_del_type(self,cred_user,id_type):
        """ajoute un type d'établissement dans la base de données"""
        # on vérifie que les données obligatoires sont remplies
        if (id_type):
            query = """delete from types_etab where id=%s"""
            # on effectue l'insertion (l'existence est à tester dans l'application cliente)
            return self.dbpool.runOperation(query, (int(id_type),)).addCallbacks(lambda x:(1,'ok'), db_client_failed)
        else:
            # des attributs manquent
            return 0,u('identifiant manquant')


