from .. import constants
from .. import settings
from ..dynamic.dynamic_item import gds_static_item, gds_dynamic_item 
from . import transformable 
from coord import coord2

from . import size_info
from copy import copy, deepcopy
from numpy import *

from ..properties.descriptor import restricted_property, function_property
from ..properties.restrictions import restrict_type
from ..properties.processors import processor_typecast
from ..properties.predefined import bool_property
from ..properties.initializer import strong_property_initializer

__all__ = ["gds_shape", "gds_dynamic_shape", "shape_property"]


#----------------------------------------------------------------------------
# Basic shape
#----------------------------------------------------------------------------

class gds_shape(gds_static_item, transformable.gds_transformable, strong_property_initializer):
        """ static shape """
        def __init__(self, coordinate_list=[], closed = None, **kwargs):
                self.points = coordinate_list
                self.__size_info__ =  None
                super(gds_shape, self).__init__(closed = closed, **kwargs)

        def clear(self):
                """ clears all points """
                self.__points__ = ndarray((0,2))
                
        def get_points(self): return self.__points__
        def set_points(self, coordinate_list):
                if isinstance(coordinate_list, gds_shape):
                        self.__points__ = array(coordinate_list.points)
                        self.closed = coordinate_list.closed
                elif isinstance(coordinate_list, (list, ndarray)):
                        if len(coordinate_list):
                                self.__points__ = array([(c[0], c[1]) for c in coordinate_list])
                        else:
                                self.__points__ = ndarray((0, 2))
                elif isinstance(coordinate_list, coord2):
                        self.__points__ = array([[coordinate_list.x, coordinate_list.y]])
                elif isinstance(coordinate_list, tuple):
                        self.__points__ = array([[coordinate_list[0], coordinate_list[1]]])
                else:
                        raise TypeError("Invalid type of points in gds_shape.set_points(): " + str(type(coordinate_list)))
                return
        points = property(get_points, set_points)
        """ shape points """

        
        #########################################################
        #  Is a shape open or closed
        #########################################################
        def close(self):
                """ closes the shape """
                # closing the shape does not reset size info
                self.set_closed(True)
        def open(self):
                """ opens the shape""" 
                # closing the shape does not reset size info
                self.set_closed(False)
        def set_closed(self, value):
                if isinstance(value, bool):
                        self.__closed__ = value
                elif value == None:
                        self.__closed__ = None
                else:
                        raise TypeError("closed attribute of gds_shape must be True, False or None")
        def is_closed(self):
                if len(self.__points__)== 0: return False
                if self.__closed__ == None:
                        return bool(prod(self.__points__[-1] == self.__points__[0]))
                else:
                        return self.__closed__
        closed = function_property(is_closed, set_closed)
        """ is the chape closed or not """

        #########################################################
        #  Some speed-functions to add points
        #########################################################
        def add_relative(self, coordinate):
                """ add a point with a relative position of the last point """
                self += coord2(self.__points__[-1][0] + coordinate[0], self.__points__[-1][1] + coordinate[1])
               
        def add_polar(self, distance, angle = 0.0):
                """ add a point with a relative distance and angle from the last point """
                self.add_relative((distance * cos(constants.DEG2RAD*angle), distance * sin(constants.DEG2RAD*angle)))

        #########################################################
        #  Transforms
        #########################################################
        def transform(self, transformation):
                """ applies transformation to the shape points """
                # applies a selected transformation
                self.__points__ = transformation.apply_to_array(self.__points__)
                return self
        
        def transform_copy(self, transformation):
                """ applies transformation to a copy of the shape """
                S = gds_shape(self.__points__, self.closed)
                return S.transform(transformation)

        def move(self, position):
                """ moves the shape """
                p = array([position[0], position[1]])
                self.__points__ += p
                return self
        
        def move_copy(self, position):
                """ moves a copy of the shape """
                S = gds_shape(self.__points__, self.closed)
                return S.move(position)

        #########################################################
        #  Calculations
        #########################################################
        def __calc__(self):
                #self.__reset_size_info__()
                #override this function to add coordinates
                pass

        def size_info(self):
                """ returns the size_info of the shape """
                if self.__size_info__ == None: 
                        self.__size_info__ = size_info.size_info_from_numpyarray(self.__points__)
                return self.__size_info__

        def __reset_size_info__(self):
                """ internal use: resets size info """
                self.__size_info__ = None
                
        def snap_to_grid(self, grids_per_unit = None):
                """ snaps all points to grid """
                if grids_per_unit == None:
                        grids_per_unit = get_grids_per_unit()
                self.__points__ = (floor(self.__points__ * grids_per_unit + 0.5) )/ grids_per_unit 
                return self
        def len_without_identicals(self):
                """ returns the length without identicals """
                if len(self.__points__) > 1:
                        identicals = sum(self.__points__ != roll(self.__points__, -1, 0), 1)
                        if not self.__closed__:
                                identicals[0] = True
                        return len(identicals.nonzero()[0])
                return len(self.__points__)
        
        def remove_identicals(self):
                """ removes consecutive identical points """
                if len(self.__points__) > 1:
                        identicals = prod(abs(self.__points__ - roll(self.__points__, -1, 0)) < 0.5/settings.get_grids_per_unit(), 1)
                        if not self.__closed__:
                                identicals[0] = False
                        self.__points__ = delete(self.__points__, identicals.nonzero()[0], 0)
                return self
            
        def remove_straight_angles(self):
                """ removes points with turn zero or 180 degrees """
                gds_shape.remove_identicals(self)
                # also removes identicals and 180deg bends
                if len(self.__points__) > 1:
                        straight = (abs(abs((self.turns_rad()+(0.5*pi))%pi)-0.5*pi) < 0.00001)# (self.turns_rad()%pi == 0.0)
                        if not self.__closed__:
                                straight[0] = False
                                straight[-1]= False
                        self.__points__ = delete(self.__points__, straight.nonzero()[0], 0)
                return self


        def remove_loops(self):
                """ removes local loops"""
                from . import shape_info
                if len(self.__points__) <= 3:
                        return self
        
                gds_shape.remove_identicals(self) # avoids recalculation
                # eliminate backloop
                nc = self.__points__
                dels = []
                i = -1
                L = len(nc)
                if self.__closed__:
                        L2 = L+1
                else:
                        L2 = L
                while i < L - 2:
                        i += 1
                        if i in dels:
                                continue
                        c1 = nc[i]
                        c2 = nc[i+1]
                        k = i + 2
                        while k < L2-1:
        
                                c3 = nc[k%L]
                                c4 = nc[(k+1)%L]
                                if shape_info.lines_cross(c1, c2, c3, c4):
                                        nc[i+1] = shape_info.intersection(c1,c2,c3,c4)
                                        dels.append(slice(i+2, k+1))
                                        c2 = nc[i+1]
                                k += 1 
                for s in reversed(dels):
                        self.__points__ = delete(nc, s, 0)
                return self
                
        def tolist(self):
                """ converts a shpa to a list of coord2 objects """
                L = [coord2(c[0], c[1]) for c in self.points]
                if self.closed and L[0] != L[-1]:
                        L.append(L[0])
                return L
        
        #######################################################
        #  Information
        #########################################################
        def distances(self):
                """ returns the distance from each point to each next point """
                return sqrt(sum((roll(self.__points__, -1, 0) - self.__points__)**2,1))

        def relative(self):
                """ returns an array with the relative displacement to each point to the next point """
                return roll(self.__points__, -1, 0) - self.__points__
        
        def cos_sin(self):
                """ returns an 2-D array containing the cos and sin of the angle between each point and the next point """
                D = self.distances()
                return self.relative() / column_stack((D, D))
        
        def length(self):
                """ returns the total length of the shape """
                S = self.distances()
                if self.closed:
                        return sum(S)
                else:
                        return sum(S) - S[-1]

        def angles_rad(self):
                """ returns the angles (radians) of the connection between each point and the next """
                R = roll(self.__points__, -1, 0)
                return arctan2( R[:,1] - self.__points__[:,1], R[:,0] - self.__points__[:,0])
        def angles_deg(self):
                """ returns the angles (degrees) of the connection between each point and the next """
                return self.angles_rad() * constants.RAD2DEG
        
        
        def turns_rad(self):
                """ returns the angles (radians) of the turn in each point """
                a = self.angles_rad()
                return (a - roll(a, 1, 0) + pi ) % (2*pi) - pi
        def turns_deg(self):
                """ returns the angles (degrees) of the turn in each point """
                return self.turns_rad() * constants.RAD2DEG
        
        def area(self):
                """ returns the area of the shape """
                T = roll(roll(self.__points__, 1, 1), 1, 0)
                return sum(abs(diff(self.__points__ * T, 1, 1)))*0.5

        def orientation(self):
                """ returns the orientation of the shape: +1(counterclock) or -1(clock) """
                T = roll(roll(self.__points__, 1, 1), 1, 0)
                return -sign(sum(diff(self.__points__ * T, 1, 1)))
        
        def winding_number_test(self, point):
                """ Returns the number of times a polygon winds around a point.
                    point can be a single point, an array or list of points or a shape. 
                    For a single point, a number is returned. For a shape or a list of points,
                    an array of values is returned """
                ## uses the winding number algorithm
                ## http://www.geometryalgorithms.com/Archive/algorithm_0103/algorithm_0103.htm#wn_PinPolygon()
                from . import shape_info
                wn = 0    # the winding number counter
                ## loop through all edges of the polygon
                S = gds_shape(point) # convert input to uniform data format shape
                P = S.__points__
                N = len(self.__points__)
                V0 = self.__points__ # starting points of edges
                V1 = roll(V0, -1, 0) # end points of edges

                wn = zeros(size(P, 0))
                for i in range(N): 
                        v_sign0 =  ((V0[i, 1] <= P[:, 1]) * 2 - 1) # positive if V0 is below P
                        v_sign1 =  ((V1[i, 1] > P[:, 1]) * 2 - 1)  # positive if V1 is above P
                        #wn += (v_sign0 == v_sign1) * shape_info.is_left(V0[i], V1[i], P)
                        wn += (v_sign0 == v_sign1) * shape_info.is_left(P, V0[i], V1[i])
                if len(wn) == 1: return wn[0]/2
                return wn/2

                #for i in range(N):  
                    ##edge from V[i] to V[i+1]
                    #if V[i][1] <= point[1]: 
                        ## start y <= point.y
                        #if V[i+1][1] > point[1]: # an upward crossing
                            #if shape_info.is_left( V[i], V[i+1], point) > 0:   # point left of edge
                                #wn += 1 # have a valid up intersect
                    #else: 
                        ## start y > point.y
                        #if V[i+1][1] <= point[1]:     # a downward crossing
                            #if shape_info.is_left( V[i], V[i+1], point) < 0:  # P right of edge
                                #wn -= 1 # have a valid down intersect
                #return wn
        
        def encloses(self, point):
                """ tests whether a point lies in the shape (closed) """
                if not self.size_info().encloses(point): return False
                return (self.winding_number_test(point) != 0)

        def x_coords(self):
                """ returns the x coordinates """
                return self.__points__[:,0]
        def y_coords(self):
                """ returns the y coordinates """
                return self.__points__[:,1]

        def cut_in_two(self, index1, index2):
                """ cuts the shape in two using the diagonal through points at index 1 and index 2
                    there is NO test wether this is a valid cut resulting in valid shapes.
                    It resturns a list with 2 CLOSED shapes """
                mini = min([index1, index2])
                maxi = max([index1, index2])
                l = len(self.__points__)
                if mini == maxi or maxi-mini == 1:
                        return [gds_shape(self.__points__)]
                S1 = gds_shape(self.__points__[mini:maxi+1], True)
                S2 = gds_shape(roll(self.__points__, -maxi, 0)[0:(l - maxi+ mini+1)])
                return [S1, S2]

        def is_empty(self):
                return self.len_without_identicals() <= 1

        def center_of_mass(self):
                COM = mean(self.__points__, 0)
                return coord2(COM[0], COM[1]) 

        #########################################################
        #  List-type behaviour
        #########################################################
        def __add__(self, pointlist):
                """ creates copy of shape with points added """
                self.__reset_size_info__()
                if isinstance(pointlist, gds_shape):
                        points = append(self.__points__, pointlist.points, 0)
                elif isinstance(pointlist, (list , ndarray)):
                        points = append(self.__points__, pointlist,0)
                elif isinstance(pointlist, (coord2, tuple)): # indiviual points
                        points = append(self.__points__, [(pointlist[0], pointlist[1])], 0)
                else:
                        raise TypeError("Wrong type " + str(type(pointlist)) + " to add to gds_shape")
                return gds_shape(points, self.closed)

        def __iadd__(self, pointlist):
                """ adds points to this shape """
                self.__reset_size_info__()
                if isinstance(pointlist, gds_shape):
                        self.__points__ = append(self.__points__, pointlist.points, 0)
                elif isinstance(pointlist, (list)):
                        for p in pointlist: self += p
                elif isinstance(pointlist, (ndarray)):
                        self.__points__ = append(self.__points__, pointlist, 0)
                elif isinstance(pointlist, (coord2, tuple)): # indiviual points
                        self.__points__ = append(self.__points__, [(pointlist[0], pointlist[1])], 0)
                else:
                        raise TypeError("Wrong type " + str(type(pointlist)) + " to add to gds_shape")
                return self

        def __len__(self):
                """ number of points in the shape """
                return size(self.__points__, 0)

        def __getitem__(self, index):
                """ access a point """
                p = self.__points__[index]
                return coord2(p[0], p[1])
        
        def __setitem__(self, index, value):
                """ sets a point """
                self.__reset_size_info__()
                self.__points__[index] = [value[0], value[1]]

        def __delitem__(self, point):
                """ removes a point """
                i = self.index(point)
                self.__points__ = delete(self.__points__, i, 0)
                self.__reset_size_info__()
                return self

        def __getslice__(self, i, j):
                """ gets a slice of points """
                self.__reset_size_info__()
                return gds_shape(self.__points__.__getslice__(i,j))

        def __setslice__(self, i, j, pointlist):
                """ set a slice of points """
                self.__reset_size_info__()
                if isinstance(pointlist, gds_shape):
                        points = pointlist.points
                else:
                        points = pointlist
                self.__points__ = row_stack((self.__points__[:i], points, self.__points__[j:]))
        

        def __delslice__(self, i, j):
                """ remove a slice of points """
                self.__reset_size_info__()
                self.__points__ = delete(self.__points__, range(i,j), 0)
                return self

        def __mul__(self, times):
                """ makes a shape with the pointlist repeated """
                points = repeat(self.__points__, times, 0)
                return gds_shape(points, self.closed)

        def __imul__(self, times):
                """ concatenates the shape e few times to itself """
                self.__points__ = repeat(self.__points, times, 0)
                return self

        def __iter__(self):
                """ returns an iterator over the coordinates """
                for c in self.__points__:
                        yield coord2(c[0], c[1])

        def append(self, point):
                """ appends points """
                self.__reset_size_info__()
                if isinstance(point, (coord2, tuple)): # indiviual points
                        self.__points__ = vstack((self.__points__, [(point[0], point[1])]))
                else:
                        raise TypeError("Wrong type " + str(type(point)) + " to append to gds_shape")
                return self

        def extend(self, pointlist):
                """ adds a list of points """
                self.__reset_size_info__()
                if isinstance(pointlist, gds_shape):
                        self.__points__ = vstack((self.__points__, pointlist.points))
                elif isinstance(pointlist, (list,ndarray)):
                        self.__points__ = vstack((self.__points__, pointlist))
                else:
                        raise TypeError("Wrong type " + str(type(pointlist)) + " to extend gds_shape with")
                return self

        def insert(self, i, item):
                """ inserts a list of points """
                self.__reset_size_info__()
                if isinstance(item, gds_shape):
                        self.__points__ = insert(self.__points__, i, item.__points__, 0)
                elif isinstance(item, (list,ndarray)):
                        self.__points__ = insert(self.__points__, i, item, 0)
                if isinstance(item, (coord2, tuple)): # indiviual points
                        self.__points__ = insert(self.__points__, i, [(item[0], item[1])], 0)
                else:
                        raise TypeError("Wrong type " + str(type(pointlist)) + " to extend gds_shape with")
                return self


        def __contains__(self, point):
                """ checks if point is on the shape """
                return prod(sum(self.__points__ == array(point[0], point[1]), 0))

        def __eq__(self, sh):
                """ checks if two shapes are equal """
                if not isinstance(sh, gds_shape): return False
                return prod(self.__points__ == sh.__points__) and (self.closed == sh.closed)
        
        def __ne__(self, sh):
                """ checks if two shapes are different """
                return not self.__eq__(sh)

        def __rmul__(self, number):
                """ repeats the pointlist of the shape a number of times """
                return __mul__(self, number)

        def count(self):
                """ number of points in the shape """
                return self.__len__()

        def index(self, item):
                """ get the index of a specific point """
                i = prod(self.__points__ == array([item[0], item[1]]), 1).nonzero()[0]
                if len(i) == 0:
                        raise IndexError("Coordinate (", + str(item[0]) + "," + str(item[1]) + ") cannot be found in shape")
                return i[0]
        
        def reverse(self):
                """ reverses the order of the shape """
                self.__points__ = flipud(self.__points__)
                return self

        def reversed(self):
                """ returns shape with reversed order """
                return gds_shape(flipud(self.__points__))
        
        def __str__(self):
                """ string representation """
                L = ["Shape ["]
                L +=  [("(" + str(c[0]) + "," + str(c[1]) + "), ") for c in self.__points__]
                L += ["]"]
                return "".join(L)

        def id_string(self):
                return self.__str__()
        
        def __hash__(self):
                return hash(self.id_string())
        
