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

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

"""Ajout/Modification/Supression matieres
(portage from DTML)
"""
import flask
from flask import g, url_for, request

from app import db, log
from app.models import Formation, Matiere, UniteEns, ScolarNews

import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu

from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc.sco_exceptions import (
    ScoValueError,
    ScoLockedFormError,
    ScoNonEmptyFormationObject,
)
from app.scodoc import html_sco_header

_matiereEditor = ndb.EditableTable(
    "notes_matieres",
    "matiere_id",
    ("matiere_id", "ue_id", "numero", "titre"),
    sortkey="numero",
    output_formators={"numero": ndb.int_null_is_zero},
)


def matiere_list(*args, **kw):
    "list matieres"
    cnx = ndb.GetDBConnexion()
    return _matiereEditor.list(cnx, *args, **kw)


def do_matiere_edit(*args, **kw):
    "edit a matiere"
    from app.scodoc import sco_edit_ue
    from app.scodoc import sco_edit_formation

    cnx = ndb.GetDBConnexion()
    # check
    mat = matiere_list({"matiere_id": args[0]["matiere_id"]})[0]
    if matiere_is_locked(mat["matiere_id"]):
        raise ScoLockedFormError()
    # edit
    _matiereEditor.edit(cnx, *args, **kw)
    formation_id = sco_edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]["formation_id"]
    db.session.get(Formation, formation_id).invalidate_cached_sems()


def do_matiere_create(args):
    "create a matiere"
    from app.scodoc import sco_edit_ue
    from app.models import ScolarNews

    cnx = ndb.GetDBConnexion()
    # check
    ue = sco_edit_ue.ue_list({"ue_id": args["ue_id"]})[0]
    # create matiere
    r = _matiereEditor.create(cnx, args)

    # news
    formation = db.session.get(Formation, ue["formation_id"])
    ScolarNews.add(
        typ=ScolarNews.NEWS_FORM,
        obj=ue["formation_id"],
        text=f"Modification de la formation {formation.acronyme}",
    )
    formation.invalidate_cached_sems()
    return r


def matiere_create(ue_id=None):
    """Creation d'une matiere"""
    ue: UniteEns = UniteEns.query.get_or_404(ue_id)
    default_numero = max([mat.numero for mat in ue.matieres] or [9]) + 1
    H = [
        html_sco_header.sco_header(page_title="Création d'une matière"),
        f"""<h2>Création d'une matière dans l'UE {ue.titre or ''} ({ue.acronyme})</h2>
        <p class="help">Les matières sont des groupes de modules dans une UE
d'une formation donnée. Les matières servent surtout pour la
présentation (bulletins, etc) mais <em>n'ont pas de rôle dans le calcul
des notes.</em>
</p>

<p class="help">Si votre formation n'utilise pas la notion de
"matières", créez une matière par UE, et donnez lui le même nom que l'UE
(en effet, tout module doit appartenir à une matière).
</p>

<p class="help">Comme les UE, les matières n'ont pas de coefficient
associé.
</p>""",
    ]
    tf = TrivialFormulator(
        request.base_url,
        scu.get_request_args(),
        (
            ("ue_id", {"input_type": "hidden", "default": ue_id}),
            (
                "titre",
                {
                    "size": 30,
                    "explanation": "nom de la matière.",
                },
            ),
            (
                "numero",
                {
                    "size": 2,
                    "explanation": "numéro (1,2,3,4...) pour affichage",
                    "type": "int",
                    "default": default_numero,
                    "allow_null": False,
                },
            ),
        ),
        submitlabel="Créer cette matière",
    )

    dest_url = url_for(
        "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=ue.formation_id
    )

    if tf[0] == 0:
        return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
    elif tf[0] == -1:
        return flask.redirect(dest_url)
    else:
        # check unicity
        mats = matiere_list(args={"ue_id": ue_id, "titre": tf[2]["titre"]})
        if mats:
            return (
                "\n".join(H)
                + tf_error_message("Titre de matière déjà existant dans cette UE")
                + tf[1]
                + html_sco_header.sco_footer()
            )
        _ = do_matiere_create(tf[2])
        return flask.redirect(dest_url)


def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]:
    "True si la matiere n'est pas utilisée dans des formsemestre"
    locked = matiere_is_locked(matiere.id)
    if locked:
        return False
    if any(m.modimpls.all() for m in matiere.modules):
        return False
    return True


def do_matiere_delete(oid):
    "delete matiere and attached modules"
    from app.scodoc import sco_edit_ue
    from app.scodoc import sco_edit_module

    cnx = ndb.GetDBConnexion()
    # check
    matiere = Matiere.query.get_or_404(oid)
    mat = matiere_list({"matiere_id": oid})[0]  # compat sco7
    ue = sco_edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]
    if not can_delete_matiere(matiere):
        # il y a au moins un modimpl dans un module de cette matière
        raise ScoNonEmptyFormationObject("Matière", matiere.titre)

    log("do_matiere_delete: matiere_id=%s" % matiere.id)
    # delete all modules in this matiere
    mods = sco_edit_module.module_list({"matiere_id": matiere.id})
    for mod in mods:
        sco_edit_module.do_module_delete(mod["module_id"])
    _matiereEditor.delete(cnx, oid)

    # news
    formation = db.session.get(Formation, ue["formation_id"])
    ScolarNews.add(
        typ=ScolarNews.NEWS_FORM,
        obj=ue["formation_id"],
        text=f"Modification de la formation {formation.acronyme}",
    )
    formation.invalidate_cached_sems()


