import gk_global

from math import * 
import gk_transformable
import gk_coord
import gk_shape
import numpy 

#----------------------------------------------------------------------------
# gds_size_info class
#----------------------------------------------------------------------------

class gds_size_info(gk_transformable.gds_transformable):
        
        """ object which contains size info about its parent """
        def __init__(self, left=None, right=None, top=None, bottom=None):
                self.__left = left
                self.__right = right
                self.__top = top
                self.__bottom = bottom
                
        def __is_initialized(self):
                """ checks whether the internal data makes sense """
                return not ((self.__left == None) or
                                (self.__right == None) or
                                (self.__top == None)or
                                (self.__bottom == None))

        # internal properties
        def get_left(self): return self.__left
        def set_left(self, value):
                self.__left = value
                if not self.__right == None:
                        self.__right = max(self.__left, self.__right)
        left = property(get_left, set_left)
        """ leftmost x-coordinate """
        
        def get_right(self): return self.__right
        def set_right(self, value):
                self.__right = value
                if not self.__left == None:
                        self.__left = min(self.__left, self.__right)
        right = property(get_right, set_right)
        """ rightmost x coordinate """

        def get_top(self): return self.__top
        def set_top(self, value):
                self.__top = value
                if not self.__bottom == None:
                        self.__bottom = min(self.__top, self.__bottom)
        top = property(get_top, set_top)
        """ highest y coordinate """

        def get_bottom(self): return self.__bottom
        def set_bottom(self, value):
                self.__bottom = value
                if not self.__top == None:
                        self.__top = max(self.__top, self.__bottom)
        bottom = property(get_bottom, set_bottom)
        """ lowest y coordinate """

        # virtual properties
        def get_center(self):
                if not self.__is_initialized(): return None
                return (0.5 * (self.__left + self.__right), 0.5 * (self.__bottom + self.__top))
        def set_center(self, value):
                # change center but keep height and width
                if self.__is_initialized():
                        wo2 = 0.5 * (self.__right - self.__left)
                        self.__left = value[0] - wo2
                        self.__right = value[0] + wo2
                        ho2 = 0.5 * (self.__top - self.__bottom)
                        self.__top = value[1] + ho2
                        self.__bottom = value[1] - ho2
                else:
                        self.__left = value[0]
                        self.__right = value[0]
                        self.__top = value[1]
                        self.__bottom = value[1]
        center = property(get_center, set_center)
        """ center coordinate """

        # virtual properties
        def get_size(self):
                if not self.__is_initialized(): return (0.0, 0.0)
                return (self.__right - self.__left, self.__top - self.__bottom)
        def set_size(self, value):
                # change width and height but keep the center
                if self.__is_initialized():
                        cw = 0.5 * (value[0] - self.__right + self.__left)
                        self.__left -= cw
                        self.__right += cw
                        ch = 0.5 * (value[1] - self.__top + self.__bottom)
                        self.__bottom -= ch
                        self.__top += ch
        size = property(get_size, set_size)
        """ size: (width, height)"""

        def get_width(self):
                if not self.__is_initialized(): return 0.0
                return self.__right - self.__left
        def set_width(self, value):
                # change width but keep center
                if self.__is_initialized():
                        cw = 0.5 * (value - self.__right + self.__left)
                        self.__left -= cw
                        self.__right += cw
        width = property(get_width, set_width)
        """ width """
        
        def get_height(self):
                if not self.__is_initialized(): return 0.0
                return self.__top - self.__bottom
        def set_height(self, value):
                # change height but keep center
                if self.__is_initialized():
                        ch = 0.5 * (value - self.__top + self.__bottom)
                        self.__bottom -= ch
                        self.__top += ch
        height = property(get_height, set_height)
        """ height """

        # read-only properties
        def get_topleft(self): return (self.__left, self.__top)
        topleft = property(get_topleft)
        """ topleft coordinate """
        def get_topright(self): return (self.__right, self.__top)
        topright = property(get_topright)
        """ top right coordinate """
        def get_bottomleft(self): return (self.__left, self.__bottom)
        bottomleft = property(get_bottomleft)
        """ bottom left coordinate """

        def get_bottomright(self): return (self.__right, self.__bottom)
        bottomright = property(get_bottomright)
        """ bottom right coordinate """


        def get_box(self):
                if not self.__is_initialized(): return None
                return gk_shape.gds_shape([(self.__left, self.__bottom) , (self.__right, self.__top)])
        box = property(get_box)
        """ box: gds_shape(bottom_left, top_right) """

        def __bounding_box_array__(self):
                """ numpy array with the corner point of the enclosing rectangle """
                if not self.__is_initialized(): return None
                return  numpy.array([(self.__left, self.__bottom) ,
                              (self.__right, self.__bottom) ,
                              (self.__right, self.__top)   ,
                              (self.__left, self.__top)])
        def get_bounding_box(self):
                if not self.__is_initialized(): return None
                return  gk_shape.gds_shape([(self.__left, self.__bottom) ,
                                            (self.__right, self.__bottom) ,
                                            (self.__right, self.__top)   ,
                                            (self.__left, self.__top)], True)
        bounding_box = property(get_bounding_box)
        """ shape with enclosing rectangle """
        
        def get_area(self):
                return self.width * self.height
        area = property(get_area)

        # check interaction with other objects
        def __contains__(self, other):
                """ checks whether point(s) is in bounding box """
                if not self.__is_initialized(): return False
                if isinstance(other, (tuple, gk_coord.coord2)):
                        return (other[0] <= self.__right) and (other[0] >= self.__left) and (other[1] <= self.__top) and (other[1] >= self.__bottom)
                elif isinstance(other, gk_shape.gds_shape):
                        for c in other:
                                if not c in self: return False
                        return True
                elif isinstance(other, gds_size_info):
                        return (other.__right <= self.__right) and (other.__left >= self.__left) and (other.__top <= self.__top) and (other.__bottom >= self.__bottom)
                else:
                        raise TypeError("Unsupported type " + str(type(other)) + " in gds_size_info.__contains__()")
                
                
        def encloses(self, point):
                """ checks whether point is in bounding box """
                return self.__contains__(point)

        # combine with another size_info object
        def __add__(self, other):
                """ gives the sizeinfo of the box enclosing the union of both boxes """
                if self.__is_initialized() and other.__is_initialized():
                        left = min(self.__left, other.__left)
                        right = max(self.__right, other.__right)
                        bottom = min(self.__bottom, other.__bottom)
                        top = max(self.__top, other.__top)
                elif other.__is_initialized():
                        left = other.__left
                        right = other.__right
                        bottom = other.__bottom
                        top = other.__top
                elif self.__is_initialized():
                        left = self.__left
                        right = self.__right
                        bottom = self.__bottom
                        top = self.__top
                else:        
                        left, right, top, bottom = None, None, None, None

                return gds_size_info(left, right, top, bottom)

        def __iadd__(self, other):
                """ expands the sizeinfo to include the other box """
                if self.__is_initialized() and other.__is_initialized():
                        self.__left = min(self.__left, other.__left)
                        self.__right = max(self.__right, other.__right)
                        self.__bottom = min(self.__bottom, other.__bottom)
                        self.__top = max(self.__top, other.__top)
                elif other.__is_initialized():
                        self.__left = other.__left
                        self.__right = other.__right
                        self.__bottom = other.__bottom
                        self.__top = other.__top
                return self

        def move(self, coordinate):
                """ translates the center """
                if self.__is_initialized():
                        self.left += coordinate[0]
                        self.right += coordinate[0]
                        self.top += coordinate[1]
                        self.bottom += coordinate[1]
                else:
                        self.left, self.right, self.top, self.bottom = None, None, None, None
                return self

        def move_copy(self, coordinate):
                """ creates a copy of this sizeinfo and moves it """
                if self.__is_initialized():
                        left = self.__left + coordinate[0]
                        right = self.__right +  coordinate[0]
                        top = self.__top + coordinate[1]
                        bottom = self.__bottom + coordinate[1]
                else:
                        left, right, top, bottom = None, None, None, None
                return gds_size_info(left, right, top, bottom)

        # apply a transformation        
        def transform(self, transformation):
                """ transforms the size_info box and makes a new cartesian box over it """
                if self.__is_initialized():
                        BB = transformation.apply_to_array(self.__bounding_box_array__())
                        LB = numpy.min(BB, 0)
                        TR = numpy.max(BB, 0)
                        self.__init__(LB[0], TR[0], TR[1], LB[1])
                return self

        def transform_copy(self, transformation):
                """ transforms a copy of the size_info box and makes a new cartesian box over it """
                if self.__is_initialized():
                        return size_info_from_numpyarray(transformation.apply_to_array(self.__bounding_box_array__()))
                else:
                        return gds_size_info()
                

                
