from copy import deepcopy

from ..properties.descriptor import restricted_property
from ..properties.restrictions import restrict_type
from ..properties.processors import processor_typecast
from types import NoneType
from ..properties.initializer import strong_property_initializer

__all__ = ["generic_transformation_property",
           "transformation_property"]

#----------------------------------------------------------------------------
# Transformation class
#----------------------------------------------------------------------------

class gds_transform(strong_property_initializer):
    """ abstract base class for generic transform """

    def apply(self, item):
        """ applies the transform to the transformable item (no copy) """
        if isinstance(item, list):
            from .shape import gds_shape
            L =gds_shape(item)
            L.transform(self)
            return L
        else: 
            return item.transform(self)


    def apply_to_copy(self, item):
        """ applies the transform to a copy of the transformable item """
        if isinstance(item, list):
            from .shape import gds_shape
            L =gds_shape(item).transform_copy(self)
            return L
        else:
            return item.transform_copy(self)

    def __call__(self, item):
        """ if item is gds_transformable: applies the transform to a copy of the transformable item
            if item is a transform: returns a new transform that is a concatenation of this one and item """
        if isinstance(item, NoneType): return self
        if isinstance(item, gds_transform):
            return item + self
        else: #shape, coordinatelist or transformable
            return self.apply_to_copy(item)

    # define addition
    def __add__(self, other):
        """ creates a new transformation that is a concatenation of this one and other """
        if other == None: return gds_compound_transform([self])
        return gds_compound_transform([self, other])

    # define subtraction
    def __sub__(self, other):
        """ creates a new transformation that is a concatenation of this one and the reverse of other """
        if other == None: return gds_compound_transform([self])
        if isinstance(other, gds_reversible_transform):
            return gds_compound_transform([self, -other])
        else:
            raise TypeError("Cannot subtract an irreversible transform")

    def is_identity(self):
        """ returns True is the transformation does nothing """
        return True
    
    def is_isometric(self):
        """ returns True if the transformation conserves angles and distances """
        return True
    
    def is_homothetic(self):
        """ returns True if the transformation conserves angles """
        return True

#----------------------------------------------------------------------------
# Reversible Transformationclass
#----------------------------------------------------------------------------

class gds_reversible_transform(gds_transform):
    """ base class for a transformation that can be reversed """

    def reverse(self, item):
        """ applies the reverse transformation on item """
        if isinstance(item, list):
            from .shape import gds_shape
            L = gds_shape(item)
            L.reverse_transform(self)
            return L
        else:
            return item.reverse_transform(self)

    # define addition
    def __add__(self, other):
        """ creates a new transformation that is a concatenation of this one and other """
        if other == None: return gds_reversible_compound_transform([self])
        if isinstance(other, gds_reversible_transform):
            return gds_reversible_compound_transform([self, other])
        else:
            return gds_compound_transform([self, other])
        # if the other is irreversible, the compound transform will be automatically adjusted

    # define subtraction
    def __sub__(self, other):
        """ creates a new transformation that is a concatenation of this one and the reverse of other """
        if other == None: return gds_reversible_compound_transform([self])
        if isinstance(other, gds_reversible_transform):
            return gds_reversible_compound_transform([self, -other])
        else:
            raise TypeError("Cannot subtract an irreversible transform")

    # should return the reversed object
    def __neg__(self):
        """ returns the reverse transformation """
        pass


##    def apply_to_coord(self, coord):
##        return coord
##
##    def reverse_on_coord(self, coord):
##        return coord

#----------------------------------------------------------------------------
# Transformations that do not distort an object
# these transformation cal all be reduced to a concatenation of
#  - a vertical flip
#  - a rotation
#  - a magnification
#  - a translation
# these are encorporated in a single class, which allows for easy concatenation of
# transformations (using the + of () operator
# Special cases are subclassed (simple translation, rotation, mirroring, ...)
# for efficiency and readability
#
# These transformations can be used on shapes, but also on elements
# in fact, on any gds_no_distort_transformable object
#
#----------------------------------------------------------------------------

class gds_generic_no_distort_transform(gds_reversible_transform):
    pass


#----------------------------------------------------------------------------
# Compound Transformations
# these can be used only on shapes.
# for concatenations, a compound transformation is created
#----------------------------------------------------------------------------

