from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
# from itertools import *
from functools import partial
from . import DefaultTable
from . import grUtils
import struct


Glat_format_0 = """
    >        # big endian
    version: 16.16F
"""

Glat_format_3 = """
    >
    version: 16.16F
    compression:L    # compression scheme or reserved 
"""

Glat_format_1_entry = """
    >
    attNum:     B    # Attribute number of first attribute
    num:        B    # Number of attributes in this run
"""
Glat_format_23_entry = """
    >
    attNum:     H    # Attribute number of first attribute
    num:        H    # Number of attributes in this run
"""

Glat_format_3_octabox_metrics = """
    >
    subboxBitmap:   H    # Which subboxes exist on 4x4 grid
    diagNegMin:     B    # Defines minimum negatively-sloped diagonal (si)
    diagNegMax:     B    # Defines maximum negatively-sloped diagonal (sa)
    diagPosMin:     B    # Defines minimum positively-sloped diagonal (di)
    diagPosMax:     B    # Defines maximum positively-sloped diagonal (da)
"""

Glat_format_3_subbox_entry = """
    >
    left:           B    # xi
    right:          B    # xa
    bottom:         B    # yi
    top:            B    # ya
    diagNegMin:     B    # Defines minimum negatively-sloped diagonal (si)
    diagNegMax:     B    # Defines maximum negatively-sloped diagonal (sa)
    diagPosMin:     B    # Defines minimum positively-sloped diagonal (di)
    diagPosMax:     B    # Defines maximum positively-sloped diagonal (da)
"""

class _Object() :
    pass

class _Dict(dict) :
    pass

