from .structure import gds_structurelist, gds_structure
from .unit_grid import unit_grid_container
from time import time
from ..properties.predefined import string_property, bool_property, time_property

from .. import settings
__all__ = ["gds_library"]


class gds_library(unit_grid_container):
    name = string_property(required = True)
    accessed = time_property()
    modified = time_property()
    layout = bool_property(default = True)
    allow_empty_structures = bool_property(default = True)
    
    def __init__(self, name , unit = None, grid = None, **kwargs ):
        self.name = name
        self.structures = gds_structurelist()

        if grid == None:
            grid = settings.get_current_library().grid
        else:
            grid = grid
    
        if unit == None:
            unit = settings.get_current_library().unit
        else:
            unit = unit

        super(gds_library, self).__init__(
            name = name,
            unit = unit,
            grid = grid,
            **kwargs 
            )

    #def set_grid (self,grid_step):
        ##if self.header_written:
            ##print >> sys.stderr, "Warning: Changing the units after the header is written can cause scaling errors"
        #self.grid = grid_step
        #if not self.unit == None:
            #self.grids_per_unit = self.unit/ self.grid

    #def set_unit (self,unit ):
        ##if self.header_written:
            ##print >> sys.stderr, "Warning: Changing the grid after the header is written can cause scaling errors"
        #self.unit = unit
        #if not self.grid == None:
            #self.grids_per_unit = self.unit / self.grid

    def snap_value(self,value):
        return settings.snap_value(value, self.grids_per_unit )

    def snap_coordinate(self,coordinate):
        return settings.snap_coordinate(coordinate, self.grids_per_unit)

    def snap_shape(self,coordinates):
        return settings.snap_shape(coordinates, self.grids_per_unit)
    def calc(self):
        for s in self.structures:
            s.calc()

    def calc_header(self):
        self.collect_references()
        self.check_references()
        if (self.unit == None) or (self.grid == None):
            print >> sys.stderr, "Error: Set Grid and Unit before writing the header of library", self.name
            raise SystemExit

    def calc_footer(self):
        pass

    def add(self,structure):
        if isinstance(structure, gds_structure) or isinstance(structure, gds_structurelist):
            self.structures.add(structure)
        else:
            raise TypeError("Wrong type " + str(type(structure)) + "of argument in gds_library.structure_exists.")

    def __iadd__(self, other):
        if isinstance(other, gds_structure) or isinstance(other, gds_structurelist):
            self.structures.add(other)
            self.structures.add(other)
        elif isinstance(other, gds_library):
            for i in other.structures:
                self.structures.add(i)
        return self

    def structure_exists(self,structure):
        return structure in self.structures

    def clean_up(self):
        if self.allow_empty_structures: return

        # remove references to empty structures
        for s in self.structures:
            empty_e = []
            e_list = s.__elements__
            for i in range(len(e_list)):
                if e_list[i].is_empty: empty_e.append(i)
                #e = e_list[i]
                #if isinstance(e, gds_ref_element):
                    #if e.reference.is_empty():
                        #empty_e.append(i)
            del s.__elements__[empty_e]

        # remove empty structures
        empty_s = []
        for s in range(0,len(self.structures)):
            if self.structures[s].is_empty(): empty_s.append(s)
        del self.structures[empty_s]

    def is_empty(self):
        return len(self.structures) == 0

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

    def flat_copy(self, level = -1):
        newlib = gds_library(self.name, self.unit, self.grid)
        for s in self.unreferenced_structures():
            newlib.add(s.flat_copy(level))
        newlib.collect_references()
        return newlib

    def flatten(self, level = -1):
        sl = gds_structurelist()
        for s in self.unreferenced_structures():
            sl.add(s.flatten(level))
        self.structures = sl
        self.collect_references()
        return self


    def top_layout(self):
        L = self.unreferenced_structures()
        if len(L) == 1:
            return L[0]
        elif len(L) == 0:
            if len(self.structures) == 0:
                return gds_structure("__empty__")
            else:
                print >> sys.stderr,  "Warning: There is no top_level structure in library", self.name, ". This could be caused by a circular reference"
                return None
        elif self.layout:
            print >> sys.stderr, "Warning: There is more than one top-level structure in library", self.name
            print >> sys.stderr, "This is ambiguous if the library is to be used as a layout"
            print >> sys.stderr, "#Please make sure that all but the top-level structures are referred to."
            print >> sys.stderr, "#The following structures are 'floating':"
            for s in L:
                print >> sys.stderr,  "#     " + s.name
            return None

    def unreferenced_structures(self):
        # returns a list of unreferenced structures
        Referred_to_list = self.referenced_structures()
        Not_Referred_to_list = gds_structurelist()
        for s in self.structures:
            if not s in Referred_to_list:
                Not_Referred_to_list.add(s)
        return Not_Referred_to_list

    def referenced_structures(self):
        # Build list of referred structures
        Referred_to_list = gds_structurelist()
        for s in self.structures:
            Referred_to_list.add(s.dependencies())
        return Referred_to_list

    def collect_references(self, structure = None):
        if structure == None:
            s_list = self.structures
        else:
            s_list = gds_structurelist(structure)
        #OK = False
        #while not OK:
            #OK = True
            #for s1 in s_list:
                #d = s1.dependencies()
                #for s2 in d:
                    #if not self.structure_exists(s2):
                        #self.add(s2)
                        #OK = False
                #if not OK: break # go over it again
        new_s = gds_structurelist()
        for s1 in s_list:
            d = s1.dependencies()
            for s2 in d:
                new_s.add(s2)
        self.add(new_s)

    def check_references(self):
        Referred_to_list = self.referenced_structures()
        
        # check if all references belong to the library
        for s in Referred_to_list:
            if not s in self.structures:
                print >> sys.stderr,  "Error: The structure \'" + s + "\' you refer to is not part of library " + self.name
                raise SystemExit

        Not_Referred_to_list = gds_structurelist()
        for s in self.structures:
            if not Referred_to_list.__fast_contains__(s.name):
                Not_Referred_to_list.__fast_add__(s)
        if len(Not_Referred_to_list) == 0:
            return -2
        return 0

    def __fast_get_structure__(self, str_name):
        # returns S if the structure exists or returns None
        for s in self.structures:
            if s.name == str_name: return s
        return None
    
    def __fast_add__(self, new_str):
        # add a structure without checking if the structure exists
        self.structures.__fast_add__(new_str)
        

    def __contains__(self, item):
        return self.structures.__contains__(item)

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

    def __getitem__(self, index):
        return self.structures[index]

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


###################################################################
## Library that can be written on the fly, Checks only when writing the footer
###################################################################
#class gds_stream_library(gds_library):
    #def __init__(self, name, grid = None, unit = None):
        #gds_library.__init__(self, name, grid, unit)

    #def calc_header(self):
        #if (self.unit == None) or (self.grid == None):
            #print >> sys.stderr, "Error: Set Grid and Unit before writing the header of library", self.name
            #raise SystemExit

    #def calc_footer(self):
        #self.collect_references()
        #self.check_references()