def size_info_from_point_list(point_list):
        """ generate a size info from a point list """
        if len(point_list) == 0: return gds_size_info()
        x = [c[0] for c in point_list]
        y = [c[1] for c in point_list]
        return gds_size_info(min(x), max(x), max(y), min(y))
        
def size_info_from_numpyarray(points):
        """ generate a size_info from a numpy array """
        if len(points) == 0: return gds_size_info()
        LB = numpy.min(points, 0)
        TR = numpy.max(points, 0)
        return gds_size_info(LB[0], TR[0], TR[1], LB[1])

def size_info_from_coord(coord):
        """ generate a size info from a single coordinate """
        return gds_size_info(coord[0], coord[0], coord[1], coord[1])

def size_info(shape):
        """ generate a size_info from a shape-like object """
        if isinstance(shape, gk_shape.gds_shape):
                return shape.size_info()
        elif isinstance(shape, numpy.ndarray):
                return size_info_from_numpyarray(shape)
        elif isinstance(shape, list):
                return size_info_from_pointlist(shape)
        elif isinstance(shape, gk_coord.coord2):
                return size_info_from_coord(shape)
        else:
                raise TypeError("Invalid type for size_info(): " + str(type(shape)))
                
#----------------------------------------------------------------------------
#shape information
#----------------------------------------------------------------------------

