import gk_shape 
import gk_transformable
from gk_coord import *
from math import *
from gk_global import *
from numpy import *
from types import *
from gk_property_descriptor import *
from copy import deepcopy

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

class gds_transform(object):
    """ abstract base class for generic transform """
    def __init__(self):
        object.__init__(self)

    def apply(self, item):
        """ applies the transform to the transformable item (no copy) """
        if isinstance(item, gk_transformable.gds_transformable):
            return item.transform(self)
        elif isinstance(item, list):
            L =gk_shape.gds_shape(item)
            L.transform(self)
            return L

    def apply_to_copy(self, item):
        """ applies the transform to a copy of the transformable item """
        if isinstance(item, gk_transformable.gds_transformable):
            return item.transform_copy(self)
        elif isinstance(item, list):
            L =gk_shape.gds_shape(item).transform_copy(self)
            return L

    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

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

class gds_reversible_transform(gds_transform):
    """ base class for a transformation that can be reversed """
    def __init__(self):
        object.__init__(self)

    def reverse(self, item):
        """ applies the reverse transformation on item """
        if isinstance(item, gk_transformable.gds_transformable):
            return item.transform(self)
        elif isinstance(item, list):
            L = gk_shape.gds_shape(item)
            L.reverse_transform(self)
            return L

    # 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 is_identity(self):
        """ returns True is the transformation does nothing """
        return True

