"""Converters for zope.schema-based datatypes.
"""
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
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):
        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:
            return self.tokens[string]
        if self.terms is not None:
            return self.terms.getValue(string)
        return self.context.source.getTermByToken(string).value

    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)
