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

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 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 datetime
import numpy as np

from app.scodoc import sco_utils as scu


class TableTag(object):
    """
    Classe mémorisant les moyennes des étudiants à différents tag et permettant de calculer les rangs et les statistiques :
    - nom : Nom représentatif des données de la Table
    - inscrlist : Les étudiants inscrits dans le TagTag avec leur information de la forme :
        { etudid : dictionnaire d'info extrait de Scodoc, ...}
    - taglist : Liste triée des noms des tags
    - resultats : Dictionnaire donnant les notes-moyennes de chaque étudiant par tag et la somme commulée
    des coeff utilisées dans le calcul de la moyenne pondérée, sous la forme :
        { tag : { etudid: (note_moy, somme_coeff_norm),
                                        ...}
    - rangs : Dictionnaire donnant les rang par tag de chaque étudiant de la forme :
        { tag : {etudid: rang, ...} }
    - nbinscrits : Nombre d'inscrits dans le semestre (pas de distinction entre les tags)
    - statistiques : Dictionnaire donnant les stastitiques (moyenne, min, max) des résultats par tag de la forme :
        { tag : (moy, min, max), ...}

    """

    def __init__(self, nom=""):
        self.nom = nom
        self.inscrlist = []
        self.identdict = {}
        self.taglist = []

        self.resultats = {}
        self.rangs = {}
        self.statistiques = {}

    # *****************************************************************************************************************
    # Accesseurs
    # *****************************************************************************************************************

    # -----------------------------------------------------------------------------------------------------------
    def get_moy_from_resultats(self, tag, etudid):
        """Renvoie la moyenne obtenue par un étudiant à un tag donné au regard du format de self.resultats"""
        return (
            self.resultats[tag][etudid][0]
            if tag in self.resultats and etudid in self.resultats[tag]
            else None
        )

    # -----------------------------------------------------------------------------------------------------------
    def get_rang_from_resultats(self, tag, etudid):
        """Renvoie le rang à un tag d'un étudiant au regard du format de self.resultats"""
        return (
            self.rangs[tag][etudid]
            if tag in self.resultats and etudid in self.resultats[tag]
            else None
        )

    # -----------------------------------------------------------------------------------------------------------
    def get_coeff_from_resultats(self, tag, etudid):
        """Renvoie la somme des coeffs de pondération normalisée utilisés dans le calcul de la moyenne à un tag d'un étudiant
        au regard du format de self.resultats.
        """
        return (
            self.resultats[tag][etudid][1]
            if tag in self.resultats and etudid in self.resultats[tag]
            else None
        )

    # -----------------------------------------------------------------------------------------------------------
    def get_all_tags(self):
        """Renvoie la liste des tags du semestre triée par ordre alphabétique"""
        # return self.taglist
        return sorted(self.resultats.keys())

    # -----------------------------------------------------------------------------------------------------------
    def get_nbinscrits(self):
        """Renvoie le nombre d'inscrits"""
        return len(self.inscrlist)

    # -----------------------------------------------------------------------------------------------------------
    def get_moy_from_stats(self, tag):
        """Renvoie la moyenne des notes calculées pour d'un tag donné"""
        return self.statistiques[tag][0] if tag in self.statistiques else None

    def get_min_from_stats(self, tag):
        """Renvoie la plus basse des notes calculées pour d'un tag donné"""
        return self.statistiques[tag][1] if tag in self.statistiques else None

    def get_max_from_stats(self, tag):
        """Renvoie la plus haute des notes calculées pour d'un tag donné"""
        return self.statistiques[tag][2] if tag in self.statistiques else None

    # -----------------------------------------------------------------------------------------------------------
    # La structure des données mémorisées pour chaque tag dans le dictionnaire de synthèse
    # d'un jury PE
    FORMAT_DONNEES_ETUDIANTS = (
        "note",
        "coeff",
        "rang",
        "nbinscrits",
        "moy",
        "max",
        "min",
    )

    def get_resultatsEtud(self, tag, etudid):
        """Renvoie un tuple (note, coeff, rang, nb_inscrit, moy, min, max) synthétisant les résultats d'un étudiant
        à un tag donné. None sinon"""
        return (
            self.get_moy_from_resultats(tag, etudid),
            self.get_coeff_from_resultats(tag, etudid),
            self.get_rang_from_resultats(tag, etudid),
            self.get_nbinscrits(),
            self.get_moy_from_stats(tag),
            self.get_min_from_stats(tag),
            self.get_max_from_stats(tag),
        )

    #        return self.tag_stats[tag]
    #    else :
    #        return self.pe_stats

    # *****************************************************************************************************************
    # Ajout des notes
    # *****************************************************************************************************************

    # -----------------------------------------------------------------------------------------------------------
    def add_moyennesTag(self, tag, listMoyEtCoeff) -> bool:
        """
        Mémorise les moyennes, les coeffs de pondération et les etudid dans resultats
        avec calcul du rang
        :param tag: Un tag
        :param listMoyEtCoeff: Une liste donnant [ (moy, coeff, etudid) ]
        """
        # ajout des moyennes au dictionnaire résultat
        if listMoyEtCoeff:
            self.resultats[tag] = {
                etudid: (moyenne, somme_coeffs)
                for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff
            }

            # Calcule les rangs
            lesMoyennesTriees = sorted(
                listMoyEtCoeff,
                reverse=True,
                key=lambda col: col[0]
                if isinstance(col[0], float)
                else 0,  # remplace les None et autres chaines par des zéros
            )  # triées
            self.rangs[tag] = scu.comp_ranks(lesMoyennesTriees)  # les rangs

            # calcul des stats
            self.comp_stats_d_un_tag(tag)
            return True
        return False

    # *****************************************************************************************************************
    # Méthodes dévolues aux calculs de statistiques (min, max, moy) sur chaque moyenne taguée
    # *****************************************************************************************************************

    def comp_stats_d_un_tag(self, tag):
        """
        Calcule la moyenne generale, le min, le max pour un tag donné,
        en ne prenant en compte que les moyennes significatives. Mémorise le resultat dans
        self.statistiques
        """
        stats = ("-NA-", "-", "-")
        if tag not in self.resultats:
            return stats

        notes = [
            self.get_moy_from_resultats(tag, etudid) for etudid in self.resultats[tag]
        ]  # les notes du tag
        notes_valides = [
            note for note in notes if isinstance(note, float) and note != None
        ]
        nb_notes_valides = len(notes_valides)
        if nb_notes_valides > 0:
            (moy, _) = moyenne_ponderee_terme_a_terme(notes_valides, force=True)
            self.statistiques[tag] = (moy, max(notes_valides), min(notes_valides))

    # ************************************************************************
    # Méthodes dévolues aux affichages -> a revoir
    # ************************************************************************
    def str_resTag_d_un_etudiant(self, tag, etudid, delim=";"):
        """Renvoie une chaine de caractères (valable pour un csv)
        décrivant la moyenne et le rang d'un étudiant, pour un tag donné ;
        """
        if tag not in self.get_all_tags() or etudid not in self.resultats[tag]:
            return ""

        moystr = TableTag.str_moytag(
            self.get_moy_from_resultats(tag, etudid),
            self.get_rang_from_resultats(tag, etudid),
            self.get_nbinscrits(),
            delim=delim,
        )
        return moystr

    def str_res_d_un_etudiant(self, etudid, delim=";"):
        """Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique)."""
        return delim.join(
            [self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
        )

    # -----------------------------------------------------------------------
    def str_moytag(cls, moyenne, rang, nbinscrit, delim=";"):
        """Renvoie une chaine de caractères représentant une moyenne (float ou string) et un rang
        pour différents formats d'affichage : HTML, debug ligne de commande, csv"""
        moystr = (
            "%2.2f%s%s%s%d" % (moyenne, delim, rang, delim, nbinscrit)
            if isinstance(moyenne, float)
            else str(moyenne) + delim + str(rang) + delim + str(nbinscrit)
        )
        return moystr

    str_moytag = classmethod(str_moytag)
    # -----------------------------------------------------------------------

    def str_tagtable(self, delim=";", decimal_sep=","):
        """Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags."""
        entete = ["etudid", "nom", "prenom"]
        for tag in self.get_all_tags():
            entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]]
        chaine = delim.join(entete) + "\n"

        for etudid in self.identdict:
            descr = delim.join(
                [
                    etudid,
                    self.identdict[etudid]["nom"],
                    self.identdict[etudid]["prenom"],
                ]
            )
            descr += delim + self.str_res_d_un_etudiant(etudid, delim)
            chaine += descr + "\n"

        # Ajout des stats ... à faire

        if decimal_sep != ".":
            return chaine.replace(".", decimal_sep)
        else:
            return chaine


