
from .output import basic_gds_output

from time import localtime
import sys
import math
from binascii import b2a_hex, a2b_hex
from struct import pack

from .. import constants
from ..geometry.shape import gds_shape
from ..geometry.shapes.basic import  shape_rect
from ..geometry.transforms.translation import gds_translation
from . import gds_records

import copy

__all__ = ["gds_output_gdsii"]

class gds_output_gdsii(basic_gds_output):

        def __init__(self, o_stream = sys.stdout, **kwargs):
                super(gds_output_gdsii, self).__init__(
                        o_stream = o_stream,
                        **kwargs)
                if sys.platform == "win32":
                        import os, msvcrt
                        msvcrt.setmode(self.o_stream.fileno(), os.O_BINARY)

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

        #----------------------------------------------------------------------------
        #Layout-level output functions
        #----------------------------------------------------------------------------

        def __list_library_header__(self, library):
                self.init_library_header(library)
                return [ __str_record__(gds_records.Header, __hex_int2__(5)),
                         __str_record__(gds_records.BgnLib, __hex_date__(library.modified) + __hex_date__(library.accessed)),
                         __str_record__(gds_records.LibName, __hex_text__(library.name)),
                         __str_record__(gds_records.Units, __hex_float__(self.library.grid/self.library.unit) + __hex_float__(self.library.grid))
                 ]

        def __list_library_footer__(self):
                self.fini_library_footer()
                return __str_record__(gds_records.EndLib)

        def __list_structure_header__(self, item):
                sname = self.scramble_structure_name(item.name)
                return [__str_record__(gds_records.BgnStr, __hex_date__(item.created) + __hex_date__(item.modified)),
                        __str_record__(gds_records.StrName, __hex_text__(sname))
                ]

        #generate the footer for any structure
        def __list_structure_footer__(self, item):
                return __str_record__(gds_records.EndStr)

        #----------------------------------------------------------------------------
        #Element level output functions
        #----------------------------------------------------------------------------

        #text
        def list_label_element (self, item, additional_transform = None):
                T = item.transformation + additional_transform # make a copy because there is also the height
                coordinates =  [T.__translate__(item.coordinate)]
                T.magnification *= item.height
                L = [ __str_record__(gds_records.Text),
                      self.__str_layer__(item.layer),
                      __str_record__(gds_records.TextType, __hex_int2__(0)),
                      __str_record__(gds_records.Presentation, __hex_int2__( (item.h_alignment + 4 * item.v_alignment + 8 * item.font%4))),
                      __str_record__(gds_records.PathType, __hex_int2__(1))]
                L += __list_transformation__(T)
                L += [self.__str_coordinatelist__(coordinates),
                      __str_record__(gds_records.String, __hex_text__(item.text)),
                      __str_record__(gds_records.EndEl)]
                return L

        #references

        def list_sref_element (self, item, additional_transform = None):

                T = item.transformation + gds_translation(item.position) + additional_transform
                coordinates = gds_shape((0.0, 0.0)).transform(T)
                sname = self.scramble_structure_name(item.reference.name)
                L = [ __str_record__(gds_records.SRef),
                      __str_record__(gds_records.SName, __hex_text__(sname))]
                L += __list_transformation__(T)
                L += [self.__str_coordinatelist__(coordinates), 
                      __str_record__(gds_records.EndEl)]
                return L

        def list_aref_element (self, item, additional_transform = None):
                T = item.transformation + gds_translation(item.origin) + additional_transform
                coordinates = gds_shape([(0.0, 0.0), (item.n_o_periods[0] * item.period[0], 0.0),(0.0, item.n_o_periods[1] * item.period[1])]).transform(T)

                sname = self.scramble_structure_name(item.reference.name)
                L = [ __str_record__(gds_records.ARef),
                      __str_record__(gds_records.SName, __hex_text__(sname)),
                      __str_record__(gds_records.ColRow, __hex_int2__(item.n_o_periods[0]) + __hex_int2__(item.n_o_periods[1]))]
                L += __list_transformation__(T)
                L += [self.__str_coordinatelist__(coordinates), 
                      __str_record__(gds_records.EndEl)]
                return L

        def list_box_element (self, item, additional_transform = None):
                T = item.transformation + additional_transform
                coordinates = T(shape_rect(item.center, item.box_size)).tolist()
                L = [__str_record__(gds_records.Box),
                     self.__str_layer__(item.layer),
                     __str_record__ (gds_records.BoxType, __hex_int2__(0)), 
                     self.__str_coordinatelist__(coordinates), 
                     __str_record__(gds_records.EndEl)]
                return L

        def list_path_element (self, layer, coordinates, line_width, path_type):
                L = [__str_record__ (gds_records.Path),
                     self.__str_layer__(layer),
                     __str_record__ (gds_records.DataType, __hex_int2__(0)),
                     __str_record__ (gds_records.PathType, __hex_int2__(path_type)),
                     __str_record__ (gds_records.Width, __hex_int4__(self.__db_value__(line_width))),
                     self.__str_coordinatelist__(coordinates),
                     __str_record__(gds_records.EndEl)]
                return L

        def list_boundary_element (self, layer, coordinates):
                L = [__str_record__ (gds_records.Boundary),
                     self.__str_layer__(layer),
                     __str_record__ (gds_records.DataType, __hex_int2__(0)),
                     self.__str_coordinatelist__(coordinates),
                     __str_record__(gds_records.EndEl)]
                return L

        #----------------------------------------------------------------------------
        # unit conversion
        #----------------------------------------------------------------------------

        def __db_value__(self, value):
                #convents absolute coordinates to database units
                return round(self.__scale_value__(value) * (self.library.grids_per_unit))

        #generate coordinate strings from a list of coordinates
        def __str_coordinatelist__ (self, coordinates):
                ret_data = [ __hex_int4__(self.__db_value__(c[0])) + __hex_int4__(self.__db_value__(c[1])) for c in coordinates]
                return __str_record__(gds_records.XY, "".join(ret_data))

        def __str_layer__(self, layer):
                return __str_record__(gds_records.Layer, __hex_int2__(layer.number))		