##    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_no_distort_transform(gds_reversible_transform):
    """ A homothetic transformation that does distort the item it is applied to (angle conservation) 
        The transform is defined by 
        - a mirror around the x-axis
        - a scaling with respect to the origin
        - a rotation around the origin
        - a translation
        
        it is also possible to set absolute magnification and rotation, which means that subsequent transformations
        will not affect the angle and size any further
        """
    def __init__(self, translation = (0.0, 0.0), rotation = 0.0, magnification = 1.0, v_mirror = False, absolute_magnification = False, absolute_rotation = False):
        # note: translation is no part of gdsII transform. It should be taken into account while writing the file
        self.translation = translation
        self.rotation = rotation
        self.magnification = magnification
        self.v_mirror = v_mirror
        self.absolute_magnification = absolute_magnification
        self.absolute_rotation = absolute_rotation

    def get_translation(self): return self.__translation__
    def set_translation (self, value): 
        if isinstance(value, coord2):
            self.__translation__ = value
        else:
            self.__translation__ = coord2(value[0], value[1])
    translation = property(get_translation, set_translation)
    """ the translation coordinate """
   
    def get_rotation (self): return self.__rotation__
    def set_rotation (self, value):
        self.__rotation__ = value % 360.0
        if self.__rotation__%90.0 == 0.0:
            # make sure sine and cosine are really zero when needed!
            if self.__rotation__ == 0.0:
                self.__ca__ = 1.0
                self.__sa__ = 0.0
            elif self.__rotation__ == 90.0:
                self.__ca__ = 0.0
                self.__sa__ = 1.0
            elif self.__rotation__ == 180.0:
                self.__ca__ = -1.0
                self.__sa__ = 0.0
            elif self.__rotation__ == 270.0:
                self.__ca__ = 0.0
                self.__sa__ = -1.0
        else:
            self.__ca__ = cos(value * DEG2RAD)
            self.__sa__ = sin(value * DEG2RAD)
    rotation = property(get_rotation, set_rotation)
    """ the rotation around the origin """

    def get_magnification(self): return self.__magnification__
    def set_magnification (self, value):
        if value == 0.0:
            stderr, "Warning: Magnification of 0 is not allowed. Setting to 1"
            self.magnification == 1.0
        else:
            self.__magnification__ = value
    magnification = property(get_magnification, set_magnification)
    """ the magnification factor """

    def get_v_mirror(self): return self.__v_mirror__
    def set_v_mirror (self, value): self.__v_mirror__ = value
    v_mirror = property(get_v_mirror, set_v_mirror)
    """ the vertical mirror """
    flip = property(get_v_mirror, set_v_mirror) # backward compatibility
    

    def get_absolute_magnification(self) : return self.__absolute_magnification__
    def set_absolute_magnification(self, value): self.__absolute_magnification__ = value
    absolute_magnification = property(get_absolute_magnification, set_absolute_magnification)
    """ set absolute magnification on or off"""

    def get_absolute_rotation(self) : return self.__absolute_rotation__
    def set_absolute_rotation(self, value): self.__absolute_rotation__ = value
    absolute_rotation = property(get_absolute_rotation, set_absolute_rotation)
    """ set absolute rotation on or off"""

    def __make_simple__(self):
        """ internal use: reduces special subclasses to this generic type """
        # if you change special types, they should revert to generic type
        self.__class__ = gds_no_distort_transform

    def __translate__(self, coord):
        """ internal use: applies translation to a coordinate """
        return coord2(coord[0] + self.translation.x, coord[1] + self.translation.y)
    
    def __rotate__(self, coord):
        """ internal use: applies rotation to a coordinate """
        return coord2(coord[0]*self.__ca__ - coord[1]*self.__sa__, coord[0]*self.__sa__ + coord[1]*self.__ca__)

    def __magnify__(self, coord):
        """ internal use: applies magnification to a coordinate """
        return coord2(coord[0] * self.magnification, coord[1] * self.magnification)

    def __v_flip__(self, coord):
        """ internal use: applies v_mirror to a coordinate """
        if self.__v_mirror__:
            return coord2(coord[0], -coord[1])
        else:
            return coord2(coord[0], coord[1])

    def __inv_translate__(self, coord):
        """ internal use: applies reverse translation to a coordinate """
        return coord2(coord[0] - self.translation.x, coord[1] - self.translation.y)
    
    def __inv_rotate__(self, coord):
        """ internal use: applies reverse rotation to a coordinate """
        return coord2(coord[0]*self.__ca__ + coord[1]*self.__sa__, - coord[0]*self.__sa__ + coord[1]*self.__ca__)

    def __inv_magnify__(self, coord):
        """ internal use: applies reverse magnification to a coordinate """
        return coord2(coord[0] / self.magnification, coord[1] / self.magnification)

    def __inv_v_flip__(self, coord):
        """ internal use: applies reverse v_mirror to a coordinate """
        if self.__v_mirror__:
            return coord2(coord[0], - coord[1])
        else:
            return coord2(coord[0], coord[1])

    def __translate_array__(self, coords):
        """ internal use: applies translation to a numpy array """
        coords += array([self.translation.x, self.translation.y])
        return coords
    
    def __rotate_array__(self, coords):
        """ internal use: applies rotation to a numpy array """
        x_a = array([self.__ca__, -self.__sa__])
        y_a = array([self.__sa__, self.__ca__])
        coords = transpose(vstack((sum(coords * x_a, 1), sum(coords * y_a, 1))))
        return coords

    def __magnify_array__(self, coords):
        """ internal use: applies magnification to a numpy array """
        coords *= array([self.magnification, self.magnification])
        return coords

    def __v_flip_array__(self, coords):
        """ internal use: applies v_mirror to a numpy array """
        coords *= (array([False, self.__v_mirror__]) * -2.0 + 1.0)
        return coords

    def __inv_translate_array__(self, coords):
        """ internal use: applies reverse translation to a numpy array """
        coords -= array([self.translation.x, self.translation.y])
        return coords
    
    def __inv_rotate_array__(self, coords):
        """ internal use: applies reverse rotation to a numpy array """
        x_a = array([self.__ca__, self.__sa__])
        y_a = array([-self.__sa__, self.__ca__])
        coords = transpose(sum(coords * x_a, 1), sum(coords * y_a, 1))
        return coords

    def __inv_magnify_array__(self, coords):
        """ internal use: applies reverse magnification to a numpy array """
        coords *= array([1.0/self.magnification, 1.0/self.magnification])
        return coords

    def __inv_v_flip_array__(self, coords):
        """ internal use: applies reverse v_mirror to a numpy array """
        coords *= array([False, self.__v_mirror__]) * (-2.0) + 1.0
        return coords

    def apply_to_coord(self, coord):
        """ applies transformation to a coordinate """
        # this could be speeded up
        # Check the right order
        coord = self.__v_flip__(coord)# first flip 
        coord = self.__rotate__(coord)# then magnify
        coord = self.__magnify__(coord)# then rotate
        coord = self.__translate__(coord) # finally translate
        return coord
    
    def reverse_on_coord(self, coord):
        """ applies reverse transformation to a coordinate """
        # this could be speeded up
        # Check the right order
        coord = self.__inv_translate__(coord) # finally translate
        coord = self.__inv_magnify__(coord)# then rotate
        coord = self.__inv_rotate__(coord)# then magtnify
        coord = self.__inv_v_flip__(coord)# first flip 
        return coord

    def apply_to_array(self, coords):
        """ internal use: applies transformation to a numpy array"""
        # this could be speeded up
        # Check the right order
        coords = self.__v_flip_array__(coords)# first flip 
        coords = self.__rotate_array__(coords)# then rotate
        coords = self.__magnify_array__(coords)# then magnify
        coords = self.__translate_array__(coords) # finally translate
        return coords
    
    def reverse_on_array(self, coord):
        """ internal use: applies reverse transformation to a numpy array """
        # this could be speeded up
        # Check the right order
        coords = self.__inv_translate_array__(coords) # finally translate
        coords = self.__inv_magnify_array__(coords)# then magnify
        coords = self.__inv_rotate_array__(coords)# then rotate
        coords = self.__inv_v_flip_array__(coords)# first flip 
        return coords

    def apply_to_angle_deg(self, angle):
        """ applies transformation to an absolute angle (degrees) """
        a = angle
        if self.__v_mirror__: a = -a
        a += self.__rotation__
        return a%360.0
    
    def reverse_on_angle_deg(self, angle):
        """ applies reverse transformation to an absolute angle (degrees)"""
        a = angle - self.__rotation__
        if self.__v_mirror__: a = -a
        return a%360.0

    def apply_to_angle_rad(self, angle):
        """ applies transformation to an absolute angle (radians) """
        a = angle
        if self.__v_mirror__: a = -a
        a += self.__rotation__ * DEG2RAD
        return a%(2*pi)
    
    def reverse_on_angle_rad(self, angle):
        """ applies reverse transformation to an absolute angle (radians) """
        a = angle - self.__rotation__ * DE2RAD
        if self.__v_mirror__: a = -a
        return a%(2*pi)

    def apply_to_length(self, length):
        """ applies transformation to a distance """
        return length * self.__magnification__

    def reverse_on_length(self, length):
        """ applies reverse transformation to a distance """
        return length / self.__magnification__

    def __neg__(self):
        """ returns the reverse transformation """
        # TODO: speed this up!
        #raise AssertionError("negative of gds_no_distort transform is not yet defined!")
        T = -gds_translation(self.translation) - gds_magnification((0.0, 0.0), self.magnification) - gds_rotation((0.0, 0.0), self.rotation) 
        if self.v_mirror:
            T += gds_v_mirror(0.0)
        return T

    def __sub__(self, other):
        """ returns the concatenation of this transform and the reverse of other """
        if other == None: return deepcopy(self)
        if not isinstance(other, gds_reversible_transform):
            raise TypeError("Cannot subtract an irreversible transform")
        return self.__add__(-other)

    def __isub__(self, other):
        """ concatenates the reverse of other to this transform """
        if other == None: return self
        if not isinstance(other, gds_reversible_transform):
            raise TypeError("Cannot subtract an irreversible transform")
        return self.__iadd__(self, -other)

    def __add__(self, other):
        """ returns the concatenation of this transform and other """
        # performs transformation "other" after "self" and returns resulting transform
        if other == None: return deepcopy(self)

        if isinstance(other, gds_no_distort_transform):
            T = gds_no_distort_transform()

            if self.absolute_magnification:
                M1 = 1.0
            else:
                M1 = other.__magnification__
            T.magnification = self.__magnification__ * M1

            #flip signs
            if other.__v_mirror__: s_1 = -1
            else:              s_1 = 1

            if not self.absolute_rotation:
                T.rotation = s_1 * self.__rotation__ + other.__rotation__
                ca = other.__ca__
                sa = other.__sa__
            else:
                T.rotation = s_1 * self.__rotation__ 
                ca = 1.0
                sa = 0.0


            # tricky part: translation
            T.translation = coord2( other.__translation__.x + ca * self.__translation__.x * M1 - s_1 * sa * self.translation.y * M1 ,
                                    other.__translation__.y + sa * self.__translation__.x * M1 + s_1 * ca * self.translation.y * M1)

            T.absolute_rotation = self.absolute_rotation or other.absolute_rotation
            T.absolute_magnification = self.absolute_magnification or other.absolute_magnification
            T.v_mirror = (not self.__v_mirror__ == other.__v_mirror__)
        else:
           T =  gds_transform.__add__(self, other)
        return T

    def __iadd__(self, other):
        """ concatenates other to this transform """
        if other == None: return self
        # performs transformation other after self and returns self
        if isinstance(other, gds_no_distort_transform):
            # tricky part: translation
            if self.absolute_magnification:
                self.magnification = self.__magnification__ * other.__magnification__
                M1 = 1
            else:
                M1 = other.__magnification__

            #flip signs
            if other.__v_mirror__: s_1 = -1
            else:              s_1 = 1


            if not self.absolute_rotation:
                self.rotation = s_1 * self.__rotation__ + other.__rotation__
                ca = other.__ca__
                sa = other.__sa__
            else:
                self.rotation = s_1 * self.__rotation__                
                ca = 1
                sa = 0

            # tricky part: translation
            self.translation = ( other.__translation__.x + ca * self.__translation__.x * M1 - s_1 * sa * self.translation.y * M1,
                                 other.__translation__.y + sa * self.__translation__.x * M1 + s_1 * ca * self.translation.y * M1)

            self.absolute_rotation = self.absolute_rotation or other.absolute_rotation
            self.absolute_magnification = self.absolute_magnification or other.absolute_magnification
            self.v_mirror = (not self.__v_mirror__ == other.__v_mirror__)
        else:
           raise TypeError ("Error: Cannot perform += operation for gds_no_distort_transform and other transform of type " + str(type(other)))
        return self
    
    def __eq__(self, other):
        """ check if the transforms do the same thing """
        if other == None: return self.is_identity()
        if not isinstance(other, gds_no_distort_transform): return False
        return ((self.__rotation__ == other.__rotation__) and
                (self.__translation__ == other.__translation__) and
                (self.__v_mirror__ == other.__v_mirror__) and
                (self.__magnification__ == other.__magnification__) and
                (self.__absolute_rotation__ == other.__absolute_rotation__) and
                (self.__absolute_magnification__ == other.__absolute_magnification__)
                )

    def __ne__(self, other):
        """ checks if the transforms do different things """

        if other == None: return not self.is_identity()
        if not isinstance(other, gds_no_distort_transform): return False
        return ((self.__rotation__ != other.__rotation__) or
                (self.__translation__ != other.__translation__) or
                (self.__v_mirror__ != other.__v_mirror__) or
                (self.__magnification__ != other.__magnification__) or
                (self.__absolute_rotation__ != other.__absolute_rotation__) or
                (self.__absolute_magnification__ != other.__absolute_magnification__)
                )
        
    def is_identity(self):
        """ returns True if the transformation does nothing """
        return ((self.__rotation__ == 0.0) and
                (self.__translation__.x == 0.0) and
                (self.__translation__.y == 0.0) and
                not (self.__v_mirror__) and 
                (self.__magnification__ == 1.0)
            )

    def id_string(self):
        """ gives a hish of the transform (for naming purposes) """
        return str(self.__hash__())

    def __hash__(self):
        return hash("R" + str(int(self.__rotation__*10000)) + 
                        "T" + str(int(self.__translation__[0] * 1000)) + "_" + str(int(self.__translation__[1] * 1000)) +
                        "M" + str(int(self.__magnification__ * 1000)) +
                        "V" + str(self.__v_mirror__) +
                        "AR" + str(self.__absolute_rotation__) +
                        "AM" + str(self.__absolute_magnification__)
                        )

