# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@viennet.net
#
##############################################################################

"""Import / Export de formations
"""
from operator import itemgetter
import xml.dom.minidom

import flask
from flask import g, url_for, request
from flask_login import current_user

import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import db
from app import log
from app.models import Formation, Module
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formsemestre
from app.scodoc import sco_news
from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
from app.scodoc import sco_xml
import sco_version
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
from app.scodoc.sco_permissions import Permission

_formationEditor = ndb.EditableTable(
    "notes_formations",
    "formation_id",
    (
        "formation_id",
        "acronyme",
        "titre",
        "titre_officiel",
        "version",
        "formation_code",
        "type_parcours",
        "code_specialite",
    ),
    filter_dept=True,
    sortkey="acronyme",
)


def formation_list(formation_id=None, args={}):
    """List formation(s) with given id, or matching args
    (when args is given, formation_id is ignored).
    """
    if not args:
        if formation_id is None:
            args = {}
        else:
            args = {"formation_id": formation_id}
    cnx = ndb.GetDBConnexion()
    r = _formationEditor.list(cnx, args=args)
    # log('%d formations found' % len(r))
    return r


def formation_has_locked_sems(formation_id):  # XXX to remove
    "backward compat: True if there is a locked formsemestre in this formation"
    formation = Formation.query.get(formation_id)
    return formation.has_locked_sems()


def formation_export(
    formation_id,
    export_ids=False,
    export_tags=True,
    export_external_ues=False,
    format=None,
):
    """Get a formation, with UE, matieres, modules
    in desired format
    """
    formation = Formation.query.get_or_404(formation_id)
    F = formation.to_dict()
    selector = {"formation_id": formation_id}
    if not export_external_ues:
        selector["is_external"] = False
    ues = sco_edit_ue.ue_list(selector)
    F["ue"] = ues
    for ue in ues:
        ue_id = ue["ue_id"]
        ue["reference"] = ue_id  # pour les coefficients
        if not export_ids:
            del ue["id"]
            del ue["ue_id"]
            del ue["formation_id"]
        if ue["ects"] is None:
            del ue["ects"]
        mats = sco_edit_matiere.matiere_list({"ue_id": ue_id})
        ue["matiere"] = mats
        for mat in mats:
            matiere_id = mat["matiere_id"]
            if not export_ids:
                del mat["id"]
                del mat["matiere_id"]
                del mat["ue_id"]
            mods = sco_edit_module.module_list({"matiere_id": matiere_id})
            mat["module"] = mods
            for mod in mods:
                module_id = mod["module_id"]
                if export_tags:
                    tags = sco_tag_module.module_tag_list(module_id=mod["module_id"])
                    if tags:
                        mod["tags"] = [{"name": x} for x in tags]
                #
                module = Module.query.get(module_id)
                if module.is_apc():
                    # Exporte les coefficients
                    mod["coefficients"] = [
                        {"ue_reference": str(ue_id), "coef": str(coef)}
                        for (ue_id, coef) in module.get_ue_coef_dict().items()
                    ]
                if not export_ids:
                    del mod["id"]
                    del mod["ue_id"]
                    del mod["matiere_id"]
                    del mod["module_id"]
                    del mod["formation_id"]
                if mod["ects"] is None:
                    del mod["ects"]

    return scu.sendResult(
        F, name="formation", format=format, force_outer_xml_tag=False, attached=True
    )


