# coding: utf-8

"""
date        - DateField
datetime    - DateTimeField

time        - нет
timedelta   - нет
buffer      - ? - used for binary data in Python 2 and 3
bytes       - ? - used for binary data in Python 3
"""

from collections import OrderedDict
from datetime import date, datetime, time

from pony.orm import ormtypes
import six
import wtforms.fields as wtf_fields
import wtforms.validators as wtf_validators

from .forms import Form, EntityField
from . import validators


class Factory(object):
    """A simple factory for functions and methods that generate form elements."""

    def __init__(self):
        self.__types = {}

    def get(self, tp):
        return self.__types.get(tp)

    def __call__(self, *types):
        def decorator(func):
            for tp in types:
                self.__types[tp] = func
            return func
        return decorator


class FormBuilder(object):
    field_constructor = Factory()

    def __init__(self, entity_class, base_class=None, excludes=None, skip_pk=True):
        self._fields = OrderedDict()
        self._buttons = OrderedDict()

        self._entity_class = entity_class
        self._base_class = base_class
        self._excludes = set(excludes or [])
        self._skip_pk = skip_pk

    def _field_numeric(self, attr, options):
        miN = attr.kwargs.get('min', attr.kwargs.get('unsigned') and 0)
        maX = attr.kwargs.get('max')

        options['validators'].append(wtf_validators.NumberRange(miN, maX))

    def _field_string(self, attr, options):
        args = list(attr.args)
        max_len = args.pop() if args else attr.kwargs.get('max_len')
        if max_len:
            options['validators'].append(wtf_validators.Length(max=max_len))

    def _get_field_method(self, tp):
        """Returns a reference to the form element's constructor method."""
        method = self.field_constructor.get(tp)
        if method and hasattr(self, method.__name__):
            return getattr(self, method.__name__)
        return method

    def _create_collection_field(self, attr, options):
        """Creates the form element for working with the collection of entities."""
        return None, options

    def _create_plain_field(self, attr, options):
        """Creates the form element."""
        method = self._get_field_method(attr.py_type) or self._create_other_field
        klass, options = method(attr, options)

        if attr.is_unique:
            options['validators'].append(validators.UniqueEntityValidator(attr.entity))

        return klass, options

    def _create_pk_field(self, attr, options):
        """Creates the form element for working with primary key."""
        if attr.auto or self._skip_pk:
            return None, options

    def _create_relational_field(self, attr, options):
        """Creates the form element for working with entity relationships."""
        options['entity_class'] = attr.py_type
        options['allow_empty'] = not attr.is_required
        return EntityField, options

    def _create_other_field(self, attr, options):
        """Creates a custom form element. Called when the element was not found."""
        return None, options

    def add(self, attr, field_class=None, **options):
        """Adds an element to the form based on the entity attribute."""
        # print(attr.name, attr.py_type, getattr(attr, 'set', None))
        # print(dir(attr))
        # print(attr, attr.is_relation, attr.is_collection)
        # print(attr.is_pk, attr.auto, attr.is_unique, attr.is_part_of_unique_index, attr.composite_keys)

        def add(klass, options):
            if klass:
                self._fields[attr.name] = field_class(**options) if field_class else klass(**options)
            return self

        kwargs = {
            'label': attr.name,
            'default': attr.default,
            'validators': [],
        }
        kwargs.update(options)

        if attr.is_pk:
            return add(*self._create_pk_field(attr, kwargs))

        if attr.is_collection:
            return add(*self._create_collection_field(attr, kwargs))

        validator = wtf_validators.InputRequired() if attr.is_required and not attr.is_pk else wtf_validators.Optional()
        kwargs['validators'].insert(0, validator)

        if attr.is_relation:
            return add(*self._create_relational_field(attr, kwargs))

        return add(*self._create_plain_field(attr, kwargs))

    def add_button(self, name, button_class=wtf_fields.SubmitField, **options):
        """Adds a button to the form."""
        self._buttons[name] = button_class(**options)

    def build_form(self):
        for attr in self._entity_class._attrs_:
            if attr.name not in self._excludes:
                self.add(attr)

    def get_form(self):
        self.build_form()
        classname = '{}Form'.format(self._entity_class.__class__.__name__)
        base = self._base_class or Form
        props = OrderedDict()
        props.update(self._fields)
        props.update(self._buttons)
        form = type(classname, (base,), props)
        form._attr_names_ = self._fields.keys()
        return form

    @field_constructor(bool)
    def field_bool(self, attr, options):
        return wtf_fields.BooleanField, options

    @field_constructor(int)
    def field_int(self, attr, options):
        self._field_numeric(attr, options)
        return wtf_fields.IntegerField, options

    @field_constructor(float)
    def field_float(self, attr, options):
        self._field_numeric(attr, options)
        return wtf_fields.FloatField, options

    @field_constructor(ormtypes.Decimal)
    def field_decimal(self, attr, options):
        self._field_numeric(attr, options)
        options.setdefault('places', None)
        return wtf_fields.DecimalField, options

    @field_constructor(str, six.text_type)
    def field_string(self, attr, options):
        self._field_string(attr, options)
        return wtf_fields.StringField, options

    @field_constructor(ormtypes.LongStr, ormtypes.LongUnicode)
    def field_textarea(self, attr, options):
        self._field_string(attr, options)
        return wtf_fields.TextAreaField, options

    @field_constructor(date)
    def field_date(self, attr, options):
        return wtf_fields.DateField, options

    @field_constructor(datetime)
    def field_datetime(self, attr, options):
        return wtf_fields.DateTimeField, options

    @field_constructor(ormtypes.Json)
    def field_json(self, attr, options):
        return wtf_fields.TextAreaField, options

    @field_constructor(ormtypes.UUID)
    def field_uuid(self, attr, options):
        """Creates a form element for the UUID type."""
        options['validators'].append(validators.UUIDValidator(attr.entity))
        return wtf_fields.StringField, options

    @classmethod
    def get_instance(cls, entity_class, *args, **kwargs):
        return cls(entity_class, *args, **kwargs).get_form()

