"""
Extension to ``htmlfill`` that can parse out schema-defining
statements.

You can either pass ``SchemaBuilder`` to ``htmlfill.render`` (the
``listen`` argument), or call ``parse_schema`` to just parse out a
``Schema`` object.
"""

import validators, schema, compound, htmlfill

__all__ = ['parse_schema', 'SchemaBuilder']


def parse_schema(form):
    """
    Given an HTML form, parse out the schema defined in it and return
    that schema.
    """
    listener = SchemaBuilder()
    p = htmlfill.FillingParser(
        defaults={}, listener=listener)
    p.feed(form)
    p.close()
    return listener.schema()


default_validators = dict(
    [(name.lower(), getattr(validators, name))
     for name in dir(validators)])


def get_messages(cls, message):
    if not message:
        return {}
    else:
        return dict([(k, message) for k in cls._messages.keys()])


def to_bool(value):
    value = value.strip().lower()
    if value in ('true', 't', 'yes', 'y', 'on', '1'):
        return True
    elif value in ('false', 'f', 'no', 'n', 'off', '0'):
        return False
    else:
        raise ValueError("Not a boolean value: %r (use 'true'/'false')")


def force_list(v):
    """
    Force single items into a list. This is useful for checkboxes.
    """
    if isinstance(v, list):
        return v
    elif isinstance(v, tuple):
        return list(v)
    else:
        return [v]


class SchemaBuilder(object):

    def __init__(self, validators=default_validators):
        self.validators = validators
        self._schema = None

    def reset(self):
        self._schema = schema.Schema()

    def schema(self):
        return self._schema

    def listen_input(self, parser, tag, attrs):
        get_attr = parser.get_attr
        name = get_attr(attrs, 'name')
        if not name:
            # @@: should warn if you try to validate unnamed fields
            return
        v = compound.All(validators.Identity())
        add_to_end = None
        # for checkboxes, we must set if_missing = False
        if tag.lower() == "input":
            type_attr = get_attr(attrs, "type").lower().strip()
            if type_attr == "submit":
                v.validators.append(validators.Bool())
            elif type_attr == "checkbox":
                v.validators.append(validators.Wrapper(to_python = force_list))
            elif type_attr == "file":
                add_to_end = validators.FieldStorageUploadConverter()
        message = get_attr(attrs, 'form:message')
        required = to_bool(get_attr(attrs, 'form:required', 'false'))
        if required:
            v.validators.append(
                validators.NotEmpty(
                messages=get_messages(validators.NotEmpty, message)))
        else:
            v.validators[0].if_missing = False
        if add_to_end:
            v.validators.append(add_to_end)
        v_type = get_attr(attrs, 'form:validate', None)
        if v_type:
            pos = v_type.find(':')
            if pos != -1:
                # @@: should parse args
                args = (v_type[pos+1:],)
                v_type = v_type[:pos]
            else:
                args = ()
            v_type = v_type.lower()
            v_class = self.validators.get(v_type)
            if not v_class:
                raise ValueError("Invalid validation type: %r" % v_type)
            kw_args={'messages': get_messages(v_class, message)}
            v_inst = v_class(
                *args, **kw_args)
            v.validators.append(v_inst)
        self._schema.add_field(name, v)
