from gk_global import *
from gk_elements import *
from gk_dynamic_item import *
import gk_library 
#import gk_ref_elements
from gk_transformable import *


from types import *

import time
import copy


class meta_gds_structure_creator(type):
    # Called when a new object is created
    def __call__(cls, *params, **keyword_params):
        # check is there is a library passed.
        # otherwise, use current library
        library = get_current_library()
        if 'library' in keyword_params:
            k = keyword_params['library']
            if k != None: library = k
            del keyword_params['library']
        else:
            for p in reversed(params):
                if isinstance(p, gk_library.gds_library):
                    library = p
                    break

        # extract the name of the new structure based on the arguments of
        # the constructor. For default structures, the name is passed as the first argument
        name = cls.__str_name__(*params, **keyword_params)
        libstr = library.__fast_get_structure__(name)
        if libstr == None:
            S = type.__call__(cls, *params, **keyword_params)
            object.__setattr__(S, "name", S.__str_name__(*params, **keyword_params))
            library.__fast_add__(S)
            return S
        else:
            return libstr
            # return the existing object


########################################################
## Basic Structure
########################################################

class gds_structure(gds_static_item, gds_stored_no_distort_transformable):
    __metaclass__ = meta_gds_structure_creator
    def __init__(self, name, data=[], library = None):
        self.__elements__ =  gds_elementlist(data)
        self.comment = ""
        gds_stored_no_distort_transformable.__init__(self,None)

        if library == None: library = get_current_library()

        self.created = time.time()
        self.modified = time.time()
        self.__grid = library.grid
        self.__unit = library.unit

    # __str_name__ returns the FULL name of the structure, based on the parameters of the constructor
    # should be overloaded for structures whose first parameter in __init__ is not the FULL name
    # When overloaded, it should be a classmethod
    @classmethod # from python 2.4
    def __str_name__(cls, name, *params, **keyword_params):
        return name

    def get_grid (self): return self.__grid
    def set_grid (self,grid_step):
        self.__grid = grid_step
        self.__grids_per_unit = self.__unit/ self.__grid
    grid = property(get_grid, set_grid)

    def get_unit (self): return self.__unit
    def set_unit (self,unit ):
        self.__unit = unit
        self.__grids_per_unit = self.__unit / self.__grid
    unit = property(get_unit, set_unit)

    def dependencies(self):
        return self.__elements__.dependencies()

    def size_info(self):
        return self.__elements__.size_info().transform(self.transformation)

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

    # adding elements
    def add_el(self, elements):
        self.__elements__ += elements

    def add_sref(self, struct, coordinate, transform= None):
        import gk_ref_elements
        self.add_el(gk_ref_elements.gds_sref_element(struct, coordinate, transform))

    def add_aref(self, struct, coordinate, period, n_o_periods, transform= None):
        import gk_ref_elements
        self.add_el(gk_ref_elements.gds_aref_element(struct, coordinate, period, n_o_periods, transform))

    def add_soft_aref(self, struct, coordinate, period, n_o_periods, transform= None):
        import gk_ref_elements
        self.add_el(gk_ref_elements.gds_soft_aref_element(struct, coordinate, period, n_o_periods, transform))

    # Shorthand for adding elements: internal addition
    def __iadd__(self, other):
        if isinstance(other, gds_element):
            self.add_el(other)
        elif isinstance(other, list):
            for t in other:
                gds_structure.__iadd__(self, t)
        else:
            raise TypeError("Invalid type " + str(type(other)) + " in gds_structure.__iadd__().")
        return self
    # addition
    def __add__(self, other):
        if isinstance(other, gds_element):
            L = copy.deepcopy(self)
            L += other
            return L
        elif isinstance(other, gds_structure):
            return gds_structurelist([self, other])
        elif isinstance(other, gds_structurelist):
            L = gds_structurelist(self)
            L.extend(other)
        else:
            raise TypeError("Invalid type " + str(type(other)) + " in gds_structure.__add__().")

    def get_elements(self):
        return self.__elements__
    def set_elements(self, value):
        self.__elements__ = value
    elements = property(get_elements, set_elements)

    def __iter__(self):
        return self.__elements__.__iter__()

    def clear(self):
        self.__elements__.clear()

    def flat_copy(self, level = -1):
        new_name = self.name + "_flat"
        S = gds_structure(new_name, self.__elements__.flat_copy(level))
        S.transform(self.transformation)
        return S

    def flatten(self, level = -1):
        self.__elements__ = self.__elements__.flat_copy(level)
        return self
   
    def expand_transform(self):
        if not self.transformation.is_identity():
            EL = copy.deepcopy(self.__elements__)
            EL.transform(self.transformation)
            self.transformation = None
            self.__elements__ = EL