def formation_import_xml(doc: str, import_tags=True):
    """Create a formation from XML representation
    (format dumped by formation_export( format='xml' ))
    XML may contain object (UE, modules) ids: this function returns two
    dicts mapping these ids to the created ids.

    Args:
        doc:    str, xml data
        import_tags: if false, does not import tags on modules.

    Returns:
        formation_id, modules_old2new, ues_old2new
    """
    from app.scodoc import sco_edit_formation

    # log("formation_import_xml: doc=%s" % doc)
    try:
        dom = xml.dom.minidom.parseString(doc)
    except:
        log("formation_import_xml: invalid XML data")
        raise ScoValueError("Fichier XML invalide")

    try:
        f = dom.getElementsByTagName("formation")[0]  # or dom.documentElement
        D = sco_xml.xml_to_dicts(f)
    except:
        raise ScoFormatError(
            """Ce document xml ne correspond pas à un programme exporté par ScoDoc. 
            (élément 'formation' inexistant par exemple)."""
        )
    assert D[0] == "formation"
    F = D[1]
    # F_quoted = F.copy()
    # ndb.quote_dict(F_quoted)
    F["dept_id"] = g.scodoc_dept_id
    # find new version number
    cnx = ndb.GetDBConnexion()
    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
    cursor.execute(
        """SELECT max(version)
        FROM notes_formations
        WHERE acronyme=%(acronyme)s and titre=%(titre)s and dept_id=%(dept_id)s
        """,
        F,
    )
    res = cursor.fetchall()
    try:
        version = int(res[0][0]) + 1
    except:
        version = 1
    F["version"] = version
    # create formation
    # F_unquoted = F.copy()
    # unescape_html_dict(F_unquoted)
    formation_id = sco_edit_formation.do_formation_create(F)
    log("formation %s created" % formation_id)
    ues_old2new = {}  # xml ue_id : new ue_id
    modules_old2new = {}  # xml module_id : new module_id
    # (nb: mecanisme utilise pour cloner semestres seulement, pas pour I/O XML)

    ue_reference_to_id = {}  # pour les coefs APC (map reference -> ue_id)
    modules_a_coefficienter = []  # Liste des modules avec coefs APC
    # -- create UEs
    for ue_info in D[2]:
        assert ue_info[0] == "ue"
        ue_info[1]["formation_id"] = formation_id
        if "ue_id" in ue_info[1]:
            xml_ue_id = int(ue_info[1]["ue_id"])
            del ue_info[1]["ue_id"]
        else:
            xml_ue_id = None
        ue_id = sco_edit_ue.do_ue_create(ue_info[1])
        if xml_ue_id:
            ues_old2new[xml_ue_id] = ue_id
        ue_reference = int(ue_info[1].get("reference"))
        if ue_reference:
            ue_reference_to_id[ue_reference] = ue_id
        # -- create matieres
        for mat_info in ue_info[2]:
            assert mat_info[0] == "matiere"
            mat_info[1]["ue_id"] = ue_id
            mat_id = sco_edit_matiere.do_matiere_create(mat_info[1])
            # -- create modules
            for mod_info in mat_info[2]:
                assert mod_info[0] == "module"
                if "module_id" in mod_info[1]:
                    xml_module_id = int(mod_info[1]["module_id"])
                    del mod_info[1]["module_id"]
                else:
                    xml_module_id = None
                mod_info[1]["formation_id"] = formation_id
                mod_info[1]["matiere_id"] = mat_id
                mod_info[1]["ue_id"] = ue_id
                mod_id = sco_edit_module.do_module_create(mod_info[1])
                if xml_module_id:
                    modules_old2new[int(xml_module_id)] = mod_id
                if len(mod_info) > 2:
                    module = Module.query.get(mod_id)
                    tag_names = []
                    ue_coef_dict = {}
                    for child in mod_info[2]:
                        if child[0] == "tags" and import_tags:
                            tag_names.append(child[1]["name"])
                        elif child[0] == "coefficients":
                            ue_reference = int(child[1]["ue_reference"])
                            coef = float(child[1]["coef"])
                            ue_coef_dict[ue_reference] = coef
                    if import_tags and tag_names:
                        sco_tag_module.module_tag_set(mod_id, tag_names)
                    if module.is_apc() and ue_coef_dict:
                        modules_a_coefficienter.append((module, ue_coef_dict))
    # Fixe les coefs APC (à la fin pour que les UE soient crées)
    for module, ue_coef_dict_ref in modules_a_coefficienter:
        # remap ue ids:
        ue_coef_dict = {ue_reference_to_id[k]: v for (k, v) in ue_coef_dict_ref.items()}
        module.set_ue_coef_dict(ue_coef_dict)
    db.session.commit()
    return formation_id, modules_old2new, ues_old2new


