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

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2022 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
#
##############################################################################

"""Opérations d'inscriptions aux modules (interface pour gérer options ou parcours)
"""
from operator import itemgetter

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

from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre

import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc.scolog import logdb
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_cache
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_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_etud
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission


def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
    """Formulaire inscription des etudiants a ce module
    * Gestion des inscriptions
         Nom          TD     TA    TP  (triable)
     [x] M. XXX YYY   -      -     -


     ajouter TD A, TD B, TP 1, TP 2 ...
     supprimer TD A, TD B, TP 1, TP 2 ...

     * Si pas les droits: idem en readonly
    """
    M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
    formsemestre_id = M["formsemestre_id"]
    mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
    sem = sco_formsemestre.get_formsemestre(formsemestre_id)
    # -- check lock
    if not sem["etat"]:
        raise ScoValueError("opération impossible: semestre verrouille")
    header = html_sco_header.sco_header(
        page_title="Inscription au module",
        init_qtip=True,
        javascripts=["js/etud_info.js"],
    )
    footer = html_sco_header.sco_footer()
    H = [
        header,
        """<h2>Inscriptions au module <a href="moduleimpl_status?moduleimpl_id=%s">%s</a> (%s)</a></h2>
    <p class="help">Cette page permet d'éditer les étudiants inscrits à ce module
    (ils doivent évidemment être inscrits au semestre).
    Les étudiants cochés sont (ou seront) inscrits. Vous pouvez facilement inscrire ou
    désinscrire tous les étudiants d'un groupe à l'aide des menus "Ajouter" et "Enlever".
    </p>
    <p class="help">Aucune modification n'est prise en compte tant que l'on n'appuie pas sur le bouton
    "Appliquer les modifications".
    </p>
    """
        % (
            moduleimpl_id,
            mod["titre"] or "(module sans titre)",
            mod["code"] or "(module sans code)",
        ),
    ]
    # Liste des inscrits à ce semestre
    inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
        formsemestre_id
    )
    for ins in inscrits:
        etuds_info = sco_etud.get_etud_info(etudid=ins["etudid"], filled=1)
        if not etuds_info:
            log(
                "moduleimpl_inscriptions_edit: incoherency for etudid=%s !"
                % ins["etudid"]
            )
            raise ScoValueError(
                "Etudiant %s inscrit mais inconnu dans la base !!!!!" % ins["etudid"]
            )
        ins["etud"] = etuds_info[0]
    inscrits.sort(key=lambda x: x["etud"]["nom"])
    in_m = sco_moduleimpl.do_moduleimpl_inscription_list(
        moduleimpl_id=M["moduleimpl_id"]
    )
    in_module = set([x["etudid"] for x in in_m])
    #
    partitions = sco_groups.get_partitions_list(formsemestre_id)
    #
    if not submitted:
        H.append(
            """<script type="text/javascript">
    function group_select(groupName, partitionIdx, check) {
    var nb_inputs_to_skip = 2; // nb d'input avant les checkbox !!!
    var elems = document.getElementById("mi_form").getElementsByTagName("input");

    if (partitionIdx==-1) {
      for (var i =nb_inputs_to_skip; i < elems.length; i++) {
         elems[i].checked=check;
      }
    } else {
     for (var i =nb_inputs_to_skip; i < elems.length; i++) {
       var cells = elems[i].parentNode.parentNode.getElementsByTagName("td")[partitionIdx].childNodes;
       if (cells.length && cells[0].nodeValue == groupName) {
          elems[i].checked=check;
       }      
     }
    }
    }

    </script>"""
        )
        H.append("""<form method="post" id="mi_form" action="%s">""" % request.base_url)
        H.append(
            """        
        <input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/>
        <input type="submit" name="submitted" value="Appliquer les modifications"/><p></p>
        """
            % M
        )
        H.append("<table><tr>")
        H.append(_make_menu(partitions, "Ajouter", "true"))
        H.append(_make_menu(partitions, "Enlever", "false"))
        H.append("</tr></table>")
        H.append(
            """
        <p><br/></p>
        <table class="sortable" id="mi_table"><tr>
        <th>Nom</th>"""
            % sem
        )
        for partition in partitions:
            if partition["partition_name"]:
                H.append("<th>%s</th>" % partition["partition_name"])
        H.append("</tr>")

        for ins in inscrits:
            etud = ins["etud"]
            if etud["etudid"] in in_module:
                checked = 'checked="checked"'
            else:
                checked = ""
            H.append(
                """<tr><td><input type="checkbox" name="etuds:list" value="%s" %s>"""
                % (etud["etudid"], checked)
            )
            H.append(
                """<a class="discretelink etudinfo" href="%s" id="%s">%s</a>"""
                % (
                    url_for(
                        "scolar.ficheEtud",
                        scodoc_dept=g.scodoc_dept,
                        etudid=etud["etudid"],
                    ),
                    etud["etudid"],
                    etud["nomprenom"],
                )
            )
            H.append("""</input></td>""")

            groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre_id)
            for partition in partitions:
                if partition["partition_name"]:
                    gr_name = ""
                    for group in groups:
                        if group["partition_id"] == partition["partition_id"]:
                            gr_name = group["group_name"]
                            break
                    # gr_name == '' si etud non inscrit dans un groupe de cette partition
                    H.append("<td>%s</td>" % gr_name)
        H.append("""</table></form>""")
    else:  # SUBMISSION
        # inscrit a ce module tous les etuds selectionnes
        sco_moduleimpl.do_moduleimpl_inscrit_etuds(
            moduleimpl_id, formsemestre_id, etuds, reset=True
        )
        return flask.redirect("moduleimpl_status?moduleimpl_id=%s" % (moduleimpl_id))
    #
    H.append(footer)
    return "\n".join(H)