class gds_special_no_distort_transform(gds_no_distort_transform):
    """ abstract base class for special transforms that are subclassed from gds_no_distort_transforms"""
    def __init__(self, additional_properties = []):
        if not hasattr(self, "__additional_properties"):
            self.__additional_properties = []
        self.__additional_properties += additional_properties
        self.__translation__ = coord2(0.0, 0.0)
        self.__rotation__ = 0.0
        self.__ca__ = 1.0
        self.__sa__ = 0.0
        self.__magnification__ = 1.0
        self.__v_mirror__ = False
        self.__absolute_magnification__ = False
        self.__absolute_rotation__ = False

    def __make_simple__(self):
        # if you change special types, they should revert to generic type
        for p in self.__additional_properties:
            self.__delattr__(p)
        self.__class__ = gds_no_distort_transform
        
    def set_translation (self, value):
        self.__make_simple__()
        gds_no_distort_transform.set_translation(self, value)
    def get_translation (self):
        return gds_no_distort_transform.get_translation(self)
    translation = property(get_translation, set_translation)
    """ the translation coordinate """
    
    def set_rotation (self, value):
        self.__make_simple__()
        gds_no_distort_transform.set_rotation(self, value)
    def get_rotation (self):
        return gds_no_distort_transform.get_rotation(self)
    rotation = property(get_rotation, set_rotation)
    """ the rotation angle (degrees) """
    
    def set_magnification (self, value):
        self.__make_simple__()
        gds_no_distort_transform.set_magnification(self, value)
    def get_magnification (self):
        return gds_no_distort_transform.get_magnification(self)
    magnification = property(get_magnification, set_magnification)
    """ the magnification factor"""

    def set_v_mirror (self, value): 
        self.__make_simple__()
        gds_no_distort_transform.set_v_mirror(self, value)
    def get_v_mirror (self): 
        return gds_no_distort_transform.get_v_mirror(self)
    v_mirror = property(get_v_mirror, set_v_mirror)
    """ vertical mirror on or off """
    flip = property(get_v_mirror, set_v_mirror) # backward compatibility
    """ vertical mirror on or off """

    def set_absolute_magnification(self, value): 
        self.__make_simple__()
        gds_no_distort_transform.set_absolute_magnification(self, value)
    def get_absolute_magnification(self): 
        return gds_no_distort_transform.get_absolute_magnification(self)
    absolute_magnification = property(get_absolute_magnification, set_absolute_magnification)
    """ absolute magnification on or off"""

    def set_absolute_rotation(self, value): 
        self.__make_simple__()
        gds_no_distort_transform.set_absolute_rotation(self, value)
    def get_absolute_rotation(self):
        return gds_no_distort_transform.get_absolute_rotation(self)
    absolute_rotation = property(get_absolute_rotation, set_absolute_rotation)
    """ absolute rotation on or off"""

    def __isub__(self, other):
        """ concatenates reverse of other to this transformation """
        self.__make_simple__()
        return gds_no_distort_transform.__isub__(self, other)

    def __iadd__(self, other):
        """ concatenates other to this transformation """
        self.__make_simple__()
        return gds_no_distort_transform.__iadd__(self, other)

    def apply_to_angle_deg(self, angle):
        """ applies transformation to absolute angle (degrees) """
        return angle
    
    def reverse_on_angle_deg(self, angle):
        """ applies reverse transformation to absolute angle (degrees) """
        return angle

    def apply_to_angle_rad(self, angle):
        """ applies transformation to absolute angle (radians) """
        return angle
    
    def reverse_on_angle_rad(self, angle):
        """ applies reverse transformation to absolute angle (radians) """
        return angle

    def apply_to_length(self, length):
        """ applies transformation to distance """
        return length

    def reverse_on_length(self, length):
        """ applies reverse transformation to distance """
        return length