class table_G__l_a_t(DefaultTable.DefaultTable):
    '''
    Support Graphite Glat tables
    '''

    def __init__(self, tag=None):
        DefaultTable.DefaultTable.__init__(self, tag)
        self.scheme = 0

    def decompile(self, data, ttFont):
        sstruct.unpack2(Glat_format_0, data, self)
        self.version = float(floatToFixedToStr(self.version, precisionBits=16))
        if self.version <= 1.9:
            decoder = partial(self.decompileAttributes12,fmt=Glat_format_1_entry)
        elif self.version <= 2.9:   
            decoder = partial(self.decompileAttributes12,fmt=Glat_format_23_entry)
        elif self.version >= 3.0:
            (data, self.scheme) = grUtils.decompress(data)
            sstruct.unpack2(Glat_format_3, data, self)
            self.hasOctaboxes = (self.compression & 1) == 1
            decoder = self.decompileAttributes3
        
        gloc = ttFont['Gloc']
        self.attributes = {}
        count = 0
        for s,e in zip(gloc,gloc[1:]):
            self.attributes[ttFont.getGlyphName(count)] = decoder(data[s:e])
            count += 1
    
    def decompileAttributes12(self, data, fmt):
        attributes = _Dict()
        while len(data) > 3:
            e, data = sstruct.unpack2(fmt, data, _Object())
            keys = range(e.attNum, e.attNum+e.num)
            if len(data) >= 2 * e.num :
                vals = struct.unpack_from(('>%dh' % e.num), data)
                attributes.update(zip(keys,vals))
                data = data[2*e.num:]
        return attributes

    def decompileAttributes3(self, data):
        if self.hasOctaboxes:
            o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
            numsub = bin(o.subboxBitmap).count("1")
            o.subboxes = []
            for b in range(numsub):
                if len(data) >= 8 :
                    subbox, data = sstruct.unpack2(Glat_format_3_subbox_entry,
                                                    data, _Object())
                    o.subboxes.append(subbox)
        attrs = self.decompileAttributes12(data, Glat_format_23_entry)
        if self.hasOctaboxes:
            attrs.octabox = o
        return attrs

    def compile(self, ttFont):
        data = sstruct.pack(Glat_format_0, self)
        if self.version <= 1.9:
            encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
        elif self.version <= 2.9:
            encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
        elif self.version >= 3.0:
            self.compression = (self.scheme << 27) + (1 if self.hasOctaboxes else 0)
            data = sstruct.pack(Glat_format_3, self)
            encoder = self.compileAttributes3

        glocs = []
        for n in range(len(self.attributes)):
            glocs.append(len(data))
            data += encoder(self.attributes[ttFont.getGlyphName(n)])
        glocs.append(len(data))
        ttFont['Gloc'].set(glocs)

        if self.version >= 3.0:
            data = grUtils.compress(self.scheme, data)
        return data

    def compileAttributes12(self, attrs, fmt):
        data = b""
        for e in grUtils.entries(attrs):
            data += sstruct.pack(fmt, {'attNum' : e[0], 'num' : e[1]}) + \
                    struct.pack(('>%dh' % len(e[2])), *e[2])
        return data
    
    def compileAttributes3(self, attrs):
        if self.hasOctaboxes:
            o = attrs.octabox
            data = sstruct.pack(Glat_format_3_octabox_metrics, o)
            numsub = bin(o.subboxBitmap).count("1")
            for b in range(numsub) :
                data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
        else:
            data = ""
        return data + self.compileAttributes12(attrs, Glat_format_23_entry)

    def toXML(self, writer, ttFont):
        writer.simpletag('version', version=self.version, compressionScheme=self.scheme)
        writer.newline()
        for n, a in sorted(self.attributes.items(), key=lambda x:ttFont.getGlyphID(x[0])):
            writer.begintag('glyph', name=n)
            writer.newline()
            if hasattr(a, 'octabox'):
                o = a.octabox
                formatstring, names, fixes = sstruct.getformat(Glat_format_3_octabox_metrics)
                vals = {}
                for k in names:
                    if k == 'subboxBitmap': continue
                    vals[k] = "{:.3f}%".format(getattr(o, k) * 100. / 255)
                vals['bitmap'] = "{:0X}".format(o.subboxBitmap)
                writer.begintag('octaboxes', **vals)
                writer.newline()
                formatstring, names, fixes = sstruct.getformat(Glat_format_3_subbox_entry)
                for s in o.subboxes:
                    vals = {}
                    for k in names:
                        vals[k] = "{:.3f}%".format(getattr(s, k) * 100. / 255)
                    writer.simpletag('octabox', **vals)
                    writer.newline()
                writer.endtag('octaboxes')
                writer.newline()
            for k, v in sorted(a.items()):
                writer.simpletag('attribute', index=k, value=v)
                writer.newline()
            writer.endtag('glyph')
            writer.newline()

    def fromXML(self, name, attrs, content, ttFont):
        if name == 'version' :
            self.version = float(safeEval(attrs['version']))
            self.scheme = int(safeEval(attrs['compressionScheme']))
        if name != 'glyph' : return
        if not hasattr(self, 'attributes'):
            self.attributes = {}
        gname = attrs['name']
        attributes = _Dict()
        for element in content:
            if not isinstance(element, tuple): continue
            tag, attrs, subcontent = element
            if tag == 'attribute' :
                k = int(safeEval(attrs['index']))
                v = int(safeEval(attrs['value']))
                attributes[k]=v
            elif tag == 'octaboxes':
                self.hasOctaboxes = True
                o = _Object()
                o.subboxBitmap = int(attrs['bitmap'], 16)
                o.subboxes = []
                del attrs['bitmap']
                for k, v in attrs.items():
                    setattr(o, k, int(float(v[:-1]) * 255. / 100. + 0.5))
                for element in subcontent:
                    if not isinstance(element, tuple): continue
                    (tag, attrs, subcontent) = element
                    so = _Object()
                    for k, v in attrs.items():
                        setattr(so, k, int(float(v[:-1]) * 255. / 100. + 0.5))
                    o.subboxes.append(so)
                attributes.octabox = o
        self.attributes[gname] = attributes
