"""Specific views for keywords / classification schemes

:organization: Logilab
:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"

from logilab.mtconverter import xml_escape

from cubicweb import Unauthorized
from cubicweb.predicates import is_instance, rql_condition, relation_possible
from cubicweb_web.view import EntityView
from cubicweb_web import stdmsgs, component, facet
from cubicweb_web.views import primary, treeview
from cubicweb_web.views.ajaxcontroller import ajaxfunc

from cubicweb_web.views import uicfg

_pvs = uicfg.primaryview_section
_pvs.tag_object_of(("*", "applied_to", "*"), "hidden")
_pvs.tag_subject_of(("*", "applied_to", "*"), "hidden")
_pvs.tag_object_of(("*", "included_in", "Classification"), "hidden")

_abaa = uicfg.actionbox_appearsin_addmenu
_abaa.tag_object_of(("CodeKeyword", "included_in", "Classification"), True)
_abaa.tag_object_of(("Keyword", "included_in", "Classification"), True)
_abaa.tag_object_of(("CodeKeyword", "subkeyword_of", "CodeKeyword"), True)
_abaa.tag_object_of(("Keyword", "subkeyword_of", "Keyword"), True)


# classification views ########################################################


class ClassificationPrimaryView(primary.PrimaryView):
    __select__ = is_instance("Classification")

    def render_entity_attributes(self, entity):
        pass

    def render_entity_relations(self, entity):
        rset = self._cw.execute(
            "Any K ORDERBY N WHERE K included_in C, "
            "NOT K subkeyword_of KK, K name N, "
            "C eid %(x)s",
            {"x": entity.eid},
        )
        self.wview("treeview", rset, "null")


# keyword views ###############################################################


class KeywordPrimaryView(primary.PrimaryView):
    __select__ = is_instance("Keyword")

    def cell_call(self, row, col, **kwargs):
        entity = self.cw_rset.get_entity(row, col)
        self.w(f'<h1 class="titleUnderline">{xml_escape(entity.dc_long_title())}</h1>')
        rset = entity.related("subkeyword_of", "object")
        self.wview("treeview", rset, "null")


class KeywordComboBoxView(treeview.TreePathView):
    """display keyword in edition's combobox"""

    __regid__ = "combobox"
    __select__ = is_instance("Keyword", "Classification")

    item_vid = "text"
    separator = " > "


# skos views ############################################################


SKOS_OPENING_ROOT = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rdfs [
    <!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <!ENTITY rdfs "http://www.w3.org/2000/01/rdf-schema#">
    <!ENTITY dc "http://purl.org/dc/elements/1.1/">
    <!ENTITY dct "http://purl.org/dc/terms/">
]>
<rdf:RDF  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
          xmlns:skos="http://www.w3.org/2004/02/skos/core#">
