from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
from . import DefaultTable
from . import grUtils
import struct

Feat_hdr_format = """
    >
    version:    16.16F
"""


class table_F__e_a_t(DefaultTable.DefaultTable):
    """Feature table

    The ``Feat`` table is used exclusively by the Graphite shaping engine
    to store features and possible settings specified in GDL. Graphite features
    determine what rules are applied to transform a glyph stream.

    Not to be confused with ``feat``, or the OpenType Layout tables
    ``GSUB``/``GPOS``.

    See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
    """

    def __init__(self, tag=None):
        DefaultTable.DefaultTable.__init__(self, tag)
        self.features = {}

    def decompile(self, data, ttFont):
        (_, data) = sstruct.unpack2(Feat_hdr_format, data, self)
        self.version = float(floatToFixedToStr(self.version, precisionBits=16))
        (numFeats,) = struct.unpack(">H", data[:2])
        data = data[8:]
        allfeats = []
        maxsetting = 0
        for i in range(numFeats):
            if self.version >= 2.0:
                (fid, nums, _, offset, flags, lid) = struct.unpack(
                    ">LHHLHH", data[16 * i : 16 * (i + 1)]
                )
                offset = int((offset - 12 - 16 * numFeats) / 4)
            else:
                (fid, nums, offset, flags, lid) = struct.unpack(
                    ">HHLHH", data[12 * i : 12 * (i + 1)]
                )
                offset = int((offset - 12 - 12 * numFeats) / 4)
            allfeats.append((fid, nums, offset, flags, lid))
            maxsetting = max(maxsetting, offset + nums)
        data = data[16 * numFeats :]
        allsettings = []
        for i in range(maxsetting):
            if len(data) >= 4 * (i + 1):
                (val, lid) = struct.unpack(">HH", data[4 * i : 4 * (i + 1)])
                allsettings.append((val, lid))
        for i, f in enumerate(allfeats):
            (fid, nums, offset, flags, lid) = f
            fobj = Feature()
            fobj.flags = flags
            fobj.label = lid
            self.features[grUtils.num2tag(fid)] = fobj
            fobj.settings = {}
            fobj.default = None
            fobj.index = i
            for i in range(offset, offset + nums):
                if i >= len(allsettings):
                    continue
                (vid, vlid) = allsettings[i]
                fobj.settings[vid] = vlid
                if fobj.default is None:
                    fobj.default = vid

    def compile(self, ttFont):
        fdat = b""
        vdat = b""
        offset = 0
        for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
            fnum = grUtils.tag2num(f)
            if self.version >= 2.0:
                fdat += struct.pack(
                    ">LHHLHH",
                    grUtils.tag2num(f),
                    len(v.settings),
                    0,
                    offset * 4 + 12 + 16 * len(self.features),
                    v.flags,
                    v.label,
                )
            elif fnum > 65535:  # self healing for alphabetic ids
                self.version = 2.0
                return self.compile(ttFont)
            else:
                fdat += struct.pack(
                    ">HHLHH",
                    grUtils.tag2num(f),
                    len(v.settings),
                    offset * 4 + 12 + 12 * len(self.features),
                    v.flags,
                    v.label,
                )
            for s, l in sorted(
                v.settings.items(), key=lambda x: (-1, x[1]) if x[0] == v.default else x
            ):
                vdat += struct.pack(">HH", s, l)
            offset += len(v.settings)
        hdr = sstruct.pack(Feat_hdr_format, self)
        return hdr + struct.pack(">HHL", len(self.features), 0, 0) + fdat + vdat

    def toXML(self, writer, ttFont):
        writer.simpletag("version", version=self.version)
        writer.newline()
        nameTable = ttFont["name"] if "name" in ttFont else None
        for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
            name = nameTable.getDebugName(v.label) if nameTable else None
            if name is not None:
                writer.newline()
                writer.comment(name)
                writer.newline()
            writer.begintag(
                "feature",
                fid=f,
                label=v.label,
                flags=v.flags,
                default=(v.default if v.default else 0),
            )
            writer.newline()
            for s, l in sorted(v.settings.items()):
                writer.simpletag("setting", value=s, label=l)
                name = nameTable.getDebugName(l) if nameTable else None
                if name is not None:
                    writer.write("  ")
                    writer.comment(name)
                writer.newline()
            writer.endtag("feature")
            writer.newline()

    def fromXML(self, name, attrs, content, ttFont):
        if name == "version":
            self.version = float(safeEval(attrs["version"]))
        elif name == "feature":
            fid = attrs["fid"]
            fobj = Feature()
            fobj.flags = int(safeEval(attrs["flags"]))
            fobj.label = int(safeEval(attrs["label"]))
            fobj.default = int(safeEval(attrs.get("default", "0")))
            fobj.index = len(self.features)
            self.features[fid] = fobj
            fobj.settings = {}
            for element in content:
                if not isinstance(element, tuple):
                    continue
                tag, a, c = element
                if tag == "setting":
                    fobj.settings[int(safeEval(a["value"]))] = int(safeEval(a["label"]))


class Feature(object):
    pass
