# -*- 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
#
##############################################################################

"""Tableau de bord module
"""
import math
import time
import datetime

from flask import g, url_for
from flask_login import current_user

from app import db
from app.auth.models import User
from app.comp import res_sem
from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, FormSemestre, Module, ModuleImpl, UniteEns
import app.scodoc.sco_utils as scu
from app.scodoc.codes_cursus import UE_SPORT
from app.scodoc.sco_exceptions import ScoInvalidIdType
from app.scodoc.sco_cursus_dut import formsemestre_has_decisions
from app.scodoc.sco_permissions import Permission

from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_cal
from app.scodoc import sco_compute_moy
from app.scodoc import sco_evaluations
from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_permissions_check
from app.tables import list_etuds


# menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
    "Menu avec actions sur une evaluation"
    modimpl: ModuleImpl = evaluation.moduleimpl
    group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
    evaluation_id = evaluation.id
    can_edit_notes = modimpl.can_edit_notes(current_user, allow_ens=False)
    can_edit_notes_ens = modimpl.can_edit_notes(current_user)

    if can_edit_notes and nbnotes != 0:
        sup_label = "Supprimer évaluation impossible (il y a des notes)"
    else:
        sup_label = "Supprimer évaluation"

    menu_eval = [
        {
            "title": "Saisir notes",
            "endpoint": "notes.saisie_notes",
            "args": {
                "evaluation_id": evaluation_id,
            },
            "enabled": can_edit_notes_ens,
        },
        {
            "title": "Modifier évaluation",
            "endpoint": "notes.evaluation_edit",
            "args": {
                "evaluation_id": evaluation_id,
            },
            "enabled": can_edit_notes,
        },
        {
            "title": sup_label,
            "endpoint": "notes.evaluation_delete",
            "args": {
                "evaluation_id": evaluation_id,
            },
            "enabled": nbnotes == 0 and can_edit_notes,
        },
        {
            "title": "Supprimer toutes les notes",
            "endpoint": "notes.evaluation_suppress_alln",
            "args": {
                "evaluation_id": evaluation_id,
            },
            "enabled": can_edit_notes,
        },
        {
            "title": "Afficher les notes",
            "endpoint": "notes.evaluation_listenotes",
            "args": {
                "evaluation_id": evaluation_id,
            },
            "enabled": nbnotes > 0,
        },
        {
            "title": "Placement étudiants",
            "endpoint": "notes.placement_eval_selectetuds",
            "args": {
                "evaluation_id": evaluation_id,
            },
            "enabled": can_edit_notes_ens,
        },
        {
            "title": "Absences ce jour",
            "endpoint": "assiduites.etat_abs_date",
            "args": {
                "group_ids": group_id,
                "evaluation_id": evaluation.id,
                "date_debut": (
                    evaluation.date_debut.isoformat() if evaluation.date_debut else ""
                ),
                "date_fin": (
                    evaluation.date_fin.isoformat() if evaluation.date_fin else ""
                ),
            },
            "enabled": evaluation.date_debut is not None
            and evaluation.date_fin is not None,
        },
        {
            "title": "Vérifier notes vs absents",
            "endpoint": "notes.evaluation_check_absences_html",
            "args": {
                "evaluation_id": evaluation_id,
            },
            "enabled": nbnotes > 0 and evaluation.date_debut is not None,
        },
    ]

    return htmlutils.make_menu("actions", menu_eval, alone=True)


def _ue_coefs_html(coefs_lst) -> str:
    """ """
    max_coef = max([x[1] for x in coefs_lst]) if coefs_lst else 1.0
    H = """
    <div id="modimpl_coefs">
     <div>Coefficients vers les UE</div>
    """
    if coefs_lst:
        H += (
            f"""
     <div class="coefs_histo" style="--max:{max_coef}">
        """
            + "\n".join(
                [
                    f"""<div style="--coef:{coef};
                    {'background-color: ' + ue.color + ';' if ue.color else ''}
                    "><div>{coef}</div>{ue.acronyme}</div>"""
                    for ue, coef in coefs_lst
                    if coef > 0
                ]
            )
            + "</div>"
        )
    else:
        H += """<div class="missing_value">non définis</div>"""
    H += "</div>"
    return H


