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

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 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
#
##############################################################################

"""Gestion des tags sur les modules

   Implementation expérimentale (Jul. 2016) pour grouper les modules sur
   les avis de poursuites d'études.


   Pour l'UI, voir https://goodies.pixabay.com/jquery/tag-editor/demo.html
"""

import types

import sco_utils as scu
import notesdb as ndb
from notes_log import log
from sco_exceptions import ScoValueError, AccessDenied
from sco_permissions import ScoEditFormationTags, ScoChangeFormation

# Opérations à implementer:
#  + liste des modules des formations de code donné (formation_code) avec ce tag
#  + liste de tous les noms de tag
#  + tag pour un nom
#  + creer un tag (nom)
#  + lier un tag à un module
#  + enlever un tag d'un module (si c'est le dernier, supprimer le tag lui même)
#
# API publiée:
#   module_tag_list_all  -> tous les noms d etags (pour l'autocomplete)
#   module_tag_list( module_id ) -> les noms de tags associés à ce module
#   module_tag_set( module_id, taglist ) -> modifie les tags


class ScoTag:
    """Generic tags for ScoDoc"""

    # must be overloaded:
    tag_table = None  # table (tag_id, title)
    assoc_table = None  # table (tag_id, object_id)
    obj_colname = None  # column name for object_id in assoc_table

    def __init__(self, context, title, object_id=""):
        """Load tag, or create if does not exist"""
        self.context = context
        self.title = title.strip()
        if not self.title:
            raise ScoValueError("invalid empty tag")
        r = ndb.SimpleDictFetch(
            context,
            "SELECT * FROM " + self.tag_table + " WHERE title = %(title)s",
            {"title": self.title},
        )
        if r:
            self.tag_id = r[0]["tag_id"]
        else:
            # Create new tag:
            log("creating new tag: %s" % self.title)
            cnx = context.GetDBConnexion()
            oid = ndb.DBInsertDict(
                cnx, self.tag_table, {"title": self.title}, commit=True
            )
            self.tag_id = ndb.SimpleDictFetch(
                context,
                "SELECT tag_id FROM " + self.tag_table + " WHERE oid=%(oid)s",
                {"oid": oid},
            )[0]["tag_id"]
        if object_id:
            self.tag_object(object_id)

    def __repr__(self):  # debug
        return '<tag "%s">' % self.title

    def delete(self):
        """Delete this tag.
        This object should not be used after this call !
        """
        args = {"tag_id": self.tag_id}
        ndb.SimpleQuery(
            self.context,
            "DELETE FROM " + self.tag_table + " t WHERE t.tag_id = %(tag_id)s",
            args,
        )

    def tag_object(self, object_id):
        """Associate tag to given object"""
        args = {self.obj_colname: object_id, "tag_id": self.tag_id}
        r = ndb.SimpleDictFetch(
            self.context,
            "SELECT * FROM "
            + self.assoc_table
            + " a WHERE a."
            + self.obj_colname
            + " = %("
            + self.obj_colname
            + ")s AND a.tag_id = %(tag_id)s",
            args,
        )
        if not r:
            log("tag %s with %s" % (object_id, self.title))
            cnx = self.context.GetDBConnexion()
            ndb.DBInsertDict(cnx, self.assoc_table, args, commit=True)

    def remove_tag_from_object(self, object_id):
        """Remove tag from module.
        If no more modules tagged with this tag, delete it.
        Return True if Tag still exists.
        """
        log("removing tag %s from %s" % (self.title, object_id))
        args = {"object_id": object_id, "tag_id": self.tag_id}
        ndb.SimpleQuery(
            self.context,
            "DELETE FROM  "
            + self.assoc_table
            + " a WHERE a."
            + self.obj_colname
            + " = %(object_id)s AND a.tag_id = %(tag_id)s",
            args,
        )
        r = ndb.SimpleDictFetch(
            self.context,
            """SELECT * FROM notes_modules_tags mt WHERE tag_id = %(tag_id)s
            """,
            args,
        )
        if not r:
            # tag no more used, delete
            ndb.SimpleQuery(
                self.context,
                """DELETE FROM notes_tags t WHERE t.tag_id = %(tag_id)s""",
                args,
            )


class ModuleTag(ScoTag):
    """Tags sur les modules dans les programmes pédagogiques"""

    tag_table = "notes_tags"  # table (tag_id, title)
    assoc_table = "notes_modules_tags"  # table (tag_id, object_id)
    obj_colname = "module_id"  # column name for object_id in assoc_table

    def list_modules(self, formation_code=""):
        """Liste des modules des formations de code donné (formation_code) avec ce tag"""
        args = {"tag_id": self.tag_id}
        if not formation_code:
            # tous les modules de toutes les formations !
            r = ndb.SimpleDictFetch(
                self.context,
                "SELECT "
                + self.obj_colname
                + " FROM "
                + self.assoc_table
                + " WHERE tag_id = %(tag_id)s",
                args,
            )
        else:
            args["formation_code"] = formation_code

            r = ndb.SimpleDictFetch(
                self.context,
                """SELECT mt.module_id 
                FROM notes_modules_tags mt, notes_modules m, notes_formations f
                WHERE mt.tag_id = %(tag_id)s 
                AND m.module_id = mt.module_id 
                AND m.formation_id = f.formation_id
                AND f.formation_code = %(formation_code)s
                """,
                args,
            )
        return [x["module_id"] for x in r]


