#
#  Copyright (c) 2013 Helmut Merz helmutm@cy55.de
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

"""
Schema fields and related classes.
"""

from datetime import datetime
from logging import getLogger
from time import strptime, strftime
from zope.app.form.browser.interfaces import ITerms
from zope.interface import implements
from zope.cachedescriptors.property import Lazy
from zope.component import adapts
from zope import component
from zope.i18n.format import DateTimeParseError
from zope.i18n.locales import locales
from zope.schema.interfaces import IVocabularyFactory, IContextSourceBinder
from zope.tales.engine import Engine
from zope.tales.tales import Context

from cybertools.composer.base import Component
from cybertools.composer.schema.interfaces import IField, IFieldInstance
from cybertools.composer.schema.interfaces import fieldTypes, undefined
from cybertools.composer.schema.schema import formErrors
from cybertools.util.format import toStr, toUnicode


class Field(Component):

    implements(IField)

    required = False
    readonly = False
    nostore = False
    standardFieldName = None
    vocabulary = None
    renderFactory = None
    default = None
    default_method = None
    defaultValueType = 'static'

    value_type = None

    fieldTypeInfo = None
    baseField = None
    instance_name = None
    input_renderer = None
    display_renderer = None
    display_format = None


    def __init__(self, name, title=None, fieldType='textline', **kw):
        assert name
        self.__name__ = name
        #title = title or u''
        title = title or name
        self.fieldType = fieldType
        super(Field, self).__init__(title, __name__=name, **kw)
        self.title = title
        for k, v in kw.items():
            setattr(self, k, v)

    def __getattr__(self, attr):
        if self.baseField is not None:
            return getattr(self.baseField, attr)
        raise AttributeError(attr)

    @property
    def name(self):
        return self.__name__

    def getDefaultValue(self):
        if callable(self.default_method):
            return self.default_method()
        if self.defaultValueType == 'tales':
            expr = Engine.compile(self.default)
            ctx = Context(Engine, self.getContextProperties())
            try:
                return expr(ctx)
            except AttributeError, KeyError:
                return u''
        return self.default
    def setDefaultValue(self, value):
        self.default = value
    defaultValue = property(getDefaultValue, setDefaultValue)

    def getDefaultValueExpr(self):
        return self.default
    def setDefaultValueExpr(self, value):
        self.default = value
    defaultValueExpr = property(getDefaultValueExpr, setDefaultValueExpr)

    @property
    def fieldRenderer(self):
        return self.getFieldTypeInfo().fieldRenderer

    @property
    def inputRenderer(self):
        return self.input_renderer or self.getFieldTypeInfo().inputRenderer

    @property
    def displayRenderer(self):
        return self.display_renderer or self.getFieldTypeInfo().displayRenderer

    @property
    def storeData(self):
        return not self.nostore and self.getFieldTypeInfo().storeData

    @property
    def required_js(self):
        return self.required and 'true' or 'false'

    def getTitleValue(self):
        return self.title or self.name

    def getVocabularyItems(self, instance=None, request=None):
        context = None
        if instance is not None:
            context = instance.context
        voc = (self.vocabulary or '')
        if isinstance(voc, basestring):
            terms = self.getVocabularyTerms(voc, context, request)
            if terms is not None:
                return terms
            voc = voc.splitlines()
            return [dict(token=t, title=t) for t in voc if t.strip()]
        elif IContextSourceBinder.providedBy(voc):
            source = voc(instance)
            terms = component.queryMultiAdapter((source, request), ITerms)
            if terms is not None:
                termsList = [terms.getTerm(value) for value in source]
                return [dict(token=t.token, title=t.title) for t in termsList]
            else:
                return None
        return [dict(token=t.token, title=t.title or t.value) for t in voc]

    def getVocabularyTerms(self, name, context, request):
        if context is None or request is None:
            return None
        source = component.queryUtility(IVocabularyFactory, name=name)
        if source is not None:
            source = source(context)
            terms = component.queryMultiAdapter((source, request), ITerms)
            if terms is not None:
                termsList = [terms.getTerm(value) for value in source]
                return [dict(token=t.token, title=t.title) for t in termsList]
        return None

    def getFieldTypeInfo(self):
        return self.fieldTypeInfo or fieldTypes.getTerm(self.fieldType)

    def getFieldInstance(self, clientInstance=None, context=None, request=None):
        instanceName = self.instance_name or self.getFieldTypeInfo().instanceName
        fi = component.queryAdapter(self, IFieldInstance, name=instanceName)
        if fi is None:
            fi = component.getAdapter(self, IFieldInstance, name='')
        fi.clientInstance = clientInstance
        fi.clientContext = context
        fi.request = request
        return fi

    def getContextProperties(self):
        return dict(context=self, user=None)


