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

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2020 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 notes_table
import datetime
import sco_codes_parcours
import sco_tag_module
import sco_utils
import pe_tagtable
import pe_jurype


class SemestreTag(pe_tagtable.TableTag):
    """Un SemestreTag représente un tableau de notes (basé sur notesTable)
    modélisant les résultats des étudiants sous forme de moyennes par tag.

    Attributs récupérés via des NotesTables :
    - nt: le tableau de notes du semestre considéré
    - nt.inscrlist: étudiants inscrits à ce semestre, par ordre alphabétique (avec demissions)
    - nt.identdict: { etudid : ident }
    - nt._modimpls : liste des moduleimpl { ... 'module_id', ...}

    Attributs supplémentaires :
    - inscrlist/identdict: étudiants inscrits hors démissionnaires ou défaillants
    - _tagdict : Dictionnaire résumant les tags et les modules du semestre auxquels ils sont liés
    - _sum_coeff_semestre : la somme des coeffs du semestre

    Attributs hérités de TableTag :
    - nom :
    - resultats: {tag: { etudid: (note_moy, somme_coff), ...} , ...}
    - rang
    - statistiques
    
    Redéfinition :
    - get_etudids() : les etudids des étudiants non défaillants ni démissionnaires
    """

    DEBUG = True

    # -----------------------------------------------------------------------------
    # Fonctions d'initialisation
    # -----------------------------------------------------------------------------
    def __init__(
        self, context, notetable, sem
    ):  # Initialisation sur la base d'une notetable
        """Instantiation d'un objet SemestreTag à partir d'un tableau de note
        et des informations sur le semestre pour le dater
        """
        pe_tagtable.TableTag.__init__(
            self,
            nom="S%d %s %s-%s"
            % (
                sem["semestre_id"],
                "ENEPS"
                if "ENEPS" in sem["titre"]
                else "UFA"
                if "UFA" in sem["titre"]
                else "FI",
                sem["annee_debut"],
                sem["annee_fin"],
            ),
        )

        # Les attributs spécifiques
        self.context = context
        self.nt = notetable

        # Les attributs hérités : la liste des étudiants
        self.inscrlist = [etud for etud in self.nt.inscrlist if etud["etat"] == "I"]
        self.identdict = {
            etudid: ident
            for (etudid, ident) in self.nt.identdict.items()
            if etudid in self.get_etudids()
        }  # Liste des étudiants non démissionnaires et non défaillants

        # Les modules pris en compte dans le calcul des moyennes par tag => ceux des UE standards
        self.modimpls = [
            modimpl
            for modimpl in self.nt._modimpls
            if modimpl["ue"]["type"] == sco_utils.UE_STANDARD
        ]  # la liste des modules (objet modimpl)
        # self._modimpl_ids = [modimpl['moduleimpl_id'] for modimpl in self._modimpls] # la liste de id des modules (modimpl_id)
        self.somme_coeffs = sum(
            [modimpl["module"]["coefficient"] for modimpl in self.modimpls]
        )

    # -----------------------------------------------------------------------------
    def comp_data_semtag(self):
        """Calcule tous les données numériques associées au semtag"""
        # Attributs relatifs aux tag pour les modules pris en compte
        self.tagdict = (
            self.do_tagdict()
        )  # Dictionnaire résumant les tags et les données (normalisées) des modules du semestre auxquels ils sont liés

        # Calcul des moyennes de chaque étudiant puis ajoute la moyenne au sens "DUT"
        for tag in self.tagdict:
            self.add_moyennesTag(tag, self.comp_MoyennesTag(tag, force=True))
        self.add_moyennesTag("dut", self.get_moyennes_DUT())
        self.taglist = sorted(
            self.tagdict.keys() + ["dut"]
        )  # actualise la liste des tags

    # -----------------------------------------------------------------------------
    def get_etudids(self):
        """Renvoie la liste des etud_id des étudiants inscrits au semestre
        """
        return [etud["etudid"] for etud in self.inscrlist]

    # -----------------------------------------------------------------------------
    def do_tagdict(self):
        """Parcourt les modimpl du semestre (instance des modules d'un programme) et synthétise leurs données sous la
        forme d'un dictionnaire reliant les tags saisis dans le programme aux
        données des modules qui les concernent, à savoir les modimpl_id, les module_id, le code du module, le coeff,
        la pondération fournie avec le tag (par défaut 1 si non indiquée).
        { tagname1 : { modimpl_id1 : { 'module_id' : ..., 'coeff' : ..., 'coeff_norm' : ..., 'ponderation' : ..., 'module_code' : ..., 'ue_xxx' : ...},
                       modimpl_id2 : ....
                     },
          tagname2 : ...
        }
        Renvoie le dictionnaire ainsi construit.

        Rq: choix fait de repérer les modules par rapport à leur modimpl_id (valable uniquement pour un semestre), car
        correspond à la majorité des calculs de moyennes pour les étudiants 
        (seuls ceux qui ont capitalisé des ue auront un régime de calcul différent).
        """
        tagdict = {}

        for modimpl in self.modimpls:
            modimpl_id = modimpl["moduleimpl_id"]
            tags = sco_tag_module.module_tag_list(
                self.context, modimpl["module_id"]
            )  # liste des tags pour le modimpl concerné

            for (
                tag
            ) in tags:  # tag de la forme "mathématiques", "théorie", "pe:0", "maths:2"
                [tagname, ponderation] = sco_tag_module.split_tagname_coeff(
                    tag
                )  # extrait un tagname et un éventuel coefficient de pondération (par defaut: 1)
                # tagname = tagname
                if not tagdict.has_key(tagname):  # Ajout d'une clé pour le tag
                    tagdict[tagname] = {}

                # Ajout du modimpl au tagname considéré
                tagdict[tagname][modimpl_id] = {
                    "module_id": modimpl["module_id"],  # 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["ue"]["ue_id"],  # les données sur l'ue
                    "ue_code": modimpl["ue"]["ue_code"],
                    "ue_acronyme": modimpl["ue"]["acronyme"],
                }
        return tagdict

    # -----------------------------------------------------------------------------
    def comp_MoyennesTag(self, tag, force=False):
        """Calcule et renvoie les "moyennes" de tous les étudiants du SemTag (non défaillants)
        à un tag donné, en prenant en compte
        tous les modimpl_id concerné par le tag, leur coeff et leur pondération.
        Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
        Renvoie les informations sous la forme d'une liste  [ (moy, somme_coeff_normalise, etudid), ...]
        """
        lesMoyennes = []
        for etudid in self.get_etudids():
            (
                notes,
                coeffs_norm,
                ponderations,
            ) = self.get_listesNotesEtCoeffsTagEtudiant(
                tag, etudid
            )  # les notes associées au tag
            coeffs = comp_coeff_pond(
                coeffs_norm, ponderations
            )  # les coeff pondérés par les tags
            (moyenne, somme_coeffs) = pe_tagtable.moyenne_ponderee_terme_a_terme(
                notes, coeffs, force=force
            )
            lesMoyennes += [
                (moyenne, somme_coeffs, etudid)
            ]  # Un tuple (pour classement résumant les données)
        return lesMoyennes

    # -----------------------------------------------------------------------------
    def get_moyennes_DUT(self):
        """Lit les moyennes DUT du semestre pour tous les étudiants
        et les renvoie au même format que comp_MoyennesTag"""
        return [(self.nt.moy_gen[etudid], 1.0, etudid) for etudid in self.get_etudids()]

    # -----------------------------------------------------------------------------
    def get_noteEtCoeff_modimpl(self, modimpl_id, etudid, profondeur=2):
        """ Renvoie un couple donnant la note et le coeff normalisé d'un étudiant à un module d'id modimpl_id.
        La note et le coeff sont extraits :
        1) soit des données du semestre en normalisant le coefficient par rapport à la somme des coefficients des modules du semestre,
        2) soit des données des UE précédemment capitalisées, en recherchant un module de même CODE que le modimpl_id proposé,
        le coefficient normalisé l'étant alors par rapport au total des coefficients du semestre auquel appartient l'ue capitalisée
        """
        (note, coeff_norm) = (None, None)

        modimpl = get_moduleimpl(self.nt, modimpl_id)  # Le module considéré
        if modimpl == None or profondeur < 0:
            return (None, None)

        # Y-a-t-il eu capitalisation d'UE ?
        ue_capitalisees = self.get_ue_capitalisees(
            etudid
        )  # les ue capitalisées des étudiants
        ue_capitalisees_id = [
            ue["ue_id"] for ue in ue_capitalisees
        ]  # les id des ue capitalisées

        # Si le module ne fait pas partie des UE capitalisées
        if modimpl["module"]["ue_id"] not in ue_capitalisees_id:
            note = self.nt.get_etud_mod_moy(modimpl_id, etudid)  # lecture de la note
            coeff = modimpl["module"]["coefficient"]  # le coeff
            coeff_norm = (
                coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0
            )  # le coeff normalisé

        # Si le module fait partie d'une UE capitalisée
        elif len(ue_capitalisees) > 0:
            moy_ue_actuelle = get_moy_ue_from_nt(
                self.nt, etudid, modimpl_id
            )  # la moyenne actuelle
            # A quel semestre correspond l'ue capitalisée et quelles sont ses notes ?
            # fid_prec = [ ue['formsemestre_id'] for ue in ue_capitalisees if ue['ue_id'] == modimpl['module']['ue_id'] ][0]
            # semestre_id = modimpl['module']['semestre_id']
            fids_prec = [
                ue["formsemestre_id"]
                for ue in ue_capitalisees
                if ue["ue_code"] == modimpl["ue"]["ue_code"]
            ]  # and ue['semestre_id'] == semestre_id]
            if len(fids_prec) > 0:
                # => le formsemestre_id du semestre dont vient la capitalisation
                fid_prec = fids_prec[0]
                # Lecture des notes de ce semestre
                nt_prec = self.context.Notes._getNotesCache().get_NotesTable(
                    self.context.Notes, fid_prec
                )  # le tableau de note du semestre considéré

                # Y-a-t-il un module équivalent c'est à dire correspondant au même code (le module_id n'étant pas significatif en cas de changement de PPN)
                modimpl_prec = [
                    module
                    for module in nt_prec._modimpls
                    if module["module"]["code"] == modimpl["module"]["code"]
                ]
                if len(modimpl_prec) > 0:  # si une correspondance est trouvée
                    modprec_id = modimpl_prec[0]["moduleimpl_id"]
                    moy_ue_capitalisee = get_moy_ue_from_nt(nt_prec, etudid, modprec_id)
                    if (
                        moy_ue_actuelle >= moy_ue_capitalisee
                    ):  # on prend la meilleure ue
                        note = self.nt.get_etud_mod_moy(
                            modimpl_id, etudid
                        )  # lecture de la note
                        coeff = modimpl["module"]["coefficient"]  # le coeff
                        coeff_norm = (
                            coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0
                        )  # le coeff normalisé
                    else:
                        semtag_prec = SemestreTag(self.context, nt_prec, nt_prec.sem)
                        (note, coeff_norm) = semtag_prec.get_noteEtCoeff_modimpl(
                            modprec_id, etudid, profondeur=profondeur - 1
                        )  # lecture de la note via le semtag associé au modimpl capitalisé

                # Sinon - pas de notes à prendre en compte
        return (note, coeff_norm)

    # -----------------------------------------------------------------------------
    def get_ue_capitalisees(self, etudid):
        """Renvoie la liste des ue_id effectivement capitalisées par un étudiant"""
        # return [ ue for ue in self.nt.ue_capitalisees[etudid] if self.nt.get_etud_ue_status(etudid,ue['ue_id'])['is_capitalized'] ]
        return self.nt.ue_capitalisees[etudid]

    # -----------------------------------------------------------------------------
    def get_listesNotesEtCoeffsTagEtudiant(self, tag, etudid):
        """Renvoie un triplet (notes, coeffs_norm, ponderations) où notes, coeff_norm et ponderation désignent trois listes
         donnant -pour un tag donné- les note, coeff et ponderation de chaque modimpl à prendre en compte dans
         le calcul de la moyenne du tag.
         Les notes et coeff_norm sont extraits grâce à SemestreTag.get_noteEtCoeff_modimpl (donc dans semestre courant ou UE capitalisée).
         Les pondérations sont celles déclarées avec le tag (cf. _tagdict). """

        notes = []
        coeffs_norm = []
        ponderations = []
        for (moduleimpl_id, modimpl) in self.tagdict[
            tag
        ].items():  # pour chaque module du semestre relatif au tag
            (note, coeff_norm) = self.get_noteEtCoeff_modimpl(moduleimpl_id, etudid)
            if note != None:
                notes.append(note)
                coeffs_norm.append(coeff_norm)
                ponderations.append(modimpl["ponderation"])
        return (notes, coeffs_norm, ponderations)

    # -----------------------------------------------------------------------------
    # Fonctions d'affichage (et d'export csv) des données du semestre en mode debug
    # -----------------------------------------------------------------------------
    def str_detail_resultat_d_un_tag(self, tag, etudid=None, delim=";"):
        """Renvoie une chaine de caractère décrivant les résultats d'étudiants à un tag :
        rappelle les notes obtenues dans les modules à prendre en compte, les moyennes et les rangs calculés.
        Si etudid=None, tous les étudiants inscrits dans le semestre sont pris en compte. Sinon seuls les étudiants indiqués sont affichés."""
        # Entete
        chaine = delim.join(["%15s" % "nom", "etudid"]) + delim
        taglist = self.get_all_tags()
        if tag in taglist:
            for mod in self.tagdict[tag].values():
                chaine += mod["module_code"] + delim
                chaine += ("%1.1f" % mod["ponderation"]) + delim
                chaine += "coeff" + delim
            chaine += delim.join(
                ["moyenne", "rang", "nbinscrit", "somme_coeff", "somme_coeff"]
            )  # ligne 1
        chaine += "\n"

        # Différents cas de boucles sur les étudiants (de 1 à plusieurs)
        if etudid == None:
            lesEtuds = self.get_etudids()
        elif isinstance(etudid, str) and etudid in self.get_etudids():
            lesEtuds = [etudid]
        elif isinstance(etudid, list):
            lesEtuds = [eid for eid in self.get_etudids() if eid in etudid]
        else:
            lesEtuds = []

        for etudid in lesEtuds:
            descr = "%15s" % self.nt.get_nom_short(etudid)[:15] + delim + etudid + delim
            if tag in taglist:
                for modimpl_id in self.tagdict[tag]:
                    (note, coeff) = self.get_noteEtCoeff_modimpl(modimpl_id, etudid)
                    descr += (
                        (
                            "%2.2f" % note
                            if note != None and isinstance(note, float)
                            else str(note)
                        )
                        + delim
                        + (
                            "%1.5f" % coeff
                            if coeff != None and isinstance(coeff, float)
                            else str(coeff)
                        )
                        + delim
                        + (
                            "%1.5f" % (coeff * self.somme_coeffs)
                            if coeff != None and isinstance(coeff, float)
                            else str(coeff * self._sum_coeff_semestre)
                        )
                        + delim
                    )
                moy = self.get_moy_from_resultats(tag, etudid)
                rang = self.get_rang_from_resultats(tag, etudid)
                coeff = self.get_coeff_from_resultats(tag, etudid)
                tot = (
                    coeff * self.somme_coeffs
                    if coeff != None
                    and self.somme_coeffs != None
                    and isinstance(coeff, float)
                    else None
                )
                descr += (
                    pe_tagtable.TableTag.str_moytag(
                        moy, rang, len(self.get_etudids()), delim=delim
                    )
                    + delim
                )
                descr += (
                    (
                        "%1.5f" % coeff
                        if coeff != None and isinstance(coeff, float)
                        else str(coeff)
                    )
                    + delim
                    + (
                        "%.2f" % (tot)
                        if tot != None
                        else str(coeff) + "*" + str(self.somme_coeffs)
                    )
                )
            chaine += descr
            chaine += "\n"
        return chaine

    def str_tagsModulesEtCoeffs(self):
        """Renvoie une chaine affichant la liste des tags associés au semestre, les modules qui les concernent et les coeffs de pondération.
        Plus concrêtement permet d'afficher le contenu de self._tagdict"""
        chaine = "Semestre %s d'id %d" % (self.nom, id(self)) + "\n"
        chaine += " -> somme de coeffs: " + str(self.somme_coeffs) + "\n"
        taglist = self.get_all_tags()
        for tag in taglist:
            chaine += " > " + tag + ": "
            for (modid, mod) in self.tagdict[tag].items():
                chaine += (
                    mod["module_code"]
                    + " ("
                    + str(mod["coeff"])
                    + "*"
                    + str(mod["ponderation"])
                    + ") "
                    + modid
                    + ", "
                )
            chaine += "\n"
        return chaine


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

