# -*- coding: utf-8 -*-
from collections import namedtuple
from lxml import etree
from Products.PloneFormGen.content.fields import FGFieldsetEnd
from Products.PloneFormGen.content.fields import FGFieldsetStart
from Products.PloneFormGen.content.fieldsBase import BaseFormField
from Products.PloneFormGen.interfaces import IPloneFormGenFieldset

import logging
import six


logger = logging.getLogger("collective.easyform.migration")


NAMESPACES = {
    "easyform": "http://namespaces.plone.org/supermodel/easyform",
    "form": "http://namespaces.plone.org/supermodel/form",
}


def append_field(schema, type_, name, properties):
    field = etree.SubElement(schema, u"field")
    field.set(u"name", name)
    field.set(u"type", type_)
    return field


def append_label_field(schema, type_, name, properties):
    field = append_field(schema, type_, name, properties)
    append_node(field, u"required", u"False")
    return field


def append_date_field(schema, type_, name, properties):
    if properties.get("fgShowHM", False):
        return append_field(schema, u"zope.schema.Datetime", name, properties)
    else:
        return append_field(schema, u"zope.schema.Date", name, properties)


def append_fieldset(schema, type_, name, properties):
    fieldset = etree.SubElement(schema, u"fieldset")
    fieldset.set(u"name", name)
    return fieldset


def set_attribute(field, name, value):
    if u":" in name:
        ns, attr = name.split(":")
        ns = NAMESPACES.get(ns, ns)
        field.set(u"{{{}}}{}".format(ns, attr), value)
    else:
        field.set(name, value)


def append_node(field, name, value):
    if ":" in name:
        ns, name = name.split(":")
        ns = NAMESPACES.get(ns, ns)
        name = "{{{}}}{}".format(ns, name)
    node = etree.SubElement(field, name)
    if isinstance(value, (list, tuple)):
        value = u" ".join(value)
    node.text = value
    return node


def append_list_node(field, name, value):
    node = append_node(field, name, None)
    for val in value:
        el = etree.SubElement(node, "element")
        if "|" in val:
            key, val = val.split("|")
            el.set("key", key)
        el.text = val


def append_required_node(field, name, value):
    if value == u"False":
        append_node(field, name, value)


def append_maxlength_node(field, name, value):
    if value != u"0":
        append_node(field, name, value)


def append_vocab_node(field, name, value):
    if field.get("type") == "zope.schema.Set":
        node = etree.SubElement(field, "value_type")
        node.set("type", "zope.schema.Choice")
    else:
        node = field

    append_list_node(node, u"values", value)


def append_default_node(field, name, value):
    if isinstance(value, list):
        return
    if field.get("type") == "collective.easyform.fields.RichLabel":
        append_node(field, "rich_label", value)
    else:
        append_node(field, name, value)


def append_widget_node(field, name, value):
    node = append_node(field, name, None)
    type_ = field.get("type")
    if type_ == "zope.schema.Set":
        if value == "select":
            widget = "z3c.form.browser.select.CollectionSelectFieldWidget"
        else:
            widget = "z3c.form.browser.checkbox.CheckBoxFieldWidget"
    else:
        if value == "select":
            widget = "z3c.form.browser.select.ChoiceWidgetDispatcher"
        else:
            widget = "z3c.form.browser.radio.RadioFieldWidget"
    set_attribute(node, "type", widget)


def append_or_set_title(field, name, value):
    if field.tag == "fieldset":
        set_attribute(field, "label", value)
    else:
        append_node(field, name, value)


def append_or_set_description(field, name, value):
    if field.tag == 'fieldset':
        set_attribute(field, 'description', value)
    else:
        append_node(field, name, value)


def convert_tales_expressions(value):
    if value == u"here/memberEmail":
        return u"python:member and member.getProperty('email', '') or ''"
    elif value == u"here/memberFullName":
        return u"python:member and member.getProperty('fullname', '') or ''"
    elif value == u"here/memberId":
        return u"python:member and member.id or ''"
    return value


def to_text(value):
    if isinstance(value, (list, tuple)):
        return [to_text(v) for v in value]
    value = str(value)
    if six.PY2:
        value = value.decode("utf8")
    return value


Type = namedtuple("Type", ["name", "handler"])
Property = namedtuple("Property", ["name", "handler"])