#----------------------------------------------------------------------------
# Low-level output
#----------------------------------------------------------------------------

#Generate record
def __str_record__(record_type, hex_data=""):
        length = len(hex_data)/2 + 4
        return __hex_int2__(length) + __hex_int2__(record_type) + hex_data

def __hex_int2__ (number):
        return __hex_text__(pack(">H",number))

def __hex_int4__ (number):
        return __hex_text__(pack(">l",number))

def __hex_float__(number):
        B1 = 0
        B2 = 0
        S3 = 0
        L4 = 0
        if not number == 0:
                if number < 0:
                        B1 = 128
                        number = abs(number)
                E = int(math.ceil(math.log(number)/math.log(16)))
                inumber = long(number * 16L**(-E+14))
                B1 += E+64
                B2 = (inumber/281474976710656)%256
                if B2 == 0:
                        B2 = 16
                        B1 += 1
                S3 = (inumber%281474976710656)/4294967296L
                L4 = inumber%4294967296L
        return __hex_text__(pack(">BBHL",B1, B2, S3, L4))

def __hex_text__ (text):
        t = b2a_hex(text)
        if not len(t)%4 == 0:
                t += "00"
        return t

#Create the date strings
def __hex_date__(T = None):
        if T == None: T = time()
        t = localtime(T)
        ret_data = [__hex_int2__(t[0]%100),
                    __hex_int2__(t[1]),
                    __hex_int2__(t[2]),
                    __hex_int2__(t[3]),
                    __hex_int2__(t[4]),
                    __hex_int2__(t[5])
            ]
        return "".join(ret_data)

#Generate comment in the Key file
def __list_comment__(text):
        #not available n GDSII. Supported for compatibility with Key
        return ""

def __list_transformation__(transform):
        strans = 0
        flip = transform.flip
        rotation = transform.rotation

        if flip:
                strans += 32768
        if transform.absolute_rotation:
                strans += 2
        if transform.absolute_magnification:
                strans += 4

        if isinstance(transform.magnification, tuple):
                T = transform.magnification[0]
        else:
                T = transform.magnification

        return [__str_record__(gds_records.STrans, __hex_int2__(strans)),
                __str_record__(gds_records.Mag, __hex_float__(T)),
                __str_record__(gds_records.Angle, __hex_float__(rotation))
        ]
