##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet.  All rights reserved.
# See LICENSE
##############################################################################

"""Classes spécifiques de calcul du bonus sport, culture ou autres activités

Les classes de Bonus fournissent deux méthodes:
    - get_bonus_ues()
    - get_bonus_moy_gen()


"""
import datetime
import numpy as np
import pandas as pd

from flask import g

from app.models.formsemestre import FormSemestre
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import ModuleType


def get_bonus_sport_class_from_name(dept_id):
    """La classe de bonus sport pour le département indiqué.
    Note: en ScoDoc 9, le bonus sport est défini gloabelement et
    ne dépend donc pas du département.
    Résultat: une sous-classe de BonusSport
    """
    raise NotImplementedError()


class BonusSport:
    """Calcul du bonus sport.

    Arguments:
        - sem_modimpl_moys :
            notes moyennes aux modules (tous les étuds x tous les modimpls)
            floats avec des NaN.
            En classique: sem_matrix, ndarray (etuds x modimpls)
            En APC: sem_cube, ndarray (etuds x modimpls x UEs non bonus)
        - ues: les ues du semestre (incluant le bonus sport)
        - modimpl_inscr_df: matrice d'inscription aux modules du semestre (etud x modimpl)
        - modimpl_coefs: les coefs des modules
            En classique: 1d ndarray de float (modimpl)
            En APC: 2d ndarray de float, (modimpl x UE) <= attention à transposer
        - etud_moy_gen: Series, index etudid, valeur float (moyenne générale avant bonus)
        - etud_moy_ue: DataFrame columns UE (sans sport), rows etudid (moyennes avant bonus)

        etud_moy_gen et etud_moy_ue ne sont PAS modifiés (mais utilisés par certains bonus non additifs).
    """

    # En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen reste None)
    classic_use_bonus_ues = False

    # Attributs virtuels:
    seuil_moy_gen = None
    proportion_point = None
    bonus_max = None

    name = "virtual"

    def __init__(
        self,
        formsemestre: FormSemestre,
        sem_modimpl_moys: np.array,
        ues: list,
        modimpl_inscr_df: pd.DataFrame,
        modimpl_coefs: np.array,
        etud_moy_gen,
        etud_moy_ue,
    ):
        self.formsemestre = formsemestre
        self.ues = ues
        self.etud_moy_gen = etud_moy_gen
        self.etud_moy_ue = etud_moy_ue
        self.etuds_idx = modimpl_inscr_df.index  # les étudiants inscrits au semestre
        self.bonus_ues: pd.DataFrame = None  # virtual
        self.bonus_moy_gen: pd.Series = None  # virtual (pour formations non apc slt)
        # Restreint aux modules standards des UE de type "sport":
        modimpl_mask = np.array(
            [
                (m.module.module_type == ModuleType.STANDARD)
                and (m.module.ue.type == UE_SPORT)
                for m in formsemestre.modimpls_sorted
            ]
        )
        if not len(modimpl_mask):
            modimpl_mask = np.s_[:]  # il n'y a rien, on prend tout donc rien
        self.modimpls_spo = [
            modimpl
            for i, modimpl in enumerate(formsemestre.modimpls_sorted)
            if modimpl_mask[i]
        ]
        "liste des modimpls sport"

        # Les moyennes des modules "sport": (une par UE en APC)
        # donc (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
        sem_modimpl_moys_spo = sem_modimpl_moys[:, modimpl_mask]
        # Les inscriptions aux modules sport:
        modimpl_inscr_spo = modimpl_inscr_df.values[:, modimpl_mask]
        # Les coefficients des modules sport (en apc: nb_mod_sport x nb_ue) (toutes ues)
        modimpl_coefs_spo = modimpl_coefs[modimpl_mask]
        # sem_modimpl_moys_spo est (nb_etuds, nb_mod_sport)
        #                       ou (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
        nb_etuds, nb_mod_sport = sem_modimpl_moys_spo.shape[:2]
        # Enlève les NaN du numérateur:
        sem_modimpl_moys_no_nan = np.nan_to_num(sem_modimpl_moys_spo, nan=0.0)

        # Annule les coefs des modules où l'étudiant n'est pas inscrit:
        if formsemestre.formation.is_apc():
            # BUT
            nb_ues_no_bonus = sem_modimpl_moys.shape[2]
            # Duplique les inscriptions sur les UEs non bonus:
            modimpl_inscr_spo_stacked = np.stack(
                [modimpl_inscr_spo] * nb_ues_no_bonus, axis=2
            )
            # Ne prend pas en compte les notes des étudiants non inscrits au module:
            # Annule les notes: (nb_etuds, nb_mod_bonus, nb_ues_non_bonus)
            sem_modimpl_moys_inscrits = np.where(
                modimpl_inscr_spo_stacked, sem_modimpl_moys_no_nan, 0.0
            )
            # Annule les coefs des modules où l'étudiant n'est pas inscrit:
            modimpl_coefs_etuds = np.where(
                modimpl_inscr_spo_stacked,
                np.stack([modimpl_coefs_spo] * nb_etuds),
                0.0,
            )
        else:
            # Formations classiques
            # Ne prend pas en compte les notes des étudiants non inscrits au module:
            # Annule les notes:
            sem_modimpl_moys_inscrits = np.where(
                modimpl_inscr_spo, sem_modimpl_moys_no_nan, 0.0
            )
            modimpl_coefs_spo = modimpl_coefs_spo.T
            if nb_etuds == 0:
                modimpl_coefs_etuds = modimpl_inscr_spo  # vide
            else:
                modimpl_coefs_etuds = np.where(
                    modimpl_inscr_spo, np.stack([modimpl_coefs_spo] * nb_etuds), 0.0
                )
        # Annule les coefs des modules NaN (nb_etuds x nb_mod_sport)
        modimpl_coefs_etuds_no_nan = np.where(
            np.isnan(sem_modimpl_moys_spo), 0.0, modimpl_coefs_etuds
        )
        #
        self.compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)

    def compute_bonus(
        self,
        sem_modimpl_moys_inscrits: np.ndarray,
        modimpl_coefs_etuds_no_nan: np.ndarray,
    ):
        """Calcul des bonus: méthode virtuelle à écraser.
        Arguments:
            - sem_modimpl_moys_inscrits:
                    ndarray (nb_etuds, mod_sport) ou en APC (nb_etuds, mods_sport, nb_ue_non_bonus)
                les notes aux modules sports auxquel l'étudiant est inscrit, 0 sinon. Pas de nans.
            - modimpl_coefs_etuds_no_nan:
                les coefficients: float ndarray

        Résultat: None
        """
        raise NotImplementedError("méthode virtuelle")

    def get_bonus_ues(self) -> pd.DataFrame:
        """Les bonus à appliquer aux UE
        Résultat: DataFrame de float, index etudid, columns: ue.id
        """
        if self.classic_use_bonus_ues or self.formsemestre.formation.is_apc():
            return self.bonus_ues
        return None

    def get_bonus_moy_gen(self):
        """Le bonus à appliquer à la moyenne générale.
        Résultat: Series de float, index etudid
        """
        if self.formsemestre.formation.is_apc():
            return None  # garde-fou
        return self.bonus_moy_gen


