# -*- coding: UTF-8 -*-
###########################################################################
# Eole NG - 2012
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  cf /root/LicenceEole.txt
# eole@ac-dijon.fr
#
# dicos.py
#
# pages et fonctions de gestion du pool de dictionnaire
#
###########################################################################
import os, base64, traceback, json
from functools import cmp_to_key
from twisted.web import static
from zephir.web.template.design import Design, proxy
from zephir.web.config import Navigation, DISTRIBS, PATH_TEMP
from zephir.web.html.erreur import *
from zephir.eolerpclib import xmlrpclib


def escape(value):
    if isinstance(value, bytes):
        return value.decode()
    return value


def sort_dicts(x, y):
    """fonction de tri des dictionnaires d'un paquet
    """
    try:
        # compare le n° de dictionnaire, puis le nom complet en cas d'égalité
        return int(x[:x.index('_')]) < int(y[:y.index('_')]) or x < y
    except:
        # erreur possible si le nom ne commence pas par un numéro
        return x < y

def gen_liste_dicos(dicos, defaults, defaults_color="red"):
    """fonction générant une liste de dictionnaires/paquets
    associés à une ressource. renvoie séparément les d'origine et modifiables (module)
    dicos: dictionnaires utilisés (dicos.list_module/variante/serveur)
    defaults: dictionnaires non modifiables d'un module

    mode variante : <tr><td><a href="edit_fichier?id=146&module=107&file=51_piwik.xml&type=dicos_var">51_piwik.xml</a></td><td align="right">(<a href="javascript:del_file('51_piwik.xml','dicos_var')">supprimer</a>)</td></tr>

    mode serveur : <tr><td><a href="edit_fichier?id=130&file=80_dico1.xml&type=dicos">80_dico1.xml</a></td><td align="right">(<a href="perso?id=130&fic_sup=80_dico1.xml&type=dicos">supprimer</a>)</td></tr>

    """

    # gestion avec le pool de dictionnaires pour ce module
    dict_orig = []
    dict_mod = []
    paq_orig = []
    paq_mod = []
    paq_origdict = {}
    paq_moddict = {}
    for dico in dicos:
        dict_dir, paq, dict_name = dico.rsplit(os.sep, 2)
        if paq in ('eole', 'local'):
            # dictionnaires isolés
            if dico in defaults:
                dict_orig.append("""<font color="%s">%s</font>""" % (defaults_color, dict_name))
            else:
                dict_mod.append("""<INPUT TYPE="checkbox" NAME="removedict_%s_%s">%s""" % (paq, dict_name, dict_name))
        else:
            # dictionnaires d'un paquet
            if dico in defaults:
                type_paq, dicos_paq = paq_origdict.get(paq, (os.path.basename(dict_dir), []))
                dicos_paq.append(dict_name)
                paq_origdict[paq] = (type_paq, dicos_paq)
            else:
                type_paq, dicos_paq = paq_moddict.get(paq, (os.path.basename(dict_dir), []))
                dicos_paq.append(dict_name)
                paq_moddict[paq] = (type_paq, dicos_paq)

    for paqs in (paq_origdict, paq_moddict):
        paq_list = list(paqs.keys())
        # tri des dictionnaires par l'indice dans le nom de paquet
        paq_list.sort()
        for paq in paq_list:
            type_paq, dicts = paqs[paq]
            if paqs is paq_moddict:
                paq_entry = """<a target="_blank" class="paquet">"""
                # associations modifiables
                paq_entry += """<INPUT TYPE="checkbox" NAME="removedict_%s_%s">%s""" % (type_paq, paq, paq)
                dicts.sort(key=cmp_to_key(sort_dicts))
                paq_entry += """<div><span></span>%s</div></a>""" % '\n'.join(dicts)
                paq_mod.append(paq_entry)
            else:
                paq_entry = """<a target="_blank" class="paquet" style='color:%s'>%s""" % (defaults_color, paq)
                dicts.sort(key=cmp_to_key(sort_dicts))
                paq_entry += """<div><span></span>%s</div></a>""" % '\n'.join(dicts)
                paq_orig.append(paq_entry)
    return (dict_mod + paq_mod, dict_orig + paq_orig)