def _make_menu(partitions, title="", check="true"):
    """Menu with list of all groups"""
    items = [{"title": "Tous", "attr": "onclick=\"group_select('', -1, %s)\"" % check}]
    p_idx = 0
    for partition in partitions:
        if partition["partition_name"] != None:
            p_idx += 1
            for group in sco_groups.get_partition_groups(partition):
                items.append(
                    {
                        "title": "%s %s"
                        % (partition["partition_name"], group["group_name"]),
                        "attr": "onclick=\"group_select('%s', %s, %s)\""
                        % (group["group_name"], p_idx, check),
                    }
                )
    return (
        '<td class="inscr_addremove_menu">'
        + htmlutils.make_menu(title, items, alone=True)
        + "</td>"
    )


def moduleimpl_inscriptions_stats(formsemestre_id):
    """Affiche quelques informations sur les inscriptions
    aux modules de ce semestre.

    Inscrits au semestre: <nb>

    Modules communs (tous inscrits): <liste des modules (codes)

    Autres modules: (regroupés par UE)
    UE 1
    <code du module>: <nb inscrits> (<description en termes de groupes>)
    ...


    descriptions:
      groupes de TD A, B et C
      tous sauf groupe de TP Z (?)
      tous sauf <liste d'au plus 7 noms>

    """
    authuser = current_user

    sem = sco_formsemestre.get_formsemestre(formsemestre_id)
    inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
        args={"formsemestre_id": formsemestre_id}
    )
    set_all = set([x["etudid"] for x in inscrits])
    partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
        formsemestre_id
    )

    can_change = authuser.has_permission(Permission.ScoEtudInscrit) and sem["etat"]

    # Liste des modules
    Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
        formsemestre_id=formsemestre_id, sort_by_ue=True
    )
    # Decrit les inscriptions aux modules:
    commons = []  # modules communs a tous les etuds du semestre
    options = []  # modules ou seuls quelques etudiants sont inscrits
    for mod in Mlist:
        tous_inscrits, nb_inscrits, descr = descr_inscrs_module(
            sem,
            mod["moduleimpl_id"],
            set_all,
            partitions,
            partitions_etud_groups,
        )
        if tous_inscrits:
            commons.append(mod)
        else:
            mod["descri"] = descr
            mod["nb_inscrits"] = nb_inscrits
            options.append(mod)
    # Page HTML:
    H = [html_sco_header.html_sem_header("Inscriptions aux modules du semestre")]

    H.append("<h3>Inscrits au semestre: %d étudiants</h3>" % len(inscrits))

    if options:
        H.append("<h3>Modules auxquels tous les étudiants ne sont pas inscrits:</h3>")
        H.append(
            '<table class="formsemestre_status formsemestre_inscr"><tr><th>UE</th><th>Code</th><th>Inscrits</th><th></th></tr>'
        )
        for mod in options:
            if can_change:
                c_link = '<a class="discretelink" href="moduleimpl_inscriptions_edit?moduleimpl_id=%s">%s</a>' % (
                    mod["moduleimpl_id"],
                    mod["descri"] or "<i>(inscrire des étudiants)</i>",
                )
            else:
                c_link = mod["descri"]
            H.append(
                '<tr class="formsemestre_status"><td>%s</td><td class="formsemestre_status_code">%s</td><td class="formsemestre_status_inscrits">%s</td><td>%s</td></tr>'
                % (
                    mod["ue"]["acronyme"] or "",
                    mod["module"]["code"] or "(module sans code)",
                    mod["nb_inscrits"],
                    c_link,
                )
            )
        H.append("</table>")
    else:
        H.append(
            '<span style="font-size:110%; font-style:italic; color: red;"">Tous les étudiants sont inscrits à tous les modules.</span>'
        )

    if commons:
        H.append(
            "<h3>Modules communs (auxquels tous les étudiants sont inscrits):</h3>"
        )
        H.append(
            '<table class="formsemestre_status formsemestre_inscr"><tr><th>UE</th><th>Code</th><th>Module</th></tr>'
        )
        for mod in commons:
            if can_change:
                c_link = (
                    '<a class="discretelink" href="moduleimpl_inscriptions_edit?moduleimpl_id=%s">%s</a>'
                    % (mod["moduleimpl_id"], mod["module"]["titre"])
                )
            else:
                c_link = mod["module"]["titre"]
            H.append(
                '<tr class="formsemestre_status_green"><td>%s</td><td class="formsemestre_status_code">%s</td><td>%s</td></tr>'
                % (
                    mod["ue"]["acronyme"],
                    mod["module"]["code"] or "(module sans code)",
                    c_link,
                )
            )
        H.append("</table>")

    # Etudiants "dispensés" d'une UE (capitalisée)
    UECaps = get_etuds_with_capitalized_ue(formsemestre_id)
    if UECaps:
        H.append('<h3>Etudiants avec UEs capitalisées:</h3><ul class="ue_inscr_list">')
        ues = [sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in UECaps.keys()]
        ues.sort(key=lambda u: u["numero"])
        for ue in ues:
            H.append(
                '<li class="tit"><span class="tit">%(acronyme)s: %(titre)s</span>' % ue
            )
            H.append("<ul>")
            for info in UECaps[ue["ue_id"]]:
                etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
                H.append(
                    '<li class="etud"><a class="discretelink" href="%s">%s</a>'
                    % (
                        url_for(
                            "scolar.ficheEtud",
                            scodoc_dept=g.scodoc_dept,
                            etudid=etud["etudid"],
                        ),
                        etud["nomprenom"],
                    )
                )
                if info["ue_status"]["event_date"]:
                    H.append(
                        "(cap. le %s)"
                        % (info["ue_status"]["event_date"]).strftime("%d/%m/%Y")
                    )

                if info["is_ins"]:
                    dm = ", ".join(
                        [
                            m["code"] or m["abbrev"] or "pas_de_code"
                            for m in info["is_ins"]
                        ]
                    )
                    H.append(
                        'actuellement inscrit dans <a title="%s" class="discretelink">%d modules</a>'
                        % (dm, len(info["is_ins"]))
                    )
                    if info["ue_status"]["is_capitalized"]:
                        H.append(
                            """<div><em style="font-size: 70%">UE actuelle moins bonne que l'UE capitalisée</em></div>"""
                        )
                    else:
                        H.append(
                            """<div><em style="font-size: 70%">UE actuelle meilleure que l'UE capitalisée</em></div>"""
                        )
                    if can_change:
                        H.append(
                            '<div><a class="stdlink" href="etud_desinscrit_ue?etudid=%s&formsemestre_id=%s&ue_id=%s">désinscrire des modules de cette UE</a></div>'
                            % (etud["etudid"], formsemestre_id, ue["ue_id"])
                        )
                else:
                    H.append("(non réinscrit dans cette UE)")
                    if can_change:
                        H.append(
                            '<div><a class="stdlink" href="etud_inscrit_ue?etudid=%s&formsemestre_id=%s&ue_id=%s">inscrire à tous les modules de cette UE</a></div>'
                            % (etud["etudid"], formsemestre_id, ue["ue_id"])
                        )
                H.append("</li>")
            H.append("</ul></li>")
        H.append("</ul>")

    H.append(
        """<hr/><p class="help">Cette page décrit les inscriptions actuelles. 
    Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en 
    cliquant sur la ligne du module.</p>
    <p  class="help">Note: la déinscription d'un module ne perd pas les notes. Ainsi, si 
    l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.</p>
    """
    )

    H.append(html_sco_header.sco_footer())
    return "\n".join(H)


