"""This module defines persistent versions of various fields.

The idea is that when a record is created, we copy relevant field properties
from the transient schema field from zope.schema, into the corresponding
persistent field. Note all field types are supported, but the common types
are.
"""

import persistent

import zope.interface

import zope.schema
import zope.schema._field
import zope.schema.vocabulary
import zope.schema.interfaces

_missing_value_marker = object()

from plone.registry.interfaces import IPersistentField

def is_primitive(value):
    return value is None or \
            isinstance(value, (int, long, bool, str, unicode, tuple, list,
                                set, frozenset, dict, float))

class DisallowedProperty(object):
    """A property that may not be set on an instance. It may still be set
    defined in a base class.
    """

    uses = []
    
    def __init__(self, name):
        self._name = name
        DisallowedProperty.uses.append(name)
    
    def __get__(self, inst, type_=None):
        # look for the object in bases
        if type_ is not None:
            for c in type_.__mro__:
                if self._name in c.__dict__ and not \
                        isinstance(c.__dict__[self._name], DisallowedProperty):
                    function = c.__dict__[self._name]
                    return function.__get__(inst, type_)
        raise AttributeError(self._name)
        
    def __set__(self, inst, value):
        raise ValueError(u"Persistent fields does not support setting the `%s` property" % self._name)

class StubbornProperty(object):
    """A property that stays stubbornly at a single, pre-defined value.
    """

    uses = []

    def __init__(self, name, value):
        StubbornProperty.uses.append(name)
        self._name = name
        self._value = value

    def __set__(self, inst, value):
        pass
        
    def __get__(self, inst, type_=None):
        return self._value

class InterfaceConstrainedProperty(object):
    """A property that may only contain values providing a certain interface.
    """
    
    uses = []
    
    def __init__(self, name, interface):
        InterfaceConstrainedProperty.uses.append((name, interface))
        self._name = name
        self._interface = interface
        
    def __set__(self, inst, value):
        if value != inst.missing_value:
            if not self._interface.providedBy(value):
                raise ValueError(u"The property `%s` may only contain objects providing `%s`." % 
                                    (self._name, self._interface.__identifier__,))
        inst.__dict__[self._name] = value

class PersistentField(persistent.Persistent):
    """Base class for persistent field definitions.
    """
    
    zope.interface.implements(IPersistentField)
    
    # Persistent fields do not have an order
    order = StubbornProperty('order', -1)
    
    # We don't allow setting a custom constraint, as this would introduce a
    # dependency on a symbol such as a function that may go away
    constraint = DisallowedProperty('constraint')

class PersistentCollectionField(PersistentField, zope.schema._field.AbstractCollection):
    """Ensure that value_type is a persistent field
    """
    
    value_type = InterfaceConstrainedProperty('value_type', IPersistentField)
    
class Bytes(PersistentField, zope.schema.Bytes):
    pass

class BytesLine(PersistentField, zope.schema.BytesLine):
    pass

class ASCII(PersistentField, zope.schema.ASCII):
    pass

class ASCIILine(PersistentField, zope.schema.ASCIILine):
    pass

class Text(PersistentField, zope.schema.Text):
    pass

class TextLine(PersistentField, zope.schema.TextLine):
    pass
    
class Bool(PersistentField, zope.schema.Bool):
    pass

class Int(PersistentField, zope.schema.Int):
    pass

class Float(PersistentField, zope.schema.Float):
    pass

class Tuple(PersistentCollectionField, zope.schema.Tuple):
    pass
    
class List(PersistentCollectionField, zope.schema.List):
    pass

class Set(PersistentCollectionField, zope.schema.Set):
    pass
    
class FrozenSet(PersistentCollectionField, zope.schema.FrozenSet):
    pass

class Password(PersistentField, zope.schema.Password):
    pass

class Dict(PersistentField, zope.schema.Dict):
    
    key_type = InterfaceConstrainedProperty('key_type', IPersistentField)
    value_type = InterfaceConstrainedProperty('value_type', IPersistentField)
    
class Datetime(PersistentField, zope.schema.Datetime):
    pass
    
class Date(PersistentField, zope.schema.Date):
    pass
    
class Timedelta(PersistentField, zope.schema.Timedelta):
    pass

class SourceText(PersistentField, zope.schema.SourceText):
    pass
    
class URI(PersistentField, zope.schema.URI):
    pass
    
class Id(PersistentField, zope.schema.Id):
    pass

class DottedName(PersistentField, zope.schema.DottedName):
    pass
    
class Choice(PersistentField, zope.schema.Choice):
    
    # We can only support string name or primitive=list vocabularies
    
    _values = None
    _vocabulary = None
    
    def __init__(self, values=None, vocabulary=None, source=None, **kw):
        
        if vocabulary is not None and not isinstance(vocabulary, basestring):
            raise ValueError("Persistent fields only support named vocabularies "
                             "or vocabularies based on simple value sets.")
        elif source is not None:
            raise ValueError("Persistent fields do not support sources, only named "
                             "vocabularies or vocabularies based on simple value sets.")

        assert not (values is None and vocabulary is None), (
               "You must specify either values or vocabulary.")
        assert values is None or vocabulary is None, (
               "You cannot specify both values and vocabulary.")

        self.vocabularyName = None
        
        if values is not None:
            
            for value in values:
                if not is_primitive(value):
                    raise ValueError("Vocabulary values may only contain primitive values.")
            
            self._values = values
        else:
            self.vocabularyName = vocabulary            
        
        self._init_field = bool(self.vocabularyName)
        super(zope.schema.Choice, self).__init__(**kw)
        self._init_field = False

    @property
    def vocabulary(self):
        # may be set by bind()
        if self._vocabulary is not None:
            return self._vocabulary
        if self._values is not None:
            return zope.schema.vocabulary.SimpleVocabulary.fromValues(self._values)
    DisallowedProperty.uses.append('vocabulary')
    
    # override bind to allow us to keep constraints on the 'vocabulary'
    # property
    def bind(self, object):
        clone = zope.schema.Field.bind(self, object)
        # get registered vocabulary if needed:
        if zope.schema.interfaces.IContextSourceBinder.providedBy(self.vocabulary):
            clone._vocabulary = self.vocabulary(object)
            assert zope.schema.interfaces.ISource.providedBy(clone.vocabulary)
        elif clone.vocabulary is None and self.vocabularyName is not None:
            vr = zope.schema.vocabulary.getVocabularyRegistry()
            clone._vocabulary = vr.get(object, self.vocabularyName)
            assert zope.schema.interfaces.ISource.providedBy(clone.vocabulary)
        return clone