class gds_translation(gds_special_no_distort_transform):
    """ translation transform """
    def __init__(self, translation = coord2(0.0, 0.0)):
        gds_special_no_distort_transform.__init__(self)
        self.translation = translation

    def set_translation (self, value):
        gds_no_distort_transform.set_translation(self, value)
    def get_translation (self):
        return gds_no_distort_transform.get_translation(self)
    translation = property(get_translation, set_translation)
    """ the translation coordinate """

    # overloading for efficiency
    def apply_to_coord(self, coord):  
        """ apply transformation to coordinate """
        return self.__translate__(coord)

    def reverse_on_coord(self, coord):      
        """ apply reverse transformation on coodinate """
        return self.__inv_translate__(coord)

    def apply_to_array(self, coords):      
        """ applies transformation to numpy array """
        return self.__translate_array__(coords)

    def reverse_on_array(self, coords):      
        """ apply reverse transformation to numpy array """
        return self.__inv_translate_array__(coords)

    def __add__(self, other):
        """ returns the concatenation of this transform and other """
        if other == None: return deepcopy(self)
        if isinstance(other, gds_translation):
            return gds_translation(coord2(self.__translation__.x + other.__translation__.x, self.__translation__[1] + other.__translation__[1]))
        else:
            return gds_special_no_distort_transform.__add__(self, other)
    
    def __iadd__(self, other):
        """ concatenates other to this transform """        
        if other == None: return self
        if isinstance(other, gds_translation):
            self.__translation__ = coord2(self.__translation__.x + other.__translation__.x, self.__translation__.y + other.__translation__.y)
            return self
        else:
            return gds_no_distort_transform.__iadd__(self, other)

    def __neg__(self):
        """ returns the reverse transformation """
        return gds_translation(coord2(-self.__translation__.x, -self.__translation__.y))

    def is_identity(self):
        """ returns True if the transformation does nothing """
        return ((self.__translation__.x == 0.0) and
                (self.__translation__.y == 0.0) 
            )