def matiere_delete(matiere_id=None):
    """Delete matière"""
    from app.scodoc import sco_edit_ue

    matiere = Matiere.query.get_or_404(matiere_id)
    if not can_delete_matiere(matiere):
        # il y a au moins un modimpl dans un module de cette matière
        raise ScoNonEmptyFormationObject(
            "Matière",
            matiere.titre,
            dest_url=url_for(
                "notes.ue_table",
                formation_id=matiere.ue.formation_id,
                semestre_idx=matiere.ue.semestre_idx,
                scodoc_dept=g.scodoc_dept,
            ),
        )

    mat = matiere_list(args={"matiere_id": matiere_id})[0]
    UE = sco_edit_ue.ue_list(args={"ue_id": mat["ue_id"]})[0]
    H = [
        html_sco_header.sco_header(page_title="Suppression d'une matière"),
        "<h2>Suppression de la matière %(titre)s" % mat,
        " dans l'UE (%(acronyme)s))</h2>" % UE,
    ]
    dest_url = url_for(
        "notes.ue_table",
        scodoc_dept=g.scodoc_dept,
        formation_id=str(UE["formation_id"]),
    )
    tf = TrivialFormulator(
        request.base_url,
        scu.get_request_args(),
        (("matiere_id", {"input_type": "hidden"}),),
        initvalues=mat,
        submitlabel="Confirmer la suppression",
        cancelbutton="Annuler",
    )
    if tf[0] == 0:
        return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
    elif tf[0] == -1:
        return flask.redirect(dest_url)
    else:
        do_matiere_delete(matiere_id)
        return flask.redirect(dest_url)


def matiere_edit(matiere_id=None):
    """Edit matiere"""
    from app.scodoc import sco_edit_ue

    F = matiere_list(args={"matiere_id": matiere_id})
    if not F:
        raise ScoValueError("Matière inexistante !")
    F = F[0]
    ues = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]})
    if not ues:
        raise ScoValueError("UE inexistante !")
    ue = ues[0]
    formation: Formation = Formation.query.get_or_404(ue["formation_id"])
    ues = sco_edit_ue.ue_list(args={"formation_id": ue["formation_id"]})
    ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues]
    ue_ids = [u["ue_id"] for u in ues]
    H = [
        html_sco_header.sco_header(page_title="Modification d'une matière"),
        """<h2>Modification de la matière %(titre)s""" % F,
        f"""(formation ({formation.acronyme}, version {formation.version})</h2>""",
    ]
    help = """<p class="help">Les matières sont des groupes de modules dans une UE
d'une formation donnée. Les matières servent surtout pour la
présentation (bulletins, etc) mais <em>n'ont pas de rôle dans le calcul
des notes.</em>
</p>

<p class="help">Si votre formation n'utilise pas la notion de
"matières", créez une matière par UE, et donnez lui le même nom que l'UE
(en effet, tout module doit appartenir à une matière).
</p>

<p class="help">Comme les UE, les matières n'ont pas de coefficient
associé.
</p>"""
    tf = TrivialFormulator(
        request.base_url,
        scu.get_request_args(),
        (
            ("matiere_id", {"input_type": "hidden"}),
            (
                "ue_id",
                {
                    "input_type": "menu",
                    "allowed_values": ue_ids,
                    "labels": ue_names,
                    "title": "UE",
                },
            ),
            ("titre", {"size": 30, "explanation": "nom de cette matière"}),
            (
                "numero",
                {
                    "size": 2,
                    "explanation": "numéro (1,2,3,4...) pour affichage",
                    "type": "int",
                },
            ),
        ),
        initvalues=F,
        submitlabel="Modifier les valeurs",
    )

    dest_url = url_for(
        "notes.ue_table",
        scodoc_dept=g.scodoc_dept,
        formation_id=str(ue["formation_id"]),
    )
    if tf[0] == 0:
        return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer()
    elif tf[0] == -1:
        return flask.redirect(dest_url)
    else:
        # check unicity
        mats = matiere_list(args={"ue_id": tf[2]["ue_id"], "titre": tf[2]["titre"]})
        if len(mats) > 1 or (len(mats) == 1 and mats[0]["matiere_id"] != matiere_id):
            return (
                "\n".join(H)
                + tf_error_message("Titre de matière déjà existant dans cette UE")
                + tf[1]
                + html_sco_header.sco_footer()
            )

        # changement d'UE ?
        if tf[2]["ue_id"] != F["ue_id"]:
            log("attaching mat %s to new UE %s" % (matiere_id, tf[2]["ue_id"]))
            ndb.SimpleQuery(
                "UPDATE notes_modules SET ue_id = %(ue_id)s WHERE matiere_id=%(matiere_id)s",
                {"ue_id": tf[2]["ue_id"], "matiere_id": matiere_id},
            )

        do_matiere_edit(tf[2])

        return flask.redirect(dest_url)


def matiere_is_locked(matiere_id):
    """True if matiere should not be modified
    (contains modules used in a locked formsemestre)
    """
    r = ndb.SimpleDictFetch(
        """SELECT ma.id
        FROM notes_matieres ma, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi
        WHERE ma.id = mod.matiere_id
        AND mi.module_id = mod.id
        AND mi.formsemestre_id = sem.id
        AND ma.id = %(matiere_id)s
        AND sem.etat = false
        """,
        {"matiere_id": matiere_id},
    )
    return len(r) > 0