def structure_property(internal_member_name, restriction = None,  **kwargs):
    R = restrict_type(gds_structure) & restriction
    return restricted_property_descriptor(internal_member_name, restriction = R)


class gds_dynamic_structure(gds_dynamic_item, gds_structure):
    # structure where elements can be calculated from parameters
    __static_item_class__ = gds_structure
    
    def __init__(self, name, library = None):
        gds_structure.__init__(self, name, [], library)

    def __calc__(self):
        # overload this function to calculate the data
        # based on additional parameters
        return
    ##########################################################
    ## Calculate Structure if you access execute one of these
    ##########################################################
    def get_elements(self):
        self.calc()
        return self.__elements__
    def set_elements(self, value):
        self.__make_static__()
        gds_structure.set_elements(self, value)
    elements = property(get_elements, set_elements)

    def dependencies(self):
        self.calc()
        return gds_structure.dependencies(self)

    def size_info(self):
        self.calc()
        return gds_structure.size_info(self)

    def is_empty(self):
        # You can overload this function for speed if needed
        self.calc()
        return gds_structure.is_empty(self)

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

    ##########################################################
    ## Make structure static if you access execute one of these
    ##########################################################

    def flatten(self, level = -1):
        self.__make_static__()
        return gds_structure.flatten(self, level)

    def expand_transform(self):
        if not self.transformation.is_identity():
            self.__make_static__(gds_structure)
            gds_structure.expand_transform(self)

    # External use only
    def add_el(self, elements):
        if not self.is_call_internal(): self.__make_static__()
        gds_structure.add_el(self,elements)

    def add_sref(self, struct, coordinate, transformation = None):
        if not self.is_call_internal(): self.__make_static__()
        gds_structure.add_sref(self, struct, coordinate, transformation)

    def add_aref(self, struct, coordinate, period, n_o_periods, transformation= None):
        if not self.is_call_internal(): self.__make_static__()
        gds_structure.add_aref(self, struct, coordinate, period, n_o_periods, transformation )

    def add_soft_aref(self, struct, coordinate, period, n_o_periods, transformation= None):
        if not self.is_call_internal(): self.__make_static__()
        gds_structure.add_soft_aref(self, struct, coordinate, period, n_o_periods, transformation )

    def __iter__(self):
        if not self.is_call_internal(): self.__make_static__()
        return gds_structure.__iter__(self)

    def __iadd__(self, other):
        if not self.is_call_internal(): self.__make_static__()
        return gds_structure.__iadd__(self,other)


#############################################################################
## Structure that calculates its own name
#############################################################################
# TODO: enforce correct syntax for subclasses using metaclass

class gds_autoname_structure(gds_dynamic_structure):
    # structure where elements and the name can be calculated from parameters
    def __init__(self, library=None):
        self.name = self.__calc_name__()
        gds_dynamic_structure.__init__(self, self.name, library)

    @classmethod # from python 2.4
    def __str_name__(cls,  library):
        # this method should have the same signature as __init__
        return "blank"

    # Override this method: should call __str_name__ with its own 
    # internal parameters   
    def __calc_name__(self):
        return self.__str_name__()
    
    # Makes sure that when a parameter is changed, the name is also changed
    # this should not be overridden
    def __setattr__(self, name, value):
        gds_dynamic_structure.__setattr__(self, name, value)
        if not self.is_call_internal(): object.__setattr__(self, "name", self.__calc_name__())