class BonusSportAdditif(BonusSport):
    """Bonus sport simples calcule un bonus à partir des notes moyennes de modules
    de l'UE sport, et ce bonus est soit ajouté à la moyenne générale (formations classiques),
    soit ajouté à chaque UE (formations APC).

    Le bonus est par défaut calculé comme:
        Les points au-dessus du seuil (par défaut) 10 sur 20 obtenus dans chacun des
        modules optionnels sont cumulés et une fraction de ces points cumulés s'ajoute
         à la moyenne générale du semestre déjà obtenue par l'étudiant.
    """

    seuil_moy_gen = 10.0  # seuls les points au dessus du seuil sont comptés
    proportion_point = 0.05  # multiplie les points au dessus du seuil

    def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
        """calcul du bonus
        sem_modimpl_moys_inscrits: les notes de sport
            En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
        modimpl_coefs_etuds_no_nan:
        """
        if 0 in sem_modimpl_moys_inscrits.shape:
            # pas d'étudiants ou pas d'UE ou pas de module...
            return
        bonus_moy_arr = np.sum(
            np.where(
                sem_modimpl_moys_inscrits > self.seuil_moy_gen,
                (sem_modimpl_moys_inscrits - self.seuil_moy_gen)
                * self.proportion_point,
                0.0,
            ),
            axis=1,
        )
        if self.bonus_max is not None:
            # Seuil: bonus limité à bonus_max points (et >= 0)
            bonus_moy_arr = np.clip(
                bonus_moy_arr, 0.0, self.bonus_max, out=bonus_moy_arr
            )
        else:  # necessaire pour éviter bonus négatifs !
            bonus_moy_arr = np.clip(bonus_moy_arr, 0.0, 20.0, out=bonus_moy_arr)

        # en APC, bonus_moy_arr est (nb_etuds, nb_ues_non_bonus)
        if self.formsemestre.formation.is_apc() or self.classic_use_bonus_ues:
            # Bonus sur les UE et None sur moyenne générale
            ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
            self.bonus_ues = pd.DataFrame(
                bonus_moy_arr, index=self.etuds_idx, columns=ues_idx, dtype=float
            )
        else:
            # Bonus sur la moyenne générale seulement
            self.bonus_moy_gen = pd.Series(
                bonus_moy_arr, index=self.etuds_idx, dtype=float
            )

        # if len(bonus_moy_arr.shape) > 1:
        #    bonus_moy_arr = bonus_moy_arr.sum(axis=1)
        # Laisse  bonus_moy_gen à None, en APC le bonus moy. gen. sera réparti sur les UEs.


