##############################################################################
#
# ScoDoc
#
# 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
#
##############################################################################

"""
Vues sur les formations BUT

Emmanuel Viennet, 2023
"""

from flask import flash, g, redirect, render_template, request, url_for

from app import db, log
from app.decorators import (
    scodoc,
    permission_required,
)

from app.forms.formation.ue_parcours_ects import UEParcoursECTSForm

from app.models import (
    ApcCompetence,
    ApcNiveau,
    ApcParcours,
    ApcReferentielCompetences,
    Formation,
    UniteEns,
)
from app.scodoc.codes_cursus import UE_STANDARD
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ScoValueError
from app.views import notes_bp as bp
from app.views import ScoData


@bp.route("/parcour_formation/<int:formation_id>/<int:parcour_id>")
@bp.route("/parcour_formation/<int:formation_id>")
@scodoc
@permission_required(Permission.ScoView)
def parcour_formation(formation_id: int, parcour_id: int = None) -> str:
    """visu HTML d'un parcours dans une formation,
    avec les compétences, niveaux et UEs associées."""
    formation: Formation = Formation.query.filter_by(
        id=formation_id, dept_id=g.scodoc_dept_id
    ).first_or_404()
    ref_comp: ApcReferentielCompetences = formation.referentiel_competence
    if ref_comp is None:
        return "pas de référentiel de compétences"
    if parcour_id is None:
        parcour = None
    else:
        parcour: ApcParcours = ref_comp.parcours.filter_by(id=parcour_id).first()
        if parcour is None:
            raise ScoValueError("parcours invalide ou hors référentiel de formation")

    competences_parcour = (
        parcour_formation_competences(parcour, formation) if parcour else None
    )

    return render_template(
        "but/parcour_formation.j2",
        formation=formation,
        parcour=parcour,
        competences_parcour=competences_parcour,
        sco=ScoData(),
        title=f"{formation.acronyme} - Niveaux et UEs",
    )


def ue_associee_au_niveau_du_parcours(
    ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
) -> UniteEns:
    "L'UE associée à ce niveau, ou None"
    ues = [ue for ue in ues_possibles if ue.niveau_competence_id == niveau.id]
    if len(ues) > 1:
        # plusieurs UEs associées à ce niveau: élimine celles sans parcours
        ues_pair_avec_parcours = [ue for ue in ues if ue.parcours]
        if ues_pair_avec_parcours:
            ues = ues_pair_avec_parcours
    if len(ues) > 1:
        log(f"_niveau_ues: {len(ues)} associées au niveau {niveau} / {sem_name}")
    return ues[0] if ues else None


def parcour_formation_competences(parcour: ApcParcours, formation: Formation) -> list:
    """
    [
        {
            'competence' : ApcCompetence,
            'niveaux' : {
                1 : { ... },
                2 : { ... },
                3 : {
                    'niveau' : ApcNiveau,
                    'ue_impair' : UniteEns, # actuellement associée
                    'ues_impair' : list[UniteEns], # choix possibles
                    'ue_pair' : UniteEns,
                    'ues_pair' : list[UniteEns],
                }
            }
        }
    ]
    """

    def _niveau_ues(competence: ApcCompetence, annee: int) -> dict:
        "niveau et ues pour cette compétence de cette année du parcours"
        niveaux = ApcNiveau.niveaux_annee_de_parcours(
            parcour, annee, competence=competence
        )
        if len(niveaux) > 0:
            if len(niveaux) > 1:
                log(
                    f"""_niveau_ues: plus d'un niveau pour {competence} 
                    annee {annee} parcours {parcour.code}"""
                )
            niveau = niveaux[0]
        elif len(niveaux) == 0:
            return {
                "niveau": None,
                "ue_pair": None,
                "ue_impair": None,
                "ues_pair": [],
                "ues_impair": [],
            }
        # Toutes les UEs de la formation dans ce parcours ou tronc commun
        ues = [
            ue
            for ue in formation.ues
            if ((not ue.parcours) or (parcour.id in (p.id for p in ue.parcours)))
            and ue.type == UE_STANDARD
        ]
        ues_pair_possibles = [ue for ue in ues if ue.semestre_idx == (2 * annee)]
        ues_impair_possibles = [ue for ue in ues if ue.semestre_idx == (2 * annee - 1)]

        # UE associée au niveau dans ce parcours
        ue_pair = ue_associee_au_niveau_du_parcours(
            ues_pair_possibles, niveau, f"S{2*annee}"
        )
        ue_impair = ue_associee_au_niveau_du_parcours(
            ues_impair_possibles, niveau, f"S{2*annee-1}"
        )

        return {
            "niveau": niveau,
            "ue_pair": ue_pair,
            "ues_pair": [
                ue
                for ue in ues_pair_possibles
                if (not ue.niveau_competence) or ue.niveau_competence.id == niveau.id
            ],
            "ue_impair": ue_impair,
            "ues_impair": [
                ue
                for ue in ues_impair_possibles
                if (not ue.niveau_competence) or ue.niveau_competence.id == niveau.id
            ],
        }

    competences = [
        {
            "competence": competence,
            "niveaux": {annee: _niveau_ues(competence, annee) for annee in (1, 2, 3)},
        }
        for competence in parcour.query_competences()
    ]
    return competences


@bp.route("/ue_parcours_ects/<int:ue_id>", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
def ue_parcours_ects(ue_id: int):
    """formulaire (div) pour associer des ECTS par parcours d'une UE"""
    ue: UniteEns = (
        UniteEns.query.filter_by(id=ue_id)
        .join(Formation)
        .filter_by(dept_id=g.scodoc_dept_id)
        .first_or_404()
    )
    if ue.type != UE_STANDARD:
        raise ScoValueError("Pas d'ECTS / Parcours pour ce type d'UE")
    ref_comp = ue.formation.referentiel_competence
    if ref_comp is None:
        raise ScoValueError("Pas référentiel de compétence pour cette UE !")
    form = UEParcoursECTSForm(ue)

    edit_url = url_for("notes.ue_edit", scodoc_dept=g.scodoc_dept, ue_id=ue.id)
    if request.method == "POST":
        if request.form.get("submit"):
            if form.validate():
                for parcour in ue.formation.referentiel_competence.parcours:
                    field = getattr(form, f"ects_parcour_{parcour.id}")
                    if field:
                        ue.set_ects(field.data, parcour=parcour)
                db.session.commit()
                flash("ECTS enregistrés")
                return redirect(edit_url)
        elif request.form.get("cancel"):
            return redirect(edit_url)

    return render_template(
        "formation/ue_assoc_parcours_ects.j2", form=form, sco=ScoData(), ue=ue
    )