TYPES_MAPPING = {
    "FormStringField": Type("zope.schema.TextLine", append_field),
    "FormPasswordField": Type("zope.schema.Password", append_field),
    "FormIntegerField": Type("zope.schema.Int", append_field),
    "FormFixedPointField": Type("zope.schema.Float", append_field),
    "FormBooleanField": Type("zope.schema.Bool", append_field),
    "FormDateField": Type("zope.schema.Date", append_date_field),
    "FormLabelField": Type("collective.easyform.fields.Label", append_label_field),
    "FormLinesField": Type("zope.schema.Text", append_field),
    "FormSelectionField": Type("zope.schema.Choice", append_field),
    "FormMultiSelectionField": Type("zope.schema.Set", append_field),
    "FormTextField": Type("zope.schema.Text", append_field),
    "FormRichTextField": Type("plone.app.textfield.RichText", append_field),
    "FormRichLabelField": Type(
        "collective.easyform.fields.RichLabel", append_label_field
    ),
    "FormFileField": Type("plone.namedfile.field.NamedBlobFile", append_field),
    "FormCaptchaField": Type("collective.easyform.fields.ReCaptcha", append_label_field),
    "FormLikertField": Type("collective.easyform.fields.Likert", append_field),
    "FieldsetStart": Type("", append_fieldset),
    "FieldsetEnd": Type("", None),
}

PROPERTIES_MAPPING = {
    "description": Property("description", append_or_set_description),
    "likertQuestions": Property("questions", append_list_node),
    "likertAnswers": Property("answers", append_list_node),
    "fgDefault": Property("default", append_default_node),
    "fgmaxlength": Property("max_length", append_maxlength_node),
    "fgsize": None,  # Not available in collective.easyform
    "fgStringValidator": Property("easyform:validators", set_attribute),
    "fgTDefault": Property("easyform:TDefault", set_attribute),
    "fgTEnabled": Property("easyform:TEnabled", set_attribute),
    "fgTValidator": Property("easyform:TValidator", set_attribute),
    "fgTVocabulary": None,  # Not available in collective.easyform
    "fgVocabulary": Property("values", append_vocab_node),
    "hidden": Property("easyform:THidden", set_attribute),
    "depends_on": Property("easyform:depends_on", set_attribute),
    "css_class": Property("easyform:css_class", set_attribute),
    "maxval": Property("max", append_node),
    "minval": Property("min", append_node),
    "placeholder": None,  # Not available in collective.easyform
    "required": Property("required", append_required_node),
    "serverSide": Property("easyform:serverSide", set_attribute),
    "title": Property("title", append_or_set_title),
    "fgFormat": Property("form:widget", append_widget_node),
}


def pfg_fields_properties(obj):
    id_ = obj.getId()
    props = {}
    props["_portal_type"] = obj.portal_type
    for field in obj.Schema().fields():
        name = field.getName()
        accessor = field.getEditAccessor(obj)
        props[name] = accessor()
    return (id_, props)


def pfg_fields(context):
    fields = []
    for obj in context.objectValues():
        if IPloneFormGenFieldset.providedBy(obj):
            # handle FieldsetFolder folderish type
            parent = obj.aq_parent
            obj_id = obj.getId()
            # append a FieldsetStart field with title and description
            start = FGFieldsetStart(obj_id).__of__(parent)
            start.setTitle(obj.Title())
            start.setDescription(obj.Description())
            fields.append(pfg_fields_properties(start))
            # add all the fieldset contained fields
            fields.extend(pfg_fields(obj))
            # finish by appending a marker FieldsetEnd field
            end = FGFieldsetEnd(obj_id).__of__(parent)
            fields.append(pfg_fields_properties(end))
            continue
        if not isinstance(obj, BaseFormField):
            continue
        fields.append(pfg_fields_properties(obj))
    return fields


def fields_model(ploneformgen):
    """Create a fields xml model from a PloneFormGen instance."""
    parser = etree.XMLParser(remove_blank_text=True)
    model = etree.fromstring(FIELDS_MODEL, parser)
    schema = model.find("{http://namespaces.plone.org/supermodel/schema}schema")
    for fieldname, properties in pfg_fields(ploneformgen):
        portal_type = properties["_portal_type"]
        if portal_type == "FieldsetEnd":
            schema = schema.getparent()
            continue

        type_ = TYPES_MAPPING.get(portal_type)
        if type_ is None:
            # ploneformgen has been renamed with _MIGRATION_ suffix
            # hence, we remove 11 chars
            pfgpath = "/".join(ploneformgen.getPhysicalPath())[:-11]
            logger.warning(
                "Ignoring field '%s' of type '%s' of form '%s'.",
                fieldname,
                portal_type,
                pfgpath,
            )
            continue

        if type_.handler is None:
            continue
        field = type_.handler(schema, type_.name, fieldname, properties)

        for name, value in properties.items():
            prop = PROPERTIES_MAPPING.get(name)
            if prop is None:
                continue

            value = to_text(value)

            # Convert TALES expressions with PFG specific methods
            if name.startswith("fgT"):
                value = convert_tales_expressions(value)

            prop.handler(field, prop.name, value)

        if portal_type == "FieldsetStart":
            schema = field

    return etree.tostring(model, pretty_print=True)


FIELDS_MODEL = b"""
<model
    xmlns="http://namespaces.plone.org/supermodel/schema"
    xmlns:easyform="http://namespaces.plone.org/supermodel/easyform"
    xmlns:i18n="http://xml.zope.org/namespaces/i18n"
    i18n:domain="collective.easyform">
  <schema></schema>
</model>
"""