class gds_rotation(gds_special_no_distort_transform):
    """ rotation around point over a given angle (degrees) """
    def __init__(self, rotation_center = coord2(0.0, 0.0), rotation = 0.0, absolute_rotation = False):
        gds_special_no_distort_transform.__init__(self, ["__rotation_center__"])
        self.rotation = rotation
        self.rotation_center = rotation_center
        self.absolute_rotation = absolute_rotation

    def set_rotation (self, value):
        gds_no_distort_transform.set_rotation(self, value)
    def get_rotation (self):
        return gds_no_distort_transform.get_rotation(self)
    rotation = property(get_rotation, set_rotation)
    """ the rotation angle """

    def set_absolute_rotation(self, value): 
        gds_no_distort_transform.set_absolute_rotation(self, value)
    def get_absolute_rotation(self):
        return gds_no_distort_transform.get_absolute_rotation(self)
    absolute_rotation = property(get_absolute_rotation, set_absolute_rotation)
    """ setabsolute rotation on or off """

    # transforms parameter rotation_center to gds_no_transform_parameters
    def get_rotation_center(self):
        return self.__rotation_center__
    def set_rotation_center(self, center):
        if not isinstance(center, coord2):
            center = coord2(center[0], center[1])
        self.__rotation_center__ = center
        self.__translation__ = coord2(center.x *( 1 -  self.__ca__) + center.y * self.__sa__,
                                      center.y *( 1 -  self.__ca__) - center.x * self.__sa__)
        #self.__translation__ = coord2(center.x *( 1 -  self.__ca__) + center.x * self.__sa__,
                                      #center.y *( 1 -  self.__ca__) - center.y * self.__sa__)
    rotation_center = property(get_rotation_center, set_rotation_center)
    """ the rotation center """

    # overloading for efficiency
    def apply_to_coord(self, coord):     
        """ apply transformation to coordinate """
        coord = self.__rotate__(coord)
        coord = self.__translate__(coord)
        return coord

    def reverse_on_coord(self, coord):      
        """ apply reverse transformation to coordinate """
        coord = self.__inv_translate__(coord)
        coord = self.__inv_rotate__(coord)
        return coord

    def apply_to_array(self, coords):      
        """ apply transformation to numpy array"""
        coords = self.__rotate_array__(coords)
        coords = self.__translate_array__(coords)
        return coords

    def reverse_on_array(self, coords):      
        """ internal use: applies reverse transformation to a numpy array """
        coords = self.__inv_translate_array__(coords)
        coords = self.__inv_rotate_array__(coords)
        return coords


    def apply_to_angle_deg(self, angle):
        """ apply transformation to absolute angle (degrees) """
        a = angle
        a += self.__rotation__
        return a%360.0
    
    def reverse_on_angle_deg(self, angle):
        """ apply transformation to absolute angle (degrees) """
        a = angle - self.__rotation__
        return a%360.0

    def apply_to_angle_rad(self, angle):
        a = angle
        a += self.__rotation__ * DEG2RAD
        return a%(2*pi)
    
    def reverse_on_angle_rad(self, angle):
        a = angle - self.__rotation__ * DE2RAD
        return a%(2*pi)


    def __neg__(self):
        """ returns the reverse transformation """
        return gds_rotation(self.rotation_center, -self.rotation)

    def is_identity(self):
        """ returns True if the transformation does nothing """
        return (self.__rotation__ == 0.0)


class gds_magnification(gds_special_no_distort_transform):
    """ scaling transformation with respect to a given point """
    def __init__(self, magnification_center = coord2(0.0, 0.0), magnification = 1.0, absolute_magnification = False):
        gds_special_no_distort_transform.__init__(self, ["__magnification_center__"])
        self.magnification = magnification
        self.magnification_center = magnification_center
        self.absolute_magnification = absolute_magnification

    def set_magnification (self, value):
        gds_no_distort_transform.set_magnification(self, value)
    def get_magnification (self):
        return gds_no_distort_transform.get_magnification(self)
    magnification = property(get_magnification, set_magnification)
    """ the magnification factor """

    def set_absolute_magnification(self, value): 
        gds_no_distort_transform.set_absolute_magnification(self, value)
    def get_absolute_magnification(self): 
        return gds_no_distort_transform.get_absolute_magnification(self)
    absolute_magnification = property(get_absolute_magnification, set_absolute_magnification)
    """ set absolute magnification on or off """

    # transforms parameter rotation_center to gds_no_transform_parameters
    def get_magnification_center(self):
        return self.__magnification_center__
    def set_magnification_center(self, center):
        if not isinstance(center, coord2):
            center = coord2(center[0], center[1])
        self.__magnification_center__ = center
        self.__translation__ = coord2((1 - self.__magnification__) * center.x,
                                      (1 - self.__magnification__) * center.y )
    magnification_center = property(get_magnification_center, set_magnification_center)
    """ the scaling center """
    
    # overloading for efficiency
    def apply_to_coord(self, coord):      
        """ apply transformation to coordinate """
        coord = self.__magnify__(coord)
        coord = self.__translate__(coord)
        return coord

    def reverse_on_coord(self, coord):      
        """ apply reverse transformation to coordinate """
        coord = self.__inv_translate__(coord)
        coord = self.__inv_magnify__(coord)
        return coord

    def apply_to_array(self, coords):      
        """ apply transformation to numpy array"""
        coords = self.__magnify_array__(coords)
        coords = self.__translate_array__(coords)
        return coords

    def reverse_on_array(self, coords):      
        """ internal use: applies reverse transformation to a numpy array """
        coords = self.__inv_translate_array__(coords)
        coords = self.__inv_magnify__array__(coords)
        return coords

    def apply_to_length(self, length):
        """ applies transformation to a distance """
        return length * self.__magnification__
    def reverse_on_length(self, length):
        """ applies reverse transformation to a distance """
        return length / self.__magnification__

    def __neg__(self):
        """ returns reverse transformation """
        return gds_magnification(self.magnification_center, 1.0/self.magnification)

    def is_identity(self):
        """ returns True if the transformation does nothing """
        return (self.__magnification__ == 1.0)


