from __future__ import unicode_literals

from .utils import digattr

from decimal import Decimal
from datetime import datetime

try:
    from django.utils.encoding import force_text
except ImportError:
    # For Django1.4
    from django.utils.encoding import force_unicode as force_text

class NoDefault(object):
    '''Indicates no default value was provided.'''

class Field(object):
    type_class = None

    def __init__(self, attribute=None, default=NoDefault, readonly=False,
                 null=True, **kwargs):
        self.attribute = attribute
        self.default = default
        self.readonly = readonly
        self.null = null
        self.kwargs = kwargs

    def _get_attrname(self, name):
        return self.attribute if self.attribute else name

    def reduce(self, value, **kwargs):
        return value

    def restore(self, value, **kwargs):
        if self.type_class is not None:
            return self.type_class(value)
        return value

    def deflate(self, name, obj, data, **kwargs):
        src = self._get_attrname(name)
        value = digattr(obj, src, self.default)
        if value is not None:
            value = self.reduce(value, **kwargs)
        data[name] = value

    def inflate(self, name, data, obj, **kwargs):
        if self.readonly:
            return
        dest = self._get_attrname(name)
        try:
            value = data[name]
        except KeyError:
            if self.default is not NoDefault:
                obj[dest] = self.default
        else:
            if value is None and self.null:
                return value
            obj[dest] = self.restore(value, **kwargs)


class BooleanField(Field):
    type_class = bool


class IntegerField(Field):
    type_class = int


class DecimalField(Field):
    type_class = Decimal

    def reduce(self, value, **kwargs):
        return float(value)


class DateTimeField(Field):

    def reduce(self, value, **kwargs):
        return value.replace(microsecond=0).isoformat(b' ')

    def restore(self, value, **kwargs):
        return datetime.strptime(value, '%Y-%m-%d %H:%M:%S')


class DateField(Field):

    def reduce(self, value, **kwargs):
        return value.isoformat()

    def restore(self, value, **kwargs):
        return datetime.strptime(value, '%Y-%m-%d').date()


class TimeField(Field):

    def reduce(self, value, **kwargs):
        return value.isoformat()

    def restore(self, value, **kwargs):
        return datetime.strptime(value, '%H:%M:%S').time()


class SerialiserField(Field):

    def __init__(self, *args, **kwargs):
        super(SerialiserField, self).__init__(*args, **kwargs)
        self.serialiser = kwargs['serialiser']

    def reduce(self, value, **kwargs):
        return self.serialiser.object_deflate(value)

    def restore(self, value, **kwargs):
        return self.serialiser.object_inflate(value)


class ManySerialiserField(Field):

    def __init__(self, *args, **kwargs):
        super(ManySerialiserField, self).__init__(*args, **kwargs)
        self.serialiser = kwargs['serialiser']

    def reduce(self, value, **kwargs):
        return self.serialiser.list_deflate(iter(value), **kwargs)

    def restore(self, value, **kwargs):
        return self.serialiser.list_inflate(value, **kwargs)


class FileField(Field):

    def reduce(self, value, **kwargs):
        return value.url

    def restore(self, value, **kwargs):
        pass


class StringField(Field):
    '''
    Like Field, but always casts value to a text type.

    Since it can't know what type to restore to, it is set readonly by
    default.  If you want it to be writable, you must clear this flag and
    provide a custom inflater.
    '''
    def __init__(self, *args, **kwargs):
        kwargs.setdefault('readonly', True)
        super(StringField, self).__init__(*args, **kwargs)

    def reduce(self, value, **kwargs):
        return force_text(value)

@@ -34,16 +34,56 @@
 terals%0A%0A
+from .exceptions import ValidationError%0A
 from .ut
@@ -1556,18 +1556,20 @@
     
-else:%0A    
+    return%0A%0A
     
@@ -1588,110 +1588,342 @@
  is 
-None and self.null:%0A                return value%0A            obj%5Bdest%5D = self.restore(value, **kwargs)
+not None:%0A            try:%0A                value = self.restore(value, **kwargs)%0A            except ValueError:%0A                raise ValidationError(%22Field '%25s' received invalid value: %25r%22 %25 (name, value))%0A        elif not self.null:%0A            raise ValidationError(%22Field '%25s' must not be None.%22 %25 name)%0A%0A        obj%5Bdest%5D = value
 %0A%0A%0Ac

Handle value inflating more precisely