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

"""Operations de base sur les formsemestres
"""

from sco_utils import *
from notesdb import *
from notes_log import log
from gen_tables import GenTable

import sco_codes_parcours
from sco_codes_parcours import NO_SEMESTRE_ID

_formsemestreEditor = EditableTable(
    "notes_formsemestre",
    "formsemestre_id",
    (
        "formsemestre_id",
        "semestre_id",
        "formation_id",
        "titre",
        "date_debut",
        "date_fin",
        "gestion_compensation",
        "gestion_semestrielle",
        "etat",
        "bul_hide_xml",
        "bul_bgcolor",
        "modalite",
        "resp_can_edit",
        "resp_can_change_ens",
        "ens_can_edit_eval",
        "elt_sem_apo",
        "elt_annee_apo",
    ),
    sortkey="date_debut",
    output_formators={
        "date_debut": DateISOtoDMY,
        "date_fin": DateISOtoDMY,
        "gestion_compensation": str,
        "gestion_semestrielle": str,
        "etat": str,
        "bul_hide_xml": str,
    },
    input_formators={
        "date_debut": DateDMYtoISO,
        "date_fin": DateDMYtoISO,
        "gestion_compensation": int,
        "gestion_semestrielle": int,
        "etat": int,
        "bul_hide_xml": int,
    },
)


def get_formsemestre(context, formsemestre_id):
    "list ONE formsemestre"
    try:
        sem = do_formsemestre_list(context, args={"formsemestre_id": formsemestre_id})[
            0
        ]
        return sem
    except:
        log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
        raise


def do_formsemestre_list(context, *a, **kw):
    "list formsemestres"
    # log('do_formsemestre_list: a=%s kw=%s' % (str(a),str(kw)))
    cnx = context.GetDBConnexion()

    sems = _formsemestreEditor.list(cnx, *a, **kw)

    # Ajoute les étapes Apogee et les responsables:
    for sem in sems:
        sem["etapes"] = read_formsemestre_etapes(context, sem["formsemestre_id"])
        sem["responsables"] = read_formsemestre_responsables(
            context, sem["formsemestre_id"]
        )

    # Filtre sur code etape si indiqué:
    if "args" in kw:
        etape = kw["args"].get("etape_apo", None)
        if etape:
            sems = [sem for sem in sems if etape in sem["etapes"]]

    for sem in sems:
        formsemestre_enrich(context, sem)

    # tri par date
    sems.sort(
        lambda x, y: cmp(
            (y["dateord"], y["semestre_id"]), (x["dateord"], x["semestre_id"])
        )
    )

    return sems


def formsemestre_enrich(context, sem):
    """Ajoute champs souvent utiles: titre + annee et dateord (pour tris)"""
    # imports ici pour eviter refs circulaires
    import sco_formsemestre_edit
    import scolars

    F = context.Notes.formation_list(args={"formation_id": sem["formation_id"]})[0]
    parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
    # 'S1', 'S2', ... ou '' pour les monosemestres
    if sem["semestre_id"] != NO_SEMESTRE_ID:
        sem["sem_id_txt"] = "S%s" % sem["semestre_id"]
    else:
        sem["sem_id_txt"] = ""
    # Nom avec numero semestre:
    sem["titre_num"] = sem["titre"]  # eg "DUT Informatique"
    if sem["semestre_id"] != NO_SEMESTRE_ID:
        sem["titre_num"] += " %s %s" % (
            parcours.SESSION_NAME,
            sem["semestre_id"],
        )  # eg "DUT Informatique semestre 2"

    sem["dateord"] = DateDMYtoISO(sem["date_debut"])
    sem["date_debut_iso"] = DateDMYtoISO(sem["date_debut"])
    sem["date_fin_iso"] = DateDMYtoISO(sem["date_fin"])
    try:
        mois_debut, annee_debut = sem["date_debut"].split("/")[1:]
    except:
        mois_debut, annee_debut = "", ""
    try:
        mois_fin, annee_fin = sem["date_fin"].split("/")[1:]
    except:
        mois_fin, annee_fin = "", ""
    sem["annee_debut"] = annee_debut
    sem["annee_fin"] = annee_fin
    sem["mois_debut_ord"] = int(mois_debut)
    sem["mois_fin_ord"] = int(mois_fin)

    sem["annee"] = annee_debut
    # 2007 ou 2007-2008:
    sem["anneescolaire"] = annee_scolaire_repr(int(annee_debut), sem["mois_debut_ord"])
    # La période: considère comme "S1" (ou S3) les débuts en aout-sept-octobre
    # devrait sans doute pouvoir etre changé...
    if sem["mois_debut_ord"] >= 8 and sem["mois_debut_ord"] <= 10:
        sem["periode"] = 1  # typiquement, début en septembre: S1, S3...
    else:
        sem["periode"] = 2  # typiquement, début en février: S2, S4...

    sem["titreannee"] = "%s %s  %s" % (
        sem["titre_num"],
        sem.get("modalite", ""),
        annee_debut,
    )
    if annee_fin != annee_debut:
        sem["titreannee"] += "-" + annee_fin
        sem["annee"] += "-" + annee_fin
    # et les dates sous la forme "oct 2007 - fev 2008"
    months = scolars.abbrvmonthsnames
    if mois_debut:
        mois_debut = months[int(mois_debut) - 1]
    if mois_fin:
        mois_fin = months[int(mois_fin) - 1]
    sem["mois_debut"] = mois_debut + " " + annee_debut
    sem["mois_fin"] = mois_fin + " " + annee_fin
    sem["titremois"] = "%s %s  (%s - %s)" % (
        sem["titre_num"],
        sem.get("modalite", ""),
        sem["mois_debut"],
        sem["mois_fin"],
    )
    sem["session_id"] = sco_formsemestre_edit.get_formsemestre_session_id(
        context, sem, F, parcours
    )
    sem["etapes"] = read_formsemestre_etapes(context, sem["formsemestre_id"])
    sem["etapes_apo_str"] = formsemestre_etape_apo_str(sem)
    sem["responsables"] = read_formsemestre_responsables(
        context, sem["formsemestre_id"]
    )


