import copy

###################################################
# restrictions
###################################################

__all__ = ["restrict_nothing",
           "restrict_len",
           "restrict_len_range",
           "restrict_range",
           "restrict_type",
           "restrict_class",
           "restrict_list",
           "restrict_typelist",
           "restrict_value_list",
           "restrict_contains"]

class RestrictionError(ValueError):
    def __init__(self, arg0):
        ValueError.__init__(self, arg0)

class __property_restriction__(object):
    """ abstract base class for restrictions on property values """
    def __init__(self):
        pass
    
    def __and__(self, other):
        if isinstance(other, __property_restriction__):
            return __and_property_restriction__(self, other)
        elif other == None:
            return self
        else:
            raise TypeError("Cannot AND __property_restriction__ with %s" % type(other))
    
    def __iand__(self, other):
        C = self.__and__(other)
        self = C
        return self

    def __or__(self, other):
        if isinstance(other, __property_restriction__):
            return __or_property_restriction__(self, other)
        elif other == None:
            return self
        else:
            raise TypeError("Cannot AND __property_restriction__ with %s" % type(other))
    
    def __ior__(self, other):
        C = self.__and__(other)
        self = C
        return self

    def __invert__(self):
        return __not_property_restriction__(self)
    
    def __call__(self, value, obj = None):
        return self.validate(value, obj)
    
    def validate(self, value, obj = None):
        """ returns True if the value passes the restriction """ 
        return True

    def __repr__(self):
        return "Generic Restriction"



class __and_property_restriction__(__property_restriction__):
    def __init__(self, restriction1, restriction2):
        self.restriction1 = restriction1
        self.restriction2 = restriction2

    def validate(self, value, obj = None):
        return self.restriction1(value, obj) and self.restriction2(value, obj)

    def __repr__(self):
        return "(%s and %s)" % (self.restriction1, self.restriction2)

class __or_property_restriction__(__property_restriction__):
    def __init__(self, restriction1, restriction2):
        self.restriction1 = restriction1
        self.restriction2 = restriction2

    def validate(self, value, obj = None):
        return self.restriction1(value, obj) or self.restriction2(value, obj)

    def __repr__(self):
        return "(%s or %s)" % (self.restriction1, self.restriction2)

class __not_property_restriction__(__property_restriction__):
    def __init__(self, restriction):
        self.restriction = restriction

    def validate(self, value, obj = None):
        return not self.restriction(value, obj)

    def __repr__(self):
        return "(not %s)" % (self.restriction)



class restrict_nothing(__property_restriction__):
    """ no restriction on the property value """
    def __add__(self, other):
        if isinstance(other, __property_restriction__):
            return copy.copy(other)
        else:
            raise TypeError("Cannot add %s to __property_restriction__" % type(other))
    
    def __iadd__(self, other):
        self = copy.copy(other)
        return self

    def __repr__(self):
        return "No Restriction"

class restrict_type(__property_restriction__):
    """ restrict the type or types the argument can have. Pass a type or tuple of types """
    def __init__(self, allowed_types):
        self.allowed_types = ()
        self .__types_set = False
        self.__add_type__(allowed_types)
        if not self.__types_set:
            raise ValueError("allowed_typed of Type Restriction should be set on initialization")
        
    def __add_type__(self, type_type):
        if isinstance(type_type, type):
            self.allowed_types += (type_type,)
            self .__types_set = True
        elif isinstance(type_type, (tuple, list)):
            for T in type_type: self.__add_type__(T)
        else:
            raise TypeError("Restrict type should have a 'type' or 'tuple' of types as argument")
        
    def validate(self, value, obj = None):
        return isinstance(value, self.allowed_types)

    def __repr__(self):
        return "Type Restriction:" +",".join([T.__name__ for T in self.allowed_types])

class restrict_class(restrict_type):
    """ restrict the base class of an argumant which is a class. Pass a class or tuple of classes """
        
    def validate(self, value, obj = None):
        return issubclass(value, self.allowed_types)

    def __repr__(self):
        return "Class Restriction:" +",".join([T.__name__ for T in self.allowed_types])