def shape_xcoords(shape):
        """ returns the x coordinates of a shape """
        return shape.__points__[:, 0].tolist()
                
def shape_ycoords(shape):
        """ returns the y coordinates of a shape """
        return shape.__points__[:, 1].tolist()

def shape_top(shape):
        """ returns the top Y of the shape """
        return numpy.max(shape.__points__[:, 1])

def shape_bottom(shape):
        """ returns the bottom Y of the shape """
        return numpy.min(shape.__points__[:, 1])

def shape_left(shape):
        """ returns the left X of the shape """
        return numpy.min(shape.__points__[:, 0])

def shape_right(shape):
        """ returns the right X of the shape """
        return numpy.max(shape.__points__[:, 0])

def shape_size(shape):
        """ returns the size of the shape """
        LB = numpy.min(shape.__points__, 0)
        TR = numpy.max(shape.__points__, 0)
        return (TR[0] - LB[0], TR[1] - LB[1])

def shape_bottom_left(shape):
        """ returns the bottom left coordinate of the shape """
        LB = numpy.min(shape.__points__, 0)
        TR = numpy.max(shape.__points__, 0)
        return gk_coord.coord2(LB[0], LB[1])

def shape_top_right(shape):
        """ returns the top right coordinate of the shape """
        TR = numpy.max(shape.__points__, 0)
        return gk_coord.coord2(TR[0], TR[1])

def shape_bottom_right(shape):
        """ returns the bottom right coordinate of the shape """
        LB = numpy.min(shape.__points__, 0)
        TR = numpy.max(shape.__points__, 0)
        return gk_coord.coord2(TR[0], LB[1])

def shape_top_left(shape):
        """ returns the top left coordinate of the shape """
        LB = numpy.min(shape.__points__, 0)
        TR = numpy.max(shape.__points__, 0)
        return gk_coord.coord2(LB[0], TR[1])

def shape_box_center(shape):
        """ returns the center coordinate of the shape """
        LB = numpy.min(shape.__points__, 0)
        TR = numpy.max(shape.__points__, 0)
        return gk_coord.coord2(0.5*(LB[0] + TR[0]), 0.5*(LB[0] + TR[1]))

def shape_box(shape):
        """ returns the (bottom_left , top_right) coordinates of the shape """
        if len(shape) == 0:
                return gk_shape.gds_shape([(0.0,0.0), (0.0,0.0)])
        else:
                LB = numpy.min(shape.__points__, 0)
                TR = numpy.max(shape.__points__, 0)
                return gk_shape.gds_shape([(LB[0], LB[1]), (TR[0], TR[1])], True)

def shape_bounding_box(coordinates):
        """ returns a rectangle shape enclosing the shape"""
        if len(shape) == 0:
                return gk_shape.gds_shape([(0.0,0.0), (0.0,0.0), (0.0, 0.0), (0.0, 0.0)], True)
        else:
                LB = numpy.min(shape.__points__, 0)
                TR = numpy.max(shape.__points__, 0)
                return gk_shape.gds_shape([(LB[0], LB[1]), (LB[0], TR[1]), (TR[0], TR[1]), (TR[0], LB[1])], True)