def moduleimpl_status(moduleimpl_id=None, partition_id=None):
    """Tableau de bord module (liste des evaluations etc)"""
    if not isinstance(moduleimpl_id, int):
        raise ScoInvalidIdType("moduleimpl_id must be an integer !")
    modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
    g.current_moduleimpl_id = modimpl.id
    module: Module = modimpl.module
    formsemestre_id = modimpl.formsemestre_id
    formsemestre: FormSemestre = modimpl.formsemestre
    mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
        moduleimpl_id=moduleimpl_id
    )

    nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
    # Evaluations, par numéros ou la plus RECENTE en tête
    evaluations = modimpl.evaluations.order_by(
        Evaluation.numero.desc(),
        Evaluation.date_debut.desc(),
    ).all()
    nb_evaluations = len(evaluations)
    max_poids = max(
        [
            max([p.poids for p in e.ue_poids] or [0]) * (e.coefficient or 0.0)
            for e in evaluations
        ]
        or [0]
    )
    #
    sem_locked = not formsemestre.etat
    can_edit_evals = (
        modimpl.can_edit_notes(current_user, allow_ens=formsemestre.ens_can_edit_eval)
        and not sem_locked
    )
    can_edit_notes = modimpl.can_edit_notes(current_user) and not sem_locked
    arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
    #
    module_resp = db.session.get(User, modimpl.responsable_id)
    mod_type_name = scu.MODULE_TYPE_NAMES[module.module_type]
    H = [
        html_sco_header.sco_header(
            page_title=f"{mod_type_name} {module.code} {module.titre}",
            javascripts=["js/etud_info.js"],
            init_qtip=True,
        ),
        f"""<h2 class="formsemestre">{mod_type_name}
        <tt>{module.code}</tt> {module.titre}
        {"dans l'UE " + modimpl.module.ue.acronyme
        if modimpl.module.module_type == scu.ModuleType.MALUS
        else ""
        }
        </h2>
        <div class="moduleimpl_tableaubord moduleimpl_type_{
            scu.ModuleType(module.module_type).name.lower()}">
        <table>
        <tr>
        <td class="fichetitre2">Responsable: </td><td class="redboldtext">
        {module_resp.get_nomcomplet()}
        <span class="blacktt">({module_resp.user_name})</span>
        """,
    ]
    if modimpl.can_change_responsable(current_user):
        H.append(
            f"""<a class="stdlink" href="{url_for("notes.edit_moduleimpl_resp",
                scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
                }" >modifier</a>"""
        )
    H.append("""</td><td>""")
    H.append(", ".join([u.get_nomprenom() for u in modimpl.enseignants]))
    H.append("""</td><td>""")
    if modimpl.can_change_ens(raise_exc=False):
        H.append(
            f"""<a class="stdlink" href="{
                url_for("notes.edit_enseignants_form",
                    scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id
                )
            }">modifier les enseignants</a>"""
        )

    H.append("""</td></tr>""")

    # 2ieme ligne: Semestre, Coef
    H.append("""<tr><td class="fichetitre2">""")
    if formsemestre.semestre_id >= 0:
        H.append("""Semestre: </td><td>%s""" % formsemestre.semestre_id)
    else:
        H.append("""</td><td>""")
    if sem_locked:
        H.append(scu.icontag("lock32_img", title="verrouillé"))
    H.append("""</td><td class="fichetitre2">""")
    if modimpl.module.is_apc():
        H.append(_ue_coefs_html(modimpl.module.ue_coefs_list()))
    else:
        H.append(
            f"""Coef. dans le semestre: {
            "non défini" if modimpl.module.coefficient is None else modimpl.module.coefficient
        }"""
        )
    H.append("""</td><td></td></tr>""")
    # 3ieme ligne: Formation
    H.append(
        f"""<tr>
            <td class="fichetitre2">Formation: </td><td>{formsemestre.formation.titre}</td>
        </tr>
        """
    )
    # Ligne: Inscrits
    H.append(
        f"""<tr><td class="fichetitre2">Inscrits: </td><td> {len(mod_inscrits)} étudiants"""
    )
    if current_user.has_permission(Permission.EtudInscrit):
        H.append(
            f"""<a class="stdlink" style="margin-left:2em;" href="moduleimpl_inscriptions_edit?moduleimpl_id={modimpl.id}">modifier</a>"""
        )
    H.append("</td></tr>")
    # Ligne: règle de calcul
    has_expression = sco_compute_moy.moduleimpl_has_expression(modimpl)
    if has_expression:
        H.append(
            """<tr>
                <td class="fichetitre2" colspan="4">Règle de calcul:
                <span class="warning">inutilisée dans cette version de ScoDoc</span>
                </td>
            </tr>
            """
        )
    else:
        H.append('<tr><td colspan="4">')
        H.append("</td></tr>")
    H.append(
        f"""<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink"
        href="{
            url_for("notes.view_module_abs", scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
        }">Absences dans ce module</a></span>"""
    )
    # Adapté à partir d'une suggestion de DS (Le Havre)
    # Liens saisies absences seulement si permission et date courante dans le semestre
    if current_user.has_permission(Permission.AbsChange) and formsemestre.est_courant():
        group_id = sco_groups.get_default_group(formsemestre_id)
        H.append(
            f"""
                <span class="moduleimpl_abs_link"><a class="stdlink" href="{
                    url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
                }?group_ids={group_id}&jour={
                    datetime.date.today().isoformat()
                }&formsemestre_id={formsemestre.id}
                &moduleimpl_id={moduleimpl_id}
                "
        >Saisie Absences journée</a></span>
        """
        )
        year, week, day = datetime.date.today().isocalendar()
        semaine: str = f"{year}-W{week}"
        H.append(
            f"""
                <span class="moduleimpl_abs_link"><a class="stdlink" href="{
                    url_for(
                    "assiduites.signal_assiduites_diff",
                    scodoc_dept=g.scodoc_dept,
                    group_ids=group_id,
                    semaine=semaine,
                    formsemestre_id=formsemestre.id,
                    moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
            )}"
        >Saisie Absences hebdo</a></span>
        """
        )

    H.append("</td></tr></table>")
    #
    if not modimpl.check_apc_conformity(nt):
        H.append(
            """<div class="warning conformite">Les poids des évaluations de ce module ne sont
            pas encore conformes au PN.
            Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
            </div>"""
        )

    if formsemestre_has_decisions(formsemestre_id):
        H.append(
            """<div class="formsemestre-warning-box">
            <div class="warning">Décisions de jury saisies: seul le ou la responsable du
            semestre peut saisir des notes (elle devra modifier les décisions de jury).
            </div>
            </div>"""
        )
    #
    H.append(
        f"""<p><form name="f">
<span style="font-size:120%%; font-weight: bold;">{nb_evaluations} évaluations :</span>
<span style="padding-left: 30px;">
<input type="hidden" name="moduleimpl_id" value="{moduleimpl_id}"/>"""
    )
    #
    # Liste les noms de partitions
    partitions = sco_groups.get_partitions_list(formsemestre.id)
    H.append(
        """Afficher les groupes
        de&nbsp;<select name="partition_id" onchange="document.f.submit();">"""
    )
    been_selected = False
    for partition in partitions:
        if not partition_id and not been_selected:
            selected = "selected"
            been_selected = True
        if partition["partition_id"] == partition_id:
            selected = "selected"
        else:
            selected = ""
        name = partition["partition_name"]
        if name is None:
            name = "Tous"
        H.append(
            f"""<option value="{partition['partition_id']}" {selected}>{name}</option>"""
        )
    H.append(
        f"""</select>
&nbsp;&nbsp;&nbsp;&nbsp;
<a class="stdlink" href="{
    url_for("notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">Voir toutes les notes</a>
</span>
</form>
</p>
"""
    )

    # -------- Tableau des evaluations
    top_table_links = ""
    if can_edit_evals:
        top_table_links = f"""<a class="stdlink" href="{
                url_for("notes.evaluation_create", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
                }">Créer nouvelle évaluation</a>
            """
        if nb_evaluations > 0:
            top_table_links += f"""
            <a class="stdlink" style="margin-left:2em;" href="{
                url_for("notes.moduleimpl_evaluation_renumber",
                scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
            }">Trier par date</a>
            """
    if nb_evaluations > 0:
        H.append(
            '<div class="moduleimpl_evaluations_top_links">'
            + top_table_links
            + "</div>"
        )
    H.append("""<table class="moduleimpl_evaluations">""")
    eval_index = nb_evaluations - 1
    first_eval = True
    for evaluation in evaluations:
        H.append(
            _ligne_evaluation(
                modimpl,
                evaluation,
                first_eval=first_eval,
                partition_id=partition_id,
                arrow_down=arrow_down,
                arrow_none=arrow_none,
                arrow_up=arrow_up,
                can_edit_evals=can_edit_evals,
                can_edit_notes=can_edit_notes,
                eval_index=eval_index,
                nb_evals=nb_evaluations,
                is_apc=nt.is_apc,
                max_poids=max_poids,
            )
        )
        eval_index -= 1
        first_eval = False
    #
    H.append("""<tr><td colspan="8">""")
    if sem_locked:
        H.append(f"""{scu.icontag("lock32_img")} semestre verrouillé""")
    elif can_edit_evals:
        H.append(top_table_links)

    H.append(
        f"""</td></tr>
</table>

<div class="list_etuds_attente">
 {_html_modimpl_etuds_attente(nt, modimpl)}
</div>

</div>

<!-- LEGENDE -->
<hr>
<h4>Légende</h4>
<ul>
<li>{scu.icontag("edit_img")} : modifie description de l'évaluation
    (date, heure, coefficient, ...)
</li>
<li>{scu.icontag("notes_img")} : saisie des notes</li>
<li>{scu.icontag("delete_img")} : indique qu'il n'y a aucune note
        entrée (cliquer pour supprimer cette évaluation)
</li>
<li>{scu.icontag("status_orange_img")} : indique qu'il manque
        quelques notes dans cette évaluation
</li>
<li>{scu.icontag("status_green_img")} : toutes les notes sont
        entrées (cliquer pour les afficher)
</li>
<li>{scu.icontag("status_visible_img")} : indique que cette évaluation
        sera mentionnée dans les bulletins au format "intermédiaire"
</li>
</ul>

<p>Rappel : seules les notes des évaluations complètement saisies
    (affichées en vert) apparaissent dans les bulletins.
</p>
    """
    )
    H.append(html_sco_header.sco_footer())
    return "".join(H)


