##############################################################################
#
# 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 Thu Sep  8 09:36:33 2016

@author: barasc
"""

import pandas as pd
import numpy as np

from app.models import Identite
from app.pe import pe_affichage
from app.pe.moys import pe_tabletags, pe_moy, pe_moytag, pe_sxtag
from app.pe.rcss import pe_rcs
import app.pe.pe_comp as pe_comp
from app.scodoc.sco_utils import ModuleType


class InterClassTag(pe_tabletags.TableTag):
    """
    Interclasse l'ensemble des étudiants diplômés à une année
    donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S'), qu'il soit
    de type SemX ou RCSemX,
    en reportant les moyennes obtenues sur à la version tagguée
    du RCS (de type SxTag ou RCSTag).
    Sont ensuite calculés les classements (uniquement)
    sur les étudiants diplômes.

    Args:
        nom_rcs: Le nom de l'aggrégat
        type_interclassement: Le type d'interclassement (par UE ou par compétences)
        etudiants_diplomes: L'identité des étudiants diplômés
        rcss: Un dictionnaire {(nom_rcs, fid_final): RCS} donnant soit
              les SemX soit les RCSemX recencés par le jury PE
        rcstag: Un dictionnaire {(nom_rcs, fid_final): RCSTag} donnant
                soit les SxTag (associés aux SemX)
                soit les RCSTags (associés au RCSemX) calculés par le jury PE
        suivis: Un dictionnaire associé à chaque étudiant son rcss
                (de la forme ``{etudid: {nom_rcs: RCS_suivi}}``)
    """

    def __init__(
        self,
        nom_rcs: str,
        type_interclassement: str,
        etudiants_diplomes: dict[int, Identite],
        rcss: dict[(str, int) : pe_rcs.RCS],
        rcstags: dict[(str, int) : pe_tabletags.TableTag],
        suivis: dict[int:dict],
    ):
        pe_tabletags.TableTag.__init__(self)

        self.nom_rcs: str = nom_rcs
        """Le nom du RCS interclassé"""

        # Le type d'interclassement
        self.type = type_interclassement

        pe_affichage.pe_print(
            f"*** Interclassement par 🗂️{type_interclassement} pour le RCS ⏯️{nom_rcs}"
        )

        # Les informations sur les étudiants diplômés
        self.etuds: list[Identite] = list(etudiants_diplomes.values())
        """Identités des étudiants diplômés"""
        self.add_etuds(self.etuds)

        self.diplomes_ids = set(etudiants_diplomes.keys())
        """Etudids des étudiants diplômés"""

        # Les RCS de l'aggrégat (SemX ou RCSemX)
        self.rcss: dict[(str, int), pe_rcs.RCS] = {}
        """Ensemble des SemX ou des RCSemX associés à l'aggrégat"""
        for (nom, fid), rcs in rcss.items():
            if nom == nom_rcs:
                self.rcss[(nom, fid)] = rcss

        # Les données tagguées
        self.rcstags: dict[(str, int), pe_tabletags.TableTag] = {}
        """Ensemble des SxTag ou des RCSTags associés à l'aggrégat"""
        for rcs_id in self.rcss:
            self.rcstags[rcs_id] = rcstags[rcs_id]

        # Les RCS (SemX ou RCSemX) suivis par les étudiants du jury,
        # en ne gardant que ceux associés aux diplomés
        self.suivis: dict[int, pe_rcs.RCS] = {}
        """Association entre chaque étudiant et le SxTag ou RCSTag à prendre 
        pour l'aggrégat"""
        for etudid in self.diplomes_ids:
            self.suivis[etudid] = suivis[etudid][nom_rcs]

        # Les données sur les tags
        self.tags_sorted = self._do_taglist()
        """Liste des tags (triés par ordre alphabétique)"""
        aff = pe_affichage.repr_tags(self.tags_sorted)
        pe_affichage.pe_print(f"--> Tags : {aff}")

        # Les données sur les UEs (si SxTag) ou compétences (si RCSTag)
        self.champs_sorted = self._do_ues_ou_competences_list()
        """Les champs (UEs ou compétences) de l'interclassement"""
        if self.type == pe_moytag.CODE_MOY_UE:
            pe_affichage.pe_print(
                f"--> UEs : {pe_affichage.aff_UEs(self.champs_sorted)}"
            )
        else:
            pe_affichage.pe_print(
                f"--> Compétences : {pe_affichage.aff_competences(self.champs_sorted)}"
            )

        # Etudids triés
        self.etudids_sorted = sorted(list(self.diplomes_ids))

        self.nom = self.get_repr()
        """Représentation textuelle de l'interclassement"""

        # Synthétise les moyennes/classements par tag
        self.moyennes_tags: dict[str, pe_moytag.MoyennesTag] = {}
        for tag in self.tags_sorted:
            # Les moyennes tous modules confondus
            notes_gen = self.compute_notes_matrice(tag)

            # Les coefficients de la moyenne générale
            coeffs = self.compute_coeffs_matrice(tag)
            aff = pe_affichage.repr_profil_coeffs(coeffs, with_index=True)
            pe_affichage.pe_print(f"--> Moyenne 👜{tag} avec coeffs: {aff} ")

            self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
                tag,
                self.type,
                notes_gen,
                coeffs,  # limite les moyennes aux étudiants de la promo
            )

    def get_repr(self) -> str:
        """Une représentation textuelle"""
        return f"{self.nom_rcs} par {self.type}"

    def _do_taglist(self):
        """Synthétise les tags à partir des TableTags (SXTag ou RCSTag)

        Returns:
            Une liste de tags triés par ordre alphabétique
        """
        tags = []
        for rcstag in self.rcstags.values():
            tags.extend(rcstag.tags_sorted)
        return sorted(set(tags))

    def compute_notes_matrice(self, tag) -> pd.DataFrame:
        """Construit la matrice de notes (etudids x champs) en
        reportant les moyennes obtenues par les étudiants
        aux semestres de l'aggrégat pour le tag visé.

        Les champs peuvent être des acronymes d'UEs ou des compétences.

        Args:
            tag: Le tag visé
        Return:
            Le dataFrame (etudids x champs)
            reportant les moyennes des étudiants aux champs
        """
        # etudids_sorted: Les etudids des étudiants (diplômés) triés
        # champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice

        # Partant d'un dataframe vierge
        df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted)

        for rcstag in self.rcstags.values():
            # Charge les moyennes au tag d'un RCStag
            if tag in rcstag.moyennes_tags:
                moytag = rcstag.moyennes_tags[tag]

                notes = moytag.matrice_notes_gen  # dataframe etudids x ues

                # Etudiants/Champs communs entre le RCSTag et les données interclassées
                (
                    etudids_communs,
                    champs_communs,
                ) = pe_comp.find_index_and_columns_communs(df, notes)

                # Injecte les notes par tag
                df.loc[etudids_communs, champs_communs] = notes.loc[
                    etudids_communs, champs_communs
                ]

        return df

    def compute_coeffs_matrice(self, tag) -> pd.DataFrame:
        """Idem que compute_notes_matrices mais pour les coeffs

        Args:
            tag: Le tag visé
        Return:
            Le dataFrame (etudids x champs)
            reportant les moyennes des étudiants aux champs
        """
        # etudids_sorted: Les etudids des étudiants (diplômés) triés
        # champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice

        # Partant d'un dataframe vierge
        df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted)

        for rcstag in self.rcstags.values():
            if tag in rcstag.moyennes_tags:
                # Charge les coeffs au tag d'un RCStag
                coeffs: pd.DataFrame = rcstag.moyennes_tags[tag].matrice_coeffs_moy_gen

                # Etudiants/Champs communs entre le RCSTag et les données interclassées
                (
                    etudids_communs,
                    champs_communs,
                ) = pe_comp.find_index_and_columns_communs(df, coeffs)

                # Injecte les coeffs par tag
                df.loc[etudids_communs, champs_communs] = coeffs.loc[
                    etudids_communs, champs_communs
                ]

        return df

    def _do_ues_ou_competences_list(self) -> list[str]:
        """Synthétise les champs (UEs ou compétences) sur lesquels
        sont calculés les moyennes.

        Returns:
            Un dictionnaire {'acronyme_ue' : 'compétences'}
        """
        dict_champs = []
        for rcstag in self.rcstags.values():
            if isinstance(rcstag, pe_sxtag.SxTag):
                champs = rcstag.acronymes_sorted
            else:  # pe_rcstag.RCSTag
                champs = rcstag.competences_sorted
            dict_champs.extend(champs)
        return sorted(set(dict_champs))

    def has_tags(self):
        """Indique si l'interclassement a des tags (cas d'un
        interclassement sur un S5 qui n'a pas eu lieu)
        """
        return len(self.tags_sorted) > 0

    def _un_rcstag_significatif(self, rcsstags: dict[(str, int):pe_tabletags]):
        """Renvoie un rcstag significatif (ayant des tags et des notes aux tags)
        parmi le dictionnaire de rcsstags"""
        for rcstag_id, rcstag in rcsstags.items():
            moystags: pe_moytag.MoyennesTag = rcstag.moyennes_tags
            for tag, moystag in moystags.items():
                tags_tries = moystag.get_all_significant_tags()
                if tags_tries:
                    return moystag
        return None

    def compute_df_synthese_moyennes_tag(
        self, tag, aggregat=None, type_colonnes=False, options={"min_max_moy": True}
    ) -> pd.DataFrame:
        """Construit le dataframe retraçant pour les données des moyennes
        pour affichage dans la synthèse du jury PE. (cf. to_df())

        Args:
            etudids_sorted: Les etudids des étudiants (diplômés) triés
            champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
        Return:
            Le dataFrame (etudids x champs)
            reportant les moyennes des étudiants aux champs
        """
        if aggregat:
            assert (
                aggregat == self.nom_rcs
            ), "L'interclassement ciblé ne correspond pas à l'aggrégat visé"

        etudids_sorted = sorted(list(self.diplomes_ids))

        if not self.rcstags:
            return None

        # Partant d'un dataframe vierge
        initialisation = False
        df = pd.DataFrame()

        # Pour chaque rcs (suivi) associe la liste des etudids l'ayant suivi
        asso_rcs_etudids = {}
        for etudid in etudids_sorted:
            rcs = self.suivis[etudid]
            if rcs:
                if rcs.rcs_id not in asso_rcs_etudids:
                    asso_rcs_etudids[rcs.rcs_id] = []
                asso_rcs_etudids[rcs.rcs_id].append(etudid)

        for rcs_id, etudids in asso_rcs_etudids.items():
            # Charge ses moyennes au RCSTag suivi
            rcstag = self.rcstags[rcs_id]  # Le SxTag ou RCSTag
            # Charge la moyenne
            if tag in rcstag.moyennes_tags:
                moytag: pd.DataFrame = rcstag.moyennes_tags[tag]
                df_moytag = moytag.to_df(
                    aggregat=aggregat, cohorte="Groupe", options=options
                )

                # Modif les colonnes au regard du 1er df_moytag significatif lu
                if not initialisation:
                    df = pd.DataFrame(
                        np.nan, index=etudids_sorted, columns=df_moytag.columns
                    )
                    colonnes = list(df_moytag.columns)
                    for col in colonnes:
                        if col.endswith("rang"):
                            df[col] = df[col].astype(str)
                    initialisation = True

                # Injecte les notes des étudiants
                df.loc[etudids, :] = df_moytag.loc[etudids, :]

        return df
