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

from app.comp.res_sem import load_formsemestre_results
from app.pe.pe_semtag import SemestreTag
import pandas as pd
import numpy as np
from app.pe.pe_rcs import RCS

from app.pe.pe_tabletags import TableTag, MoyenneTag


class RCSTag(TableTag):
    def __init__(
        self, rcs: RCS, semestres_taggues: dict[int, SemestreTag]
    ):
        """Calcule les moyennes par tag d'une combinaison de semestres
        (RCS), pour extraire les classements par tag pour un
        groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous
        participé au semestre terminal.


        Args:
            rcs: Un RCS (identifié par un nom et l'id de son semestre terminal)
            semestres_taggues: Les données sur les semestres taggués
        """
        TableTag.__init__(self)


        self.rcs_id = rcs.rcs_id
        """Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)"""

        self.rcs = rcs
        """RCS associé au RCS taggué"""

        self.nom = self.get_repr()
        """Représentation textuelle du RCS taggué"""

        self.formsemestre_terminal = rcs.formsemestre_final
        """Le formsemestre terminal"""

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

        self.semestres_aggreges = rcs.semestres_aggreges
        """Les semestres aggrégés"""

        self.semestres_tags_aggreges = {}
        """Les semestres tags associés aux semestres aggrégés"""
        for frmsem_id in self.semestres_aggreges:
            try:
                self.semestres_tags_aggreges[frmsem_id] = semestres_taggues[frmsem_id]
            except:
                raise ValueError("Semestres taggués manquants")

        """Les étudiants (état civil + cursus connu)"""
        self.etuds = nt.etuds

        # assert self.etuds == trajectoire.suivi  # manque-t-il des étudiants ?
        self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}

        self.tags_sorted = self.do_taglist()
        """Tags extraits de tous les semestres"""

        self.notes_cube = self.compute_notes_cube()
        """Cube de notes"""

        etudids = list(self.etudiants.keys())
        self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
        """Calcul les moyennes par tag sous forme d'un dataframe"""

        self.moyennes_tags: dict[str, MoyenneTag] = {}
        """Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
        for tag in self.tags_sorted:
            moy_gen_tag = self.notes[tag]
            self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)

    def __eq__(self, other):
        """Egalité de 2 RCS taggués sur la base de leur identifiant"""
        return self.rcs_id == other.rcs_id

    def get_repr(self, verbose=False) -> str:
        """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
        est basée)"""
        return self.rcs.get_repr(verbose=verbose)

    def compute_notes_cube(self):
        """Construit le cube de notes (etudid x tags x semestre_aggregé)
        nécessaire au calcul des moyennes de l'aggrégat
        """
        # nb_tags = len(self.tags_sorted)
        # nb_etudiants = len(self.etuds)
        # nb_semestres = len(self.semestres_tags_aggreges)

        # Index du cube (etudids -> dim 0, tags -> dim 1)
        etudids = [etud.etudid for etud in self.etuds]
        tags = self.tags_sorted
        semestres_id = list(self.semestres_tags_aggreges.keys())

        dfs = {}

        for frmsem_id in semestres_id:
            # Partant d'un dataframe vierge
            df = pd.DataFrame(np.nan, index=etudids, columns=tags)

            # Charge les notes du semestre tag
            notes = self.semestres_tags_aggreges[frmsem_id].notes

            # Les étudiants & les tags commun au dataframe final et aux notes du semestre)
            etudids_communs = df.index.intersection(notes.index)
            tags_communs = df.columns.intersection(notes.columns)

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

            # Supprime tout ce qui n'est pas numérique
            for col in df.columns:
                df[col] = pd.to_numeric(df[col], errors="coerce")

            # Stocke le df
            dfs[frmsem_id] = df

        """Réunit les notes sous forme d'un cube etdids x tags x semestres"""
        semestres_x_etudids_x_tags = [dfs[fid].values for fid in dfs]
        etudids_x_tags_x_semestres = np.stack(semestres_x_etudids_x_tags, axis=-1)

        return etudids_x_tags_x_semestres

    def do_taglist(self):
        """Synthétise les tags à partir des semestres (taggués) aggrégés

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


def compute_tag_moy(set_cube: np.array, etudids: list, tags: list):
    """Calcul de la moyenne par tag sur plusieurs semestres.
    La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles

    *Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag
    par aggrégat de plusieurs semestres.

    Args:
        set_cube: notes moyennes aux modules ndarray
                 (etuds x modimpls x UEs), des floats avec des NaN
        etudids: liste des étudiants (dim. 0 du cube)
        tags: liste des tags (dim. 1 du cube)
    Returns:
        Un DataFrame avec pour columns les moyennes par tags,
        et pour rows les etudid
    """
    nb_etuds, nb_tags, nb_semestres = set_cube.shape
    assert nb_etuds == len(etudids)
    assert nb_tags == len(tags)

    # Quelles entrées du cube contiennent des notes ?
    mask = ~np.isnan(set_cube)

    # Enlève les NaN du cube pour les entrées manquantes
    set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0)

    # Les moyennes par tag
    with np.errstate(invalid="ignore"):  # ignore les 0/0 (-> NaN)
        etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2)

    # Le dataFrame
    etud_moy_tag_df = pd.DataFrame(
        etud_moy_tag,
        index=etudids,  # les etudids
        columns=tags,  # les tags
    )

    etud_moy_tag_df.fillna(np.nan)

    return etud_moy_tag_df
