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

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

"""Edition des PV de jury

Formulaires paramétrage PV et génération des tables
"""
import collections
import time
from reportlab.platypus import Paragraph
from reportlab.lib import styles

import flask
from flask import flash, redirect, url_for
from flask import g, request

from app.models import FormSemestre, Identite

import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import html_sco_header
from app.scodoc import codes_cursus
from app.scodoc import sco_pv_dict
from app.scodoc import sco_etud
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_pdf
from app.scodoc import sco_preferences
from app.scodoc import sco_pv_pdf
from app.scodoc import sco_pv_lettres_inviduelles
from app.scodoc.gen_tables import GenTable
from app.scodoc.codes_cursus import NO_SEMESTRE_ID
from app.scodoc.sco_pdf import PDFLOCK
from app.scodoc.TrivialFormulator import TrivialFormulator


def _descr_decision_sem_abbrev(etat, decision_sem):
    "résumé textuel tres court (code) de la décision de semestre"
    if etat == "D":
        decision = "Démission"
    else:
        if decision_sem:
            decision = decision_sem["code"]
        else:
            decision = ""
    return decision


def pvjury_table(
    dpv,
    only_diplome=False,
    anonymous=False,
    with_parcours_decisions=False,
    with_paragraph_nom=False,  # cellule paragraphe avec nom, date, code NIP
):
    """idem mais rend list de dicts
    Si only_diplome, n'extrait que les etudiants qui valident leur diplome.
    """
    sem = dpv["formsemestre"]
    formsemestre_id = sem["formsemestre_id"]
    sem_id_txt_sp = sem["sem_id_txt"]
    if sem_id_txt_sp:
        sem_id_txt_sp = " " + sem_id_txt_sp
    titles = {
        "etudid": "etudid",
        "code_nip": "NIP",
        "nomprenom": "Nom",  # si with_paragraph_nom, sera un Paragraph
        "parcours": "Parcours",
        "decision": "Décision" + sem_id_txt_sp,
        "mention": "Mention",
        "ue_cap": "UE" + sem_id_txt_sp + " capitalisées",
        "ects": "ECTS",
        "devenir": "Devenir",
        "validation_parcours_code": "Résultat au diplôme",
        "observations": "Observations",
    }
    if anonymous:
        titles["nomprenom"] = "Code"
    columns_ids = ["nomprenom", "parcours"]

    if with_parcours_decisions:
        all_idx = set()
        for e in dpv["decisions"]:
            all_idx |= set(e["parcours_decisions"].keys())
        sem_ids = sorted(all_idx)
        for i in sem_ids:
            if i != NO_SEMESTRE_ID:
                titles[i] = "S%d" % i
            else:
                titles[i] = "S"  # pas très parlant ?
            columns_ids += [i]

    if dpv["has_prev"]:
        id_prev = sem["semestre_id"] - 1  # numero du semestre precedent
        titles["prev_decision"] = f"Décision S{id_prev}"
        columns_ids += ["prev_decision"]

    if not dpv["is_apc"]:
        # Décision de jury sur le semestre, sauf en BUT
        columns_ids += ["decision"]

    if sco_preferences.get_preference("bul_show_mention", formsemestre_id):
        columns_ids += ["mention"]
    columns_ids += ["ue_cap"]
    if sco_preferences.get_preference("bul_show_ects", formsemestre_id):
        columns_ids += ["ects"]

    # XXX if not dpv["semestre_non_terminal"]:
    # La colonne doit être présente: redoublants validant leur diplome
    # en répétant un semestre ancien: exemple: S1 (ADM), S2 (ADM), S3 (AJ), S4 (ADM), S3 (ADM)=> diplôme
    columns_ids += ["validation_parcours_code"]
    columns_ids += ["devenir"]
    columns_ids += ["observations"]

    lines = []
    for e in dpv["decisions"]:
        sco_etud.format_etud_ident(e["identite"])
        l = {
            "etudid": e["identite"]["etudid"],
            "code_nip": e["identite"]["code_nip"],
            "nomprenom": e["identite"]["nomprenom"],
            "_nomprenom_target": url_for(
                "scolar.ficheEtud",
                scodoc_dept=g.scodoc_dept,
                etudid=e["identite"]["etudid"],
            ),
            "_nomprenom_td_attrs": f"""id="{e['identite']['etudid']}" class="etudinfo" """,
            "parcours": e["parcours"],
            "decision": _descr_decision_sem_abbrev(e["etat"], e["decision_sem"]),
            "ue_cap": e["decisions_ue_descr"],
            "validation_parcours_code": "ADM" if e["validation_parcours"] else "",
            "devenir": e["autorisations_descr"],
            "observations": ndb.unquote(e["observation"]),
            "mention": e["mention"],
            "ects": str(e["sum_ects"]),
        }
        if with_paragraph_nom:
            cell_style = styles.ParagraphStyle({})
            cell_style.fontSize = sco_preferences.get_preference(
                "SCOLAR_FONT_SIZE", formsemestre_id
            )
            cell_style.fontName = sco_preferences.get_preference(
                "PV_FONTNAME", formsemestre_id
            )
            cell_style.leading = 1.0 * sco_preferences.get_preference(
                "SCOLAR_FONT_SIZE", formsemestre_id
            )  # vertical space
            i = e["identite"]
            l["nomprenom"] = [
                Paragraph(sco_pdf.SU(i["nomprenom"]), cell_style),
                Paragraph(sco_pdf.SU(i["code_nip"]), cell_style),
                Paragraph(
                    sco_pdf.SU(
                        "Né le %s" % i["date_naissance"]
                        + (" à %s" % i["lieu_naissance"] if i["lieu_naissance"] else "")
                        + (" (%s)" % i["dept_naissance"] if i["dept_naissance"] else "")
                    ),
                    cell_style,
                ),
            ]
        if anonymous:
            # Mode anonyme: affiche INE ou sinon NIP, ou id
            l["nomprenom"] = (
                e["identite"]["code_ine"]
                or e["identite"]["code_nip"]
                or e["identite"]["etudid"]
            )
        if with_parcours_decisions:
            for i in e[
                "parcours_decisions"
            ]:  # or equivalently: l.update(e['parcours_decisions'])
                l[i] = e["parcours_decisions"][i]

        if e["validation_parcours"]:
            l["devenir"] = "Diplôme obtenu"
        if dpv["has_prev"]:
            l["prev_decision"] = _descr_decision_sem_abbrev(
                None, e["prev_decision_sem"]
            )
        if e["validation_parcours"] or not only_diplome:
            lines.append(l)
    return lines, titles, columns_ids