def shape_orientation(coordinates):
        """ returns true for clockwise orientation """
        #returns True if the coordinates are clockwise or False if not
        turn = 0.0
        L = len(coordinates)
        if coordinates[0] == coordinates[-1]:
                L -= 1
        for i in range(L):
                angle1 = atan2(coordinates[i][1] - coordinates[(i-1)%L][1], coordinates[i][0] - coordinates[(i-1)%L][0])
                angle2 = atan2(coordinates[(i+1)%L][1] - coordinates[i][1], coordinates[(i+1)%L][0] - coordinates[i][0])
                turn += ((angle2 - angle1 + pi)%(2*pi))-pi
        return turn < 0
        

def distance(coord, origin = (0.0,0.0)):
        """ distance of coordinate to origin """
        return sqrt((coord[0]-origin[0])**2 + (coord[1]-origin[1])**2)

def angle_rad(coord, origin = (0.0,0.0)):
        """ absolute angle (radians) of coordinate with respect to origin"""
        return atan2(coord[1]-origin[1], coord[0]-origin[0])

def angle_deg(coord, origin = (0.0,0.0)):
        """ absolute angle (radians) of coordinate with respect to origin"""
        return angle_rad(coord, origin) * gk_global.RAD2DEG


def is_left(point, line_point1, line_point2):
        """ checks if point lies to the left (>0), the right (<0) or on (=0) of the line defined by line_point1 and 2 
        point can be a single point or a numpy array"""
        S = gk_shape.gds_shape(point)
        P1 = numpy.array([line_point1[0], line_point1[1]])
        P2 = numpy.array([line_point2[0], line_point2[1]])
        R = numpy.diff(  (S.__points__ - P1) * numpy.flipud(P2 - P1) , 1, 1)
        if len(R) == 1: 
                return R[0]
        else:
                return R
        
                #return ( (line_point2[0] - line_point1[0]) * (point[1]- line_point1[1])
                         #- (point[0] - line_point1[0]) * (line_point2[1] - line_point1[1]) )


def lines_cross(begin1, end1, begin2, end2):
        """ returns true if the line segments intersect """
        # check if line ends between points cross
        # rechte = Ax + By - C = 0
        A1 = end1[1] - begin1[1]
        B1 = -end1[0] + begin1[0]
        C1 = - (begin1[1] * B1 + begin1[0] * A1)

        A2 = end2[1] - begin2[1]
        B2 = -end2[0] + begin2[0]
        C2 = - (begin2[1] * B2 + begin2[0] * A2)

        if A1*B2 == A2*B1:
                return False

        return ((A1 * begin2[0] + B1 * begin2[1] + C1) * (A1 * end2[0] + B1 * end2[1] + C1) < 0 and
                        (A2 * begin1[0] + B2 * begin1[1] + C2) * (A2 * end1[0] + B2 * end1[1] + C2) < 0)

        
def intersection(begin1, end1, begin2, end2):
        """ gives the intersection between two lines (not sections) """
        # calculate the intersection of 2 lines through points
        # rechte = Ax + By  = C
        A1 = end1[1] - begin1[1]
        B1 = -end1[0] + begin1[0]
        C1 = begin1[1] * B1 + begin1[0] * A1

        A2 = end2[1] - begin2[1]
        B2 = -end2[0] + begin2[0]
        C2 = begin2[1] * B2 + begin2[0] * A2

        # check if lines aren't parallel
        if A1*B2 == A2*B1:
                print >> stderr, "Error: Can't intersect parallel lines"
                raise SystemExit

        x = (C1*B2 - C2*B1)/(A1*B2 - A2*B1)
        y = (C1*A2 - C2*A1)/(B1*A2 - B2*A1)
        return (x, y)

def midpoint(P1, P2, ratio =0.5):
        return ratio * gk_coord.coord2(P1[0], P1[1]) + (1-ratio) * gk_coord.coord2(P2[0], P2[1])

def point_in_triangle(P, P1, P2, P3):
        """ returns true if point P is in the triangle (P1,P2,P3) """
        # for speed
        x, y = P[0], P[1]
        x1, y1 = P1[0], P1[1] 
        x2, y2 = P2[0], P3[1] 
        x3, y3 = P3[0], P3[1] 
        f12 = (y-y1)*(x2-x1) - (x-x1)*(y2-y1)
        f31 =  (y-y3)*(x1-x3) - (x-x3)*(y1-y3)
        f23 =  (y-y2)*(x3-x2) - (x-x2)*(y3-y2)
        return (f12*f23)>0 and (f23 * f31) > 0


def shape_length(coordinates):
        """ length of a shape """
        #returns the length of a shape
        L = 0
        last_c = coordinates[0]
        for c in coordinates[1:]:
                L+= distance(c, last_c)
                last_c = c
        return L


