from ..primitives import elements, shape_elements, box_elements, structure ,text_elements, ref_elements, library
from ..geometry import shape_cut 
from ..geometry.shapes import advanced, modifiers 
from ..geometry import shape_info 
from ..properties.initializer import strong_property_initializer
from ..properties.descriptor import property_descriptor
from ..properties.predefined import bool_property, RESTRICT_POSITIVE, int_property
from .. import constants
import sys

GDS_MAX_VERTEX_COUNT = 4000

all = []

#######################################################################
## Basic output module
#######################################################################
class basic_output(strong_property_initializer):
    o_stream = property_descriptor(default = sys.stdout)
    def __init__(self, o_stream = sys.stdout, **kwargs):
        super(basic_output, self).__init__(
            o_stream = o_stream,
            **kwargs)

    def write(self, item):
        self.o_stream.write(self.str(item))
        self.o_stream.flush()

    def str(self, item):
        if isinstance(item, str):
            return item
        else:
            raise TypeError ("basic_output.write() supports only string parameters")


#######################################################################
## Basic GDS output stream
#######################################################################
class basic_gds_output(basic_output):
    paths_to_boundaries = bool_property(default = True)
    name_scrambling = bool_property(default = False)
    max_name_length = int_property(restriction = RESTRICT_POSITIVE, default = 255)

    write_empty = bool_property(default = False)
    echo = bool_property(default = False)


    def set_current_structure(self, S):
        self.__current_structure__ = S
        self.__structure_scale__ = S.unit / self.library.unit
    
    def __init__(self, o_stream = sys.stdout, **kwargs):
        super(basic_gds_output, self).__init__(
            o_stream = o_stream,
            **kwargs)
        self.library = None
        self.header_written = False
        self.footer_written = False
        self.__current_structure__ = None

    def str(self, item):
        return "".join(self.list(item))
    def list(self, item):
        if isinstance(item, str):               return basic_output.str(self, item)
        elif isinstance(item,elements.gds_element):      return self.list_element(item)
        elif isinstance(item,structure.gds_structure):    return self.list_structure(item)
        elif isinstance(item,elements.gds_elementlist):  return self.list_elementlist(item)
        elif isinstance(item,structure.gds_structurelist): return self.list_structurelist(item)
        elif isinstance(item,library.gds_library):      return self.list_library(item)
        else: raise TypeError ("Unsupported type " + str(type(item)) + " for basic_gds_output.str().")

    def list_element(self, element, additional_transform = None):
        if element.is_empty() and (not self.write_empty): return []
        elif isinstance(element,elements.gds_compound_element):    return self.list_compound_element(element, additional_transform)
        elif isinstance(element,shape_elements.gds_single_shape_element):         return self.list_shape_element(element, additional_transform)
        elif isinstance(element,ref_elements.gds_ref_element):         return self.list_ref_element(element, additional_transform)
        elif isinstance(element,box_elements.gds_box_element):         return self.list_box_element(element, additional_transform)
        elif isinstance(element,text_elements.gds_label_element):       return self.list_label_element(element, additional_transform)
        #elif isinstance(element,gds_dynamic_element):     return self.list_dynamic_element(element, additional_transform) #backward compatibility
        #elif isinstance(element,gds_element):            return self.list_static_element(element, additional_transform)  #backward compatibility
        else: raise TypeError ("Unsupported type " + str(type(element)) + " for basic_gds_output.list_element().")

    def list_structure(self, item):
        if self.echo: print >> sys.stderr, "Defining Structure %s with %d elements." %( item.name, len(item.elements))
        if item.is_empty() and (not self.write_empty): return []
        elif isinstance(item,structure.gds_dynamic_structure):     return self.list_dynamic_structure(item) 
        else:                                            return self.list_static_structure(item)  

    def list_dynamic_structure(self, item):
        if not item.is_calculated(): item.calc()
        return self.list_static_structure(item)

    def list_static_structure(self, structure):
        self.set_current_structure(structure)
        st = self.__list_structure_header__(structure)
        for s in structure.elements:
            st += self.list_element(s, structure.transformation)
        st += self.__list_structure_footer__(structure)
        return st
    
    def list_library(self, library):
        st = self.__list_library_header__(library)
        st += self.list_structurelist(library.structures)
        st += self.__list_library_footer__()
        return st

    def init_library_header(self, library):
        self.library =  library
        self.library.calc_header()
        self.header_written = True

    def fini_library_footer(self):
        self.library.calc_footer()
        self.library = None
        self.footer_written = True

    def list_elementlist(self, item, additional_transform = None):
        if item.is_empty() and (not self.write_empty): return []
        st = []
        for s in item:
            st += self.list_element(s, additional_transform)
        return st

    def list_structurelist(self, item):
        if item.is_empty() and (not self.write_empty): return []
        st = []
        for s in item:
            st += self.list_structure(s)
        return st

    def list_compound_element(self, item, additional_transform = None):
        if not item.is_calculated(): item.calc()
        st =self.list_elementlist(item.__subelements__, item.transformation + additional_transform)
        return st
    
    # will not be used anymore
    def list_dynamic_element(self, item, additional_transform = None):
        if not item.is_calculated(): item.calc()
        return self.list_static_element(e.data, additional_transform)

    # wil not be used anymore
    def list_static_element(self, item , additional_transform = None):
        return item.data # this will be used with elements that are wrapped around a string (backward compatility)

    def list_shape_element(self, item, additional_transform = None):
        shape = item.shape.transform_copy(item.transformation + additional_transform)
        shape.snap_to_grid(self.library.grids_per_unit)
        shape.remove_identicals()
        coordinates = shape#.tolist()
        if item.line_width == 0.0:
            # BOUNDARIES
            if len(shape) < 3:
                if self.write_empty:
                    print >> sys.stderr, "Warning: BOUNDARY with fewer than 3 coordinates not allowed in structure", self.__current_structure__.name
                return []
            if len(shape) > GDS_MAX_VERTEX_COUNT:
                    print >> sys.stderr, "Warning: BOUNDARY with more than", GDS_MAX_VERTEX_COUNT, "coordinates not supported by Cadence in structure", self.__current_structure__.name
            # shape must be closed!
            if not (coordinates[0] == coordinates[-1]):
                coordinates.append(coordinates[0])
            return self.list_boundary_element(item.layer, coordinates)

        if len(coordinates) < 2:
            if self.write_empty:
                print >> sys.stderr, "Warning: PATH with fewer than 2 coordinates not allowed"
            return []

        if item.shape.closed:
            min_sections = 2
            if (coordinates[-1] == coordinates[0]):
                coordinates.append(coordinates[1]) # create overlap
            elif ((coordinates[-2] != coordinates[0]) or (coordinates[-1] != coordinates[1])):
                coordinates.append(coordinates[0]) # close
                coordinates.append(coordinates[1]) # create overlap
            coordinates.end_angle = shape_info.angle_deg(coordinates[2], coordinates[1])
            coordinates.start_angle = shape_info.angle_deg(coordinates[-2], coordinates[-3])
        else:
            min_sections = 1

        if item.absolute_line_width:
            line_width = - item.line_width
        else:
            line_width = item.line_width * item.transformation.magnification
            
        ret_data = []
        if self.paths_to_boundaries:
            paths = shape_cut.cut_open_shape_in_sections_with_overlap (coordinates, constants.GDSII_MAX_COORDINATES/2, 1, min_sections)
            for p in paths:
                ret_data += self.list_element(shape_elements.el_boundary(item.layer, modifiers.shape_path(p, abs(line_width) , item.path_type)))
        else:
            paths = shape_cut.cut_open_shape_in_sections_with_overlap (coordinates, constants.GDSII_MAX_COORDINATES, 1, min_sections)
            for p in paths:
                ret_data += self.list_path_element(item.layer, p, line_width , item.path_type )
        return ret_data

    def list_ref_element(self, item, additional_transform = None):
        if isinstance(item,ref_elements.gds_sref_element):             return self.list_sref_element(item, additional_transform)
        elif isinstance(item,ref_elements.gds_aref_element):           return self.list_aref_element(item, additional_transform)
        else:
            raise TypeError("Unsupported type " + str(type(item)) + " in basic_gds_output.list_ref_element")


    def scramble_structure_name(self,name):
        name = name.replace("-","_")
        name = name.replace(" ","_")
        if self.name_scrambling:
            hashval = hash(name)
            if hashval < 0:
                prefix = "S_"
            else:
                prefix = "S"
            name = prefix + str(abs(hashval))
            name = name.upper()
        elif self.max_name_length != None:
            if  self.max_name_length > 0 and len(name) > self.max_name_length:
                hashval = str(abs(hash(name)))
                L = len(hashval)
                if L > self.max_name_length + 1:
                    raise ValueError("max_name_length in basic_gds_output is too short for hash algortithm")
                name = name[0:self.max_name_length - L - 1] + "_" + hashval
                print >> sys.stderr, "Too long name encountered. Current length =", len(name)
        return name

    def __scale_value__(self, value):
        return value * self.__structure_scale__