class gds_v_mirror(gds_special_no_distort_transform):
    """ mirror transformation around y-plane """
    def __init__(self, mirror_plane_y = 0.0):
        gds_special_no_distort_transform.__init__(self, ["__mirror_plane_y__"])
        self.__v_mirror__ = True
        self.mirror_plane_y = mirror_plane_y
        
    def get_mirror_plane_y(self): return self.__mirror_plane_y__
    def set_mirror_plane_y(self, value):
        self.__mirror_plane_y__ = value
        self.__translation__ = coord2(0.0, 2.0 * value)
    mirror_plane_y = property(get_mirror_plane_y, set_mirror_plane_y)
    """ mirror y plane """

    # overloading for efficiency
    def apply_to_coord(self, coord):      
        """ apply transformation to coordinate """
        coord = self.__v_flip__(coord)
        coord = self.__translate__(coord)
        return coord

    def reverse_on_coord(self, coord):      
        """ apply reverse transformation to coordinate """
        coord = self.__inv_translate__(coord)
        coord = self.__inv_v_flip__(coord)
        return coord

    def apply_to_array(self, coords):      
        """ apply transformation to numpy array"""
        coords = self.__v_flip_array__(coords)
        coords = self.__translate_array__(coords)
        return coords

    def reverse_on_array(self, coords):      
        """ internal use: applies reverse transformation to a numpy array """
        coords = self.__inv_translate_array__(coords)
        coords = self.__inv_v_flip_array__(coords)
        return coords

    def __neg__(self):
        """ returns reverse transformation """
        return gds_v_mirror(self.mirror_plane_y)

    def is_identity(self):
        """ returns True if the transformation does nothing """
        return False

    def apply_to_angle_deg(self, angle):
        """ apply transformation to absolute angle (degrees) """
        return (-angle)%360.0
    
    def reverse_on_angle_deg(self, angle):
        """ apply reverse transformation to absolute angle (degrees) """
        return (-angle)%360.0

    def apply_to_angle_rad(self, angle):
        """ apply transformation to absolute angle (radians) """
        return (-angle)%(2*pi)
    
    def reverse_on_angle_rad(self, angle):
        """ apply reverse transformation to absolute angle (radians) """
        return (-angle)%(2*pi)



class gds_h_mirror(gds_special_no_distort_transform):
    """ mirror transformation around x plane """
    def __init__(self, mirror_plane_x = 0.0):
        gds_special_no_distort_transform.__init__(self, ["__mirror_plane_x__"])
        self.__v_mirror__ = True
        self.mirror_plane_x = mirror_plane_x

    def get_mirror_plane_x(self): return self.__mirror_plane_x__
    def set_mirror_plane_x(self, value):
        self.__mirror_plane_x__ = value
        self.__translation__ = coord2(2.0 * value, 0.0)
        self.__rotation__ = 180.0
        self.__ca__ = -1.0
        self.__sa__ = 0.0
    mirror_plane_x = property(get_mirror_plane_x, set_mirror_plane_x)
    """ mirror x plane """

    def __h_flip__ (self, coord):
        """ internal use: mirror coordinate around y-axis """
        return (-coord[0], coord[1])

    def __h_flip_array__ (self, coords):
        """ internal use: mirror array around y axis """
        coords *= array([-1, 1])
        return coords

    # overloading for efficiency
    def apply_to_coord(self, coord):      
        """ apply transformation to coordinate """
        # faster than rotation!
        coord = self.__h_flip__(coord)
        coord = self.__translate__(coord)
        return coord

    def reverse_on_coord(self, coord):      
        """ apply reverse transformation to coordinate """
        # faster than rotation!
        coord = self.__inv_translate__(coord)
        coord = self.__h_flip__(coord)
        return coord

    def apply_to_array(self, coords):      
        """ apply transformation to numpy array"""
        # faster than rotation!
        coords = self.__h_flip_array__(coords)
        coords = self.__translate_array__(coords)
        return coords

    def reverse_on_array(self, coords):      
        """ internal use: applies reverse transformation to a numpy array """
        # faster than rotation!
        coords = self.__inv_translate_array__(coords)
        coords = self.__h_flip_array__(coords)
        return coords

    def apply_to_angle_deg(self, angle):
        """ apply transformation to absolute angle (degrees) """
        return (180.0-angle)%360.0
    
    def reverse_on_angle_deg(self, angle):
        """ apply reverse transformation to absolute angle (degrees) """
        return (180.0-angle)%360.0

    def apply_to_angle_rad(self, angle):
        """ apply transformation to absolute angle (radians) """
        return (pi-angle)%(2*pi)
    
    def reverse_on_angle_rad(self, angle):
        """ apply reverse transformation to absolute angle (radians) """
        return (pi-angle)%(2*pi)
 
    def __neg__(self):
        """ returns reverse transformation """        
        return gds_h_mirror(self.mirror_plane_x)

    def is_identity(self):
        """ returns True if the transformation does nothing """
        return False