def formsemestre_etape_apo_str(sem):
    "chaine décrivant le(s) codes étapes Apogée"
    return etapes_apo_str(sem["etapes"])


def etapes_apo_str(etapes):
    "Chaine decrivant une liste d'instance de ApoEtapeVDI"
    return ", ".join([str(x) for x in etapes])


def do_formsemestre_edit(context, sem, cnx=None, **kw):
    if not cnx:
        cnx = context.GetDBConnexion()

    _formsemestreEditor.edit(cnx, sem, **kw)
    write_formsemestre_etapes(context, sem)
    write_formsemestre_responsables(context, sem)

    context._inval_cache(formsemestre_id=sem["formsemestre_id"])  # > modif formsemestre


def read_formsemestre_responsables(context, formsemestre_id):
    """recupere liste des responsables de ce semestre
    :returns: liste de chaines
    """
    r = SimpleDictFetch(
        context,
        "SELECT responsable_id FROM notes_formsemestre_responsables WHERE formsemestre_id = %(formsemestre_id)s",
        {"formsemestre_id": formsemestre_id},
    )
    return [x["responsable_id"] for x in r]


def write_formsemestre_responsables(context, sem):
    return _write_formsemestre_aux(context, sem, "responsables", "responsable_id")


def read_formsemestre_etapes(context, formsemestre_id):
    """recupere liste des codes etapes associés à ce semestre
    :returns: liste d'instance de ApoEtapeVDI
    """
    r = SimpleDictFetch(
        context,
        "SELECT etape_apo FROM notes_formsemestre_etapes WHERE formsemestre_id = %(formsemestre_id)s",
        {"formsemestre_id": formsemestre_id},
    )
    return [ApoEtapeVDI(x["etape_apo"]) for x in r if x["etape_apo"]]


def write_formsemestre_etapes(context, sem):
    return _write_formsemestre_aux(context, sem, "etapes", "etape_apo")


def _write_formsemestre_aux(context, sem, fieldname, valuename):
    """fieldname: 'etapes' ou 'responsables'
    valuename: 'etape_apo' ou 'responsable_id'
    """
    if not "etapes" in sem:
        return
    cnx = context.GetDBConnexion(autocommit=False)
    cursor = cnx.cursor(cursor_factory=ScoDocCursor)
    tablename = "notes_formsemestre_" + fieldname
    try:
        cursor.execute(
            "DELETE from " + tablename + " where formsemestre_id = %(formsemestre_id)s",
            {"formsemestre_id": sem["formsemestre_id"]},
        )
        for item in sem[fieldname]:
            if item:
                cursor.execute(
                    "INSERT INTO "
                    + tablename
                    + " (formsemestre_id, "
                    + valuename
                    + ") VALUES (%(formsemestre_id)s, %("
                    + valuename
                    + ")s)",
                    {"formsemestre_id": sem["formsemestre_id"], valuename: str(item)},
                )
    except:
        log("Warning: exception in write_formsemestre_aux !")
        cnx.rollback()
        raise
    cnx.commit()


