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

##############################################################################
#  Module "Avis de poursuite d'étude"
#  conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################

"""
Created on Fri Sep  9 09:15:05 2016

@author: barasc
"""
import numpy as np

import app.pe.pe_etudiant
from app import db, log, ScoValueError
from app.comp import res_sem, moy_ue, moy_sem
from app.comp.moy_sem import comp_ranks_series
from app.comp.res_compat import NotesTableCompat
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre
from app.models.moduleimpls import ModuleImpl

from app.scodoc import sco_tag_module
from app.scodoc.codes_cursus import UE_SPORT
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_tabletags import TableTag, TAGS_RESERVES, MoyenneTag
import pandas as pd


class SemestreTag(TableTag):
    def __init__(self, formsemestre_id: int):
        """
        Un SemestreTag représente les résultats des étudiants à un semestre, en donnant
        accès aux moyennes par tag.
        Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT.

        Args:
            nom: Nom à donner au SemestreTag
            formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base
        """
        TableTag.__init__(self)

        # Le semestre
        self.formsemestre_id = formsemestre_id
        self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)

        # Le nom du semestre taggué
        self.nom = self.get_repr()

        # Les résultats du semestre
        self.nt = load_formsemestre_results(self.formsemestre)

        # Les étudiants
        self.etuds = self.nt.etuds
        self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}

        # Les notes, les modules implémentés triés, les étudiants, les coeffs,
        # récupérés notamment de py:mod:`res_but`
        self.sem_cube = self.nt.sem_cube
        self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted
        self.modimpl_coefs_df = self.nt.modimpl_coefs_df

        # Les inscriptions au module et les dispenses d'UE
        self.modimpl_inscr_df = self.nt.modimpl_inscr_df
        self.ues = self.nt.ues
        self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours()
        self.dispense_ues = self.nt.dispense_ues

        # Les tags :
        ## Saisis par l'utilisateur
        tags_personnalises = get_synthese_tags_personnalises_semestre(
            self.nt.formsemestre
        )
        noms_tags_perso = list(set(tags_personnalises.keys()))

        ## Déduit des compétences
        dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre)
        noms_tags_comp = list(set(dict_ues_competences.values()))
        noms_tags_auto = ["but"] + noms_tags_comp
        self.tags = (
            noms_tags_perso + noms_tags_auto
        )
        """Tags du semestre taggué"""

        ## Vérifie l'unicité des tags
        if len(set(self.tags)) != len(self.tags):
            intersection = list(set(noms_tags_perso) & set(noms_tags_auto))
            liste_intersection = "\n".join([f"<li><code>{tag}</code></li>" for tag in intersection])
            message = f"""Erreur dans le module PE : Un des tags saisis dans votre programme de formation
            fait parti des tags réservés. En particulier,
            votre semestre <em>{self.formsemestre.titre_annee()}</em> 
            contient le(s) tag(s) réservé(s) suivant  : 
            <ul>
            {liste_intersection}
            </ul>
            Modifiez votre programme de formation pour le(s) supprimer. Il(s) sera(ont) automatiquement à vos documents de poursuites d'études.
            """
            raise ScoValueError(
                message
            )

        # Calcul des moyennes & les classements de chaque étudiant à chaque tag
        self.moyennes_tags = {}

        for tag in tags_personnalises:
            # pe_affichage.pe_print(f"  -> Traitement du tag {tag}")
            moy_gen_tag = self.compute_moyenne_tag(tag, tags_personnalises)
            self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)

        # Ajoute les moyennes générales de BUT pour le semestre considéré
        moy_gen_but = self.nt.etud_moy_gen
        self.moyennes_tags["but"] = MoyenneTag("but", moy_gen_but)

        # Ajoute les moyennes par compétence
        for ue_id, competence in dict_ues_competences.items():
            if competence not in self.moyennes_tags:
                moy_ue = self.nt.etud_moy_ue[ue_id]
                self.moyennes_tags[competence] = MoyenneTag(competence, moy_ue)

        self.tags_sorted = self.get_all_tags()
        """Tags (personnalisés+compétences) par ordre alphabétique"""

        # Synthétise l'ensemble des moyennes dans un dataframe

        self.notes = self.df_notes()
        """Dataframe synthétique des notes par tag"""

        pe_affichage.pe_print(
            f"   => Traitement des tags {', '.join(self.tags_sorted)}"
        )

    def get_repr(self):
        """Nom affiché pour le semestre taggué"""
        return app.pe.pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)

    def compute_moyenne_tag(self, tag: str, tags_infos: dict) -> pd.Series:
        """Calcule la moyenne des étudiants pour le tag indiqué,
        pour ce SemestreTag, en ayant connaissance des informations sur
        les tags (dictionnaire donnant les coeff de repondération)

        Sont pris en compte les modules implémentés associés au tag,
        avec leur éventuel coefficient de **repondération**, en utilisant les notes
        chargées pour ce SemestreTag.

        Force ou non le calcul de la moyenne lorsque des notes sont manquantes.

        Returns:
            La série des moyennes
        """

        """Adaptation du mask de calcul des moyennes au tag visé"""
        modimpls_mask = [
            modimpl.module.ue.type != UE_SPORT
            for modimpl in self.formsemestre.modimpls_sorted
        ]

        """Désactive tous les modules qui ne sont pas pris en compte pour ce tag"""
        for i, modimpl in enumerate(self.formsemestre.modimpls_sorted):
            if modimpl.moduleimpl_id not in tags_infos[tag]:
                modimpls_mask[i] = False

        """Applique la pondération des coefficients"""
        modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
        for modimpl_id in tags_infos[tag]:
            ponderation = tags_infos[tag][modimpl_id]["ponderation"]
            modimpl_coefs_ponderes_df[modimpl_id] *= ponderation

        """Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)"""
        moyennes_ues_tag = moy_ue.compute_ue_moys_apc(
            self.sem_cube,
            self.etuds,
            self.formsemestre.modimpls_sorted,
            self.modimpl_inscr_df,
            modimpl_coefs_ponderes_df,
            modimpls_mask,
            self.dispense_ues,
            block=self.formsemestre.block_moyennes,
        )

        """Les ects"""
        ects = self.ues_inscr_parcours_df.fillna(0.0) * [
            ue.ects for ue in self.ues if ue.type != UE_SPORT
        ]

        """Calcule la moyenne générale dans le semestre (pondérée par le ECTS)"""
        moy_gen_tag = moy_sem.compute_sem_moys_apc_using_ects(
            moyennes_ues_tag,
            ects,
            formation_id=self.formsemestre.formation_id,
            skip_empty_ues=True,
        )

        return moy_gen_tag