class gds_c_mirror(gds_special_no_distort_transform):
    """ mirror around point (= 180 degree turn)"""
    def __init__(self, mirror_center = coord2(0.0, 0.0)):
        gds_special_no_distort_transform.__init__(self, ["__mirror_center__"])
        self.mirror_center = mirror_center

    def get_mirror_center(self): return self.__mirror_center__
    def set_mirror_center(self, center):
        if not isinstance(center, coord2):
            center = coord2(center[0], center[1])
        self.__mirror_center__ = center
        self.__translation__ = coord2(2.0 * center.x, 2.0 * center.y)
        self.__rotation__ = 180.0
        self.__ca__ = -1.0
        self.__sa__ = 0.0
    mirror_center = property(get_mirror_center, set_mirror_center)
    """ mirror center """

    def __c_flip__ (self, coord):
        """ internal use: point mirror coordinate """
        return (-coord[0], -coord[1])
    

    def __c_flip_array__ (self, coords):
        """ internal use: point mirror numpy array """
        coords *= array([-1, -1])
        return coords

    # overloading for efficiency
    def apply_to_coord(self, coord):      
        """ apply transformation to coordinate """
        # faster than rotation!
        coord = self.__c_flip__(coord)
        coord = self.__translate__(coord)
        return coord

    def reverse_on_coord(self, coord):      
        """ apply reverse transformation to coordinate """
        # faster than rotation!
        coord = self.__inv_translate__(coord)
        coord = self.__c_flip__(coord)
        return coord

    def apply_to_array(self, coords):      
        """ apply transformation to numpy array"""
        # faster than rotation!
        coords = self.__c_flip_array__(coords)
        coords = self.__translate_array__(coords)
        return coords

    def reverse_on_array(self, coords):      
        """ internal use: applies reverse transformation to a numpy array """
        # faster than rotation!
        coords = self.__inv_translate_array__(coords)
        coords = self.__c_flip_array__(coords)
        return coords

    def __neg__(self):
        """ returns reverse transformation """
        return gds_c_mirror(self.mirror_center)
    
    def is_identity(self):
        """ returns True if the transformation does nothing """
        return False


class gds_identity_transform(gds_translation, gds_rotation, gds_magnification, gds_special_no_distort_transform ):
    """ transform that leaves an object unchanged """
    def __init__(self):
        gds_special_no_distort_transform.__init__(self, ["__magnification_center__", "__rotation_center__"])
        self.__magnification_center__ = coord2(0.0, 0.0)
        self.__rotation_center__ = coord2(0.0, 0.0)
    
    def apply(self, item):
        
        if isinstance(item, gk_transformable.gds_transformable):
            return item
        elif isinstance(item, list):
            return gk_shape.gds_shape(item)
            
    def reverse(self, shape):
        if isinstance(item, gk_transformable.gds_transformable):
            return item
        elif isinstance(item, list):
            return gk_shape.gds_shape(item)

    def apply_to_coord(self, coord):
        """ apply transformation to coordinate """
        return coord

    def reverse_on_coord(self, coord):
        """ apply reverse transformation to coordinate """
        return coord

    def apply_to_array(self, coords):
        """ apply transformation to numpy array"""
        return coords

    def reverse_on_array(self, coords):
        """ internal use: applies reverse transformation to a numpy array """
        return coords

    def __neg__(self):
        return gds_identity_transform()

    def __add__(self, other):
        """ returns the concatenation of this transform and other """
        if isinstance(other, (NoneType, gds_identity_transform)):
            return gds_identity_transform()
        elif isinstance(other, gds_translation):
            return gds_translation(other.__translation__)
        elif isinstance(other, gds_rotation):
            return gds_rotation(other.__rotation_center__, other.__rotation__, other.__absolute_rotation__)
        elif isinstance(other, gds_magnification):
            return gds_magnification(other.__magnification_center__, other.__magnification__, other.__absolute_magnification__)
        else:
            return gds_special_no_distort_transform.__add__(self, other)
    
    def __iadd__(self, other):
        """ concatenates other to this transform """
        if isinstance(other, (NoneType, gds_identity_transform)):
            return self
        else:
            return gds_special_no_distort_transform.__iadd__(self, other)

    def is_identity(self):
        """ returns True if the transformation does nothing """
        return True




#----------------------------------------------------------------------------
# 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 = []):
        if isinstance(transforms, list):
            self.__subtransforms__ = transforms
        elif isinstance(transforms, gds_compound_transform):
            self.__subtransforms__ = []
            self.__subtransforms__.extend(transforms)
        else:            
            self.__subtransforms__ = [transforms]

    def apply(self, item):
        """ apply the transform to the transformable item """
        if isinstance(item, gk_transformable.gds_transformable):
            for c in self.__subtransforms__:
                item = c.apply(item)
        elif isinstance(item, list):
            L =gk_shape.gds_shape(item)
            for c in self.__subtransforms__:
                L = c.apply(L)
            return L

    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

