from ..geometry.transforms.translation import gds_translation
from ..geometry.coord import coord2, coord2_property
from ..geometry import shape
from ..geometry import shape_info
from .elements import gds_element, gds_dynamic_element, gds_elementlist, gds_compound_element
from . import structure as structure_module
from .. import constants
from .. import settings

from ..properties.descriptor import restricted_property
from ..properties.predefined import RESTRICT_INT_TUPLE2, RESTRICT_POSITIVE, int_property
from types import NoneType
from copy import copy, deepcopy
from numpy import transpose, reshape, meshgrid, array
#import shape_elements

__all__ = ["gds_aref_element",
           "gds_compound_aref_element",
           "gds_ref_element",
           "gds_softrotation_aref_element",
           "gds_soft_aref_element",
           "gds_sref_element",
           "gds_stack_aref_element",
           "el_aref",
           "el_softrotation_aref",
           "el_soft_aref",
           "el_sref"]

##########################################################
# Basic Reference Element
##########################################################

class gds_ref_element(gds_dynamic_element):
    reference = structure_module.structure_property(required = True)
    def __init__(self,
                 reference,
                 transformation = None,
                 **kwargs):
        super(gds_ref_element, self).__init__(reference = reference, transformation = transformation, **kwargs)

    def __calc__(self):
        self.reference.calc()

    def dependencies(self):
        d = structure_module.gds_structurelist()
        if not self.is_empty():
            d.add(self.reference)
            d.add(self.reference.dependencies())
        return d

    def expand_transform(self):
        if not self.transformation.is_identity():
            S = structure_module.gds_structure(self.reference.name + self.transformation.id_string(),
                              deepcopy(self.reference.elements)
                              )
            self.reference = S
            S.transform(self.transformation)
            self.transformation = None


    def is_empty(self):
        if self.reference == None:
            return True
        else:
            return self.reference.is_empty()


class gds_sref_element(gds_ref_element):
    position = coord2_property(default = (0.0, 0.0) )
    def __init__(self,
                 reference,
                 position = (0.0, 0.0),
                 transformation = None):
        super(gds_sref_element, self).__init__(reference = reference, position = position, transformation = transformation)
        
    def size_info(self):
        return self.reference.size_info().transform(self.transformation + gds_translation(self.position))

    def move(self, position):
        self.__reset_calc__()
        self.__position__ = coord2(self.position[0] + position[0], self.position[1] + position[1])

    def __copy__(self):
        return gds_sref_element(self.reference, self.position, self.transformation)

    def __deepcopy__(self, memo):
        return gds_sref_element(self.reference, self.position, deepcopy(self.transformation))

    def flat_copy(self, level = -1):
        self.calc()
        if level == 0: return gds_elementlist(self.__copy__())
        el = self.reference.__elements__.flat_copy(level-1)
        el.transform(self.reference.transformation + self.transformation + gds_translation(self.position))
        return el

    
gds_sref_element.__static_item_class__ = gds_sref_element
    