def descr_inscrs_module(
    sem, moduleimpl_id, set_all, partitions, partitions_etud_groups
):
    """returns tous_inscrits, nb_inscrits, descr"""
    ins = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)
    set_m = set([x["etudid"] for x in ins])  # ens. des inscrits au module
    non_inscrits = set_all - set_m
    if len(non_inscrits) == 0:
        return True, len(ins), ""  # tous inscrits
    if len(non_inscrits) <= 7:  # seuil arbitraire
        return False, len(ins), "tous sauf " + _fmt_etud_set(non_inscrits)
    # Cherche les groupes:
    gr = []  #  [ ( partition_name , [ group_names ] ) ]
    for partition in partitions:
        grp = []  # groupe de cette partition
        for group in sco_groups.get_partition_groups(partition):
            members = sco_groups.get_group_members(group["group_id"])
            set_g = set([m["etudid"] for m in members])
            if set_g.issubset(set_m):
                grp.append(group["group_name"])
                set_m = set_m - set_g
        gr.append((partition["partition_name"], grp))
    #
    d = []
    for (partition_name, grp) in gr:
        if grp:
            d.append("groupes de %s: %s" % (partition_name, ", ".join(grp)))
    r = []
    if d:
        r.append(", ".join(d))
    if set_m:
        r.append(_fmt_etud_set(set_m))
    #
    return False, len(ins), " et ".join(r)