# coding: utf-8

"""
date        - DateField
datetime    - DateTimeField

time        - нет
timedelta   - нет
buffer      - ? - used for binary data in Python 2 and 3
bytes       - ? - used for binary data in Python 3
"""

from collections import OrderedDict
from datetime import date, datetime, time

from pony.orm import ormtypes
import six
import wtforms.fields as wtf_fields
import wtforms.validators as wtf_validators

from .forms import Form, EntityField
from . import validators


class Factory(object):
    """A simple factory for functions and methods that generate form elements."""

    def __init__(self):
        self.__types = {}

    def get(self, tp):
        return self.__types.get(tp)

    def __call__(self, *types):
        def decorator(func):
            for tp in types:
                self.__types[tp] = func
            return func
        return decorator


class FormBuilder(object):
    field_constructor = Factory()

    def __init__(self, entity_class, base_class=None, excludes=None, skip_pk=True):
        self._fields = OrderedDict()
        self._buttons = OrderedDict()

        self._entity_class = entity_class
        self._base_class = base_class
        self._excludes = set(excludes or [])
        self._skip_pk = skip_pk

    def _field_numeric(self, attr, options):
        miN = attr.kwargs.get('min', attr.kwargs.get('unsigned') and 0)
        maX = attr.kwargs.get('max')

        options['validators'].append(wtf_validators.NumberRange(miN, maX))

    def _field_string(self, attr, options):
        args = list(attr.args)
        max_len = args.pop() if args else attr.kwargs.get('max_len')
        if max_len:
            options['validators'].append(wtf_validators.Length(max=max_len))

    def _get_field_method(self, tp):
        """Returns a reference to the form element's constructor method."""
        method = self.field_constructor.get(tp)
        if method and hasattr(self, method.__name__):
            return getattr(self, method.__name__)
        return method

    def _create_collection_field(self, attr, options):
        """Creates the form element for working with the collection of entities."""
        return None, options

    def _create_plain_field(self, attr, options):
        """Creates the form element."""
        method = self._get_field_method(attr.py_type) or self._create_other_field
        klass, options = method(attr, options)

        if attr.is_unique:
            options['validators'].append(validators.UniqueEntityValidator(attr.entity))

        return klass, options

    def _create_pk_field(self, attr, options):
        """Creates the form element for working with primary key."""
        if attr.auto or self._skip_pk:
            return None, options

    def _create_relational_field(self, attr, options):
        """Creates the form element for working with entity relationships."""
        options['entity_class'] = attr.py_type
        options['allow_empty'] = not attr.is_required
        return EntityField, options

    def _create_other_field(self, attr, options):
        """Creates a custom form element. Called when the element was not found."""
        return None, options

    def add(self, attr, field_class=None, **options):
        """Adds an element to the form based on the entity attribute."""
        # print(attr.name, attr.py_type, getattr(attr, 'set', None))
        # print(dir(attr))
        # print(attr, attr.is_relation, attr.is_collection)
        # print(attr.is_pk, attr.auto, attr.is_unique, attr.is_part_of_unique_index, attr.composite_keys)

        def add(klass, options):
            if klass:
                self._fields[attr.name] = field_class(**options) if field_class else klass(**options)
            return self

        kwargs = {
            'label': attr.name,
            'default': attr.default,
            'validators': [],
        }
        kwargs.update(options)

        if attr.is_pk:
            return add(*self._create_pk_field(attr, kwargs))

        if attr.is_collection:
            return add(*self._create_collection_field(attr, kwargs))

        validator = wtf_validators.InputRequired() if attr.is_required and not attr.is_pk else wtf_validators.Optional()
        kwargs['validators'].insert(0, validator)

        if attr.is_relation:
            return add(*self._create_relational_field(attr, kwargs))

        return add(*self._create_plain_field(attr, kwargs))

    def add_button(self, name, button_class=wtf_fields.SubmitField, **options):
        """Adds a button to the form."""
        self._buttons[name] = button_class(**options)

    def build_form(self):
        for attr in self._entity_class._attrs_:
            if attr.name not in self._excludes:
                self.add(attr)

    def get_form(self):
        self.build_form()
        classname = '{}Form'.format(self._entity_class.__class__.__name__)
        base = self._base_class or Form
        props = OrderedDict()
        props.update(self._fields)
        props.update(self._buttons)
        form = type(classname, (base,), props)
        form._attr_names_ = self._fields.keys()
        return form

    @field_constructor(bool)
    def field_bool(self, attr, options):
        return wtf_fields.BooleanField, options

    @field_constructor(int)
    def field_int(self, attr, options):
        self._field_numeric(attr, options)
        return wtf_fields.IntegerField, options

    @field_constructor(float)
    def field_float(self, attr, options):
        self._field_numeric(attr, options)
        return wtf_fields.FloatField, options

    @field_constructor(ormtypes.Decimal)
    def field_decimal(self, attr, options):
        self._field_numeric(attr, options)
        options.setdefault('places', None)
        return wtf_fields.DecimalField, options

    @field_constructor(str, six.text_type)
    def field_string(self, attr, options):
        self._field_string(attr, options)
        return wtf_fields.StringField, options

    @field_constructor(ormtypes.LongStr, ormtypes.LongUnicode)
    def field_textarea(self, attr, options):
        self._field_string(attr, options)
        return wtf_fields.TextAreaField, options

    @field_constructor(date)
    def field_date(self, attr, options):
        return wtf_fields.DateField, options

    @field_constructor(datetime)
    def field_datetime(self, attr, options):
        return wtf_fields.DateTimeField, options

    @field_constructor(ormtypes.Json)
    def field_json(self, attr, options):
        return wtf_fields.TextAreaField, options

    @field_constructor(ormtypes.UUID)
    def field_uuid(self, attr, options):
        """Creates a form element for the UUID type."""
        options['validators'].append(validators.UUIDValidator(attr.entity))
        return wtf_fields.StringField, options

    @classmethod
    def get_instance(cls, entity_class, *args, **kwargs):
        return cls(entity_class, *args, **kwargs).get_form()

# coding: utf-8 date        - DateField datetime    - DateTimeField  time        - нет timedelta   - нет buffer      - ? - used for binary data in Python 2 and 3 bytes       - ? - used for binary data in Python 3 A simple factory for functions and methods that generate form elements. Returns a reference to the form element's constructor method. Creates the form element for working with the collection of entities. Creates the form element. Creates the form element for working with primary key. Creates the form element for working with entity relationships. Creates a custom form element. Called when the element was not found. Adds an element to the form based on the entity attribute. # print(attr.name, attr.py_type, getattr(attr, 'set', None)) # print(dir(attr)) # print(attr, attr.is_relation, attr.is_collection) # print(attr.is_pk, attr.auto, attr.is_unique, attr.is_part_of_unique_index, attr.composite_keys) Adds a button to the form. Creates a form element for the UUID type.