class gds_aref_element(gds_ref_element):
    origin = coord2_property(default = (0.0, 0.0))
    period = coord2_property(required = True)
    n_o_periods = restricted_property(restriction = RESTRICT_INT_TUPLE2, required = True)
    
    def __init__(self,
                 reference,
                 origin = (0.0,0.0),
                 period = (1.0, 1.0) ,
                 n_o_periods = (1,1),
                 transformation= None,
                 **kwargs):
        super(gds_aref_element, self).__init__(reference = reference, transformation = transformation, origin = origin, period = period, n_o_periods = n_o_periods, **kwargs)

    #def set_origin(self, value):
        #self.__origin__ = coord2(value[0], value[1])
    #def get_origin(self):
        #return self.__origin__
    #origin = property(get_origin, set_origin)

    #def set_period(self, value):
        #self.__period__ = coord2(value[0], value[1])
    #def get_period(self):
        #return self.__period__
    #position = property(get_period, set_period)

    def move(self, position):
        self.__reset_calc__()
        self.origin = (self.origin[0] + origin[0], self.origin[1] + origin[1])

    def size_info(self):
        S = self.reference.size_info()
        S2 = (  S
              + S.move_copy(((self.n_o_periods[0]-1) * self.period[0], (self.n_o_periods[1]-1) * self.period[1]))
                )
        return S2.transform(self.transformation + gds_translation(self.origin))

    def __untransformed_positions__(self):
        p = shape.gds_shape(transpose(reshape(meshgrid(range(self.n_o_periods[0]), range(self.n_o_periods[1])),(2, self.n_o_periods[0]* self.n_o_periods[1]))) * array([self.period[0], self.period[1]]))
        return p

    def __positions__(self):
        p = self.__untransformed_positions__()
        return p.transform(self.transformation + gds_translation(self.origin))

    def __copy__(self):
        return gds_aref_element(self.reference, self.origin, self.period, self.n_o_periods, self.transformation)

    def __deepcopy__(self, memo):
        return gds_aref_element(self.reference, self.origin, self.period, self.n_o_periods, deepcopy(self.transformation))

    def flat_copy(self, level = -1):
        if level == 0: return gds_elementlist(self.__copy__())
        self.calc()
        el = self.reference.__elements__.flat_copy(level-1) 
        el_tot = gds_elementlist()
        T = self.reference.transformation + self.transformation - gds_translation(self.transformation.translation)
        for p in self.__positions__():
            T2 = T + gds_translation(p)  
            el_tot += el.transform_copy(T2)
        return el_tot


    def is_empty(self):
        return gds_ref_element.is_empty(self) or (self.n_o_periods[0] ==0) or (self.n_o_periods[1] ==0)

gds_aref_element.__static_item_class__ = gds_aref_element

class gds_compound_aref_element(gds_compound_element, gds_aref_element):
    def __init__(self,
                 reference,
                 origin = (0.0,0.0),
                 period = (1.0, 1.0) ,
                 n_o_periods = (1,1),
                 transformation= None,
                 **kwargs):
        super(gds_compound_aref_element, self).__init__(reference = reference,
                                                        origin = origin,
                                                        period = period,
                                                        n_o_periods = n_o_periods,
                                                        transformation = transformation,
                                                        **kwargs)

    def flat_copy(self, level = -1):
        return gds_aref_element.flat_copy(self, level)

    def is_empty(self):
        return gds_aref_element.is_empty(self)

gds_compound_aref_element.__static_item_class__ = gds_compound_element



class gds_soft_aref_element(gds_compound_aref_element):
    #def __init__(self,
                 #ref_structure,
                 #origin = (0.0,0.0),
                 #period = (1.0, 1.0) ,
                 #n_o_periods = (1,1),
                 #transformation= None):
        #gds_compound_aref_element.__init__(self, ref_structure, origin, period, n_o_periods, transformation)

    def __calc__(self):
        if not self.reference.is_empty():
            P = self.__untransformed_positions__().translate(self.origin)
            
            T = self.transformation + gds_translation(self.origin) - self.transformation - gds_translation(self.origin) 
            for pos in P:
                self.append(gds_sref_element(self.reference, pos, T))

    def size_info(self):
        return gds_aref_element.size_info(self)

    #def __copy__(self):
        #return gds_soft_aref_element(self.reference, self.origin, self.period, self.n_o_periods, self.transformation)

    #def __deepcopy__(self, memo):
        #return gds_soft_aref_element(self.reference, self.origin, self.period, self.n_o_periods, copy.deepcopy(self.transformation))