class BonusSportMultiplicatif(BonusSport):
    """Bonus sport qui multiplie les moyennes d'UE par un facteur"""

    seuil_moy_gen = 10.0  # seuls les points au dessus du seuil sont comptés
    amplitude = 0.005  # multiplie les points au dessus du seuil
    # En classique, les bonus multiplicatifs agissent par défaut sur les UE:
    classic_use_bonus_ues = True

    # C'est un bonus "multiplicatif": on l'exprime en additif,
    # sur chaque moyenne d'UE m_0
    # Augmenter de 5% correspond à multiplier par a=1.05
    # m_1 = a . m_0
    # m_1 = m_0 + bonus
    # bonus = m_0 (a - 1)
    def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
        """calcul du bonus"""
        if 0 in sem_modimpl_moys_inscrits.shape:
            # pas d'étudiants ou pas d'UE ou pas de module...
            return
        # Calcule moyenne pondérée des notes de sport:
        notes = np.sum(
            sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
        ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
        notes = np.nan_to_num(notes, copy=False)
        factor = (notes - self.seuil_moy_gen) * self.amplitude  # 5% si note=20
        factor[factor <= 0] = 0.0  # note < seuil_moy_gen, pas de bonus

        # Ne s'applique qu'aux moyennes d'UE
        if len(factor.shape) == 1:  # classic
            factor = factor[:, np.newaxis]
        bonus = self.etud_moy_ue * factor
        if self.bonus_max is not None:
            # Seuil: bonus limité à bonus_max points
            bonus.clip(upper=self.bonus_max, inplace=True)

        self.bonus_ues = bonus  # DataFrame

        # Les bonus multiplicatifs ne s'appliquent pas à la moyenne générale
        self.bonus_moy_gen = None


class BonusDirect(BonusSportAdditif):
    """Bonus direct: les points sont directement ajoutés à la moyenne générale.
    Les coefficients sont ignorés: tous les points de bonus sont sommés.
    (rappel: la note est ramenée sur 20 avant application).
    """

    name = "bonus_direct"
    displayed_name = 'Bonus "direct"'
    seuil_moy_gen = 0.0  # tous les points sont comptés
    proportion_point = 1.0


class BonusBethune(BonusSportMultiplicatif):
    """Calcul bonus modules optionels (sport), règle IUT de Béthune.

    Les points au dessus de la moyenne de 10 apportent un bonus pour le semestre.
    Ce bonus est égal au nombre de points divisé par 200 et multiplié par la
    moyenne générale du semestre de l'étudiant.
    """

    name = "bonus_iutbethune"
    displayed_name = "IUT de Béthune"
    seuil_moy_gen = 10.0
    amplitude = 0.005


class BonusBezier(BonusSportAdditif):
    """Calcul bonus modules optionels (sport, culture), règle IUT de Bézier.

    Les étudiants de l'IUT peuvent suivre des enseignements optionnels
    sport , etc) non rattachés à une unité d'enseignement. Les points
    au-dessus de 10 sur 20 obtenus dans chacune des matières
    optionnelles sont cumulés et 3% de ces points cumulés s'ajoutent à
    la moyenne générale du semestre déjà obtenue par l'étudiant, dans
    la limite de 0,3 points.
    """

    # note: cela revient à dire que l'on ajoute 5% des points au dessus de 10,
    # et qu'on limite à 5% de 10, soit 0.5 points
    # ce bonus est donc strictement identique à celui de St Denis (BonusIUTStDenis)
    name = "bonus_iutbeziers"
    displayed_name = "IUT de Bézier"
    bonus_max = 0.3
    seuil_moy_gen = 10.0  # tous les points sont comptés
    proportion_point = 0.03


class BonusBordeaux1(BonusSportMultiplicatif):
    """Calcul bonus modules optionels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale
    et UE.

    Les étudiants de l'IUT peuvent suivre des enseignements optionnels
    de l'Université Bordeaux 1 (sport, théâtre) non rattachés à une unité d'enseignement.

    Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un %
    qui augmente la moyenne de chaque UE et la moyenne générale.
    Formule : le % = points>moyenne / 2
    Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.

    """

    name = "bonus_iutBordeaux1"
    displayed_name = "IUT de Bordeaux 1"
    classic_use_bonus_ues = True  # s'applique aux UEs en DUT et LP
    seuil_moy_gen = 10.0
    amplitude = 0.005


class BonusColmar(BonusSportAdditif):
    """Calcul bonus modules optionels (sport, culture), règle IUT Colmar.

    Les étudiants de l'IUT peuvent suivre des enseignements optionnels
    de l'U.H.A.  (sports, musique, deuxième langue, culture, etc) non
    rattachés à une unité d'enseignement. Les points au-dessus de 10
    sur 20 obtenus dans chacune des matières optionnelles sont cumulés
    dans la limite de 10 points. 5% de ces points cumulés s'ajoutent à
    la moyenne générale du semestre déjà obtenue par l'étudiant.
    """

    # note: cela revient à dire que l'on ajoute 5% des points au dessus de 10,
    # et qu'on limite à 5% de 10, soit 0.5 points
    # ce bonus est donc strictement identique à celui de St Denis (BonusIUTStDenis)
    name = "bonus_colmar"
    displayed_name = "IUT de Colmar"
    bonus_max = 0.5
    seuil_moy_gen = 10.0  # tous les points sont comptés
    proportion_point = 0.05


class BonusGrenobleIUT1(BonusSportMultiplicatif):
    """Bonus IUT1 de Grenoble

    À compter de sept. 2021:
    La note de sport est sur 20, et on calcule une bonification (en %)
    qui va s'appliquer à la moyenne de chaque UE du semestre en appliquant
    la formule : bonification (en %) = (note-10)*0,5.

    Bonification qui ne s'applique que si la note est >10.

    (Une note de 10 donne donc 0% de bonif ; note de 20 : 5% de bonif)

    Avant sept. 2021, la note de sport allait de 0 à 5 points (sur 20).
    Chaque point correspondait à 0.25% d'augmentation de la moyenne
    générale.
    Par exemple : note de sport 2/5 : la moyenne générale était augmentée de 0.5%.
    """

    name = "bonus_iut1grenoble_2017"
    displayed_name = "IUT de Grenoble 1"
    # C'est un bonus "multiplicatif": on l'exprime en additif,
    # sur chaque moyenne d'UE m_0
    # Augmenter de 5% correspond à multiplier par a=1.05
    # m_1 = a . m_0
    # m_1 = m_0 + bonus
    # bonus = m_0 (a - 1)
    def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
        """calcul du bonus, avec réglage différent suivant la date"""

        if self.formsemestre.date_debut > datetime.date(2021, 7, 15):
            self.seuil_moy_gen = 10.0
            self.amplitude = 0.005
        else:  # anciens semestres
            self.seuil_moy_gen = 0.0
            self.amplitude = 1 / 400.0

        super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)


