"""Converters for zope.schema-based datatypes.
"""
import datetime
import grok
from zope.component import getMultiAdapter
from zope.publisher.browser import TestRequest
try:
    from zope.app.form.browser.interfaces import ITerms
except ImportError:
    from zope.browser.interfaces import ITerms
from zope.schema.interfaces import IBool, IText, IInt, IChoice, IDate
from waeup.sirp.interfaces import ISchemaTypeConverter

# If a string has this value, it is considered as 'missing_value' or None.
NONE_STRING_VALUE = ''

class Converter(grok.Adapter):
    """Base for string-or-none to zope.schema field converters.
    """
    grok.baseclass()
    grok.provides(ISchemaTypeConverter)
    
    def __init__(self, context):
        """Create a converter with context, a zope.schema.Field, as context.
        """
        self.context = context

    def _convertValueFromString(self, string):
        """You must at least override this method to build a working
           converter.
        """
        raise NotImplementedError('method not implemented')
    
    def fromString(self, string=None, strict=True):
        """Convert ``string`` to value according to assigned field type.
        """
        result = None
        if string is NONE_STRING_VALUE:
            string = None
        if string is None:
            if self.context.required is True:
                result = self.context.default
            else:
                result = self.context.missing_value
        else:
            result = self._convertValueFromString(string)
        if strict:
            self.context.validate(result)
        return result

    def _convertValueToString(self, value):
        return str(value)

    def toString(self, value, strict=True):
        """Convert given `value` to string according to assigned field type.
        """
        if strict:
            self.context.validate(value)
        if value == self.context.missing_value:
            return None
        return self._convertValueToString(value)

class BoolConverter(Converter):
    """A converter for zope.schema.Bool fields.
    """
    grok.context(IBool)
    grok.provides(ISchemaTypeConverter)

    def _convertValueFromString(self, string):
        if string is NONE_STRING_VALUE:
            string = None
        if string is None:
            return None
        if string.lower() in ['1', 'true', 'yes']:
            return True
        return False

    def _convertValueToString(self, value):
        if value is None:
            return None
        if value:
            return '1'
        return '0'
    
class TextConverter(Converter):
    """A converter for zope.schema.interfaces.IText fields.
    """
    grok.context(IText)
    grok.provides(ISchemaTypeConverter)

    def _convertValueFromString(self, string):
        return unicode(string)

class IntConverter(Converter):
    """A converter for zope.schema.Int fields.
    """
    grok.context(IInt)
    grok.provides(ISchemaTypeConverter)

    def _convertValueFromString(self, string):
        return int(string)

class ChoiceConverter(Converter):
    """A converter for zope.schema.Choice fields.
    """
    grok.context(IChoice)
    grok.provides(ISchemaTypeConverter)

    tokens = None
    values = None
    
    def __init__(self, context):
        self.context = context
        if not hasattr(self.context.source, 'factory'):
            try:
                self.terms = getMultiAdapter(
                    (self.context.source, TestRequest()), ITerms)
            except:
                self.terms = None

            return
        if not hasattr(self.context.source.factory, 'getToken'):
            return
        # For expensive token/key lookups we create a 'cache'
        # here. This speeds up mass operations with many conversions
        # by factor 10 or more.
        
        # Mapping token -> value
        self.tokens = dict([(self.context.source.factory.getToken(x), x)
                            for x in self.context.source.factory.getValues()])
        # Mapping value -> token
        self.values = dict([(y,x) for x,y in self.tokens.items()])

    def _convertValueFromString(self, string):
        if self.tokens is not None:
            result = None
            try:
                result = self.tokens[string]
            except KeyError:
                # Be gentle...
                try:
                    result = self.tokens[string.lower()]
                    return result
                except KeyError:
                    tokenlist = (','.join(self.tokens[:2]))
                    raise ValueError(
                        'The token %s is not valid. Use one of %s, ...' % (
                            string, tokenlist))
            return result
        if self.terms is not None:
            return self.terms.getValue(string)
        result = self.context.source.getTermByToken(string).value
        return result

    def _convertValueToString(self, value):
        if self.values is not None:
            return self.values[value]
        if self.terms is not None:
            return self.terms.getTerm(value).token
        return str(value)

    def fromString(self, string=None, strict=False):
        """Convert ``string`` to value according to assigned field type.

        We change the default for ``strict``: this disables extra
        validation checks for Choice fields and saves lots of time. If
        a string/value is out of allowed range we get a value or key
        error anyway.
        """
        return super(ChoiceConverter, self).fromString(string=string,
                                                       strict=strict)

    def toString(self, value, strict=False):
        """Convert ``value`` to string according to assigned field type.

        We change the default for ``strict``: this disables extra
        validation checks for Choice fields and saves lots of time. If
        a string/value is out of allowed range we get a value or key
        error anyway.
        """
        return super(ChoiceConverter, self).toString(value=value,
                                                     strict=strict)

class DateConverter(Converter):
    """A converter for zope.schema.IDate fields.

    Converts date to string and vice versa. Stringified dates are
    expected in format ``YYYY-MM-DD``.

    To support at least some other formats, we accept for conversion
    from string also the following formats:

    * ``YYYY/MM/DD``
    
    * ``DD/MM/YYYY``
    
    * ``D/M/YYYY``

    * ``DD.MM.YYYY``

    * ``D.M.YYYY``

    When converting to strings, always 'YYYY-MM-DD' is returned.

    For convenience, when converting from strings also string data
    separated by space is stripped before processing. Therefore
    strings like '1990-04-01 12:12:01 GMT +1' will also work.
    """
    grok.context(IDate)
    grok.provides(ISchemaTypeConverter)

    #: List of supported date formats in `strftime()` notation.
    formats = ['%Y-%m-%d', '%Y/%m/%d' , '%d/%m/%Y', '%D/%M/%Y',
               '%d.%m.%Y', '%D.%M.%Y']
    
    def _convertValueFromString(self, string):
        orig_string = string
        if string is NONE_STRING_VALUE:
            string = None
        if string is None:
            return None
        value = None
        if ' ' in string:
            string = string.split(' ')[0]
        for format in self.formats:
            try:
                value = datetime.datetime.strptime(string, format)
                break
            except ValueError:
                pass
        if value is None:
            raise ValueError(
                'Cannot convert to date: %s. Use YYYY-MM-DD.' %
                orig_string)
        value = value.date()
        return value

    def _convertValueToString(self, value):
        return datetime.date.strftime(value, '%Y-%m-%d')