#############################################################################
## Structure list
#############################################################################

class gds_structurelist(list):
    def __init__(self, structures = []):
        if isinstance(structures, gds_structure):
            list.__init__(self, [structures])
        elif isinstance(structures, gds_structurelist):
            list.__init__(self, structures)
        elif isinstance(structures, list):
            list.__init__(self, [])
            for s in structures:
                self.add(s) #eliminates structures with duplicate names

    def is_empty(self):
        if (len(self) == 0): return True
        for e in self:
            if not e.is_empty(): return False
        return True

    # overload acces routines to get dictionary behaviour but without
    # using the name as primary key
    def __getitem__(self, key):
        if isinstance(key, str):
            for i in self:
                if i.name == key: return i
            raise IndexError("Structure " + key + " cannot be found in gds_structurelist.")
        else:
            return list.__getitem__(self,key)

    def __setitem__(self, key, value):
        if isinstance(key, str):
            for i in range(0, len(self)):
                if self[i].name == key: return list.__setitem__(self, i, value)
            list.append(self, value)
        else:
            return list.__setitem__(self,key, value)

    def __delitem__(self, key):
        if isinstance(key, str):
            for i in range(0, len(self)):
                if self[i].name == key: return list.__delitem__(self,i)
                return
            return list.__delitem__(self,key)
        else:
            return list.__delitem__(self,key)

    def __contains__(self, item):
        if isinstance(item, gds_structure):
            name = item.name
        else:
            name = item
        if isinstance(name, str):
            for i in self:
                if i.name == name: return True
            return False
        else:
            return list.__contains__(self,item)
        
    def __fast_contains__(self, name):
        for i in self:
            if i.name == name: return True
        return False
        

    def index(self, item):
        if isinstance(item, str):
            for i in range(0, len(self)):
                if list.__getitem__(self, i).name == item:
                    return i
            raise ValueError("Structure " + item + " is not in gds_structurelist")
        else:
            list.index(self, item)

    def __fast_add__(self, new_str):
        # adds a structure ithout checking if it already exists
        # for library and object creation only
        list.append(self, new_str)
            
    def add(self, item, overwrite = False):
        if isinstance(item, gds_structure):
            if overwrite:
                self[item.name] = item
                return
            elif not self.__fast_contains__(item.name):
                list.append(self, item)
        elif isinstance(item, gds_structurelist):
            for s in item:
                self.add(s, overwrite)
        else:
            raise TypeError("Wrong type of data to append to gds_structurelist: " + str(type(item)))


    def append(self, other, overwrite = False):
        return self.add(other, overwrite)

    def extend(self, other, overwrite = False):
        return self.add(other, overwrite)

    def __add__(self, other):
        L = gds_structurelist(self)
        if isinstance(other, gds_structure) or isinstance(other, list):
            L.add(other)
            return L
        else:
            return super(list,L).__add__(other)

    def __radd__(self, other):
        if isinstance(other, gds_structure) or isinstance(other, list) :
            L = gds_structurelist(other)
            L.add(self)
            return L
        else:
            return list__radd__(self, other)

    def __iadd__(self, other):
        if isinstance(other, gds_structure) or isinstance(other, list) :
            self.add(other)
            return self
        else:
            return list.__iadd__(self, other)

    def clear(self):
        del self[:]

    def is_calculated(self):
        for s in self:
            if not self[s].is_calculated():
                return False
        return True

    def __calc__(self):
        for s in self:
            if not self[s].is_calculated():
                self[s].calc()
