"""Adapters for converting to and from a type's value according to an
IConvertible protocol.
"""

from datetime import date, time
import decimal
from zope.interface import implementer


from . import iformal, validation


class _Adapter(object):
    def __init__(self, original):
        self.original = original


@implementer( iformal.IStringConvertible )
class NullConverter(_Adapter):

    def fromType(self, value):
        if value is None:
            return None
        return value

    def toType(self, value):
        if value is None:
            return None
        return value


@implementer( iformal.IStringConvertible )
class ForceStringConverter(_Adapter):
    cast = None

    def fromType(self, value):
        if value is None:
            return None
        return str(value)

    def toType(self, value):
        if not value:
            return None
        if isinstance(value, bytes):
            return value.decode("utf-8", "replace")
        return value


@implementer( iformal.IStringConvertible )
class NumberToStringConverter(_Adapter):
    cast = None

    def fromType(self, value):
        if value is None:
            return None
        return str(value)

    def toType(self, value):
        if value is not None:
            value = value.strip()
        if not value:
            return None
        # "Cast" the value to the correct type. For some strange reason,
        # Python's decimal.Decimal type raises an ArithmeticError when it's
        # given a dodgy value.
        try:
            value = self.cast(value)
        except (ValueError, ArithmeticError):
            raise validation.FieldValidationError("Not a valid number")
        return value


class IntegerToStringConverter(NumberToStringConverter):
    cast = int


class FloatToStringConverter(NumberToStringConverter):
    cast = float


class DecimalToStringConverter(NumberToStringConverter):
    cast = decimal.Decimal


@implementer( iformal.IStringConvertible )
class BooleanToStringConverter(_Adapter):

    def fromType(self, value):
        if value is None:
            return None
        if value:
            return 'True'
        return 'False'

    def toType(self, value):
        if value is not None:
            value = value.strip()
        if not value:
            return None
        if value not in ('True', 'False'):
            raise validation.FieldValidationError('%r should be either True or False'%value)
        return value == 'True'


@implementer( iformal.IStringConvertible )
class DateToStringConverter(_Adapter):

    def fromType(self, value):
        if value is None:
            return None
        return value.isoformat()

    def toType(self, value):
        if value is not None:
            value = value.strip()
        if not value:
            return None
        return self.parseDate(value)

    def parseDate(self, value):
        try:
            y, m, d = [int(p) for p in value.split('-')]
        except ValueError:
            raise validation.FieldValidationError('Invalid date')
        try:
            value = date(y, m, d)
        except ValueError as e:
            raise validation.FieldValidationError('Invalid date: '+str(e))
        return value


@implementer( iformal.IStringConvertible )
class TimeToStringConverter(_Adapter):

    def fromType(self, value):
        if value is None:
            return None
        if isinstance(value, str):
            return value
        return value.isoformat()

    def toType(self, value):
        if value is not None:
            value = value.strip()
        if not value:
            return None
        return self.parseTime(value)

    def parseTime(self, value):

        if '.' in value:
            value, ms = value.split('.')
        else:
            ms = 0

        try:
            parts = value.split(':')
            if len(parts)<2 or len(parts)>3:
                raise ValueError()
            if len(parts) == 2:
                h, m = parts
                s = 0
            else:
                h, m, s = parts
            h, m, s, ms = int(h), int(m), int(s), int(ms)
        except:
            raise validation.FieldValidationError('Invalid time')

        try:
            value = time(h, m, s, ms)
        except ValueError as e:
            raise validation.FieldValidationError('Invalid time: '+str(e))

        return value


@implementer( iformal.IDateTupleConvertible )
class DateToDateTupleConverter(_Adapter):

    def fromType(self, value):
        if value is None:
            return None, None, None
        return value.year, value.month, value.day

    def toType(self, value):
        if value is None:
            return None
        try:
            value = date(*value)
        except (TypeError, ValueError) as e:
            raise validation.FieldValidationError('Invalid date: '+str(e))
        return value



@implementer(iformal.IStringConvertible)
class SequenceToStringConverter(_Adapter):

    def fromType(self, value):
        if value is None:
            return None

        cnv = iformal.IStringConvertible(self.original.instance).fromType
        return " ".join(cnv(i) for i in value)


    def toType(self, value):
        if not value:
            return None

        cnv = iformal.IStringConvertible(self.original.instance).toType
        return [cnv(i) for i in value.split()]