class BonusLaRochelle(BonusSportAdditif):
    """Calcul bonus modules optionels (sport, culture), règle IUT de La Rochelle.

    Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.
    Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette
    note sur la moyenne générale du semestre (ou sur les UE en BUT).
    """

    name = "bonus_iutlr"
    displayed_name = "IUT de La Rochelle"
    seuil_moy_gen = 10.0  # tous les points sont comptés
    proportion_point = 0.01


class BonusLeHavre(BonusSportMultiplicatif):
    """Bonus sport IUT du Havre sur moyenne générale et UE

    Les points des modules bonus au dessus de 10/20 sont ajoutés,
    et les moyennes d'UE augmentées de 5% de ces points.
    """

    name = "bonus_iutlh"
    displayed_name = "IUT du Havre"
    seuil_moy_gen = 10.0  # seuls les points au dessus du seuil sont comptés
    amplitude = 0.005  # multiplie les points au dessus du seuil


class BonusLeMans(BonusSportAdditif):
    """Calcul bonus modules optionnels (sport, culture), règle IUT Le Mans.

    Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
    optionnelles sont cumulés.


    En BUT: la moyenne de chacune des UE du semestre est augmentée de
    2% du cumul des points de bonus,

    En DUT/LP: la moyenne générale est augmentée de 5% du cumul des points bonus.

    Dans tous les cas, le bonus est dans la limite de 0,5 point.
    """

    name = "bonus_iutlemans"
    displayed_name = "IUT du Mans"
    seuil_moy_gen = 10.0  # points comptés au dessus de 10.
    bonus_max = 0.5  #

    def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
        """calcul du bonus"""
        # La date du semestre ?
        if self.formsemestre.formation.is_apc():
            self.proportion_point = 0.02
        else:
            self.proportion_point = 0.05
        return super().compute_bonus(
            sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
        )