def formation_list_table(formation_id=None, args={}):
    """List formation, grouped by titre and sorted by versions
    and listing associated semestres
    returns a table
    """
    formations = formation_list(formation_id=formation_id, args=args)
    title = "Programmes pédagogiques"
    lockicon = scu.icontag(
        "lock32_img", title="Comporte des semestres verrouillés", border="0"
    )
    suppricon = scu.icontag(
        "delete_small_img", border="0", alt="supprimer", title="Supprimer"
    )
    editicon = scu.icontag(
        "edit_img", border="0", alt="modifier", title="Modifier titres et code"
    )

    editable = current_user.has_permission(Permission.ScoChangeFormation)

    # Traduit/ajoute des champs à afficher:
    for f in formations:
        try:
            f["parcours_name"] = sco_codes_parcours.get_parcours_from_code(
                f["type_parcours"]
            ).NAME
        except:
            f["parcours_name"] = ""
        f["_titre_target"] = url_for(
            "notes.ue_table",
            scodoc_dept=g.scodoc_dept,
            formation_id=str(f["formation_id"]),
        )
        f["_titre_link_class"] = "stdlink"
        f["_titre_id"] = "titre-%s" % f["acronyme"].lower().replace(" ", "-")
        # Ajoute les semestres associés à chaque formation:
        f["sems"] = sco_formsemestre.do_formsemestre_list(
            args={"formation_id": f["formation_id"]}
        )
        f["sems_list_txt"] = ", ".join([s["session_id"] for s in f["sems"]])
        f["_sems_list_txt_html"] = ", ".join(
            [
                '<a class="discretelink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%('
                "session_id)s<a> " % s
                for s in f["sems"]
            ]
            + [
                '<a class="stdlink" id="add-semestre-%s" '
                'href="formsemestre_createwithmodules?formation_id=%s&semestre_id=1">ajouter</a> '
                % (f["acronyme"].lower().replace(" ", "-"), f["formation_id"])
            ]
        )
        if f["sems"]:
            f["date_fin_dernier_sem"] = max([s["date_fin_iso"] for s in f["sems"]])
            f["annee_dernier_sem"] = f["date_fin_dernier_sem"].split("-")[0]
        else:
            f["date_fin_dernier_sem"] = ""
            f["annee_dernier_sem"] = ""
        locked = formation_has_locked_sems(f["formation_id"])
        #
        if locked:
            but_locked = lockicon
        else:
            but_locked = '<span class="but_placeholder"></span>'
        if editable and not locked:
            but_suppr = '<a class="stdlink" href="formation_delete?formation_id=%s" id="delete-formation-%s">%s</a>' % (
                f["formation_id"],
                f["acronyme"].lower().replace(" ", "-"),
                suppricon,
            )
        else:
            but_suppr = '<span class="but_placeholder"></span>'
        if editable:
            but_edit = (
                '<a class="stdlink" href="formation_edit?formation_id=%s" id="edit-formation-%s">%s</a>'
                % (f["formation_id"], f["acronyme"].lower().replace(" ", "-"), editicon)
            )
        else:
            but_edit = '<span class="but_placeholder"></span>'
        f["buttons"] = ""
        f["_buttons_html"] = but_locked + but_suppr + but_edit
    # Tri par annee_denier_sem, type, acronyme, titre, version décroissante
    formations.sort(key=itemgetter("version"), reverse=True)
    formations.sort(key=itemgetter("titre"))
    formations.sort(key=itemgetter("acronyme"))
    formations.sort(key=itemgetter("parcours_name"))
    formations.sort(
        key=itemgetter("annee_dernier_sem"), reverse=True
    )  # plus recemments utilises en tete

    #
    columns_ids = (
        "buttons",
        "acronyme",
        "parcours_name",
        "formation_code",
        "version",
        "titre",
        "sems_list_txt",
    )
    titles = {
        "buttons": "",
        "acronyme": "Acro.",
        "parcours_name": "Type",
        "titre": "Titre",
        "version": "Version",
        "formation_code": "Code",
        "sems_list_txt": "Semestres",
    }
    return GenTable(
        columns_ids=columns_ids,
        rows=formations,
        titles=titles,
        origin="Généré par %s le " % sco_version.SCONAME
        + scu.timedate_human_repr()
        + "",
        caption=title,
        html_caption=title,
        table_id="formation_list_table",
        html_class="formation_list_table table_leftalign",
        html_with_td_classes=True,
        html_sortable=True,
        base_url="%s?formation_id=%s" % (request.base_url, formation_id),
        page_title=title,
        pdf_title=title,
        preferences=sco_preferences.SemPreferences(),
    )


def formation_create_new_version(formation_id, redirect=True):
    "duplicate formation, with new version number"
    resp = formation_export(formation_id, export_ids=True, format="xml")
    xml_data = resp.get_data(as_text=True)
    new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data)
    # news
    F = formation_list(args={"formation_id": new_id})[0]
    sco_news.add(
        typ=sco_news.NEWS_FORM,
        object=new_id,
        text="Nouvelle version de la formation %(acronyme)s" % F,
    )
    if redirect:
        return flask.redirect(
            url_for(
                "notes.ue_table",
                scodoc_dept=g.scodoc_dept,
                formation_id=new_id,
                msg="Nouvelle version !",
            )
        )
    else:
        return new_id, modules_old2new, ues_old2new