class restrict_list(__property_restriction__):
    """ subject all individual elements of an iterable to a certain rerstriction """
    def __init__(self, restriction):
        self.restriction = restriction

    def validate(self, value, obj = None):
        try:
            for i in value:
                if not self.restriction.validate(i):
                    return False
            return True
        except:
            return False

    def __repr__(self):
        return "List Restriction: %s" % self.restriction
 
   
class restrict_typelist(restrict_list):
    """ restrict the argument to a list which contains a given type or types. Pass a type or tuple of types """
    def __init__(self, allowed_types):
    
        restrict_list.__init__(self, restriction = restrict_type(allowed_types))

    def __repr__(self):
        return "Type List Restriction:" +",".join([T.__name__ for T in self.restriction.allowed_types])

class restrict_value_list(__property_restriction__):
    def __init__(self, allowed_values):
        self.allowed_values = allowed_values

    def validate(self, value, obj = None):
        return value in self.allowed_values

    def __repr__(self):
        return "Value List Restriction: [" +",".join([T.__name__ for T in self.allowed_values])+"]"

class restrict_range(__property_restriction__):
    def __init__(self, lower = None, upper = None, lower_inc = True, upper_inc = False):
        self.lower = lower
        self.upper = upper
        self.lower_inc = lower_inc
        self.upper_inc = upper_inc
        if lower == None and upper == None:
            raise ValueError("Range Restriction should have an upper or lower limit")
        if not upper == None and not lower == None:
            if lower > upper:
                raise ValueError("lower limit should be smaller than upper limit in Range Restriction")

    def validate(self, value, obj = None):
        if self.lower == None:
            if self.upper_inc:
                return value < self.upper
            else:
                return value <= self.upper
        elif self.upper == None:
            if self.lower_inc:
                return value >= self.lower
            else:
                return value > self.lower
        else:
            if self.lower_inc:
                T1 = value >= self.lower
            else:
                T1 =  value > self.lower
            if self.upper_inc:
                T2 = value <=self.upper
            else:
                T2 =  value < self.upper
            return T1 and T2

    def __repr__(self):
        if self.lower_inc: left_b = "["
        else: left_b = "]"
        
        if self.upper_inc: right_b = "]"
        else: right_b = "["
        S = "Range Restriction: %s%s,%s%s" % (left_b, str(self.lower), str(self.upper), right_b)
        return S

class restrict_len_range(__property_restriction__):
    """ restrict the length of a list, tuple, shape, ... . Value must support __len__ method """
    def __init__(self, min_length = None, max_length = None):
        self.min_length = min_length
        self.max_length = max_length
        if min_length== None and max_length== None:
            raise ValueError("Len Range Restriction should have a minimum and/or maximum value")
        if not min_length == None and not max_length == None:
            if max_length > min_length:
                raise ValueError("Min_length should be smaller than Max_length Len Range Restriction")
    def validate(self, value, obj = None):
        L = len(value)
        if self.min_length== None:
            return L <= self.max_length
        elif upper == None:
            return L >= self.min_length
        else:
            T1 =  L >= self.min_length
            T2 =  L <= self.max_length
            return T1 and T2

    def __repr__(self):
        return  "Len Range Restriction: %s-%s" % (str(self.min_length), str(self.max_length))

class restrict_len(__property_restriction__):
    """ restrict the length of a list, tuple, shape, ... . Value must support __len__ method """
    def __init__(self, length):
        self.length = length
    def validate(self, value, obj = None):
        return len(value) == self.length
    def __repr__(self):
        return  "Len Restriction: %s" % (str(self.length))
 
class restrict_contains(__property_restriction__):
    def __init__(self, allowed_values):
        self.allowed_values = allowed_values
    def validate(self, value, obj = None):
        for v in self.allowed_values:
            if v in value: return True
        return False
    def __repr__(self):
        return  "Contains Restriction: %s" % (str(self.allowed_values))
        