def gen_available_dicos(dicos, available, detected=None):
    """retourne les dictionnaires disponibles pour une ressource (options d'un select)
    dicos : dictionnaires utilisés par la ressource
    available : liste des dictionnaires disponibles pour une version de la distribution eole
    detected : liste des dictionnaires recommandés (installés sur un serveur mais non activés)
    """
    used_paqs = {'local':[], 'eole':[]}
    used_dicts = {'local':[], 'eole':[]}
    # listes des paquets/dicos utilisés
    for dico in dicos:
        dict_dir, paq, dict_name = dico.rsplit(os.sep, 2)
        if paq == 'local' and dico.endswith('.xml'):
            used_dicts['local'].append(dict_name)
        elif paq == 'eole' and dico.endswith('.xml'):
            used_dicts['eole'].append(dict_name)
        elif os.path.basename(dict_dir) == 'local':
            # si plusieurs dicos dans un paquet, on aura plusieurs entrées
            if paq not in used_paqs['local']:
                used_paqs['local'].append(paq)
        else:
            if paq not in used_paqs['eole']:
                used_paqs['eole'].append(paq)
    # construction des entrées dans la liste
    avail_paqs = {'eole':[], 'local':[]}
    avail_dicts = {'eole':[], 'local':[]}
    for type_dict in ('eole', 'local'):
        for eole_paq in available[type_dict]['paqs']:
            if eole_paq not in used_paqs[type_dict] and eole_paq not in avail_paqs[type_dict]:
                avail_paqs[type_dict].append(eole_paq)
        for eole_dict in available[type_dict]['dicos']:
            if eole_dict not in used_dicts[type_dict] and eole_dict not in avail_dicts[type_dict]:
                avail_dicts[type_dict].append(eole_dict)
        # tri des dicos/paquets
        avail_paqs[type_dict].sort()
        avail_dicts[type_dict].sort(key=cmp_to_key(sort_dicts))
    result = ['<option value="" selected="selected"></option>']
    # construction des optgroups/options
    if avail_paqs['local']:
        result.append('<optgroup label="Paquets locaux">')
        for paq in avail_paqs['local']:
            if detected and paq in detected:
                class_info = 'class="dictpaq" '
            else:
                class_info = ''
            result.append('<option %svalue="local_%s">%s</option>' % (class_info, paq, paq))
    if avail_dicts['local']:
        result.append('<optgroup label="Dictionnaires locaux">')
        for dict in avail_dicts['local']:
            result.append('<option value="local_%s">%s</option>' % (dict, dict))
    if avail_paqs['eole']:
        result.append('<optgroup label="Paquets EOLE">')
        for paq in avail_paqs['eole']:
            if detected and paq in detected:
                class_info = 'class="dictpaq" '
            else:
                class_info = ''
            result.append('<option %svalue="eole_%s">%s</option>' % (class_info, paq, paq))
    if avail_dicts['eole']:
        result.append('<optgroup label="Dictionnaires EOLE">')
        for dict in avail_dicts['eole']:
            result.append('<option value="eole_%s">%s</option>' % (dict, dict))
    return '\n'.join(result)

def list_local_dicos(available):
    """retourne la liste des (paquets de) dictionnaires locaux
    """
    result = ['<option value="" selected="selected">- Nouveau -</option>']
    liste_paqs = list(available['paqs'].keys())
    liste_paqs.sort()
    # construction des optgroups/options
    if available['dicos']:
        result.append('<optgroup label="Dictionnaires locaux">')
        dicts = list(available['dicos'].keys())
        dicts.sort()
        for dict_name in dicts:
            result.append('<option value="local/%s">%s</option>' % (dict_name, dict_name))
    for paq_name in liste_paqs:
        result.append('<optgroup label="Paquet %s">' % paq_name)
        dicts = [os.path.basename(dict_name) for dict_name in available['paqs'][paq_name]]
        dicts.sort()
        for dict_name in dicts:
            result.append('<option value="%s/%s">%s</option>' % (paq_name, dict_name, dict_name))
    return '\n'.join(result)

