from ..geometry import size_info
from ..geometry import transformable 
from ..dynamic.dynamic_item import gds_dynamic_item, gds_static_item
import copy
from .layer import layer_property, gds_layer
#from ipkiss.properties.initializer import strong_property_initializer
from ..properties.initializer import strong_property_initializer


__all__ = ["gds_compound_element", "gds_compound_layer_element", "gds_elementlist"]


##########################################################
# Basic Element
##########################################################
class gds_element(gds_static_item, transformable.gds_stored_no_distort_transformable, strong_property_initializer):
    def __init__(self, transformation = None, **kwargs):
        super(gds_element, self).__init__(transformation = transformation, **kwargs)

    def clear(self):
        pass
    
    def dependencies(self):
        from . import structure
        return structure.gds_structurelist()

    def __add__(self, other):
        if isinstance(other, list):
            l = gds_elementlist([self])
            l.extend(other)
            return l
        elif isinstance(other, gds_element):
            return gds_elementlist([self, other])
        else:
            raise TypeError("Wrong type of argument for addition in gds_element: " + str(type(other)))

    def __radd__(self, other):
        if isinstance(other, list) :
            l = gds_elementlist(other)
            l.append(self)
            return l
        elif isinstance(other, gds_element):
            return gds_elementlist([other, self])
        else:
            raise TypeError("Wrong type of argument for addition in gds_element: " + str(type(other)))


##    def __str__(self):
##        return get_current_output().str(self)

##########################################################
# Element with dynamic properties
##########################################################

class gds_dynamic_element(gds_dynamic_item, gds_element):
    __static_item_class__ = gds_element
    def __calc__(self):
        # overload to fill in data here
        return

    def clear(self):
        self.__reset_calc__()


##########################################################
# Element with a layer
##########################################################
class gds_layer_element(gds_dynamic_element):
    layer = layer_property(required = True)
    def __init__(self, layer = 0, transformation = None, **kwargs):
        super(gds_layer_element, self).__init__(transformation = transformation, layer = layer, **kwargs)
 
##########################################################
# Element consisting of other subelements
##########################################################

class gds_compound_element(gds_dynamic_element):
    def __init__(self, elements = [], transformation = None, **kwargs):
        self.__subelements__ = gds_elementlist(elements)
        super(gds_compound_element, self).__init__(transformation = transformation, **kwargs)
    
    def clear(self):
        self.__subelements__.clear()

    def is_calculated(self):
        return gds_dynamic_element.is_calculated(self) #and self.__subelements__.is_calculated()

    def __calc__(self):
        gds_dynamic_element.clear(self)
        self.__subelements__.calc()

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

    def append(self, element):
        # for internal additions (in calc())
        # this function does not change the class type
        if not self.is_call_internal(): self.__make_static__()
        self.__subelements__.append(element)
        #element.transform(self.transformation)

    def extend(self, elements):
        # for internal additions (in calc())
        # this function does not change the class type
        if not self.is_call_internal(): self.__make_static__()
        #elements.transform(self.transformation)
        if isinstance(elements, gds_compound_element):
            self.__substructures.extend(elements.__subelements__)
        else:
            self.__subelements__.extend(elements)

    def __iadd__(self, element):
        # for external additions: add element and reduce the class to a simple compound element
        if not self.is_call_internal(): self.__make_static__()
        if isinstance(element, list):
            self.extend(element)
        else: 
            self.append(element)
        return self

    def size_info(self):
        self.calc()
        return self.__subelements__.size_info().transform(self.transformation)

    def flat_copy(self, level = -1):
        if not level == 0:
            self.calc()
            return self.__subelements__.flat_copy(level).transform(self.transformation)
        else:
            return gds_elementlist(self.__subelements__)


    def expand_transform(self):
        if not self.transformation.is_identity():
            self.__subelements__.transform(self.transformation)
            self.transformation = None

    def __copy__(self):
        return gds_compound_element(self.__subelements__)

    def __deepcopy__(self, memo):
        return gds_compound_element(copy.deepcopy(self.__subelements__, memo))

    def is_empty(self):
        self.calc()
        return self.__subelements__.is_empty()

gds_compound_element.__static_item_class__ = gds_compound_element


##########################################################
# Compound Element with a layer
##########################################################

class gds_compound_layer_element(gds_compound_element, gds_layer_element):
    def __init__(self, layer = 0, elements = [], transformation = None, **kwargs):
        super(gds_compound_layer_element, self).__init__(layer = layer, elements = elements, transformation = transformation, **kwargs)
        
##########################################################
# List of elements
##########################################################
class gds_elementlist(list, gds_static_item, transformable.gds_no_distort_transformable):
    def __init__(self, elements = []):
        if isinstance(elements, gds_element):
            list.__init__(self, [elements])
        elif isinstance(elements, list):
            list.__init__(self, elements)

    def __add__(self, other):
        L = gds_elementlist(self)
        if isinstance(other, gds_element):
            L.append(other)
            return L
        else:
            return list.__add__(self,other)

    def __radd__(self, other):
        if isinstance(other, gds_element):
            L = gds_elementlist([other])
            L.extend(self)

        elif isinstance(other, list):

            L = gds_elementlist(other)
            L.extend(self)
            return L

    def __iadd__(self, other):
        if isinstance(other, gds_element):
            self.append(other)
            return self
        else:
            return list.__iadd__(self, other)

    def dependencies(self):
        from . import structure
        d = structure.gds_structurelist()
        for e in self:
            d.add(e.dependencies())
        return d

    def clear(self):
        del self[:]

    def append(self, item):
        if isinstance(item, gds_element):
            list.append(self, item)
        else:
            raise TypeError("Wrong type to append to gds_elementlist: " + str(type(item))) 

    def is_calculated(self):
        for e in self:
            if e.is_calculated() == False:
                return False
        return True            

    def __calc__(self):
        for e in self:
            if not e.is_calculated():
                e.calc()

    def size_info(self):
        self.__calc__()
        if len(self) == 0:
            return size_info.gds_size_info()
        else:
            SI = self[0].size_info()
            for e in self[1::]:
                SI += e.size_info()
            return SI

    def move(self, position):
        for c in self:
            c.move(position)
        return self

    def transform(self, transform):
        for c in self:
            c.transform(transform)
        return self

    def flat_copy(self, level = -1):
        el = gds_elementlist()
        for e in self:
            el += e.flat_copy(level)
        return el


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