def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
    """Page récapitulant les décisions de jury
    En classique: table spécifique avec les deux semestres pour le DUT
    En APC/BUT: renvoie vers table recap, en mode jury.
    """
    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
    is_apc = formsemestre.formation.is_apc()
    if fmt == "html" and is_apc:
        return redirect(
            url_for(
                "notes.formsemestre_recapcomplet",
                scodoc_dept=g.scodoc_dept,
                formsemestre_id=formsemestre_id,
                mode_jury=1,
            )
        )

    footer = html_sco_header.sco_footer()

    dpv = sco_pv_dict.dict_pvjury(formsemestre_id, with_prev=True)
    if not dpv:
        if fmt == "html":
            return (
                html_sco_header.sco_header()
                + "<h2>Aucune information disponible !</h2>"
                + footer
            )
        else:
            return None
    sem = dpv["formsemestre"]
    formsemestre_id = sem["formsemestre_id"]

    rows, titles, columns_ids = pvjury_table(dpv)
    if fmt != "html" and fmt != "pdf":
        columns_ids = ["etudid", "code_nip"] + columns_ids

    tab = GenTable(
        rows=rows,
        titles=titles,
        columns_ids=columns_ids,
        filename=scu.make_filename("decisions " + sem["titreannee"]),
        origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
        caption="Décisions jury pour " + sem["titreannee"],
        html_class="table_leftalign",
        html_sortable=True,
        preferences=sco_preferences.SemPreferences(formsemestre_id),
    )
    if fmt != "html":
        return tab.make_page(
            fmt=fmt,
            with_html_headers=False,
            publish=publish,
        )
    tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
    H = [
        html_sco_header.html_sem_header(
            "Décisions du jury pour le semestre",
            init_qtip=True,
            javascripts=["js/etud_info.js"],
        ),
        """<p>(dernière modif le %s)</p>""" % dpv["date"],
    ]

    H.append(
        '<ul><li><a class="stdlink" href="formsemestre_lettres_individuelles?formsemestre_id=%s">Courriers individuels (classeur pdf)</a></li>'
        % formsemestre_id
    )
    H.append(
        '<li><a class="stdlink" href="formsemestre_pvjury_pdf?formsemestre_id=%s">PV officiel (pdf)</a></li></ul>'
        % formsemestre_id
    )

    H.append(tab.html())

    # Count number of cases for each decision
    counts = collections.defaultdict(int)
    for row in rows:
        counts[row["decision"]] += 1
        # add codes for previous (for explanation, without count)
        if "prev_decision" in row and row["prev_decision"]:
            counts[row["prev_decision"]] += 0
    # Légende des codes
    codes = list(counts.keys())
    codes.sort()

    H.append("""<div class="codes"><h3>Explication des codes</h3>""")
    lines = []
    for code in codes:
        lines.append(
            {
                "code": code,
                "count": counts[code],
                "expl": codes_cursus.CODES_EXPL.get(code, ""),
            }
        )

    H.append(
        GenTable(
            rows=lines,
            titles={"code": "Code", "count": "Nombre", "expl": ""},
            columns_ids=("code", "count", "expl"),
            html_class="table_leftalign codes-jury",
            html_class_ignore_default=True,  # pas une DataTable
            html_sortable=True,
            html_with_td_classes=True,
            preferences=sco_preferences.SemPreferences(formsemestre_id),
        ).html()
    )
    H.append(
        """<style>
    div.codes {
        margin-bottom: 12px;
    }
    table.codes-jury th, table.codes-jury td {
        padding: 4px 8px 4px 8px;
    }
    table.codes-jury td {
        background-color: #CCCCCC;
    }
    table.codes-jury td.count {
        text-align: right;
    }
    </style>
    """
    )
    H.append("</div>")  # /codes
    return "\n".join(H) + footer