# ------ Utilisé pour stocker le VDI avec le code étape (noms de fichiers maquettes et code semestres)
class ApoEtapeVDI:
    _ETAPE_VDI_SEP = "!"

    def __init__(self, etape_vdi=None, etape="", vdi=""):
        """Build from string representation, e.g. 'V1RT!111'"""
        if etape_vdi:
            self.etape_vdi = etape_vdi
            self.etape, self.vdi = self.split_etape_vdi(etape_vdi)
        elif etape:
            if self._ETAPE_VDI_SEP in etape:
                raise ScoValueError("valeur code etape invalide")
            self.etape, self.vdi = etape, vdi
            self.etape_vdi = self.concat_etape_vdi(etape, vdi)
        else:
            self.etape_vdi, self.etape, self.vdi = "", "", ""

    def __repr__(self):
        return self.__class__.__name__ + "('" + str(self) + "')"

    def __str__(self):
        return self.etape_vdi

    def __cmp__(self, other):
        """Test égalité de deux codes étapes.
        Si le VDI des deux est spécifié, on l'utilise. Sinon, seul le code étape est pris en compte.
        Donc V1RT == V1RT!111, V1RT!110 == V1RT, V1RT!77 != V1RT!78, ...
        """
        if other is None:
            return -1
        if type(other) == str:
            other = ApoEtapeVDI(other)

        if self.vdi and other.vdi:
            return cmp((self.etape, self.vdi), (other.etape, other.vdi))
        else:
            return cmp(self.etape, other.etape)

    def split_etape_vdi(self, etape_vdi):
        """Etape Apogee can be stored as 'V1RT' or, including the VDI version,
        as 'V1RT!111'
        Returns etape, VDI
        """
        if etape_vdi:
            t = etape_vdi.split(self._ETAPE_VDI_SEP)
            if len(t) == 1:
                etape = etape_vdi
                vdi = ""
            elif len(t) == 2:
                etape, vdi = t
            else:
                raise ValueError("invalid code etape")
            return etape, vdi
        else:
            return etape_vdi, ""

    def concat_etape_vdi(self, etape, vdi=""):
        if vdi:
            return self._ETAPE_VDI_SEP.join([etape, vdi])
        else:
            return etape


"""
[ ApoEtapeVDI('V1RT!111'), ApoEtapeVDI('V1RT!112'), ApoEtapeVDI('VCRT'), ApoEtapeVDI('V1RT') ]  
"""


def sem_set_responsable_name(context, sem):
    "ajoute champs responsable_name"
    sem["responsable_name"] = ", ".join(
        [
            context.Users.user_info(responsable_id)["nomprenom"]
            for responsable_id in sem["responsables"]
        ]
    )


def sem_in_semestre_scolaire(context, sem, year=False, saison=0, REQUEST=None):
    """n'utilise que la date de debut, pivot au 1er aout
    si annee non specifiée, année scolaire courante
    Patch Jmp: ajout du parametre optionnel saison
    1 = sept, 0 = janvier, None = année complète
    si saison non spécifiée: année complète
    pivot de saison au 1er décembre
    XXX TODO: la période (ici appelée "saison" devrait être éditable
    manuellement dans le formsemestre_edit afin de couvrir els cas particulier
    comme un semestre S2 qui commecerait en décembre... voire novembre.
    )
    """
    if not year:
        year = AnneeScolaire(REQUEST)
    # est-on dans la même année universitaire ?
    if sem["mois_debut_ord"] > 7:
        if sem["annee_debut"] != str(year):
            return False
    else:
        if sem["annee_debut"] != str(year + 1):
            return False
    # rafinement éventuel sur le semestre
    # saison is None => pas de rafinement => True
    if saison == 0:
        return True
    elif saison == 1:  # calcul en fonction de la saison
        return sem["mois_debut_ord"] > 7 and sem["mois_debut_ord"] < 12
    else:  # saison == 0
        return sem["mois_debut_ord"] <= 7 or sem["mois_debut_ord"] == 12


def sem_in_annee_scolaire(context, sem, year=False, REQUEST=None):
    """Test si sem appartient à l'année scolaire year (int).
    N'utilise que la date de debut, pivot au 1er août.
    Si annee non specifiée, année scolaire courante
    """
    if not year:
        year = AnneeScolaire(REQUEST)
    return ((sem["annee_debut"] == str(year)) and (sem["mois_debut_ord"] > 7)) or (
        (sem["annee_debut"] == str(year + 1)) and (sem["mois_debut_ord"] <= 7)
    )