# Bonus simple, mais avec changement de paramètres en 2010 !
class BonusLille(BonusSportAdditif):
    """Calcul bonus modules optionels (sport, culture), règle IUT Villeneuve d'Ascq

    Les étudiants de l'IUT peuvent suivre des enseignements optionnels
    de l'Université Lille (sports, etc) non rattachés à une unité d'enseignement.

    Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
    optionnelles sont cumulés et 4% (2% avant août 2010) de ces points cumulés
    s'ajoutent à la moyenne générale du semestre déjà obtenue par l'étudiant.
    """

    name = "bonus_lille"
    displayed_name = "IUT de Lille"
    seuil_moy_gen = 10.0  # points comptés au dessus de 10.

    def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
        """calcul du bonus"""
        # La date du semestre ?
        if self.formsemestre.date_debut > datetime.date(2010, 8, 1):
            self.proportion_point = 0.04
        else:
            self.proportion_point = 0.02
        return super().compute_bonus(
            sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
        )


class BonusLyonProvisoire(BonusSportAdditif):
    """Calcul bonus modules optionnels (sport, culture), règle IUT de Lyon (provisoire)

    Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
    optionnelles sont cumulés et 1,8% de ces points cumulés
    s'ajoutent aux moyennes, dans la limite d'1/2 point.
    """

    name = "bonus_lyon_provisoire"
    displayed_name = "IUT de Lyon (provisoire)"
    seuil_moy_gen = 10.0  # points comptés au dessus de 10.
    proportion_point = 0.018
    bonus_max = 0.5


class BonusMulhouse(BonusSportAdditif):
    """Calcul bonus modules optionnels (sport, culture), règle IUT de Mulhouse

    La moyenne  de chacune des UE du  semestre sera  majorée à  hauteur de
    5% du  cumul des points supérieurs à 10 obtenus en matières optionnelles,
    dans la limite de 0,5 point.
    """

    name = "bonus_iutmulhouse"
    displayed_name = "IUT de Mulhouse"
    seuil_moy_gen = 10.0  # points comptés au dessus de 10.
    proportion_point = 0.05
    bonus_max = 0.5  #


class BonusNantes(BonusSportAdditif):
    """IUT de Nantes (Septembre 2018)

    Nous avons différents types de bonification
    (sport, culture, engagement citoyen).

    Nous ajoutons aux moyennes une bonification de 0,2 pour chaque item
    la bonification totale ne doit pas excéder les 0,5 point.
    Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications.

    Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura des modules
    pour chaque activité (Sport, Associations, ...)
    avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la
    valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale)
    """

    name = "bonus_nantes"
    displayed_name = "IUT de Nantes"
    seuil_moy_gen = 0.0  # seuls les points au dessus du seuil sont comptés
    proportion_point = 1  # multiplie les points au dessus du seuil
    bonus_max = 0.5  # plafonnement à 0.5 points