# ************************************************************************
# Fonctions diverses
# ************************************************************************


# *********************************************
def moyenne_ponderee_terme_a_terme(notes, coefs=None, force=False):
    """
    Calcule la moyenne pondérée d'une liste de notes avec d'éventuels coeffs de pondération.
    Renvoie le résultat sous forme d'un tuple (moy, somme_coeff)

    La liste de notes contient soit :
    1) des valeurs numériques
    2) des strings "-NA-" (pas de notes) ou "-NI-" (pas inscrit) ou "-c-" ue capitalisée,
    3) None.

    Le paramètre force indique si le calcul de la moyenne doit être forcée ou non, c'est à
    dire s'il y a ou non omission des notes non numériques (auquel cas la moyenne est
    calculée sur les notes disponibles) ; sinon renvoie (None, None).
    """
    # Vérification des paramètres d'entrée
    if not isinstance(notes, list) or (
        coefs != None and not isinstance(coefs, list) and len(coefs) != len(notes)
    ):
        raise ValueError("Erreur de paramètres dans moyenne_ponderee_terme_a_terme")

    # Récupération des valeurs des paramètres d'entrée
    coefs = [1] * len(notes) if coefs is None else coefs

    # S'il n'y a pas de notes
    if not notes:  # Si notes = []
        return (None, None)

    # Liste indiquant les notes valides
    notes_valides = [
        (isinstance(note, float) and not np.isnan(note)) or isinstance(note, int)
        for note in notes
    ]
    # Si on force le calcul de la moyenne ou qu'on ne le force pas
    #    et qu'on a le bon nombre de notes
    if force or sum(notes_valides) == len(notes):
        moyenne, ponderation = 0.0, 0.0
        for i in range(len(notes)):
            if notes_valides[i]:
                moyenne += coefs[i] * notes[i]
                ponderation += coefs[i]
        return (
            (moyenne / (ponderation * 1.0), ponderation)
            if ponderation != 0
            else (None, 0)
        )
    # Si on ne force pas le calcul de la moyenne
    return (None, None)


# -------------------------------------------------------------------------------------------
def conversionDate_StrToDate(date_fin):
    """Conversion d'une date fournie sous la forme d'une chaine de caractère de
    type 'jj/mm/aaaa' en un objet date du package datetime.
    Fonction servant au tri des semestres par date
    """
    (d, m, y) = [int(x) for x in date_fin.split("/")]
    date_fin_dst = datetime.date(y, m, d)
    return date_fin_dst