class gds_softrotation_aref_element(gds_compound_aref_element):
    #def __init__(self,
                 #ref_structure,
                 #origin = (0.0,0.0),
                 #period = (1.0, 1.0) ,
                 #n_o_periods = (1,1),
                 #transformation= None):
        #gds_compound_aref_element.__init__(self, ref_structure, origin, period, n_o_periods, transformation)

    def __calc__(self):
        if not self.reference.is_empty():
            if self.transformation.rotation == 0.0:
                period = self.transformation.apply_to_coord(self.period)
                self.append(gds_aref_element(self.reference, self.origin, period, self.n_o_periods, self.transformation))
            elif (self.transformation.rotation % 360.0) == 90.0:
                transform = deepcopy(self.transformation)
                transform.rotation = 0.0
                period = transform.apply_to_coord(self.period)
                period = (period[1], period[0])
                n_o_periods = (self.n_o_periods[1], self.n_o_periods[0])
                zero = (self.origin[0] - (n_o_periods[0] - 1) * period[0], self.origin[1])
                self.append(gds_aref_element(self.reference, zero, period, n_o_periods, transform))
            elif (self.transformation.rotation%360.0) == 270.0:
                transform = deepcopy(self.transformation)
                transform.rotation = 0.0
                period = transform.apply_to_coord(self.period)
                period = (period[1], period[0])
                n_o_periods = (self.n_o_periods[1], self.n_o_periods[0])
                zero = (self.origin[0] , self.origin[1] - (n_o_periods[1] - 1) * period[1])
                self.append(gds_aref_element(self.reference, zero, period, n_o_periods, transform))
            else:
                self.append(gds_soft_aref_element(self.reference, self.origin, period, self.n_o_periods, self.transformation))

    def size_info(self):
        return gds_aref_element.size_info(self)

    #def __copy__(self):
        #return gds_soft_aref_element(self.reference, self.origin, self.period, self.n_o_periods, self.transformation)

    #def __deepcopy__(self, memo):
        #return gds_soft_aref_element(self.reference, self.origin, self.period, self.n_o_periods, copy.deepcopy(self.transformation))




class gds_stack_aref_element(gds_compound_aref_element):
    stack_size = int_property(restriction = RESTRICT_POSITIVE, default = 20)
    def __init__(self,
                 reference,
                 origin = (0.0,0.0),
                 period = (1.0, 1.0) ,
                 n_o_periods = (1,1),
                 transformation= None,
                 stack_size = 20,
                 **kwargs):
        super(gds_stack_aref_element, self).__init__(reference = reference, 
                                                     origin = origin, 
                                                     period = period, 
                                                     n_o_periods = n_o_periods, 
                                                     transformation = transformation, 
                                                     stack_size = stack_size, 
                                                     **kwargs)

    def __calc__(self):
        # X_periodicity: 1
        if self.n_o_periods[0] == 1:
            # do not create X-cell, but use self.reference for Y-periodicity
            x_cell = self.reference
            x_cell_content = gds_sref_element(self.reference, (0.0, 0.0))
        # X-periodicity: smaller than 2 * stack size:
        elif self.n_o_periods[0] < 2 * self.stack_size:
            # use soft_aref
            x_cell_content = gds_soft_aref_element(self.reference, (0.0, 0.0), self.period, (self.n_o_periods, 1))
        # X-periodicity: larger than 2 * stack size
        else:
            # should become autoname structure
            x_cell_stack = structure_module.gds_structure("R_" + self.reference.name + "_SX" + str(int(self.stack_size)),
                                   gds_soft_aref_element(self.reference, (0.0, 0.0), self.period, (self.stack_size, 1)))
            x_cell_content  = gds_soft_aref_element(x_cell_stack, (0.0, 0.0), (self.period[0] * self.stack_size, self.period[1]), (self.n_o_periods[0] / self.stack_size, 1))
            x_cell_content += gds_soft_aref_element(self.reference, (self.period[0] * self.stack_size * (self.n_o_periods[0] / self.stack_size), 0.0), self.period, (self.n_o_periods[0] % self.stack_size, 1))
            
        # do not yet create the X-cell

        # Y-periodicity: 1
        if self.n_o_periods[1] == 1:
            x_cell_content.move(self.origin)
            # transformation is passed automatically through the compound_element transformation
            self += x_cell_content
            # do not create X-cell, but add the previously defined content to self, return
            return

        # create x_cell
        # this should become an autoname_structure
        x_cell = structure_module.gds_structure("R_" + self.reference + "_X" + str(int(n_o_periods[0])),
                               x_cell_content)
        
        if self.n_o_periods[1] < 2 * self.stack_size:
            # soft_aref of X_cell
            self += gds_soft_aref_element(x_cell, self.origin, self.period,(1, self.n_o_periods[1]))
        else: # Y_periodicity < 2* stack_size:
            y_cell_stack = structure_module.gds_structure("R_" + x_cell.name + "_SY" + str(int(self.stack_size)),
                                         gds_soft_aref_element(x_cell, (0.0, 0.0), (self.period[0], self.stack_size * self.period[1]), (1, self.n_o_periods[1] / self.stack_size)))
            self += gds_soft_aref_element(y_cell_stack, self.origin, (self.period[0], self.stack_size * self.period[1]), (1, self.n_o_periods[1] / self.stack_size))
            self += gds_soft_aref_element(x_cell, (self.origin[0], self.origin[1] + self.period[1] * self.stack_size * (self.n_o_periods[1] / self.stack_size)), self.period, (1, self.n_o_periods[1] % self.stack_size))
            
    def size_info(self):
        return gds_aref_element.size_info(self)

    #def __copy__(self):
        #return gds_soft_aref_element(self.reference, self.origin, self.period, self.n_o_periods, self.transformation)

    #def __deepcopy__(self, memo):
        #return gds_soft_aref_element(self.reference, self.origin, self.period, self.n_o_periods, copy.deepcopy(self.transformation))

       