class PoolDicos(Design):
    """Page d'ajout/suppression de (paquets de) dictionnaires
    """
    def __init__(self, *args, **kwargs):
        self.entites = {
            'get_dict': GetDict,
                }
        super().__init__(*args, **kwargs)

    def getChild(self, name, request):
        if static.isDangerous(name):
            return static.dangerousPathError
        name = name.decode()
        if name in self.entites:
            return self.entites[name]()
        return self

    def wmfactory_title(self, request):
        return "Gestion des dictionnaires locaux"

    def wmfactory_content(self, request):
        return self.content

    def _dump_scripts(self, mod_version, dict_resources):
        return """
<script language='javascript'>
var resources = %s;
function redirect(type_dict) {
    dict_name = document.getElementsByName('dict_name')[0].value;
    paq_name = document.getElementsByName('paq_name')[0].value;
    resource_detected = false;
    msg_confirm = 'Ce dictionnaire est utilisé par des variantes/serveurs,\\ncontinuer la suppression ?';
    if ( paq_name != "" ) {
        if ( resources[paq_name]['serveur'].length + resources[paq_name]['variante'].length > 0 ) {
            resource_detected = true;
            msg_confirm = 'Ce paquet est utilisé par des variantes/serveurs,\\ncontinuer la suppression ?'
        }
    } else if ( resources[dict_name]['serveur'].length + resources[dict_name]['variante'].length > 0 ) {
            resource_detected = true;
    }
    if ( type_dict == "paq" ) {
        redir_url = '/dicos?del_dict=' + paq_name + '&module_version=%s';
    } else {
        redir_url = '/dicos?del_dict=' + dict_name + '&del_paq=' + paq_name + '&module_version=%s';
    }
    if ( resource_detected == true ) {
        if ( confirm(msg_confirm) == false) { return false }
    }
    window.location = redir_url;
}
function check_dict() {
    dict_name = document.getElementsByName('dict_name')[0];
    paq_name = document.getElementsByName('paq_name')[0];
    dict_temp = dict_name.disabled;
    paq_temp = paq_name.disabled;
    f_input = document.getElementsByName('dict_data')[0];
    if ( f_input.value != "" ) {
        if ( dict_name.value != "" ) {
            dict_name.disabled = false;
            paq_name.disabled = false;
            return true;
        }
    }
    alert("veuillez renseigner un nom de dictionnaire et son contenu");
    dict_name.disabled = dict_temp;
    paq_name.disabled = paq_temp;
    return false;
};
function update_file(copy_name){
    f_input = document.getElementsByName('dict_data')[0];
    dict_name = document.getElementsByName('dict_name')[0];
    if ( f_input.value != "" ) {
        //recopie du nom de fichier si non saisi et activation du bouton d'import
        if ( ( dict_name.value == "" ) && ( copy_name == true ) ) {
            f_name = f_input.files[0].name;
            dict_name.value = f_name;
        }
    }
    if ( ( f_input.value != "" ) && ( dict_name.value != "") ) {
        document.getElementsByName('import_btn')[0].disabled = false;
    } else {
        document.getElementsByName('import_btn')[0].disabled = true;
    }
};
function toggle_del(){
    button_dict = document.getElementsByName('button_dict')[0];
    button_paq = document.getElementsByName('button_paq')[0];
    div_get = document.getElementsByName('div_get')[0];
    dict_name = document.getElementsByName('dict_name')[0].value;
    paq_name = document.getElementsByName('paq_name')[0].value;
    if ( dict_name != "" ) {
        button_dict.style.display="";
        div_get.style.display = "inline";
    } else {
        button_dict.style.display="none";
        div_get.style.display = "none";
    }
    if ( paq_name != "" ) {
        button_paq.style.display="";
    } else {
        button_paq.style.display="none";
    }
    update_file(false);
};
function switch_dict(dict_sel) {
    dict_name = document.getElementsByName('dict_name')[0];
    paq_name = document.getElementsByName('paq_name')[0];
    div_get = document.getElementsByName('div_get')[0];
    div_variante = document.getElementsByName('div_variante')[0];
    div_serveur = document.getElementsByName('div_serveur')[0];
    div_resource = document.getElementsByName('div_resource')[0];
    get_dict = document.getElementsByName('get_dict')[0];
    val = dict_sel.options[dict_sel.selectedIndex].value;
    div_variante.style.display = "none";
    div_serveur.style.display = "none";
    div_resource.style.display = "none";
    num_serv = 0;
    num_var = 0;
    if ( val != "" ) {
        paq = val.split('/')[0];
        dict = val.split('/')[1];
        if ( paq == 'local' ) {
            paq_name.disabled = true;
            paq_name.value = "";
            dict_name.disabled = true;
            dict_name.value = dict;
            serveurs = resources[dict]['serveur'];
            variantes = resources[dict]['variante'];
        } else {
            paq_name.value = paq;
            paq_name.disabled = true;
            dict_name.value = dict;
            dict_name.disabled = false;
            serveurs = resources[paq]['serveur'];
            variantes = resources[paq]['variante'];
        }
        if ( serveurs.length > 0) {
            document.getElementsByName('serveurs')[0].innerHTML = serveurs.length;
            document.getElementsByName('list_serv')[0].innerHTML = "<span></span>" + serveurs.join(', ');
            div_serveur.style.display = "inline";
            div_resource.style.display = "inline";
        }
        if ( variantes.length > 0) {
            document.getElementsByName('variantes')[0].innerHTML = variantes.length;
            document.getElementsByName('list_var')[0].innerHTML = "<span></span>" + variantes.join(', ');
            div_variante.style.display = "inline";
            div_resource.style.display = "inline";
        }
        get_url = '/dicos/get_dict?type_dict=local&module_version=%s&dict_name='+dict_name.value;
        if ( paq_name.value != "" ) {
            get_url = get_url + '&paq_name='+paq_name.value;
        }
        get_dict.href = get_url;
        div_get.style.display = "inline";
    } else {
        paq_name.value = "";
        paq_name.disabled = false;
        dict_name.value = "";
        dict_name.disabled = false;
        div_get.style.display = "none";
    }
    toggle_del();
};
</script>""" % (json.dumps(dict_resources), mod_version, mod_version, mod_version)

    def _dump_html(self, mod_version, available, info_msg):
        """formulaire d'ajout/suppression
        """
        dic_mod = list_local_dicos(available)
        return """
<p>%s</p>
<table><tr><td>
<select name="dict_sel" onChange="switch_dict(this);">%s</select><br/><br/>
(Importer un nouveau dictionnaire ou<br />
sélectionner un dictionnaire à mettre à jour)<br />
<div style="display:none;" name="div_resource">Utilisé par :
<div style="display:none;" name="div_variante"><span name="variantes">0</span> <a target="_blank" class="paquet">variante(s)<div name="list_var"></div></a></div>
<div style="display:none;" name="div_serveur"><span name="serveurs">0</span> <a target="_blank" class="paquet">serveur(s)<div name="list_serv"></div></a></div>
<br/><br/></div>
</td>
<td width="30px"></td>
<td>
<form name="form_add" enctype="multipart/form-data" action="/dicos" method="POST" onSubmit="return check_dict();">
<input type="hidden" name="module_version" value="%s"/>
<table>
<tr><td>Paquet (facultatif)</td><td><input type="text" name="paq_name" onBlur="toggle_del()" value=""/></td>
<td><button type="button" name="button_paq" style="display:none" onclick="redirect('paq');">supprimer le paquet</button></td></tr>
<tr><td>Dictionnaire</td><td><input type="text" name="dict_name" onBlur="toggle_del()" value=""/></td>
</tr><tr><td>Contenu</td><td><input type="file" name="dict_data" onchange="update_file(true);"></td></tr>
<tr><td></td><td><input type="submit" name="import_btn" value="Importer" disabled="disabled"/></td></tr>
<tr><td></td><td><button type="button" name="button_dict" style="display:none" onclick="redirect('dict');">supprimer</button></td></tr>
<tr><td colspan="2"><div name="div_get" style="display:none"><a name="get_dict" href="#">Générer un lien de téléchargement</a></div></td></tr>
</table>
</form>
</td></tr></table>
<p><a href="/module">Retour à la liste des modules</a></p>
""" % (info_msg, dic_mod, str(mod_version))

    def renderView(self, request):
        try:
            info_msg = ""
            try:
                mod_version = int(escape(request.args[b'module_version'][0]))
            except:
                raise FrontendError("Version de distribution EOLE")
            self.content = """
            <h1>Gestion des dictionnaires locaux - EOLE %s</h1><br>
            """ % DISTRIBS[mod_version][1]
            try:
                dict_name = escape(request.args[b'dict_name'][0])
            except:
                dict_name = ""
            try:
                paq_name = escape(request.args[b'paq_name'][0])
            except:
                paq_name = ""
            try:
                # récupération du contenu du fichier uploadé
                dict_data = request.args[b'dict_data'][0].decode()
            except:
                dict_data = ""
            try:
                del_paq = escape(request.args[b'del_paq'][0])
            except:
                del_paq = ""
            try:
                del_dict = escape(request.args[b'del_dict'][0])
            except:
                del_dict = ""
            if del_dict or del_paq:
                # supression d'un dictionnaire / paquet
                try:
                    res = backend(proxy(request).dicos.remove_dict(mod_version, del_dict, del_paq))
                    if res == False:
                        info_msg = """<br/><font color="red">Erreur lors de la suppression (%s inexistant ?)</font>""" % del_dict
                    else:
                        info_msg = """<br/><font color="blue">%s supprimé</font>""" % del_dict
                except xmlrpclib.ProtocolError:
                    raise BackendError("""Vous n'êtes pas autorisé à effectuer cette action""")
                except BackendError as e:
                    # problème d'accès à une ressource ? on extrait le message
                    msg_err = str(e)
                    msg_err = msg_err[msg_err.index('<i>') + 5:msg_err.index('</i>')].strip()
                    info_msg = """<br/><font color="red">Echec de suppression du dictionnaire (%s)</font>""" % msg_err
            if dict_data:
                # ajout/mise à jour d'un dictionnaire
                try:
                    assert dict_data != ""
                    assert dict_name != ""
                    dict_data = base64.encodebytes(dict_data.encode())
                    res = backend(proxy(request).dicos.add_dict(mod_version, dict_name, dict_data, paq_name))
                    info_msg = """<br/>Dictionnaire sauvegardé : <font color="blue">%s</font>""" % dict_name
                except xmlrpclib.ProtocolError:
                    raise BackendError("""<p><span id="alerte">Vous n'êtes pas autorisé à effectuer cette action</span></p>""")
                except BackendError as e:
                    # problème d'accès à une ressource ? on extrait le message
                    msg_err = str(e)
                    msg_err = msg_err[msg_err.index('<i>') + 5:msg_err.index('</i>')].strip()
                    info_msg = """<br/><font color="red">Echec d'envoi du dictionnaire (%s)</font>""" % msg_err

            # liste des dictionnaires existants pour cette version de la distribution
            try:
                # on force une vérification des dictionnaires locaux disponibles pour cette distribution
                dict_updated = backend(proxy(request).dicos.update_dicts(mod_version))
                available = backend(proxy(request).dicos.list_available(mod_version)).get('local', {})
                # informations sur les ressources utilisant les dictionnaires
                dict_resources = {}
                for type_res in available:
                    for dico in available[type_res]:
                        resources = backend(proxy(request).dicos.get_dict_resources(mod_version, 'local', dico))
                        dict_resources[dico] = {'variante':resources['variante'], 'serveur':resources['serveur']}
                self.content += self._dump_scripts(mod_version, dict_resources)
                self.content += self._dump_html(mod_version, available, info_msg)
            except xmlrpclib.ProtocolError:
                raise BackendError("""<p><span id="alerte">Vous n'êtes pas autorisé à effectuer cette action</span></p>""")
        except Exception as e:
            traceback.print_exc()
            self.content = e

        return self.content

