from . import descriptor as _descriptor
from ..mixin.mixin import meta_mixin_bowl

SUPPRESSED = (None,) # placeholder


class meta_property_initializer(meta_mixin_bowl):
    def __init__(cls, name, bases, dict):
        cls.__all_props__ = []
        cls.bind_properties()
        super(meta_property_initializer, cls).__init__(name, bases, dict)
        cls.compile_doc()
    
    def bind_properties(cls, Force = False):
        props = cls.__properties__()
        for p in props:
            if not p in cls.__all_props__:
                prop = getattr(cls, p)
                if hasattr(prop, "bind_to_class"): prop.bind_to_class(cls, p)
                prop.validate_definition(cls, p)
        cls.__all_props__ = props

    def mixin_first(cls, mixin_class):
        meta_mixin_bowl.mixin_first(cls,mixin_class)
        cls.bind_properties()
        
    def mixin_last(cls, mixin_class):
        meta_mixin_bowl.mixin_last(cls,mixin_class)
        cls.bind_properties()
        
        
            
    def compile_doc(cls):
        #props = cls.__all_props__ #cls.__properties__()
        if cls.__doc__:
            doc = cls.__doc__
        else:
            doc = ""
            
        
        rp = cls.__required_properties__()
        if len(rp)>0:
            doc += "\nRequired Properties:\n"
            for p in rp:
                doc += p + "\n"
                
        rp = cls.__optional_properties__()
        if len(rp)>0:
            doc += "\nOptional Properties:\n"
            for p in rp:
                doc += p+ "\n"
                
        rp = cls.__locked_properties__()
        if len(rp)>0:
            doc += "\nLocked Properties:\n"
            for p in rp:
                doc += p+ "\n"
        cls.__doc__ = doc
          
            

    # add validation: 
    # - property should be required or have a default value, or locked
    # - warning when init has no **kwargs attribute
    
    
    # change documentation to include properties

def is_suppressed(propvalue):
    if isinstance(propvalue, tuple):
        return SUPPRESSED == propvalue
    else:
        return False
    
class property_filter(object):
    def __init__(self, O):
        self.O = O
    def __call__(self, item):
        return isinstance(getattr(self.O, item))

    
    
    
class property_initializer(object):
    __metaclass__ = meta_property_initializer
    def __init__(self, **kwargs):
        # initialize defaults (required for functions)
        props = self.__all_props__ #self.__properties__()
            
        # assign properties
        for (key, value) in kwargs.items():
            if not is_suppressed(value): 
                setattr(self, key, value)

    @classmethod
    def __properties__(cls):
        return filter(cls.__is_property__, dir(cls))

    @classmethod
    def __required_properties__(cls, prop_list=None):
        if prop_list == None:
            prop_list = cls.__all_props__
        return filter(cls.__is_required__, prop_list)

    @classmethod
    def __optional_properties__(cls, prop_list=None):
        if prop_list == None:
            prop_list = cls.__all_props__ #cls.__properties__()
        return filter(cls.__is_optional__, prop_list)
    
    
    @classmethod
    def __unlocked_properties__(cls, prop_list=None):
        if prop_list == None:
            prop_list = cls.__all_props__ #cls.__properties__()
        return filter(cls.__is_unlocked__, prop_list)

    @classmethod
    def __locked_properties__(cls, prop_list=None):
        if prop_list == None:
            prop_list = cls.__all_props__ #cls.__properties__()
        return filter(cls.__is_locked__, prop_list)
    
    @classmethod
    def __is_property__(cls, item):
        return isinstance(getattr(cls, item), _descriptor.base_property_descriptor)

    @classmethod
    def __is_required__(cls, item):
        return getattr(cls, item).required

    @classmethod
    def __is_optional__(cls, item):
        I = getattr(cls, item)
        return not (I.required or I.locked)

    
    @classmethod
    def __is_locked__(cls, item):
        return not getattr(cls, item).locked

    @classmethod
    def __is_unlocked__(cls, item):
        return not getattr(cls, item).locked
        
    def __init_property_default__(self, item):
        P = getattr(type(self), item)
        if hasattr(P, 'default'):
            setattr(self, item, P.__get_default__())
        
    
    def __copy__(self):
        req_props = self.__unlocked_properties__() #self.__properties__()
        kwargs = {}
        for p in req_props:
            kwargs[p] = getattr(self, p)
        return self.__class__(**kwargs)

class strong_property_initializer(property_initializer):
    def __init__(self, **kwargs):
        props = self.__all_props__ #self.__properties__()
        req_props = self.__required_properties__(props)
        
        # assign properties
        for (key, value) in kwargs.items():
            if not key in props:
                raise AttributeError("Keyword Argument %s does not match a property of %s." % (key, str(type(self))))
            if not is_suppressed(value): 
                setattr(self, key, value)
        
        for p in req_props:
            if not p in kwargs:
                raise AttributeError("Required Property %s is not found in keyword arguments of %s initialization." % (p, str(type(self))))
                