"""
SKOS_CLOSING_ROOT = "</rdf:RDF>"


class SkosView(EntityView):
    __regid__ = "skos"
    content_type = "application/xml"
    templatable = False
    __select__ = is_instance("Keyword", "Classification")

    def call(self, **kwargs):
        self.w(SKOS_OPENING_ROOT)
        for i in range(self.rset.rowcount):
            self.cell_call(i, 0)
        self.w(SKOS_CLOSING_ROOT)

    def cell_call(self, row, col):
        self.wview("skositemview", self.rset, row=row, col=col)


class SkosItemView(EntityView):
    __regid__ = "skositemview"
    content_type = "application/xml"
    __select__ = is_instance("Keyword", "Classification")

    def cell_call(self, row, col, show_parent=True, stop=False):
        w = self.w
        entity = self.complete_entity(row, col)
        eschema = entity.e_schema
        w(f"<skos:{eschema}>")
        w(f"<skos:prefLabel>{xml_escape(entity.name)}</skos:prefLabel>")
        if not stop:
            if show_parent and not entity.is_root():
                par = entity.parent()
                w("<skos:broader>")
                par.view("skositemview", show_parent=False, stop=True, w=self.w)
                w("</skos:broader>")
            for child in entity.children(entities=True):
                w("<skos:narrower>")
                self.wview("skositemview", child.as_rset(), show_parent=False)
                w("</skos:narrower>")
        w(f"</skos:{eschema}>")


# keyword component ###########################################################


class KeywordBarVComponent(component.EntityCtxComponent):
    """the keywords path bar: display keywords of a tagged entity"""

    __regid__ = "keywordsbar"
    __select__ = component.EntityCtxComponent.__select__ & relation_possible(
        "applied_to", "object", "Keyword"
    )
    context = "header"
    order = 152
    htmlclass = "navigation"

    def get_keywords(self):
        """helper method for subclasses redefinition"""
        return self.entity.related("applied_to", "object")

    def render_body(self, w):
        rset = self.get_keywords()
        if rset:
            w(f'<div class="{self.cssclass}" id="{self.domid}">\n')
            w(f"<span>{self._cw._('keywords:')}</span>&nbsp;")
            self._cw.view("csv", rset, "null", w=w)
            w("</div>\n")
        else:
            w(f'<div class="{self.cssclass} hidden" id="{self.domid}"></div>\n')


class AddKeywordVComponent(component.EntityCtxComponent):
    """the 'add keyword' component"""

    __regid__ = "addkeywords"
    __select__ = (
        component.EntityCtxComponent.__select__
        & relation_possible("applied_to", "object", "Keyword", action="add")
        & rql_condition("X is ET, CL classifies ET")
    )

    context = "header"
    order = 153
    htmlclass = "navigation"

    def render_body(self, w):
        entity = self.entity
        self._cw.add_js(["cubicweb.widgets.js", "cubes.keyword.js"])
        self._cw.add_css("cubicweb.suggest.css")
        w("<table><tr><td>")
        w(
            "<a class=\"button sglink\" href=\"javascript: showKeywordSelector({}, '{}', '{}');\">{}</a></td>".format(
                entity.eid,
                self._cw._(stdmsgs.BUTTON_OK[0]),
                self._cw._(stdmsgs.BUTTON_CANCEL[0]),
                self._cw._("add keywords"),
            )
        )
        w('<td><div id="kwformholder"></div>')
        w("</td></tr></table>")


# applied_to relation facet ####################################################


class AppliedToFacet(facet.RelationFacet):
    __regid__ = "applied-to-facet"
    rtype = "applied_to"
    role = "object"
    target_attr = "name"

    def rset_vocabulary(self, rset):
        _ = self._cw._
        vocab = []
        scheme = None
        for e in sorted(
            rset.entities(),
            key=lambda e: (
                e.cw_adapt_to("ITree").classification.name,
                e.view("combobox"),
            ),
        ):
            classification_name = e.cw_adapt_to("ITree").classification.name
            if scheme != classification_name:
                vocab.append((_(classification_name), None))
            vocab.append((e.view("combobox"), e.eid))
        return vocab


class ClassificationFacet(facet.RelationFacet):
    """abstract per-classification facet

    subclasses must define their own id the classification name, e.g :

    class Classifaction1Facet(ClassificationFacet):
        __regid__ = 'classif1'
        classification = u'classification1'

    """

    __abstract__ = True
    classification = None
    rtype = "applied_to"
    role = "object"

    def vocabulary(self):
        """return vocabulary for this facet, eg a list of 2-uple (label, value)"""
        rqlst = self.rqlst
        rqlst.save_state()
        try:
            mainvar = self.filtered_variable  # X
            keyword_var = rqlst.make_variable()  # K
            keyword_name_var = rqlst.make_variable()  # KN
            classif_var = rqlst.make_variable()  # C
            rqlst.make_variable()  # CN
            rqlst.add_relation(keyword_var, "applied_to", mainvar)  # K applied_to X
            rqlst.add_relation(keyword_var, "name", keyword_name_var)  # K name KN
            rqlst.add_relation(
                keyword_var, "included_in", classif_var
            )  # K included_in C
            # C name "classification-name"
            rqlst.add_constant_restriction(
                classif_var, "name", self.classification, "String"
            )
            rqlst.add_selected(keyword_var)
            rqlst.add_selected(keyword_name_var)
            # ORDERBY KN
            rqlst.add_sort_var(keyword_name_var, True)
            try:
                rset = self._cw.execute(
                    rqlst.as_string(), self.rset.args, self.rset.cachekey
                )
            except Unauthorized:
                return []
        finally:
            rqlst.recover()
        return self.rset_vocabulary(rset)

    @property
    def title(self):
        return self._cw._(self.classification)

    def support_and(self):
        return False


# add some classification schema related methods to the Jsoncontroller ########


@ajaxfunc(output_type="json")
def js_possible_keywords(self, eid):
    rql = (
        "DISTINCT Any N WHERE K is Keyword, K name N, NOT K applied_to X, "
        "X eid %(x)s, K included_in C, C classifies ET, X is ET"
    )
    rset = self.cursor.execute(rql, {"x": eid, "u": self._cw.user.eid}, "x")
    return [name for (name,) in rset]


@ajaxfunc(output_type="json")
def js_add_keywords(self, eid, kwlist):
    msg = self._cw._("keywords applied")
    kwrset = self.cursor.execute(
        "Any K,N,C WHERE K is Keyword, K name N, K included_in C, "
        "C classifies ET, X eid %(x)s, X is ET",
        {"x": eid},
        "x",
    )
    if not kwrset:
        return self._cw._("No suitable classification scheme found")
    classification = kwrset[0][2]  # XXX what if we have several classifications ?
    valid_keywords = {kwname for _, kwname, _ in kwrset}
    user_keywords = set(kwlist)
    invalid_keywords = sorted(user_keywords - valid_keywords)
    kweids = {
        kwname: str(kweid) for kweid, kwname, _ in kwrset if kwname in user_keywords
    }
    if invalid_keywords:
        for keyword in invalid_keywords:
            neweid = self.cursor.execute(
                "INSERT Keyword K: K name %(name)s, K included_in C WHERE C eid %(c)s",
                {"name": keyword, "c": classification},
                "c",
            )[0][0]
            kweids[keyword] = str(neweid)
    if kweids:
        self.cursor.execute(
            "SET KW applied_to X WHERE X eid %%(x)s, KW eid IN (%s)"
            % ",".join(kweids.values()),
            {"x": eid},
            "x",
        )
    return msg