# *********************************************
def comp_coeff_pond(coeffs, ponderations):
    """
    Applique une ponderation (indiquée dans la liste ponderations) à une liste de coefficients :
    ex: coeff = [2, 3, 1, None], ponderation = [1, 2, 0, 1] => [2*1, 3*2, 1*0, None]
    Les coeff peuvent éventuellement être None auquel cas None est conservé ;
    Les pondérations sont des floattants
    """
    if (
        coeffs == None
        or ponderations == None
        or not isinstance(coeffs, list)
        or not isinstance(ponderations, list)
        or len(coeffs) != len(ponderations)
    ):
        raise ValueError("Erreur de paramètres dans comp_coeff_pond")
    return [
        (None if coeffs[i] == None else coeffs[i] * ponderations[i])
        for i in range(len(coeffs))
    ]


# -----------------------------------------------------------------------------
def get_moduleimpl(nt, modimpl_id):
    """Renvoie l'objet modimpl dont l'id est modimpl_id fourni dans la note table nt,
       en utilisant l'attribut nt._modimpls"""
    modimplids = [
        modimpl["moduleimpl_id"] for modimpl in nt._modimpls
    ]  # la liste de id des modules (modimpl_id)
    if modimpl_id not in modimplids:
        if SemestreTag.DEBUG:
            print "SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas" % (
                modimpl_id
            )
        return None
    return nt._modimpls[modimplids.index(modimpl_id)]


# **********************************************
def get_moy_ue_from_nt(nt, etudid, modimpl_id):
    """Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve le module de modimpl_id
    en partant du note table nt"""
    mod = get_moduleimpl(nt, modimpl_id)  # le module
    indice = 0
    while indice < len(nt._ues):
        if (
            nt._ues[indice]["ue_id"] == mod["module"]["ue_id"]
        ):  # si les ue_id correspond
            data = [
                ligne for ligne in nt.T if ligne[-1] == etudid
            ]  # les notes de l'étudiant
            if data:
                return data[0][indice + 1]  # la moyenne à l'ue
            else:
                indice += 1
        return None  # si non trouvé