def _ligne_evaluation(
    modimpl: ModuleImpl,
    evaluation: Evaluation,
    first_eval: bool = True,
    partition_id: int = None,
    arrow_down=None,
    arrow_none=None,
    arrow_up=None,
    can_edit_evals: bool = False,
    can_edit_notes: bool = False,
    eval_index: int = 0,
    nb_evals: int = 0,
    is_apc: bool = False,
    max_poids: float = 0.0,
) -> str:
    """Ligne <tr> décrivant une évaluation dans le tableau de bord moduleimpl."""
    H = []
    # evaluation: Evaluation = db.session.get(Evaluation, eval_dict["evaluation_id"])
    etat = sco_evaluations.do_evaluation_etat(
        evaluation.id,
        partition_id=partition_id,
        select_first_partition=True,
    )
    if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
        tr_class = "mievr mievr_rattr"
    elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
        tr_class = "mievr mievr_session2"
    elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
        tr_class = "mievr mievr_bonus"
    else:
        tr_class = "mievr"

    if not evaluation.visibulletin:
        tr_class += " non_visible_inter"
    tr_class_1 = "mievr"
    if evaluation.is_blocked():
        tr_class += " evaluation_blocked"
        tr_class_1 += " evaluation_blocked"

    if not first_eval:
        H.append("""<tr><td colspan="8">&nbsp;</td></tr>""")
        tr_class_1 += " mievr_spaced"
    H.append(
        f"""<tr class="{tr_class_1} mievr_tit"><td class="mievr_tit" colspan="8">"""
    )
    coef = evaluation.coefficient
    if is_apc:
        if not evaluation.get_ue_poids_dict():
            # Au cas où les poids par défaut n'existent pas encore:
            if evaluation.set_default_poids():
                db.session.commit()
        coef *= sum(evaluation.get_ue_poids_dict().values())

    if modimpl.module.ue.type != UE_SPORT:
        # Avertissement si coefs x poids nuls
        if coef < scu.NOTES_PRECISION:
            if modimpl.module.module_type == scu.ModuleType.MALUS:
                H.append("""<span class="eval_warning_coef">malus</span>""")
            else:
                H.append("""<span class="eval_warning_coef">coef. nul !</span>""")
        elif is_apc:
            # visualisation des poids (Hinton map)
            H.append(_evaluation_poids_html(evaluation, max_poids))
    H.append("""<div class="evaluation_titre">""")
    if evaluation.date_debut:
        H.append(evaluation.descr_date())
    else:
        H.append(
            f"""<a href="{url_for("notes.evaluation_edit",
                    scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
                }" class="mievr_evalnodate">Évaluation sans date</a>"""
        )
    H.append(f"&nbsp;&nbsp;&nbsp; <em>{evaluation.description}</em>")
    if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
        H.append(
            """<span class="mievr_rattr" title="remplace si meilleure note">rattrapage</span>"""
        )
    elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
        H.append(
            """<span class="mievr_session2" title="remplace autres notes">session 2</span>"""
        )
    elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
        H.append(
            """<span class="mievr_bonus" title="s'ajoute aux moyennes de ce module">bonus</span>"""
        )
    #
    if etat["last_modif"]:
        H.append(
            f"""<span class="mievr_lastmodif">(dernière modif le {
                etat["last_modif"].strftime("%d/%m/%Y à %Hh%M")})</span>"""
        )
    #
    H.append(
        f"""
        </td>
        <td class="evaluation_order">
        <span class="evalindex" title="Indice dans les vecteurs (formules)">{
            eval_index:2}</span>
        <span class="eval_arrows_chld">
        """
    )
    # Fleches:
    if eval_index != (nb_evals - 1) and can_edit_evals:
        H.append(
            f"""<a href="{url_for("notes.moduleimpl_evaluation_move",
            scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id, after=0)
            }" class="aud">{arrow_up}</a>"""
        )
    else:
        H.append(arrow_none)
    if (eval_index > 0) and can_edit_evals:
        H.append(
            f"""<a href="{url_for("notes.moduleimpl_evaluation_move",
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id, after=1)
            }" class="aud">{arrow_down}</a>"""
        )
    else:
        H.append(arrow_none)

    if evaluation.is_blocked():
        etat_txt = f"""évaluation bloquée {
            "jusqu'au " + evaluation.blocked_until.strftime("%d/%m/%Y")
            if evaluation.blocked_until < Evaluation.BLOCKED_FOREVER
            else "" }
        """
        etat_descr = """prise en compte bloquée"""
    elif etat["evalcomplete"]:
        etat_txt = f"""Moyenne (prise en compte{
            ""
            if evaluation.visibulletin
            else ", cachée en intermédiaire"})
        """
        etat_descr = f"""notes utilisées dans les moyennes{
            ", évaluation cachée sur les bulletins en version intermédiaire et sur la passerelle"
            }"""
    elif etat["evalattente"] and not evaluation.publish_incomplete:
        etat_txt = "Moyenne (prise en compte, mais <b>notes en attente</b>)"
        etat_descr = "il y a des notes en attente"
    elif evaluation.publish_incomplete:
        etat_txt = """(prise en compte <b>immédiate</b>)"""
        etat_descr = (
            "il manque des notes, mais la prise en compte immédiate a été demandée"
        )
    elif etat["nb_notes"] != 0:
        etat_txt = "Moyenne (<b>non</b> prise en compte)"
        etat_descr = "il manque des notes"
    else:
        etat_txt = ""
    if etat_txt:
        if can_edit_evals:
            etat_txt = f"""<a href="{ url_for("notes.evaluation_edit",
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
                }" title="{etat_descr}">{etat_txt}</a>"""

    H.append(
        f"""</span></span></td>
        </tr>
        <tr class="{tr_class} mievr_in">
            <th class="moduleimpl_evaluations" colspan="2">&nbsp;</th>
            <th class="moduleimpl_evaluations">Durée</th>
            <th class="moduleimpl_evaluations">Coef.</th>
            <th class="moduleimpl_evaluations">Notes</th>
            <th class="moduleimpl_evaluations">Abs</th>
            <th class="moduleimpl_evaluations">N</th>
            <th class="moduleimpl_evaluations moduleimpl_evaluation_moy" colspan="2"><span>{etat_txt}</span></th>
        </tr>
        <tr class="{tr_class} mievr_in">
            <td class="mievr">"""
    )
    if can_edit_evals:
        H.append(
            f"""<a class="smallbutton" href="{url_for('notes.evaluation_edit',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
            }">{scu.icontag("edit_img", alt="modifier", title="Modifier informations")}</a>"""
        )
    if can_edit_notes:
        H.append(
            f"""<a class="smallbutton" href="{url_for('notes.saisie_notes',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
            }">{scu.icontag("notes_img", alt="saisie notes", title="Saisie des notes")}</a>"""
        )
    if etat["nb_notes"] == 0:
        if can_edit_evals:
            H.append(
                f"""<a class="smallbutton" href="{url_for('notes.evaluation_delete',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
                }">"""
            )
        H.append(scu.icontag("delete_img", alt="supprimer", title="Supprimer"))
        if can_edit_evals:
            H.append("""</a>""")
    elif etat["evalcomplete"]:
        H.append(
            f"""<a class="smallbutton" href="{url_for('notes.evaluation_listenotes',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
            }">{scu.icontag("status_green_img", title="ok")}</a>"""
        )
    else:
        if etat["evalattente"]:
            H.append(
                f"""<a class="smallbutton" href="{url_for('notes.evaluation_listenotes',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
                }">{scu.icontag(
                        "status_greenorange_img",
                        file_format="gif",
                        title="notes en attente",
                    )}</a>"""
            )
        else:
            H.append(
                f"""<a class="smallbutton" href="{url_for('notes.evaluation_listenotes',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
                }">{scu.icontag("status_orange_img", title="il manque des notes")}</a>"""
            )
    #
    if evaluation.visibulletin:
        H.append(
            scu.icontag(
                "status_visible_img", title="visible dans bulletins intermédiaires"
            )
        )
    else:
        H.append("&nbsp;")
    H.append('</td><td class="mievr_menu">')
    if can_edit_notes:
        H.append(
            moduleimpl_evaluation_menu(
                evaluation,
                nbnotes=etat["nb_notes"],
            )
        )
    #
    H.append(
        f"""</td>
        <td class="mievr_dur">{evaluation.descr_duree()}</td>
        <td class="rightcell mievr_coef">{evaluation.coefficient:g}</td>
        """
    )
    H.append(
        f"""
        <td class="rightcell mievr_nbnotes">{etat["nb_notes"]} / {etat["nb_inscrits"]}</td>
        <td class="rightcell mievr_coef">{etat["nb_abs"]}</td>
        <td class="rightcell mievr_coef">{etat["nb_neutre"]}</td>
        <td class="rightcell" colspan="2">"""
        % etat
    )
    if etat["moy"]:
        H.append(
            f"""<b>{etat["moy"]} / 20</b>
            &nbsp; (<a class="stdlink" href="{
                url_for('notes.evaluation_listenotes',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
            }">afficher</a>)"""
        )
    else:
        H.append(
            f"""<a class="redlink" href="{url_for('notes.saisie_notes',
                scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
                }">saisir notes</a>
                """
        )
    H.append("""</td></tr>""")
    #
    if etat["nb_notes"] == 0:
        H.append(f"""<tr class="{tr_class}"><td></td>""")
        if modimpl.module.is_apc():
            H.append(
                f"""<td colspan="8" class="eval_poids">{
                    evaluation.get_ue_poids_str()}</td>"""
            )
        else:
            H.append('<td colspan="8"></td>')
        H.append("""</tr>""")
    else:  # il y a deja des notes saisies
        gr_moyennes = etat["gr_moyennes"]
        first_group = True
        for gr_moyenne in gr_moyennes:
            H.append(f"""<tr class="{tr_class}"><td>&nbsp;</td>""")
            if first_group and modimpl.module.is_apc():
                H.append(
                    f"""<td class="eval_poids" colspan="4">{
                        evaluation.get_ue_poids_str()}</td>"""
                )
            else:
                H.append("""<td colspan="4"></td>""")
            first_group = False
            if gr_moyenne["group_name"] is None:
                name = "Tous"  # tous
            else:
                name = f"""Groupe {gr_moyenne["group_name"]}"""
            H.append(
                f"""<td colspan="2" class="mievr_grtit">{name} &nbsp;</td>
                <td colspan="2">"""
            )
            if gr_moyenne["gr_nb_notes"] > 0:
                H.append(
                    f"""{gr_moyenne["gr_moy"]}&nbsp; (<a href="{
                        url_for('notes.evaluation_listenotes',
                        scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id,
                        tf_submitted=1, **{'group_ids:list': gr_moyenne["group_id"]})
                        }">{gr_moyenne["gr_nb_notes"]} notes</a>"""
                )
                if gr_moyenne["gr_nb_att"] > 0:
                    H.append(
                        f""", <span class="redboldtext">{
                                gr_moyenne["gr_nb_att"]} en attente</span>"""
                    )
                H.append(""")""")
                if gr_moyenne["group_id"] in etat["gr_incomplets"]:
                    H.append("""[<font color="red">""")
                    if can_edit_notes:
                        H.append(
                            f"""<a class="redlink" href="{url_for('notes.saisie_notes',
                            scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id,
                            **{'group_ids:list': gr_moyenne["group_id"]})
                            }">incomplet</a></font>]"""
                        )
                    else:
                        H.append("""incomplet</font>]""")
            else:
                H.append("""<span class="redboldtext">&nbsp; """)
                if can_edit_notes:
                    H.append(
                        f"""<a class="redlink" href="{url_for('notes.saisie_notes',
                        scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id,
                        **{'group_ids:list': gr_moyenne["group_id"]})
                        }">"""
                    )
                H.append("pas de notes")
                if can_edit_notes:
                    H.append("""</a>""")
                H.append("</span>")
            H.append("""</td></tr>""")
    return "\n".join(H)