def sem_une_annee(context, sem):
    """Test si sem est entièrement sur la même année scolaire.
    (ce n'est pas obligatoire mais si ce n'est pas le cas les exports Apogée ne vont pas fonctionner)
    pivot au 1er août.
    """
    if sem["date_debut_iso"] > sem["date_fin_iso"]:
        log("Warning: semestre %(formsemestre_id)s begins after ending !" % sem)
        return False

    debut = int(sem["annee_debut"])
    if sem["mois_debut_ord"] < 8:  # considere que debut sur l'anne scolaire precedente
        debut -= 1
    fin = int(sem["annee_fin"])
    if (
        sem["mois_fin_ord"] < 9
    ):  # 9 (sept) pour autoriser un début en sept et une fin en aout
        fin -= 1
    return debut == fin


def scodoc_get_all_unlocked_sems(context):
    """Liste de tous les semestres non verrouillés de tous les départements"""
    depts = context.list_depts()
    semdepts = []
    for dept in depts:
        semdepts += [
            (sem, dept.Scolarite.Notes)
            for sem in do_formsemestre_list(dept.Scolarite.Notes)
            if sem["etat"] == "1"
        ]
    return semdepts


def table_formsemestres(
    context,
    sems,
    columns_ids=(),
    sup_columns_ids=(),
    html_title="<h2>Semestres</h2>",
    html_next_section="",
    REQUEST=None,
):
    """Une table presentant des semestres"""
    for sem in sems:
        sem_set_responsable_name(context, sem)
        sem["_titre_num_target"] = (
            "formsemestre_status?formsemestre_id=%s" % sem["formsemestre_id"]
        )

    if not columns_ids:
        columns_ids = (
            "etat",
            "modalite",
            "mois_debut",
            "mois_fin",
            "titre_num",
            "responsable_name",
            "etapes_apo_str",
        )
    columns_ids += sup_columns_ids

    titles = {
        "modalite": "",
        "mois_debut": "Début",
        "mois_fin": "Fin",
        "titre_num": "Semestre",
        "responsable_name": "Resp.",
        "etapes_apo_str": "Apo.",
    }
    if sems:
        preferences = context.get_preferences(sems[0]["formsemestre_id"])
    else:
        preferences = context.get_preferences()
    tab = GenTable(
        columns_ids=columns_ids,
        rows=sems,
        titles=titles,
        html_class="table_leftalign",
        html_sortable=True,
        html_title=html_title,
        html_next_section=html_next_section,
        html_empty_element="<p><em>aucun résultat</em></p>",
        page_title="Semestres",
        preferences=preferences,
    )
    return tab


def list_formsemestre_by_etape(
    context, etape_apo=False, annee_scolaire=False, REQUEST=None
):
    """Liste des semestres de cette etape, pour l'annee scolaire indiquée (sinon, pour toutes)"""
    ds = {}  # formsemestre_id : sem
    if etape_apo:
        sems = do_formsemestre_list(context, args={"etape_apo": etape_apo})
        for sem in sems:
            if annee_scolaire:  # restriction annee scolaire
                if sem_in_annee_scolaire(
                    context, sem, year=int(annee_scolaire), REQUEST=REQUEST
                ):
                    ds[sem["formsemestre_id"]] = sem
        sems = ds.values()
    else:
        sems = do_formsemestre_list(context)
        if annee_scolaire:
            sems = [
                sem
                for sem in sems
                if sem_in_annee_scolaire(
                    context, sem, year=int(annee_scolaire), REQUEST=REQUEST
                )
            ]

    sems.sort(key=lambda s: (s["modalite"], s["dateord"]))
    return sems


def view_formsemestre_by_etape(context, etape_apo=None, format="html", REQUEST=None):
    """Affiche table des semestres correspondants à l'étape"""
    if etape_apo:
        html_title = (
            """<h2>Semestres courants de l'étape <tt>%s</tt></h2>""" % etape_apo
        )
    else:
        html_title = """<h2>Semestres courants</h2>"""
    tab = table_formsemestres(
        context,
        list_formsemestre_by_etape(
            context, etape_apo=etape_apo, annee_scolaire=AnneeScolaire(REQUEST)
        ),
        html_title=html_title,
        html_next_section="""<form action="view_formsemestre_by_etape">
    Etape: <input name="etape_apo" type="text" size="8"></input>    
        </form>""",
        REQUEST=REQUEST,
    )
    tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "")
    return tab.make_page(context, format=format, REQUEST=REQUEST)


def sem_has_etape(sem, code_etape):
    return code_etape in sem["etapes"]