class BonusRoanne(BonusSportAdditif):
    """IUT de Roanne.

    Le bonus est compris entre 0 et 0.6 points
    et est toujours appliqué aux UEs.
    """

    name = "bonus_iutr"
    displayed_name = "IUT de Roanne"
    seuil_moy_gen = 0.0
    bonus_max = 0.6  # plafonnement à 0.6 points
    apply_bonus_mg_to_ues = True  # sur les UE, même en DUT et LP


class BonusStDenis(BonusSportAdditif):
    """Calcul bonus modules optionels (sport, culture), règle IUT Saint-Denis

    Les étudiants de l'IUT peuvent suivre des enseignements optionnels
    de l'Université Paris 13 (sports, musique, deuxième langue,
    culture, etc) non rattachés à une unité d'enseignement. Les points
    au-dessus de 10 sur 20 obtenus dans chacune des matières
    optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
    la moyenne générale du semestre déjà obtenue par l'étudiant, dans la limite
    d'1/2 point.
    """

    name = "bonus_iut_stdenis"
    displayed_name = "IUT de Saint-Denis"
    bonus_max = 0.5


class BonusTours(BonusDirect):
    """Calcul bonus sport & culture IUT Tours.

    Les notes des UE bonus (ramenées sur 20) sont sommées
    et 1/40 (2,5%) est ajouté  aux moyennes: soit à la moyenne générale,
    soit pour le BUT à chaque moyenne d'UE.

    Attention: en GEII, facteur 1/40, ailleurs facteur 1.

    Le bonus total est limité à 1 point.
    """

    name = "bonus_tours"
    displayed_name = "IUT de Tours"
    bonus_max = 1.0  #
    seuil_moy_gen = 0.0  # seuls les points au dessus du seuil sont comptés
    proportion_point = 1.0 / 40.0

    def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
        """calcul différencié selon le département !"""
        if g.scodoc_dept == "GEII":
            self.proportion_point = 1.0 / 40.0
        else:
            self.proportion_point = 1.0
        return super().compute_bonus(
            sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
        )


class BonusVilleAvray(BonusSport):
    """Bonus modules optionels (sport, culture), règle IUT Ville d'Avray.

    Les étudiants de l'IUT peuvent suivre des enseignements optionnels
    de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement.
    Si la note est >= 10 et < 12, bonus de 0.1 point
    Si la note est >= 12 et < 16, bonus de 0.2 point
    Si la note est >= 16, bonus de 0.3 point
    Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
    l'étudiant.
    """

    name = "bonus_iutva"
    displayed_name = "IUT de Ville d'Avray"

    def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
        """calcul du bonus"""
        # Calcule moyenne pondérée des notes de sport:
        bonus_moy_arr = np.sum(
            sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
        ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
        bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
        bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
        bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3

        # Bonus moyenne générale, et 0 sur les UE
        self.bonus_moy_gen = pd.Series(bonus_moy_arr, index=self.etuds_idx, dtype=float)
        if self.bonus_max is not None:
            # Seuil: bonus (sur moy. gen.) limité à bonus_max points
            self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max)

        # Laisse bonus_ues à None, en APC le bonus moy. gen. sera réparti sur les UEs.


class BonusIUTV(BonusSportAdditif):
    """Calcul bonus modules optionels (sport, culture), règle IUT Villetaneuse

    Les étudiants de l'IUT peuvent suivre des enseignements optionnels
    de l'Université Paris 13 (sports, musique, deuxième langue,
    culture, etc) non rattachés à une unité d'enseignement. Les points
    au-dessus de 10 sur 20 obtenus dans chacune des matières
    optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
    la moyenne générale du semestre déjà obtenue par l'étudiant.
    """

    name = "bonus_iutv"
    displayed_name = "IUT de Villetaneuse"
    pass  # oui, c'ets le bonus par défaut


def get_bonus_class_dict(start=BonusSport, d=None):
    """Dictionnaire des classes de bonus
    (liste les sous-classes de BonusSport ayant un nom)
    Resultat: { name : class }
    """
    if d is None:
        d = {}
    if start.name != "virtual":
        d[start.name] = start
    for subclass in start.__subclasses__():
        get_bonus_class_dict(subclass, d=d)
    return d
