from .input import basic_gds_input
from time import mktime
from binascii import b2a_hex
from struct import unpack

from . import gds_records
from .. import constants
from ..primitives import fonts
from ..geometry.transforms.no_distort import gds_no_distort_transform
from ..geometry.shape import gds_shape
from ..primitives.structure import gds_structure
from ..primitives.shape_elements import el_boundary, el_path
from ..primitives.text_elements import gds_label_element
from ..primitives.box_elements import gds_box_element
from ..primitives.ref_elements import gds_sref_element, gds_aref_element

import sys

__all__ = ["gds_input_gdsii"]


class gdsii_record(object):
    def __init__(self,rtype, length):
        self.rtype = rtype
        self.length = length

class gds_input_gdsii(basic_gds_input):
    def __init__(self, i_stream = sys.stdin, **kwargs):
        super(gds_input_gdsii, self).__init__(
            i_stream = i_stream,
            **kwargs)
        self.__current_structure__ = None

    def __parse_library__ (self):
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength
            if t == gds_records.BgnStr:
                created = self.__parse_time__()
                modified = self.__parse_time__()
                self.__parse_structure__(created, modified)
            elif t == gds_records.Header:
                self.i_stream.read(l) #header is not parsed in an import
            elif t == gds_records.BgnLib:
                self.library.modified = self.__parse_time__()
                self.library.accessed = self.__parse_time__()
            elif t == gds_records.Units:
                U = self.__parse_real8__()
                self.library.set_grid (self.__parse_real8__())
                self.library.set_unit (self.library.grid/U)
            elif t == gds_records.LibName:
                self.library.name = self.__parse_string__(l)
            elif t == gds_records.EndLib:
                break
            elif t == gds_records.RefLibs:
                print >> sys.stderr, "Warning: REFLIBS is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.Fonts:
                print >> sys.stderr, "Warning: FONTS is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.AttrTable:
                print >> sys.stderr, "Warning: ATTRTABLE is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.Generations:
                print >> sys.stderr, "Warning: GENERATIONS is not supported. This will be ignored."
                self.i_stream.read(l)
            else:
                print >> sys.stderr, "Error: Unsupported record type in File:", hex(t)
                raise SystemExit

        return self.library

    def __parse_structure__(self, created, modified):
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength
            if t == gds_records.Boundary:
                S.add_el(self.__parse_boundary_element__())
            elif t == gds_records.Path:
                S.add_el(self.__parse_path_element__())
            elif t == gds_records.SRef:
                S.add_el(self.__parse_sref_element__())
            elif t == gds_records.ARef:
                S.add_el(self.__parse_aref_element__())
            elif t == gds_records.Box:
                S.add_el(self.__parse_box_element__())
            elif t == gds_records.Text:
                S.add_el(self.__parse_label_element__())
            elif t == gds_records.StrName:
                name = self.make_structure_name(self.__parse_string__(l))
                S = gds_structure(name, [], self.library)
                S.created = created
                S.modified = modified
                S.grid = self.library.grid
                S.unit = self.library.unit
                self.__current_structure__= S
            elif t == gds_records.EndStr:
                self.i_stream.read(l)
                break
            elif t == gds_records.StrClass:
                print >> sys.stderr, "Warning: STRCLASS is not supported. This will be ignored"
                self.i_stream.read(l)
            else:
                print >> sys.stderr, "Error: Unsupported element type in Structure:", hex(t)
                raise SystemExit

        return S


    def __parse_length__(self):
        ### Solve unit problem!!!
        return self.__parse_int4__() * self.scaling / self.library.grids_per_unit

    def __parse_coordinate__(self):
        return (self.__parse_length__(), self.__parse_length__())

    def __parse_record__(self):
        try:
            datalen = int (b2a_hex(self.i_stream.read(2)), 16) - 4
            rtype = int (b2a_hex(self.i_stream.read(2)), 16)
        except:
            print >> sys.stderr, "Error: Could not read record"
            raise SystemExit
        return gdsii_record(rtype, datalen)

    def __parse_shape__(self,n_o_points):
        return gds_shape( [ self.__parse_coordinate__() for i in range(n_o_points)])
    
    def __parse_boundary_element__(self):
        # read parameters
        layer = 0
        datatype = 0
        coords = []
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength
            if t == gds_records.Layer:
                layer = self.map_layer(self.__parse_int2__())
            elif t == gds_records.DataType:
                datatype = self.__parse_int2__()
            elif t == gds_records.XY:
                nxy = l/8
                coords = self.__parse_shape__(nxy)
                if not coords.closed:
                    print >> sys.stderr, "Warning: BOUNDARY Coordinate list not closed in structure " + self.__current_structure__.name 
                coords.close()
            elif t == gds_records.EndEl:
                break
            elif t == gds_records.ElFlags:
                print >> sys.stderr, "Warning: ELFLAGS in BOUNDARY is not supported. This will be ignored"
                self.i_stream.read(l)
            elif t == gds_records.Plex:
                print >> sys.stderr, "Warning: PLEX in BOUNDARY is not supported. This will be ignored"
                self.i_stream.read(l)
            else:
                print >> sys.stderr, "Error: Unsupported type in BOUNDARY:", hex(t)
                raise SystemExit
        return el_boundary(layer, coords)

    def __parse_path_element__ (self):
        # read parameters
        layer = 0
        datatype = 0
        pathtype = constants.PATH_TYPE_NORMAL
        width = 0
        coords = []
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength
            if t == gds_records.Layer:
                layer = self.map_layer(self.__parse_int2__())
            elif t == gds_records.DataType:
                datatype = self.__parse_int2__()
            elif t == gds_records.PathType:
                pathtype = self.__parse_int2__()
            elif t == gds_records.Width:
                width = self.__parse_length__()
            elif t == gds_records.XY:
                nxy = l/8
                coords = self.__parse_shape__(nxy)
            elif t == gds_records.EndEl:
                self.i_stream.read(l)
                break
            elif t == gds_records.ElFlags:
                print >> sys.stderr, "Warning: ELFLAGS in PATH is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.Plex:
                print >> sys.stderr, "Warning: PLEX in PATH is not supported. This will be ignored."
                self.i_stream.read(l)
            else:
                print >> sys.stderr, "Error: Unsupported type in PATH:", hex(t)
                raise SystemExit
        return el_path(layer, coords, width, pathtype)

    def __parse_sref_element__(self):
        name = ""
        coord = (0.0, 0.0)
        transform = gds_no_distort_transform()
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength
            if t == gds_records.SName:
                name = self.__parse_string__(l)
            elif t == gds_records.XY:
                nxy = l/8
                if not nxy == 1:
                    print >> sys.stderr, "Error: SREF only supports a single coordinate."
                    raise SystemExit
                coord = self.__parse_coordinate__()
            elif t == gds_records.EndEl:
                break
            elif t == gds_records.ElFlags:
                print >> sys.stderr, "Warning: ELFLAGS in SREF is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.Plex:
                print >> sys.stderr, "Warning: PLEX in SREF is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.STrans:
                self.__parse_transformation__(transform)

            elif t == gds_records.Mag:
                self.__parse_magnification__(transform)
            elif t == gds_records.Angle:
                self.__parse_rotation__(transform)
            else:
                print >> sys.stderr, "Error: Unsupported type in SREF:", hex(t)
                raise SystemExit

        if name =="":
            print >> sys.stderr, "Error: SREF: name of structure is empty"
            raise SystemExit
        else:
            name = self.make_structure_name(name)

        if self.library.structure_exists(name):
            S = self.library[name]
        else:
            # add dummy structure (which will be overwritten later)
            S = gds_structure(name, [], self.library)
        V = gds_sref_element(S, coord, transform)
        return V

    def __parse_aref_element__(self):
        name = ""
        coord_zero = (0.0, 0.0)
        coord_x = (0.0, 0.0)
        coord_y = (0.0, 0.0)
        col = 1
        row = 1
        transform = gds_no_distort_transform()
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength
            if t == gds_records.ColRow:
                col = self.__parse_int2__()
                row = self.__parse_int2__()
                if col <= 0 or row <=0:
                    print >> sys.stderr, "Error: Invalid COLROW in AREF"
                    raise SystemExit
            elif t == gds_records.SName:
                name = self.__parse_string__(l)
            elif t == gds_records.XY:
                nxy = l/8
                if not nxy == 3:
                    print >> sys.stderr, "Error: AREF supports exactly three coordinates."
                    raise SystemExit
                coord_zero = self.__parse_coordinate__()
                coord_x = self.__parse_coordinate__()
                coord_y = self.__parse_coordinate__()
    ##            if not ((coord_x[1]-coord_zero[1])==0 and (coord_y[0]-coord_zero[0])==0) :
    ##                print >> sys.stderr, "Warning: AREF with rotated coordinates is not supported. Structure will not be rotated"
    ##            if (coord_x[0]-coord_zero[0])<0:
    ##                sign_x = -1
    ##            else:
    ##                sign_x = +1
    ##            if (coord_y[1]-coord_zero[1])<0:
    ##                sign_y = -1
    ##            else:
    ##                sign_y = +1
    ##
    ##            x = sign_x * distance(coord_x, coord_zero)/col
    ##            y = sign_y * distance(coord_y, coord_zero)/row
            elif t == gds_records.EndEl:
                break
            elif t == gds_records.ElFlags:
                print >> sys.stderr, "Warning: ELFLAGS in AREF is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.Plex:
                print >> sys.stderr, "Warning: PLEX in AREF is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.STrans:
                self.__parse_transformation__(transform)
            elif t == gds_records.Mag:
                self.__parse_magnification__(transform)
            elif t == gds_records.Angle:
                self.__parse_rotation__(transform)
            else:
                print >> sys.stderr, "Error: Unsupported type in AREF:", hex(t)
                raise SystemExit

        coordinates = gds_shape([coord_zero, coord_x, coord_y])
        cc= self.library.snap_shape(transform.reverse(coordinates.move_copy((-coord_zero[0], -coord_zero[1]))))
        if (not cc[1][1] == 0.0) or (not cc[2][0] == 0.0):
            print >> sys.stderr, "Error: Coordinates in AREF do not match Transformation STRANS, MAG, ANGLE in structure " + self.__current_structure__.name

        if name =="":
            print >> sys.stderr, "Error: AREF: name of structure is empty"
            raise SystemExit
        else:
            name = self.make_structure_name(name)

        if self.library.structure_exists(name):
            S = self.library[name]
        else:
            # add dummy structure (which will be overwritten later)
            S = gds_structure(name, [], self.library)
        # try to fix arefs with negative period
        czx = coord_zero[0]
        px = cc[1][0]
        py = cc[2][1]
        #if px < 0:
            #px *= -1
            #czx -= px
        czy = coord_zero[1]
        #if py < 0:
            #py *= -1
            #czy -= py
        coord_zero = (czx, czy)
        period = (px/col, py/row)
            
        origin = (czx, czy)
        #period = (distance(coord_x, coord_zero)/col, distance(coord_y, coord_zero)/row)
        V = gds_aref_element(S, origin, period, (col, row), transform)
        return V

    def __parse_box_element__(self):
        # read parameters
        layer = 0
        boxtype = 0
        coords = []
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength

            if t == gds_records.Layer:
                layer = self.map_layer(self.__parse_int2__())
            elif t == gds_records.BoxType:
                boxtype = self.__parse_int2__()
            elif t == gds_records.XY:
                nxy = l/8
                coords = self.__parse_shape__( nxy)
            elif t == gds_records.EndEl:
                break
            elif t == gds_records.ElFlags:
                print >> sys.stderr, "Warning: ELFLAGS in BOX is not supported. This will be ignored"
                self.i_stream.read(l)
            elif t == gds_records.Plex:
                print >> sys.stderr, "Warning: PLEX in BOX is not supported. This will be ignored"
                self.i_stream.read(l)
            else:
                print >> sys.stderr, "Error: Unsupported type in BOX:", hex(t)
                raise SystemExit
        c = coords[0]
        xmin = c[0]
        ymin = c[1]
        xmax = c[0]
        ymax = c[1]
        for c in coords:
            xmin = min(xmin, c[0])
            ymin = min(ymin, c[1])
            xmax = max(xmax, c[0])
            ymax = max(ymax, c[1])
        return gds_box_element(layer, (0.5*(xmin+xmax), 0.5*(ymin+ymax)), (xmax-xmin, ymax-ymin))

    def __parse_label_element__(self):
        # read parameters
        transform = gds_no_distort_transform()
        layer = 0
        texttype = 0
        font = fonts.TEXT_FONT_DEFAULT
        ver_alignment = constants.TEXT_ALIGN_TOP
        hor_alignment = constants.TEXT_ALIGN_LEFT
        text = ""
        width = 1.0
        coords = []
        height = 1.0
        while 1:
            r = self.__parse_record__()
            t = r.rtype #type
            l = r.length #datalength
            if t == gds_records.Layer:
                layer = self.map_layer(self.__parse_int2__())
            elif t == gds_records.TextType:
                datatype = self.__parse_int2__()
            elif t == gds_records.PathType:
                pathtype = self.__parse_int2__()
            elif t == gds_records.Presentation:
                presentation = self.__parse_int2__()
                hor_alignment = presentation % 4
                ver_alignment = (presentation - hor_alignment) % 16 / 4
                font = (presentation - hor_alignment - 4 * ver_alignment ) % 64 / 16
            elif t == gds_records.Width:
                width = self.__parse_length__()
            elif t == gds_records.XY:
                nxy = l/8
                if not nxy == 1:
                    print >> sys.stderr, "Error: TEXT (Label) only supports a single coordinate. "
                    raise SystemExit
                coord = self.__parse_coordinate__()
            elif t == gds_records.String:
                text = self.__parse_string__( l)
            elif t == gds_records.EndEl:
                break
            elif t == gds_records.ElFlags:
                print >> sys.stderr, "Warning: ELFLAGS in TEXT (Label) is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.Plex:
                print >> sys.stderr, "Warning: PLEX in TEXT (Label) is not supported. This will be ignored."
                self.i_stream.read(l)
            elif t == gds_records.STrans:
                self.__parse_transformation__(transform)
            elif t == gds_records.Mag:
                self.__parse_magnification__(transform)
            elif t == gds_records.Angle:
                self.__parse_rotation__(transform)
            else:
                print >> sys.stderr, "Error: Unsupported type in TEXT (label):", hex(t)
                raise SystemExit
        return gds_label_element (layer, text, coord, (hor_alignment, ver_alignment) , font, 1.0, transform)
            

    def __parse_magnification__(self, T):
        m = self.__parse_real8__()
        T.magnification = m

    def __parse_rotation__ (self, T):
        T.rotation = self.__parse_real8__()

    def __parse_transformation__(self, T):
        bits = self.__parse_int2__()
        T.v_mirror = (bits < 0)
        bits = bits % 8
        T.absolute_magnification = (bits >= 4)
        bits = bits % 4
        T.absolute_rotation = (bits >= 2)
        return 

    def __parse_int2__(self):
        try:
            return unpack(">h",self.i_stream.read(2))[0]
        except:
            print >> sys.stderr, "Error: Could not read INT2"
            raise SystemError

    def __parse_int4__(self):
        try:
            return unpack(">l",self.i_stream.read(4))[0]
        except:
            print >> sys.stderr, "Error: Could not read INT4"
            raise SystemError

    def __parse_real8__(self):
        #value = (mantissa/(2^56)) * (16^(exponent-64))
        try:
            data = unpack(">BBHL",self.i_stream.read(8))
        except:
            print >> sys.stderr, "Error: Could not read REAL8"
            raise SystemError
        if (data[0]==0 and data[1]==0 and data[2]== 0 and data[3]==0):
            return 0.0
        if data[0] > 128:
            sign = -1
        else:
            sign = 1
        exponent = (data[0]%128 - 78)
        m1 = float(data[1] * 65536 + data[2])
        m2 = float(data[3])
        return sign * (m1*(16**8) + m2) * (16**exponent)

    def __parse_string__(self, length):
        s = ""
        t = ""
        try:
            if length > 0:
                s = self.i_stream.read(length-1)
            t = self.i_stream.read(1)
        except:
            print >> sys.stderr, "Error: Could not read STRING"
            raise SystemError
        if not b2a_hex(t) == "00":
            s += t 
        return s

    def __parse_time__(self):
        year = self.__parse_int2__()
        month= self.__parse_int2__()
        day= self.__parse_int2__()
        hour= self.__parse_int2__()
        minute= self.__parse_int2__()
        second= self.__parse_int2__()
        if year >= 100 and year < 1900:
            year += 1900
	elif year < 0:
	    year = 2008		# quick & dirty hack
        return mktime((year, month, day, hour, minute, second, 0, 0, -1))