def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> str:
    """graphe html (Hinton map) montrant les poids x coef de l'évaluation"""
    ue_poids = evaluation.get_ue_poids_dict(sort=True)  # { ue_id : poids }
    if not ue_poids:
        return ""
    if max_poids < scu.NOTES_PRECISION:
        return ""
    H = (
        """<div class="evaluation_poids">"""
        + "\n".join(
            [
                f"""<div title="poids vers {ue.acronyme}: {poids:g}">
                    <div style="--size:{math.sqrt(poids*(evaluation.coefficient)/max_poids*144)}px;
                        {'background-color: ' + ue.color + ';' if ue.color else ''}
                    "></div>
                </div>"""
                for ue, poids in (
                    (db.session.get(UniteEns, ue_id), poids)
                    for ue_id, poids in ue_poids.items()
                )
            ]
        )
        + "</div>"
    )
    return H


def _html_modimpl_etuds_attente(res: ResultatsSemestre, modimpl: ModuleImpl) -> str:
    """Affiche la liste des étudiants ayant au moins une note en attente dans ce modimpl"""
    m_res = res.modimpls_results.get(modimpl.id)
    if m_res:
        if not m_res.etudids_attente:
            return "<div><em>Aucun étudiant n'a de notes en attente.</em></div>"
        elif len(m_res.etudids_attente) < 10:
            return f"""
            <h4>Étudiants avec une note en attente&nbsp;:</h4>
            {list_etuds.html_table_etuds(m_res.etudids_attente)}
            """
        else:
            return f"""<div class="warning"><em>{
                len(m_res.etudids_attente)
                } étudiants ont des notes en attente.</em></div>"""

    return ""