#########################################################################################################
# Backward compatibility
# refedine functions to take string as argument: perform lookup to extract the structure
#########################################################################################################

el_sref = gds_sref_element
el_aref = gds_aref_element
el_soft_aref = gds_soft_aref_element
el_softrotation_aref = gds_softrotation_aref_element
        
#def el_sref(structure, position= (0.0,0.0), transformation = None, library= None):
    #if isinstance(structure, (structure_module.gds_structure, NoneType)):
        #return gds_sref_element(structure, position, transformation)
    #elif isinstance(structure, str): # lookup structure in library
        #if library == None: library = settings.get_current_library()
        #if library.structure_exists(structure):
            #S= library[structure]
            #return gds_sref_element(S, position, transformation)
        #else:
            #print >> sys.stderr, "Structure", structure, " in el_sref to cannot be found in library", library.name
            #raise SystemExit
    #else:
        #raise TypeError("Invalid type " + str(type(structure)) + " in el_sref")


#def el_aref(name, origin = (0.0,0.0), period = (1.0, 1.0) , n_o_periods = (1,1), transformation= None, library= None):
    #if isinstance(name, (structure_module.gds_structure, NoneType)):
        #return gds_aref_element(name, origin, period, n_o_periods, transformation)
    #elif isinstance(name, str): # lookup structure in library
        #if library == None: library = settings.get_current_library()
        #if library.structure_exists(name):
            #structure = library[name]
            #return gds_aref_element(structure, origin, period, n_o_periods, transformation)
        #else:
            #print >> sys.stderr, "Structure", name, " in el_aref to cannot be found in library", library.name
            #raise SystemExit
    #else:
        #raise TypeError("Invalid type " + str(type(name)) + " in el_soft_aref")

#def el_soft_aref(name, origin = (0.0,0.0), period = (1.0, 1.0) , n_o_periods = (1,1), transformation= None, library= None):
    #if isinstance(name, (structure_module.gds_structure, NoneType)):
        #return gds_soft_aref_element(name, origin, period, n_o_periods, transformation)
    #elif isinstance(name, str): # lookup structure in library
        #if library == None: library = settings.get_current_library()
        #if library.structure_exists(name):
            #structure = library[name]
            #return gds_soft_aref_element(structure, origin, period, n_o_periods, transformation)
        #else:
            #print >> sys.stderr, "Structure", name, " in el_soft_aref to cannot be found in library", library.name
            #raise SystemExit
    #else:
        #raise TypeError("Invalid type " + str(type(name)) + " in el_soft_aref")

#def el_softrotation_aref(name, origin = (0.0,0.0), period = (1.0, 1.0) , n_o_periods = (1,1), transformation= None, library= None):
    #if isinstance(name, (structure_module.gds_structure, NoneType)):
        #return gds_softrotation_aref_element(name, origin, period, n_o_periods, transformation)
    #elif isinstance(name, str): # lookup structure in library
        #if library == None: library = settings.get_current_library()
        #if library.structure_exists(name):
            #structure = library[name]
            #return gds_softrotation_aref_element(structure, origin, period, n_o_periods, transformation)
        #else:
            #print >> sys.stderr, "Structure", name, " in el_softrotation_aref to cannot be found in library", library.name
            #raise SystemExit
    #else:
        #raise TypeError("Invalid type " + str(type(name)) + " in el_softrotation_aref")

