from . import transformable
from ..properties.descriptor import restricted_property
from ..properties.restrictions import restrict_type, restrict_list
from ..properties.predefined import RESTRICT_NONNEGATIVE
from ..properties.processors import processor_typecast
from ..settings import get_grids_per_unit
import math

__all__ = ["coord2", "coord2_property", "size2_property", "coord2_match_position", "RESTRICT_COORD2", "RESTRICT_SIZE2"]

# base class (can also be used for 3-D coords, etc.
class gds_coord(transformable.gds_transformable):
    pass

class coord2(gds_coord):
    """ 2-D coordinate: initialize using
        coord2(x, y)  # x, y are numbers
        coord2(C)     # C is a 2-tuple of numbers or another coordinate
        """
    def __init__(self, *args):
        if len(args) == 2:
            self.x, self.y = args
        elif len(args) == 1:
            self.x, self.y = args[0][0], args[0][1]
    
    def __getitem__(self, index):
        if index == 0: return self.x
        if index == 1: return self.y
        raise IndexError("coord2 type only supports index 0 and 1")
    
    def __setitem__(self, index, value):
        if index == 0: 
            self.x = value
        elif index ==1: 
            self.y = value
        else:
            raise IndexError("coord2 type only supports index 0 and 1")
        return

    def __iter__(self):
        for index in range(2):
            yield self[index]

    def transform(self, transformation):
        C = transformation.apply_to_coord(self)
        self.x = C.x
        self.y = C.y
        return self
    
    def transform_copy(self, transformation):
        return transformation.apply_to_coord(coord2(self.x, self.y))
    
    def move(self, position):
        self.x += position[0] # compatibility with tuples used for quick notation
        self.y += position[1] # compatibility with tuples used for quick notation
        return self
    
    def move_copy(self, position):
        return coord2(self.x + position[0], self.y + position[1])

    def snap_to_grid(self, grids_per_unit = None):
            if grids_per_unit == None:
                    grids_per_unit = get_grids_per_unit()
            self.x = round(self.x * grids_per_unit)/grids_per_unit
            self.y = round(self.y * grids_per_unit)/grids_per_unit
            return self
        
    def __str__(self):
        return "("+str(self.x) + "," + str(self.y) + ")"

    def __eq__(self, other):
        return (self[0] == other[0]) and (self[1] == other[1])
    def __ne__(self, other):
        return (self[0] != other[0]) or (self[1] != other[1])

    def distance(self, other):
        return sqrt((other[0]-self.x)**2 + (other[1]-self.y)**2)

    def angle_deg(self, other=(0.0, 0.0)):
        return RAD2DEG*self.angle_rad(other)

    def angle_rad(self, other=(0.0, 0.0)):
        return atan2(self.y - other[1], self.x - other[0])
    
    def __iadd__(self, other):
        self.x += other[0]
        self.y += other[1]
        return self
        
    def __add__(self, other):
        return coord2 (self.x + other[0], self.y + other[1])
    
    def __isub__(self, other):
        self.x -= other[0]
        self.y -= other[1]
        return self
        
    def __sub__(self, other):
        return coord2 (self.x - other[0], self.y - other[1])
    
    def __neg__(self):
        return coord2 (-self.x, -self.y)
    
    
    def __imul__(self, other):
        self.x *= other
        self.y *= other
        return self
    
    def __mul__(self, other):
        return coord2(self.x * other, self.y * other)
    def __rmul__(self, other):
        return coord2(self.x * other, self.y * other)
    
    def __repr__(self):
        return "C2(%f, %f)" % (self.x, self.y)
        
    def id_string(self):
        return "%d_%d" % (self.x*1000, self.y*1000)
    
#class processor_coord2(processor_typecast):
    #def __init__(self):
        #processor_typecast.__init__(self, coord2)
    #def process(self, value, obj = None):
        #if isinstance(value, tuple):
            #return coord2(value[0], value[1])
        #else:
            #return processor_typecast.process(self, value, obj)

RESTRICT_COORD2 = restrict_type(coord2)
RESTRICT_SIZE2 = RESTRICT_COORD2 & restrict_list(RESTRICT_NONNEGATIVE) 
            
def coord2_property(internal_member_name= None, restriction = None, preprocess = None, **kwargs):
    R = RESTRICT_COORD2 & restriction
    P = processor_typecast(coord2) + preprocess
    return restricted_property(internal_member_name = internal_member_name, restriction = R, preprocess = P, **kwargs )

def size2_property(internal_member_name= None, restriction = None, preprocess = None,**kwargs):
    R = RESTRICT_SIZE2 & restriction
    P = processor_typecast(coord2) + preprocess
    return restricted_property(internal_member_name = internal_member_name, restriction = R,preprocess = P,  **kwargs )


def coord2_match_position(P1, P2):
    """ returns position (or vector) to translate port 1 in order to coindice with port 2 """
    return coord2(P2[0], P2[1]) - coord2(P1[0], P1[1]) 