class gds_reversible_compound_transform(gds_compound_transform, gds_reversible_transform):
    """ a store for the concatenation of (non-homthetic) reversible transformas """
    def __init__(self, transforms = []):
        if isinstance(transforms, list):
            self.__subtransforms__ = transforms
        elif isinstance(transforms, gds_transform):
            self.__subtransforms__ = [transforms]

    def __make_irreversible__(self):
        self.__class__ = gds_compound_transform

    def reverse(self, item):
        """ applies the reverse transform on the transformable item"""
        if isinstance(item, gk_transformable.gds_transformable):
            for c in reversed(self.__subtransforms__):
                item = c.reverse(item)
        elif isinstance(item, list):
            L =gk_shape.gds_shape(item)
            for c in reversed(self.__subtransforms__):
                L = c.reverse(L)
            return L

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

class gds_stretch(gds_reversible_transform):
    """ non-homothetic scaling """
    def __init__(self, stretch_center = (0.0, 0.0), stretch_factor = (1.0, 1.0)):
        gds_reversible_transform.__init__(self)
        self.stretch_center = stretch_center
        self.stretch_factor = stretch_factor

    def get_stretch_center(self): return self.__stretch_center__
    def set_stretch_center(self, value): 
        if isinstance(value, coord2):
            self.__stretch_center__ = value
        else:
            self.__stretch_center__= coord2(value[0], value[1])
    stretch_center = property(get_stretch_center, set_stretch_center)
    """ stretch center """

    def get_stretch_factor(self): return self.__stretch_factor__
    def set_stretch_factor(self, value):
        if isinstance(value, coord2):
            self.__stretch_factor__ = value
        else:
            self.__stretch_factor__= coord2(value[0], value[1])
        if self.__stretch_factor__ == 0.0 or self.__stretch_factor__ == 0.0:
            raise AttributeError("Error: Stretch factor cannot be zero in gds_strech transform")
    stretch_factor = property(get_stretch_factor, set_stretch_factor)
    """ stretch factor (x, y) """

    def apply_to_coord(self, coord):
        """ apply transformation to coordinate """
        return coord2(self.__stretch_factor__[0] * coord[0] + (1- self.__stretch_factor__[0]) * self.__stretch_center[0],
                      self.__stretch_factor__[1] * coord[1] + (1- self.__stretch_factor__[1]) * self.__stretch_center[1])

    def reverse_on_coord(self, coord):
        """ apply reverse transformation to coordinate """
        return coord2(1.0/self.__stretch_factor__[0] * coord[0] + (1- 1.0/self.__stretch_factor__[0]) * self.__stretch_center[0],
                      1.0/self.__stretch_factor__[1] * coord[1] + (1- 1.0/self.__stretch_factor__[1]) * self.__stretch_center[1])
    
    def apply_to_array(self, coords):
        """ apply transformation to numpy array"""
        coords  *= array([self.stretch_factor.x, self.stretch_factor.y])
        coords += array([(1- self.__stretch_factor__.x) * self.__stretch_center.x, (1- self.__stretch_factor__.y) * self.__stretch_center.y])
        return coords


    def reverse_on_array(self, coords):
        """ internal use: applies reverse transformation to a numpy array """
        coords  *= array([1.0/self.stretch_factor.x, 1.0/self.stretch_factor.y])
        coords += array([(1- 1.0/self.__stretch_factor__.x) * self.__stretch_center.x, (1- 1.0/self.__stretch_factor__.y) * self.__stretch_center.y])
        return coords
    
    def is_identity(self):
        """ returns True if the transformation does nothing """
        return ((self.__stretch_factor__.x == 1.0) and
                (self.__stretch_factor__.y == 1.0))

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

def shape_translate(shape, translation_vector = (1.0,0.0)):
    """ legacy: apply a translation to a shape """
    return gds_translation(translation_vector)(shape)

def shape_scale(shape, scaling=(1.0, 1.0), scale_center=(0.0, 0.0)):
    """ legacy: apply a magnification to a shape """
    if scaling[0] == scaling[1]:
        return gds_magnification(scale_center, scaling[0])(shape)
    else:
        return gds_stretch(scale_center, scaling)(shape)
    
def shape_rotate (shape, origin = (0.0, 0.0), angle = 90.0):
    """ legacy: apply a rotation to a shape """
    return gds_rotation(origin, angle)(shape)

def shape_hflip (shape, flip_plane_x = 0.0):
    """ legacy: apply a h_mirror to a shape """
    return gds_h_mirror(flip_plane_x)(shape)

def shape_vflip (shape, flip_plane_y = 0.0):
    """ legacy: apply a v_mirror to a shape """
    return gds_v_mirror(flip_plane_y)(shape)


class processor_transformation(processor_typecast):
    def __init__(self):
        processor_typecast.__init__(self, gds_transform)
    
    def process(self, value, obj= None):
        if value == None:
            return gds_identity_transform()
        else:
            return processor_typecast.process(self, value, obj)
        
class processor_no_distort_transformation(processor_transformation):
    def __init__(self):
        processor_typecast.__init__(self, gds_no_distort_transform)
    
        

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

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

#class transformation_property(gds_property_descriptor):
    #__transformation_type__ = gds_transform
    #def __set__(self, obj, value):
        #if value == None:
            #gds_property_descriptor.__set__(self, obj, gds_identity_transform())
            #return self
        #if isinstance(value, self.__transformation_type__):
            #gds_property_descriptor.__set__(self, obj, value)
        #else:
            #raise TypeError("Wrong type " + str(type(value)) + " for transformation property")
        #return self

#class no_distort_transformation_property(transformation_property):
    #__transformation_type__ = gds_no_distort_transform