# API


def module_tag_search(context, term, REQUEST=None):
    """List all used tag names (for auto-completion)"""
    # restrict charset to avoid injections
    if not scu.ALPHANUM_EXP.match(term.decode(scu.SCO_ENCODING)):
        data = []
    else:
        r = ndb.SimpleDictFetch(
            context,
            "SELECT title FROM notes_tags WHERE title LIKE %(term)s",
            {"term": term + "%"},
        )
        data = [x["title"] for x in r]

    return scu.sendJSON(REQUEST, data)


def module_tag_list(context, module_id=""):
    """les noms de tags associés à ce module"""
    r = ndb.SimpleDictFetch(
        context,
        """SELECT t.title
          FROM notes_modules_tags mt, notes_tags t
          WHERE mt.tag_id = t.tag_id
          AND mt.module_id = %(module_id)s
          """,
        {"module_id": module_id},
    )
    return [x["title"] for x in r]


def module_tag_set(context, module_id="", taglist=[], REQUEST=None):
    """taglist may either be:
    a string with tag names separated by commas ("un;deux")
    or a list of strings (["un", "deux"])
    """
    # We check permission here to allow old Admins (withn only ScoChangeFormation perm)
    if REQUEST:  # called from Web
        authuser = REQUEST.AUTHENTICATED_USER
        tag_editable = authuser.has_permission(
            ScoEditFormationTags, context
        ) or authuser.has_permission(ScoChangeFormation, context)
        if not tag_editable:
            raise AccessDenied("Modification des tags impossible pour %s" % authuser)
    #
    if not taglist:
        taglist = []
    elif type(taglist) == types.StringType:
        taglist = taglist.split(",")
    taglist = [t.strip() for t in taglist]
    log("module_tag_set: module_id=%s taglist=%s" % (module_id, taglist))
    # Sanity check:
    Mod = context.do_module_list(args={"module_id": module_id})
    if not Mod:
        raise ScoValueError("invalid module !")

    newtags = set(taglist)
    oldtags = set(module_tag_list(context, module_id))
    to_del = oldtags - newtags
    to_add = newtags - oldtags

    # should be atomic, but it's not.
    for tagname in to_add:
        t = ModuleTag(context, tagname, object_id=module_id)
    for tagname in to_del:
        t = ModuleTag(context, tagname)
        t.remove_tag_from_object(module_id)


def get_etud_tagged_modules(context, etudid, tagname):
    """Liste d'infos sur les modules de ce semestre avec ce tag.
    Cherche dans tous les semestres dans lesquel l'étudiant est ou a été inscrit.
    Construit la liste des modules avec le tag donné par tagname
    """
    etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
    R = []
    for sem in etud["sems"]:
        nt = context._getNotesCache().get_NotesTable(context, sem["formsemestre_id"])
        modimpls = nt.get_modimpls()
        for modimpl in modimpls:
            tags = module_tag_list(context, module_id=modimpl["module_id"])
            if tagname in tags:
                moy = nt.get_etud_mod_moy(
                    modimpl["moduleimpl_id"], etudid
                )  # ou NI si non inscrit
                R.append(
                    {
                        "sem": sem,
                        "moy": moy,  # valeur réelle, ou NI (non inscrit au module ou NA0 (pas de note)
                        "moduleimpl": modimpl,
                        "tags": tags,
                    }
                )
    return R


def split_tagname_coeff(tag, separateur=":"):
    """Découpe un tag saisi par un utilisateur pour en extraire un tagname
    (chaine de caractère correspondant au tag)
    et un éventuel coefficient de pondération, avec le séparateur fourni (par défaut ":").
    Renvoie le résultat sous la forme d'une liste [tagname, pond] où pond est un float

    Auteur: CB
    """
    if separateur in tag:
        temp = tag.split(":")
        try:
            pond = float(temp[1])
            return [temp[0], pond]
        except:
            return [tag, 1.0]  # renvoie tout le tag si le découpage à échouer
    else:
        # initialise le coeff de pondération à 1 lorsqu'aucun coeff de pondération n'est indiqué dans le tag
        return [tag, 1.0]


"""Tests:
from debug import *
from sco_tag_module import *
context = go_dept(app, 'RT').Notes

t = ModuleTag(context, 'essai')
t.tag_module('totoro') # error (module invalide)
t.tag_module('MOD21460')
t.delete() # detruit tag et assoc
t = ModuleTag(context, 'essai2')
t.tag_module('MOD21460')
t.tag_module('MOD21464')
t.list_modules()
t.list_modules(formation_code='ccc') # empty list
t.list_modules(formation_code='FCOD2')


Un essai de get_etud_tagged_modules:
from debug import *
from sco_tag_module import *
context = go_dept(app, 'GEA').Notes

etudid='GEAEID80687'
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
sem = etud['sems'][0]

[ tm['moy'] for tm in get_etud_tagged_modules(context, etudid, 'allo') ]

# si besoin après modif par le Web:
# context._inval_cache()
"""