# ---------------------------------------------------------------------------


def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid=None):
    """Generation PV jury en PDF: saisie des paramètres
    Si etudid, PV pour un seul etudiant. Sinon, tout les inscrits au groupe indiqué.
    """
    group_ids = group_ids or []
    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
    # Mise à jour des groupes d'étapes:
    sco_groups.create_etapes_partition(formsemestre_id)
    groups_infos = None
    if etudid:
        # PV pour ce seul étudiant:
        etud = Identite.get_etud(etudid)
        etuddescr = f"""<a class="discretelink" href="{
            url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
        }">{etud.nomprenom}</a>"""
        etudids = [etudid]
    else:
        etuddescr = ""
        if not group_ids:
            # tous les inscrits du semestre
            group_ids = [sco_groups.get_default_group(formsemestre_id)]

        groups_infos = sco_groups_view.DisplayedGroupsInfos(
            group_ids, formsemestre_id=formsemestre_id
        )
        etudids = [m["etudid"] for m in groups_infos.members]

    H = [
        html_sco_header.html_sem_header(
            f"Édition du PV de jury {etuddescr}",
            javascripts=sco_groups_view.JAVASCRIPTS,
            cssstyles=sco_groups_view.CSSSTYLES,
            init_qtip=True,
        ),
        f"""<div class="help">Utiliser cette page pour éditer des versions provisoires des PV.
          <span class="fontred">Il est recommandé d'archiver les versions définitives:
           <a class="stdlink" href="{url_for(
            'notes.formsemestre_archive',
            scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
          }">voir cette page</a></span>
          </div>""",
    ]
    F = [
        """<p><em>Voir aussi si besoin les réglages sur la page "Paramétrage"
        (accessible à l'administrateur du département).</em>
        </p>""",
        html_sco_header.sco_footer(),
    ]
    descr = descrform_pvjury(formsemestre)
    if etudid:
        descr.append(("etudid", {"input_type": "hidden"}))

    if groups_infos:
        menu_choix_groupe = (
            """<div class="group_ids_sel_menu">Groupes d'étudiants à lister sur le PV: """
            + sco_groups_view.menu_groups_choice(groups_infos)
            + """</div>"""
        )
    else:
        menu_choix_groupe = ""  # un seul etudiant à editer
    tf = TrivialFormulator(
        request.base_url,
        scu.get_request_args(),
        descr,
        cancelbutton="Annuler",
        submitlabel="Générer document",
        name="tf",
        formid="group_selector",
        html_foot_markup=menu_choix_groupe,
    )
    if tf[0] == 0:
        return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
    elif tf[0] == -1:
        return flask.redirect(
            url_for(
                "notes.formsemestre_pvjury",
                scodoc_dept=g.scodoc_dept,
                formsemestre_id=formsemestre_id,
            )
        )
    else:
        # submit
        tf[2]["show_title"] = bool(tf[2]["show_title"])
        tf[2]["anonymous"] = bool(tf[2]["anonymous"])
        try:
            PDFLOCK.acquire()
            pdfdoc = sco_pv_pdf.pvjury_pdf(
                formsemestre,
                etudids,
                numero_arrete=tf[2]["numero_arrete"],
                code_vdi=tf[2]["code_vdi"],
                date_commission=tf[2]["date_commission"],
                date_jury=tf[2]["date_jury"],
                show_title=tf[2]["show_title"],
                pv_title_session=tf[2]["pv_title_session"],
                pv_title=tf[2]["pv_title"],
                with_paragraph_nom=tf[2]["with_paragraph_nom"],
                anonymous=tf[2]["anonymous"],
            )
        finally:
            PDFLOCK.release()
        date_iso = time.strftime("%Y-%m-%d")
        if groups_infos:
            groups_filename = "-" + groups_infos.groups_filename
        else:
            groups_filename = ""
        filename = f"""PV-{formsemestre.titre_num()}{groups_filename}-{date_iso}.pdf"""
        return scu.sendPDFFile(pdfdoc, filename)