class gds_compound_transform(gds_transform):
    """ a store for the concatenation of (non-homothetic) transforms """
    # a concatenation of transforms
    def __init__(self, transforms = [], **kwargs):
        if isinstance(transforms, list):
            self.__subtransforms__ = transforms
        elif isinstance(transforms, gds_compound_transform):
            self.__subtransforms__ = []
            self.__subtransforms__.extend(transforms)
        else:            
            self.__subtransforms__ = [transforms]
        super(gds_compound_transform, self).__init__(**kwargs)

    def apply(self, item):
        """ apply the transform to the transformable item """
        if isinstance(item, list):
            from .shape import gds_shape
            L =gds_shape(item)
            for c in self.__subtransforms__:
                L = c.apply(L)
            return L
        else:
            for c in self.__subtransforms__:
                item = c.apply(item)

    def apply_to_coord(self, coord):
        """ apply transformation to coordinate """
        for c in self.__subtransforms__:
            coord = c.apply_to_coord(coord)
        return coord

    def apply_to_array(self, coords):
        """ apply transformation to numpy array"""
        for c in self.__subtransforms__:
            coords = c.apply_to_array(coords)
        return coords

    def __add__(self, other):
        """ returns the concatenation of this transform and other """
        T = gds_compound_transform(self)
        T.add(other)
        return T

    def __iadd__(self, other):
        """ concatenates other to this transform """
        self.add(other)
        return self

    def add(self, other):
        """ concatenate another transform to the compound transform """
        if other == None: return 
        if isinstance(other, gds_compound_transform):
            for c in other.__subtransforms__:
                self.add(other)
        elif isinstance(other, gds_transform):
            self.__subelements__.append(other)
        else:
            raise TypeError("Cannot add object of type " + str(type(other)) + " to transform")
        
    def is_identity(self):
        """ returns True if the transformation does nothing """
        for c in self.__subtransforms__:
            if not c.is_identity(): return False
        return True

    def is_isometric(self):
        """ returns True if the transformation conserves angles and distances """
        for c in self.__subtransforms__:
            if not c.is_isometric(): return False
        return True
    
    def is_homothetic(self):
        """ returns True if the transformation conserves angles """
        """ returns True if the transformation does nothing """
        for c in self.__subtransforms__:
            if not c.is_homothetic(): return False
        return True

        
class gds_reversible_compound_transform(gds_compound_transform, gds_reversible_transform):
    """ a store for the concatenation of (non-homthetic) reversible transformas """

    def __make_irreversible__(self):
        self.__class__ = gds_compound_transform

    def reverse(self, item):
        """ applies the reverse transform on the transformable item"""
        if isinstance(item, list):
            from .shape import gds_shape
            L =gds_shape(item)
            for c in reversed(self.__subtransforms__):
                L = c.reverse(L)
            return L
        else:
            for c in reversed(self.__subtransforms__):
                item = c.reverse(item)


    def reverse_on_coord(self, coord):
        """ apply reverse transformation to coordinate """
        for c in reversed(self.__subtransforms__):
            coord = c.reverse_on_coord(coord)
        return coord

    def reverse_on_array(self, coords):
        """ internal use: applies reverse transformation to a numpy array """
        for c in reversed(self.__subtransforms__):
            coords = c.reverse_on_array(coords)
        return coords

    def __add__(self, other):
        """ returns the concatenation of this transform and other """
        T = gds_reversible_compound_transform(self)
        if other != None: T.add(other)
        return T

    def __iadd__(self, other):
        """ concatenates other to this transform """
        self.add(other)
        return self

    def __sub__(self, other):
        """ returns the concatenation of this transform and the reverse of other """
        T = gds_reversible_compound_transform(self)
        T.add(-other)
        return T

    def __isub__(self, other):
        """ concatenates the reverse of other to this transform """
        self.add(- other)
        return self


    def add(self, other):
        """ concatenates the other transform to this """
        if isinstance(other, gds_compound_transform):
            for c in other.__subtransforms__:
                self.add(other)
        if isinstance(other, gds_reversible_transform):
            self.__subelements__.append(other)
        elif isinstance(other, gds_transform):
            self.__make_irreversible__()
            self.__subelements__.append(other)
        else:
            raise TypeError("Cannot add object of type " + str(type(other)) + " to transform")

    def __neg__(self):
        """ returns the reverse transform """
        T = gds_reversible_compound_transform()
        for c in reversed(self):
            T.add(-c)
        return T

#----------------------------------------------------------------------------
# Transformations that do distort the shape
# these can be used only on shapes.
# for concatenations, a compound transformation is created
#----------------------------------------------------------------------------

    
#----------------------------------------------------------------------------
# Shape rotations and translation functions
#----------------------------------------------------------------------------


class processor_transformation(processor_typecast):
    def __init__(self):
        processor_typecast.__init__(self, gds_transform)
    
    def process(self, value, obj= None):
        from .transforms.identity import gds_identity_transform
        if value == None:
            A = gds_identity_transform()
            return A
        else:
            return processor_typecast.process(self, value, obj)
        
class processor_no_distort_transformation(processor_transformation):
    def __init__(self):
        processor_typecast.__init__(self, gds_generic_no_distort_transform)
    
        

def generic_transformation_property(internal_member_name = None, restriction = None, preprocess = None, **kwargs):
    if kwargs.has_key("default"): default = kwargs["default"]
    else: default = None
    R = restrict_type(gds_transform) & restriction
    P = processor_transformation() + preprocess
    return restricted_property(internal_member_name, default = default, restriction = R, preprocess = P)

def transformation_property(internal_member_name = None, restriction = None, preprocess = None,**kwargs):
    if kwargs.has_key("default"): default = kwargs["default"]
    else: default = None
    R = restrict_type(gds_generic_no_distort_transform) & restriction
    P = processor_no_distort_transformation() + preprocess
    return restricted_property(internal_member_name, default = default, restriction = R, preprocess = P)



