#
#  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
#

"""
Instance adapter classes for schemas.
"""

from BTrees.OOBTree import OOBTree
from zope.cachedescriptors.property import Lazy
from zope.component import adapts
from zope.interface import implements, Interface

from cybertools.composer.instance import Instance as BaseInstance
from cybertools.composer.interfaces import IInstance
from cybertools.composer.schema.interfaces import IClient
from cybertools.composer.schema.schema import FormState
from cybertools.util.jeep import Jeep


class Instance(BaseInstance):

    implements(IInstance)
    adapts(Interface)

    aspect = 'schema.editor.default'
    template = None
    view = None

    def applyTemplate(self, *args, **kw):
        result = {}
        mode = kw.get('mode', 'view')
        template = self.template
        if template is not None:
            for f in template.components:
                if not f.storeData:
                    # a dummy field, e.g. a spacer
                    continue
                fi = f.getFieldInstance(self, context=kw.get('context'),
                                              request=kw.get('request'))
                name = f.name
                value = getattr(self.context, name) or fi.default
                if mode in ('view', 'preview'):
                    value = fi.display(value)
                else:
                    value = fi.marshall(value)
                result[name] = value
        return result

    def getFieldInstances(self):
        fieldInstances = Jeep()
        template = self.template
        if template is not None:
            for f in template.components:
                fieldInstances[f.name] = f.getFieldInstance(self, 
                                                        context=self.context)
        return fieldInstances

    @Lazy
    def fieldInstances(self):
        return self.getFieldInstances()


class Editor(BaseInstance):

    implements(IInstance)
    adapts(Interface)

    aspect = 'schema.editor.default'
    template = None

    def applyTemplate(self, data={}, *args, **kw):
        fieldHandlers = kw.get('fieldHandlers', {})
        ignoreValidation = kw.get('ignoreValidation', False)
        template = self.template
        context = self.context
        formState = self.validate(data)
        if template is None:
            return formState
        if formState.severity > 0 and not ignoreValidation:
            # don't do anything if there is an error
            return formState
        for f in template.components:
            if not f.storeData or f.readonly:
                # a dummy field, e.g. a spacer
                continue
            name = f.name
            ftype = f.fieldType
            fi = formState.fieldInstances[name]
            #rawValue = data.get(name, u'')
            rawValue = fi.getRawValue(data, name, u'')
            value = fi.unmarshall(rawValue)
            if ftype in fieldHandlers:  # caller wants special treatment of field
                fieldHandlers[ftype](context, value, fi, formState)
            else:
                oldValue = getattr(context, name, None)
                if value not in ('__no_change__', oldValue):
                    setattr(context, name, value)
                    fi.change = (oldValue, value)
                    formState.changed = True
        return formState

    def validate(self, data):
        formState = FormState()
        if self.template is None:
            return formState
        for f in self.template.components:
            if f.readonly:
                continue
            fi = f.getFieldInstance(self, context=self.context)
            #value = data.get(f.name)
            value = fi.getRawValue(data, f.name)
            fi.validate(value, data)
            formState.fieldInstances.append(fi)
            formState.severity = max(formState.severity, fi.severity)
        return formState


class ClientInstance(object):

    implements(IInstance)
    adapts(IClient)

    attrsName = '__schema_attributes__'

    template = None
    baseAspect = 'schema.client.'

    @property
    def aspect(self):
        return self.baseAspect + self.template.name

    @property
    def standardAspect(self):
        return self.baseAspect + '__standard__'

    def __init__(self, context):
        self.context = context

    def applyTemplate(self, **kw):
        """ Return a mapping of field names from self.template (a schema)
            to the corresponding values from the context object.
        """
        result = dict(__name__=self.context.__name__)
        mode = kw.get('mode', 'view')
        attrs = getattr(self.context, self.attrsName, None)
        if attrs is None:
            return result
        template = self.template
        if template is not None:
            values = attrs.get(self.aspect, {})
            for f in template.getFields():
                if not f.storeData:
                    # a dummy field, e.g. a spacer
                    continue
                fi = f.getFieldInstance(self)
                name = f.name
                #value = values.get(name, u'')
                value = values.get(name, f.getDefaultValue())
                value = mode == 'view' and fi.display(value) or fi.marshall(value)
                result[name] = value
        # update result with standard fields:
        for k, v in attrs.get(self.standardAspect, {}).items():
            result['standard.' + k] = v
        return result


class ClientInstanceEditor(ClientInstance):

    def applyTemplate(self, data={}, **kw):
        """ Store the attributes described by self.template (a schema)
            using corresponding values from the data argument.
            Return the resulting form state (an object providing IFormState).
        """
        template = self.template
        if template is None:
            return FormState()
        ignoreValidation = kw.get('ignoreValidation', False)
        formState = self.validate(data)
        if formState.severity > 0 and not ignoreValidation:
            # don't do anything if there is an error
            return formState
        attrs = getattr(self.context, self.attrsName, None)
        if attrs is None:
            attrs = OOBTree()
            setattr(self.context, self.attrsName, attrs)
        values = attrs.setdefault(self.aspect, OOBTree())
        for f in template.fields:
            name = f.name
            if not f.storeData:
                # a dummy field, e.g. a spacer
                continue
            if name in data:
                fi = formState.fieldInstances[name]
                if fi.severity > 0:  # never store faulty field input
                    continue
                value = fi.unmarshall(data.get(name))
                oldValue = values.get(name)
                if value != oldValue:
                    values[name] = value
                    fi.change = (oldValue, value)
                    formState.changed = True
                # update standard field if appropriate:
                standardFieldName = f.standardFieldName
                if standardFieldName:
                    standardValues = attrs.setdefault(self.standardAspect, OOBTree())
                    if value != standardValues.get(standardFieldName):
                        standardValues[standardFieldName] = value
        return formState

    def validate(self, data):
        formState = FormState()
        for f in self.template.fields:
            fi = f.getFieldInstance()
            #value = fi.unmarshall(data.get(f.name))
            value = data.get(f.name)
            fi.validate(value, data)
            formState.fieldInstances.append(fi)
            formState.severity = max(formState.severity, fi.severity)
        return formState