def descrform_pvjury(formsemestre: FormSemestre):
    """Définition de formulaire pour PV jury PDF"""
    f_dict = formsemestre.formation.to_dict()
    return [
        (
            "date_commission",
            {
                "input_type": "text",
                "size": 50,
                "title": "Date de la commission",
                "explanation": "(format libre)",
            },
        ),
        (
            "date_jury",
            {
                "input_type": "text",
                "size": 50,
                "title": "Date du Jury",
                "explanation": "(si le jury a eu lieu)",
            },
        ),
        (
            "numero_arrete",
            {
                "input_type": "text",
                "size": 50,
                "title": "Numéro de l'arrêté du président",
                "explanation": "le président de l'Université prend chaque année un arrêté formant les jurys",
            },
        ),
        (
            "code_vdi",
            {
                "input_type": "text",
                "size": 15,
                "title": "VDI et Code",
                "explanation": "VDI et code du diplôme Apogée (format libre, n'est pas vérifié par ScoDoc)",
            },
        ),
        (
            "pv_title_session",
            {
                "input_type": "text",
                "size": 48,
                "title": "Nom de la session",
                "explanation": "utilisé dans le titre du PV",
                "default": "Session unique",
            },
        ),
        (
            "pv_title",
            {
                "input_type": "text",
                "size": 96,
                "title": "Titre du PV",
                "explanation": "par défaut, titre officiel de la formation",
                "default": f_dict["titre_officiel"],
            },
        ),
        (
            "show_title",
            {
                "input_type": "checkbox",
                "title": "Indiquer en plus le titre du semestre sur le PV",
                "explanation": f'(le titre est "{formsemestre.titre}")',
                "labels": [""],
                "allowed_values": ("1",),
            },
        ),
        (
            "with_paragraph_nom",
            {
                "input_type": "boolcheckbox",
                "title": "Avec date naissance et code",
                "explanation": "ajoute informations sous le nom",
                "default": True,
            },
        ),
        (
            "anonymous",
            {
                "input_type": "checkbox",
                "title": "PV anonyme",
                "explanation": "remplace nom par code étudiant (INE ou NIP)",
                "labels": [""],
                "allowed_values": ("1",),
            },
        ),
        ("formsemestre_id", {"input_type": "hidden"}),
    ]