def get_moduleimpl(modimpl_id) -> dict:
    """Renvoie l'objet modimpl dont l'id est modimpl_id"""
    modimpl = db.session.get(ModuleImpl, modimpl_id)
    if modimpl:
        return modimpl
    if SemestreTag.DEBUG:
        log(
            "SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas"
            % (modimpl_id)
        )
    return None


def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
    """Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve
    le module de modimpl_id
    """
    # ré-écrit
    modimpl = get_moduleimpl(modimpl_id)  # le module
    ue_status = nt.get_etud_ue_status(etudid, modimpl.module.ue.id)
    if ue_status is None:
        return None
    return ue_status["moy"]


def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
    """Etant données les implémentations des modules du semestre (modimpls),
    synthétise les tags renseignés dans le programme pédagogique &
    associés aux modules du semestre,
    en les associant aux modimpls qui les concernent (modimpl_id) et
    aucoeff et pondération fournie avec le tag (par défaut 1 si non indiquée)).


    Args:
        formsemestre: Le formsemestre à la base de la recherche des tags

    Return:
        Un dictionnaire de tags
    """
    synthese_tags = {}

    """Instance des modules du semestre"""
    modimpls = formsemestre.modimpls_sorted

    for modimpl in modimpls:
        modimpl_id = modimpl.id

        """Liste des tags pour le module concerné"""
        tags = sco_tag_module.module_tag_list(modimpl.module.id)

        """Traitement des tags recensés, chacun pouvant étant de la forme 
        "mathématiques", "théorie", "pe:0", "maths:2"
        """
        for tag in tags:
            """Extraction du nom du tag et du coeff de pondération"""
            (tagname, ponderation) = sco_tag_module.split_tagname_coeff(tag)

            """Ajout d'une clé pour le tag"""
            if tagname not in synthese_tags:
                synthese_tags[tagname] = {}

            """Ajout du module (modimpl) au tagname considéré"""
            synthese_tags[tagname][modimpl_id] = {
                "modimpl": modimpl,  # les données sur le module
                # "coeff": modimpl.module.coefficient,  # le coeff du module dans le semestre
                "ponderation": ponderation,  # la pondération demandée pour le tag sur le module
                # "module_code": modimpl.module.code,  # le code qui doit se retrouver à l'identique dans des ue capitalisee
                # "ue_id": modimpl.module.ue.id,  # les données sur l'ue
                # "ue_code": modimpl.module.ue.ue_code,
                # "ue_acronyme": modimpl.module.ue.acronyme,
            }

    return synthese_tags


def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]:
    """Partant d'un formsemestre, extrait le nom des compétences associés
    à (ou aux) parcours des étudiants du formsemestre.

    Args:
        formsemestre: Un FormSemestre

    Returns:
        Dictionnaire {ue_id: nom_competence} lisant tous les noms des compétences
        en les raccrochant à leur ue
    """
    # Les résultats du semestre
    nt = load_formsemestre_results(formsemestre)

    noms_competences = {}
    for ue in nt.ues:
        if ue.type != UE_SPORT:
            ordre = ue.niveau_competence.ordre
            nom = ue.niveau_competence.competence.titre
            noms_competences[ue.ue_id] = f"comp. {nom}"
    return noms_competences