class GetDict(Design):
    """Préparation du téléchargement d'un dictionnaire du pool
    """
    isLeaf = True

    def getChild(self, name, request):
        if static.isDangerous(name):
            return static.dangerousPathError
        if name == b"get_dict":
            return self
        return PoolDicos()

    def wmfactory_title(self, request):
        return "Téléchargement du dictionnaire"

    def wmfactory_content(self, request):
        return self.content

    def wmfactory_menu(self, request):
        return Navigation().menu()

    def renderView(self, request):
        try:
            mod_version = int(escape(request.args[b'module_version'][0]))
        except:
            raise FrontendError("Version de distribution EOLE")
        try:
            type_dict = escape(request.args[b'type_dict'][0])
        except:
            type_dict = ""
        try:
            dict_name = escape(request.args[b'dict_name'][0])
        except:
            dict_name = ""
        try:
            paq_name = escape(request.args[b'paq_name'][0])
        except:
            paq_name = ""
        if paq_name:
            paq_info = ' (paquet %s)' % paq_name
        else:
            paq_info = ''
        self.content = """<h1>Téléchargement du dictionnaire local selectionné</h1>
        """
        try:
            try:
                f_content = backend(proxy(request).dicos.get_dict(mod_version, type_dict, dict_name, paq_name))
            except xmlrpclib.ProtocolError:
                raise BackendError("""Vous n'êtes pas autorisé à effectuer cette action""")
            # écriture du fichier à télécharger dans PATH_TMP
            filename = "zephir_%s_%s" % (type_dict, dict_name)
            # on vérifie que le répertoire temporaire existe
            if not os.path.isdir(PATH_TEMP):
                os.makedirs(PATH_TEMP)
            path_temp = os.path.join(PATH_TEMP, filename)
            try:
                with open(path_temp, 'wb') as f_temp:
                    f_temp.write(base64.decodebytes(f_content.encode()))
            except:
                return 0, u("Erreur d'écriture du fichier à télécharger")
            self.content += """
            <p><a href="/tmp/%s">Lien temporaire vers le dictionnaire %s%s</a></p>
            <p>(faire un clic droit → Enregistrer la cible du lien sous…)</p>
            <p><a href="/dicos/?type_dict=local&module_version=%s">Retour vers la page de gestion des dictionnaires locaux</a></p>
            """ % (filename, dict_name, paq_info, mod_version)
        except Exception as e:
            traceback.print_exc()
            self.content = str(e)