def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
    "Lettres avis jury en PDF"
    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
    if not group_ids:
        # tous les inscrits du semestre
        group_ids = [sco_groups.get_default_group(formsemestre_id)]
    groups_infos = sco_groups_view.DisplayedGroupsInfos(
        group_ids, formsemestre_id=formsemestre_id
    )
    etudids = [m["etudid"] for m in groups_infos.members]

    H = [
        html_sco_header.html_sem_header(
            "Édition des lettres individuelles",
            javascripts=sco_groups_view.JAVASCRIPTS,
            cssstyles=sco_groups_view.CSSSTYLES,
            init_qtip=True,
        ),
        f"""<p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
          <span class="fontred">Il est recommandé d'archiver les versions définitives: <a
          href="{url_for(
                "notes.formsemestre_archive",
                scodoc_dept=g.scodoc_dept,
                formsemestre_id=formsemestre_id,
            )}"
          >voir cette page</a></span></p>
         """,
    ]
    F = html_sco_header.sco_footer()
    descr = descrform_lettres_individuelles()
    menu_choix_groupe = (
        """<div class="group_ids_sel_menu">Groupes d'étudiants à lister: """
        + sco_groups_view.menu_groups_choice(groups_infos)
        + """</div>"""
    )

    tf = TrivialFormulator(
        request.base_url,
        scu.get_request_args(),
        descr,
        cancelbutton="Annuler",
        submitlabel="Générer document",
        name="tf",
        formid="group_selector",
        html_foot_markup=menu_choix_groupe,
    )
    if tf[0] == 0:
        return "\n".join(H) + "\n" + tf[1] + F
    elif tf[0] == -1:
        return flask.redirect(
            url_for(
                "notes.formsemestre_pvjury",
                scodoc_dept=g.scodoc_dept,
                formsemestre_id=formsemestre_id,
            )
        )
    else:
        # submit
        sf = tf[2]["signature"]
        signature = sf.read()  # image of signature
        try:
            PDFLOCK.acquire()
            pdfdoc = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
                formsemestre_id,
                etudids=etudids,
                date_jury=tf[2]["date_jury"],
                date_commission=tf[2]["date_commission"],
                signature=signature,
            )
        finally:
            PDFLOCK.release()
        if not pdfdoc:
            flash("Aucun étudiant n'a de décision de jury !")
            return flask.redirect(
                url_for(
                    "notes.formsemestre_status",
                    scodoc_dept=g.scodoc_dept,
                    formsemestre_id=formsemestre_id,
                )
            )

        groups_filename = "-" + groups_infos.groups_filename
        filename = f"""lettres-{formsemestre.titre_num()}{groups_filename}-{time.strftime("%Y-%m-%d")}.pdf"""
        return scu.sendPDFFile(pdfdoc, filename)


def descrform_lettres_individuelles():
    return [
        (
            "date_commission",
            {
                "input_type": "text",
                "size": 50,
                "title": "Date de la commission",
                "explanation": "(format libre)",
            },
        ),
        (
            "date_jury",
            {
                "input_type": "text",
                "size": 50,
                "title": "Date du Jury",
                "explanation": "(si le jury a eu lieu)",
            },
        ),
        (
            "signature",
            {
                "input_type": "file",
                "size": 30,
                "explanation": "optionnel: image scannée de la signature",
            },
        ),
        ("formsemestre_id", {"input_type": "hidden"}),
    ]