# empty shape for quick reference
empty_shape = gds_shape()

#----------------------------------------------------------------------------
# dynamic shape: that fills its own parameters
#----------------------------------------------------------------------------
class gds_dynamic_shape(gds_dynamic_item, gds_shape):
        """ shape which can calculate its point on the fly based on parameters """
        __static_item_class__ = gds_shape
        def __init__(self, **kwargs):
                super(gds_dynamic_shape, self).__init__(**kwargs)

        def clear(self):
                """ removes all points """
                gds_shape.clear(self)
                self.__reset_calc__()

        def get_points(self): 
                self.calc()
                return self.__points__
        def set_points(self, coordinate_list):
                if not self.is_call_internal(): self.__make_static__()
                gds_shape.set_points(self, coordinate_list)
        points = property(get_points, set_points)
        """ accesses the point array """

        # transformations: make structure static first!
        # (this can be overloaded for certain shapes)
        def transform(self, transformation):
                """ transforms the shape and makes it static """
                # applies a selected transformation
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.transform(self, transformation)

        def transform_copy(self, transformation):
                """ makes a static copy of the shape and transforms it """
                # applies a selected transformation
                self.calc()
                return gds_shape.transform_copy(self, transformation)
                
        def reverse_transform(self, transformation):
                """ makes the shape static and reverse transforms it """
                # applies a selected transformation in the reverse form
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.reverse_transform(self,transformation)

        def move(self, position):
                """ makes the shape static and moves it """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.move(self.position)

        def snap_to_grid(self, grids_per_unit = None):
                """ makes the shape static and snaps it to the grid """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.snap_to_grid(self, grids_per_unit)
        def remove_identicals(self):
                """ makes the shape static and removes consecutive identical points"""
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.remove_identicals(self)
        def remove_straight_angles(self):
                """ makes the shape static and removes points with straight angles """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.remove_straight_angles(self)
        def remove_loops(self):
                """ makes the shape static and removes internal loops """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.remove_loops(self)

        def close(self):
                """ makes the shape static and closes it """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.close(self)
        def open(self):
                """ makes the shape static and opens it """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.open(self)

        def set_closed(self, value):
                if not self.is_call_internal(2): self.__make_static__()
                gds_shape.set_closed(self, value)
        def is_closed(self):
                self.calc()
                return gds_shape.is_closed(self)
        closed = function_property(is_closed, set_closed)
        """ is the shape open or closed """

        def add_relative(self, coordinate):
                """ add a point with a relative position of the last point """
                if not self.is_call_internal(): self.__make_static__()
                gds_shape.add_relative(self, coordinate)
               
        def add_polar(self, distance, angle = 0.0):
                """ add a point with a relative distance and angle from the last point """
                if not self.is_call_internal(): self.__make_static__()
                gds_shape.add_polar(self, distance, angle)


        def __reset_calc__ (self, value = False):
                """ internal use: resets calculation cache """
                if not value: self.__size_info__ = None 
                gds_dynamic_item.__reset_calc__(self, value)

        def extend(self, listofpoints):
                """ makes the shape static and adds points """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.extend(self, listofpoints)

        def append(self, point):
                """ makes the shape static and add point """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.append(self, point)


        #######################################################
        #  Information
        #########################################################
        def distances(self):
                """ array with distances between each point a,d the next point"""
                self.calc()
                return gds_shape.distances(self)
        
        def relative(self):
                """ returns an array with the relative displacement to each point to the next point"""
                self.calc()
                return gds_shape.relative(self)

        def angles_rad(self):
                """ eturns the angles (radians) of the connection between each point and the next """
                self.calc()
                return gds_shape.angles_rad(self)
        
        def area(self):
                """ area of the shape """
                self.calc()
                return gds_shape.area(self)

        def winding_number_test(self, point):
                """Returns the number of times a polygon winds around a point.
                   point can be a single point, an array or list of points or a shape. 
                   For a single point, a number is returned. For a shape or a list of points,
                   an array of values is returned """
                self.calc()
                return gds_shape.winding_number_test(self, point)
        def x_coords(self):
                """ returns the x coordinates """
                self.calc()
                return gds_shape.x_coords(self)
        def y_coords(self):
                """ returns the y coordinates """
                self.calc()
                return gds_shape.y_coords(self)

        def cut_in_two(self, index1, index2):
                """ cuts the shape in two using the diagonal through points at index 1 and index 2
                    there is NO test wether this is a valid cut resulting in valid shapes.
                    It resturns a list with 2 CLOSED shapes """
                self.calc()
                return gds_shape.cut_in_two(self, index1, index2)

        # overload all list functions that add/delete or modify items
        def __iadd__(self, pointlist):
                """ makes the shape static and adds a point """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.__iadd__(self, pointlist)

        def __delitem__(self, point):
                """ makes the shape static and deletes a point """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.__delitem__(self, point)

        def __delslice__(self, i, j):
                """ makes the shape static and deletes a slice """
                if not self.is_call_internal(): self.__make_static__()
                return list.__delslice__(self, i, j)

        def __imul__(self, integer):
                """ makes the shape static and repeats it a number or times """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.imul__(self,integer)

        def __setitem__(self, key, value):
                """ makes the shape static and sets a point to a given value """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.__setitem__(self,key,value)

        def __setslice__(self, i, j, pointlist):
                """ makes the shape static and changes a slice """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.__setslice__(self,i, j, pointlist)


        def insert(self, i, item):
                """ makes th shape static and inserts points """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.insert(self,i, item)

        def pop(self, point):
                """ makes the shape static and pops the last point """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.pop(self,point)

        def remove(self, item):
                """ makes the shape static and removes a point """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.remove(self,item)

        def reverse(self):
                """ makes the shape static and reverses it """
                if not self.is_call_internal(): self.__make_static__()
                return gds_shape.reverse(self)

        def reversed(self):
                """ returns a reversed copy of the shape """
                self.calc()
                return gds_shape.reversed(self)

        # overload all list functions that require the point lists
        def __mul__(self, integer):
                """ repeats the shape a number of times """
                self.calc()
                return gds_shape.__mul__(self,integer)

        def __add__(self, point):
                """ adds the pointlist of the two shapes to make a new shape """
                self.calc()
                return gds_shape.__add__(self,point)

        def __contains__(self, point):
                """ checks if point is a vertext of the shape """
                self.calc()
                return gds_shape.__contains__(self,point)

        def __eq__(self, sh):
                """ checks if shapes are identical """
                self.calc()
                return gds_shape.__eq__(self,sh)


        def __getitem__(self, index):
                """ gets a point from the shape """
                self.calc()
                return gds_shape.__getitem__(self, index)

        def __iter__(self):
                """ returns iterator over the points in the shape """
                self.calc()
                return gds_shape.__iter__(self)

        def __len__(self):
                """ number of points in the shape """
                self.calc()
                return gds_shape.__len__(self)

        def __rmul__(self, number):
                """ repeats the pointlist of the shape a number of times """
                self.calc()
                return gds_shape.__rmul__(self, number)

        def count(self):
                """ the number of points in the shape """
                self.calc()
                return gds_shape.count(self)

        def index(self, item):
                """ returns the index of a given point """
                self.calc()
                return gds_shape.index(self, item)

        def size_info(self):
                """ returns the size_info object of the shape """
                self.calc()
                return gds_shape.size_info(self)

        def __copy__(self):
                """ copies the shape """
                t = self.__class__
                # temprorily static
                self.__class__ = gds_shape
                c = copy(self)
                c.__class__ = t
                self.__class__ = t
                return c        

        def __deepcopy__(self, memo):
                """ deepcopy the shape """
                t = self.__class__
                # temprorily static
                self.__class__ = gds_shape
                c = deepcopy(self, memo)
                c.__class__ = t
                self.__class__ = t
                return c

def shape_property(internal_member_name = None, restriction = None, preprocess = None, **kwargs):
        R = restrict_type(gds_shape) & restriction
        P = processor_typecast(gds_shape) + preprocess
        return restricted_property(internal_member_name, restriction = R, preprocess = P, **kwargs )