class FieldInstance(object):

    implements(IFieldInstance)
    adapts(IField)

    clientInstance = None
    clientContext = None
    value = undefined
    request = None
    index = None

    def __init__(self, context):
        self.context = context
        self.name = self.__name__ = context.name
        self.errors = []
        self.severity = 0
        self.change = None

    @property
    def default(self):
        dm = self.context.default_method
        if dm and isinstance(dm, str) and self.clientInstance:
            method = getattr(self.clientInstance.context, dm, None)
            if method:
                return method()
        return self.context.getDefaultValue()

    def getRawValue(self, data, key, default=None):
        return data.get(key, default)

    def marshall(self, value):
        return value or u''
        #return toStr(value)

    def display(self, value):
        return value or u''
        #return toStr(value)

    def unmarshall(self, value):
        return toUnicode(value) or u''

    def validate(self, value, data=None):
        if not value and self.context.required:
            self.setError('required_missing')

    def setError(self, errorName, formErrors=formErrors):
        error = formErrors[errorName]
        self.errors.append(error)
        self.severity = max(error.severity, self.severity)

    def getRenderer(self, name):
        return None


class NumberFieldInstance(FieldInstance):

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

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

    def unmarshall(self, value):
        if not value:
            return None
        try:
            #return int(str(value))
            return int(value)
        except (TypeError, ValueError):
            return float(value)

    def validate(self, value, data=None):
        if value in ('', None):
            if self.context.required:
                self.setError('required_missing')
        else:
            try:
                self.unmarshall(value)
            except (TypeError, ValueError):
                self.setError('invalid_number')


class DateFieldInstance(NumberFieldInstance):

    def marshall(self, value):
        if value is None:
            return ''
        if isinstance(value, basestring):
            return value
        return strftime('%Y-%m-%dT%H:%M', value.timetuple())

    def display(self, value):
        if value is None:
            return ''
        view = self.clientInstance.view
        langInfo = view and getattr(view, 'languageInfo', None) or None
        format = self.context.display_format or ('dateTime', 'short')
        if langInfo:
            locale = locales.getLocale(langInfo.language)
            fmt = locale.dates.getFormatter(*format)
            return fmt.format(value)
        return str(value)

    def unmarshall(self, value):
        if not value:
            return None
        if len(value) > 1 and value[0] and not value[1]:
            value[1] = 'T00:00:00'
        value = ''.join(value)
        if value:
            try:
                return datetime(*(strptime(value, '%Y-%m-%dT%H:%M:%S')[:6]))
            except ValueError:
                try:
                    return datetime(*(strptime(value, '%Y-%m-%dT%H:%M')[:6]))
                except ValueError:
                    return datetime(*(strptime(value, '%Y-%m-%d')[:6]))
        return None

    def validate(self, value, data=None):
        if value in ('', ['', ''], None):
            if self.context.required:
                self.setError('required_missing')
        else:
            try:
                self.unmarshall(value)
            except (TypeError, ValueError, DateTimeParseError), e:
                getLogger('cybertools').warn(
                        'DateFieldInstance: invalid datetime: %s, %s' % (value, e))
                self.setError('invalid_datetime')


class FileUploadFieldInstance(FieldInstance):

    def marshall(self, value):
        return value

    def unmarshall(self, value):
        return value


class EmailFieldInstance(FieldInstance):

    def validate(self, value, data=None):
        if not value and self.context.required:
            self.setError('required_missing')
        if value and '@' not in value:
            self.setError('invalid_email_address')


class BooleanFieldInstance(FieldInstance):

    def marshall(self, value):
        return value

    def display(self, value):
        #return value and _(u'Yes') or _(u'No')
        return value and u'X' or u'-'

    def unmarshall(self, value):
        return bool(value)


class CalculatedFieldInstance(FieldInstance):

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


class ListFieldInstance(FieldInstance):

    @Lazy
    def valueType(self):
        return self.context.value_type

    @Lazy
    def valueFieldInstance(self):
        if self.valueType is None:
            return FieldInstance(self.context)
        else:
            instanceName = (self.valueType.instance_name or
                            self.valueType.getFieldTypeInfo().instanceName)
        return component.getAdapter(self.valueType, IFieldInstance, name=instanceName)

    def marshall(self, value):
        if isinstance(value, basestring):
            return value
        if value is None:
            return u''
        return u'\n'.join(unicode(self.valueFieldInstance.marshall(v)) for v in value)
        #return [self.valueFieldInstance.marshall(v) for v in value]

    def display(self, value):
        if not value:
            return u''
        if isinstance(value, basestring):
            return value
        return u' | '.join(unicode(self.valueFieldInstance.display(v)) for v in value)

    def unmarshall(self, value):
        if isinstance(value, basestring):
            value = value.split('\n')
        return [self.valueFieldInstance.unmarshall(v.strip())
                        for v in value if v.strip()]


class CheckBoxesFieldInstance(ListFieldInstance):

    def marshal(self, value):
        return value

    def ummarshal(self, value):
        return value


class DropdownFieldInstance(FieldInstance):

    def display(self, value):
        items = self.context.getVocabularyItems(self.clientInstance, self.request)
        for item in items:
            if item['token'] == value:
                return item['title']
        return value or u''