def _fmt_etud_set(ins, max_list_size=7):
    # max_list_size est le nombre max de noms d'etudiants listés
    # au delà, on indique juste le nombre, sans les noms.
    if len(ins) > max_list_size:
        return "%d étudiants" % len(ins)
    etuds = []
    for etudid in ins:
        etuds.append(sco_etud.get_etud_info(etudid=etudid, filled=True)[0])
    etuds.sort(key=itemgetter("nom"))
    return ", ".join(
        [
            '<a class="discretelink" href="%s">%s</a>'
            % (
                url_for(
                    "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
                ),
                etud["nomprenom"],
            )
            for etud in etuds
        ]
    )


def get_etuds_with_capitalized_ue(formsemestre_id):
    """For each UE, computes list of students capitalizing the UE.
    returns { ue_id : [ { infos } ] }
    """
    UECaps = scu.DictDefault(defaultvalue=[])
    formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
    nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)

    inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
        args={"formsemestre_id": formsemestre_id}
    )
    ues = nt.get_ues_stat_dict()
    for ue in ues:
        for etud in inscrits:
            ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
            if ue_status and ue_status["was_capitalized"]:
                UECaps[ue["ue_id"]].append(
                    {
                        "etudid": etud["etudid"],
                        "ue_status": ue_status,
                        "is_ins": is_inscrit_ue(
                            etud["etudid"], formsemestre_id, ue["ue_id"]
                        ),
                    }
                )
    return UECaps


def is_inscrit_ue(etudid, formsemestre_id, ue_id):
    """Modules de cette UE dans ce semestre
    auxquels l'étudiant est inscrit.
    """
    r = ndb.SimpleDictFetch(
        """SELECT mod.id AS module_id, mod.*
    FROM notes_moduleimpl mi, notes_modules mod,
         notes_formsemestre sem, notes_moduleimpl_inscription i
    WHERE sem.id = %(formsemestre_id)s
    AND mi.formsemestre_id = sem.id
    AND mod.id = mi.module_id
    AND mod.ue_id = %(ue_id)s
    AND i.moduleimpl_id = mi.id
    AND i.etudid = %(etudid)s
    ORDER BY mod.numero
    """,
        {"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
    )
    return r


def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
    """Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
    cnx = ndb.GetDBConnexion()
    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
    cursor.execute(
        """DELETE FROM notes_moduleimpl_inscription
    WHERE id IN (
      SELECT i.id FROM
        notes_moduleimpl mi, notes_modules mod,
        notes_formsemestre sem, notes_moduleimpl_inscription i
      WHERE sem.id = %(formsemestre_id)s
      AND mi.formsemestre_id = sem.id
      AND mod.id = mi.module_id
      AND mod.ue_id = %(ue_id)s
      AND i.moduleimpl_id = mi.id
      AND i.etudid = %(etudid)s
    )
    """,
        {"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
    )
    logdb(
        cnx,
        method="etud_desinscrit_ue",
        etudid=etudid,
        msg="desinscription UE %s" % ue_id,
        commit=False,
    )
    sco_cache.invalidate_formsemestre(
        formsemestre_id=formsemestre_id
    )  # > desinscription etudiant des modules


def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id):
    """Incrit l'etudiant de tous les modules de cette UE dans ce semestre."""
    # Verifie qu'il est bien inscrit au semestre
    insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
        args={"formsemestre_id": formsemestre_id, "etudid": etudid}
    )
    if not insem:
        raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid)

    cnx = ndb.GetDBConnexion()
    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
    cursor.execute(
        """SELECT mi.id 
      FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem
      WHERE sem.id = %(formsemestre_id)s
      AND mi.formsemestre_id = sem.id
      AND mod.id = mi.module_id
      AND mod.ue_id = %(ue_id)s
     """,
        {"formsemestre_id": formsemestre_id, "ue_id": ue_id},
    )
    res = cursor.dictfetchall()
    for moduleimpl_id in [x["id"] for x in res]:
        sco_moduleimpl.do_moduleimpl_inscription_create(
            {"moduleimpl_id": moduleimpl_id, "etudid": etudid},
            formsemestre_id=formsemestre_id,
        )