# -*- coding: utf-8 -*-
from datetime import date, datetime, time
from decimal import Decimal
from uuid import UUID, uuid4

from attr import NOTHING, attrib
from future.moves.urllib.parse import ParseResult
from six import string_types

from . import _init_fields, converters, types, validators


def BooleanField(default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new bool field on a model.

    :param default: any boolean value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, bool)
    return attrib(
        default=default, validator=validator, repr=repr, cmp=cmp, metadata=dict(key=key)
    )


def ChildField(cls, default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new child field on a model.

    :param cls: class (or name) of the model to be related.
    :param default: any object value of type cls
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    converter = converters.to_child_field(cls)
    validator = _init_fields.init_validator(
        required, object if isinstance(cls, str) else cls
    )
    return attrib(
        default=default,
        converter=converter,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )


def DateField(
    formatter=types.DEFAULT_DATE_FORMAT,
    default=NOTHING,
    required=True,
    repr=True,
    cmp=True,
    key=None,
):
    """
    Create new date field on a model.

    :param formatter: date formatter string (default: "%Y-%m-%d")
    :param default: any date or string that can be converted to a date value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, date)
    converter = converters.to_date_field(formatter)
    return attrib(
        default=default,
        converter=converter,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(formatter=formatter, key=key),
    )


def DateTimeField(
    formatter=types.DEFAULT_DATETIME_FORMAT,
    default=NOTHING,
    required=True,
    repr=True,
    cmp=True,
    key=None,
):
    """
    Create new datetime field on a model.

    :param formatter: datetime formatter string (default: "ISO_FORMAT")
    :param default: any datetime or string that can be converted to a datetime
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, datetime)
    converter = converters.to_datetime_field(formatter)
    return attrib(
        default=default,
        converter=converter,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(formatter=formatter, key=key),
    )


def TimeField(
    formatter=types.DEFAULT_TIME_FORMAT,
    default=NOTHING,
    required=True,
    repr=True,
    cmp=True,
    key=None,
):
    """
    Create new time field on a model.

    :param formatter: time formatter string (default: "%H:%M:%S")
    :param default: any time or string that can be converted to a time value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, time)
    converter = converters.to_time_field(formatter)
    return attrib(
        default=default,
        converter=converter,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(formatter=formatter, key=key),
    )


def FloatField(default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new float field on a model.

    :param default: any float value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, float)
    return attrib(
        default=default,
        converter=converters.float_if_not_none,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )


def IntegerField(default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new int field on a model.

    :param default: any integer value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, int)
    return attrib(
        default=default,
        converter=converters.int_if_not_none,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )


def MappingField(cls, child_key, default=NOTHING, required=True, repr=False, key=None):
    """
    Create new mapping field on a model.

    :param cls: class (or name) of the model to be related in Sequence.
    :param child_key: key field on the child object to be used as the map key.
    :param default: any mapping type
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, {})
    converter = converters.to_mapping_field(cls, child_key)
    validator = _init_fields.init_validator(required, types.TypedMapping)
    return attrib(
        default=default,
        converter=converter,
        validator=validator,
        repr=repr,
        metadata=dict(key=key),
    )


def RegexField(regex, default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new str field on a model.

    :param regex: regex validation string (e.g. "[^@]+@[^@]+" for email)
    :param default: any string value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(
        required, string_types, validators.regex(regex)
    )
    return attrib(
        default=default,
        converter=converters.str_if_not_none,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )


def SequenceField(cls, default=NOTHING, required=True, repr=False, key=None):
    """
    Create new sequence field on a model.

    :param cls: class (or name) of the model to be related in Sequence.
    :param default: any TypedSequence or list
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, [])
    converter = converters.to_sequence_field(cls)
    validator = _init_fields.init_validator(required, types.TypedSequence)
    return attrib(
        default=default,
        converter=converter,
        validator=validator,
        repr=repr,
        metadata=dict(key=key),
    )


def SetField(cls, default=NOTHING, required=True, repr=False, key=None):
    """
    Create new set field on a model.

    :param cls: class (or name) of the model to be related in Set.
    :param default: any TypedSet or set
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, set())
    converter = converters.to_set_field(cls)
    validator = _init_fields.init_validator(required, types.TypedSet)
    return attrib(
        default=default,
        converter=converter,
        validator=validator,
        repr=repr,
        metadata=dict(key=key),
    )


def StringField(default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new str field on a model.

    :param default: any string value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, string_types)
    return attrib(
        default=default,
        converter=converters.str_if_not_none,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )


def URLField(default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new UUID field on a model.

    :param default: any value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    cls = ParseResult
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, cls)
    return attrib(
        default=default,
        converter=converters.str_to_url,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )


def UUIDField(default=NOTHING, required=False, repr=True, cmp=True, key=None):
    """
    Create new UUID field on a model.

    :param default: any value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    cls = UUID
    default = _init_fields.init_default(required, default, uuid4)
    validator = _init_fields.init_validator(required, cls)
    return attrib(
        default=default,
        converter=converters.str_to_uuid,
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )


def DecimalField(default=NOTHING, required=True, repr=True, cmp=True, key=None):
    """
    Create new decimal field on a model.

    :param default: any decimal value
    :param bool required: whether or not the object is invalid if not provided.
    :param bool repr: include this field should appear in object's repr.
    :param bool cmp: include this field in generated comparison.
    :param string key: override name of the value when converted to dict.
    """
    default = _init_fields.init_default(required, default, None)
    validator = _init_fields.init_validator(required, Decimal)
    return attrib(
        default=default,
        converter=lambda x: Decimal(x),
        validator=validator,
        repr=repr,
        cmp=cmp,
        metadata=dict(key=key),
    )
