from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTLibError
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e, NameRecord
from io import BytesIO
import unittest


FVAR_DATA = deHexStr(
    "00 01 00 00 00 10 00 02 00 02 00 14 00 02 00 0C "
    "77 67 68 74 00 64 00 00 01 90 00 00 03 84 00 00 00 00 01 01 "
    "77 64 74 68 00 32 00 00 00 64 00 00 00 c8 00 00 00 00 01 02 "
    "01 03 00 00 01 2c 00 00 00 64 00 00 "
    "01 04 00 00 01 2c 00 00 00 4b 00 00"
)

FVAR_AXIS_DATA = deHexStr("6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59")

FVAR_INSTANCE_DATA_WITHOUT_PSNAME = deHexStr("01 59 00 00 00 00 b3 33 00 00 80 00")

FVAR_INSTANCE_DATA_WITH_PSNAME = FVAR_INSTANCE_DATA_WITHOUT_PSNAME + deHexStr("02 34")


def xml_lines(writer):
    content = writer.file.getvalue().decode("utf-8")
    return [line.strip() for line in content.splitlines()][1:]


def AddName(font, name):
    nameTable = font.get("name")
    if nameTable is None:
        nameTable = font["name"] = table__n_a_m_e()
        nameTable.names = []
    namerec = NameRecord()
    namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
    namerec.string = name.encode("mac_roman")
    namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
    nameTable.names.append(namerec)
    return namerec


def MakeFont():
    axes = [("wght", "Weight", 100, 400, 900), ("wdth", "Width", 50, 100, 200)]
    instances = [("Light", 300, 100), ("Light Condensed", 300, 75)]
    fvarTable = table__f_v_a_r()
    font = {"fvar": fvarTable}
    for tag, name, minValue, defaultValue, maxValue in axes:
        axis = Axis()
        axis.axisTag = tag
        axis.defaultValue = defaultValue
        axis.minValue, axis.maxValue = minValue, maxValue
        axis.axisNameID = AddName(font, name).nameID
        fvarTable.axes.append(axis)
    for name, weight, width in instances:
        inst = NamedInstance()
        inst.subfamilyNameID = AddName(font, name).nameID
        inst.coordinates = {"wght": weight, "wdth": width}
        fvarTable.instances.append(inst)
    return font


class FontVariationTableTest(unittest.TestCase):
    def test_compile(self):
        font = MakeFont()
        h = font["fvar"].compile(font)
        self.assertEqual(FVAR_DATA, font["fvar"].compile(font))

    def test_decompile(self):
        fvar = table__f_v_a_r()
        fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar})
        self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes])
        self.assertEqual([259, 260], [i.subfamilyNameID for i in fvar.instances])

    def test_toXML(self):
        font = MakeFont()
        writer = XMLWriter(BytesIO())
        font["fvar"].toXML(writer, font)
        xml = writer.file.getvalue().decode("utf-8")
        self.assertEqual(2, xml.count("<Axis>"))
        self.assertTrue("<AxisTag>wght</AxisTag>" in xml)
        self.assertTrue("<AxisTag>wdth</AxisTag>" in xml)
        self.assertEqual(2, xml.count("<NamedInstance "))
        self.assertTrue("<!-- Light -->" in xml)
        self.assertTrue("<!-- Light Condensed -->" in xml)

    def test_fromXML(self):
        fvar = table__f_v_a_r()
        for name, attrs, content in parseXML(
            "<Axis>"
            "    <AxisTag>opsz</AxisTag>"
            "</Axis>"
            "<Axis>"
            "    <AxisTag>slnt</AxisTag>"
            "    <Flags>0x123</Flags>"
            "</Axis>"
            '<NamedInstance subfamilyNameID="765"/>'
            '<NamedInstance subfamilyNameID="234"/>'
        ):
            fvar.fromXML(name, attrs, content, ttFont=None)
        self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes])
        self.assertEqual([0, 0x123], [a.flags for a in fvar.axes])
        self.assertEqual([765, 234], [i.subfamilyNameID for i in fvar.instances])


class AxisTest(unittest.TestCase):
    def test_compile(self):
        axis = Axis()
        axis.axisTag, axis.axisNameID = ("opsz", 345)
        axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5)
        self.assertEqual(FVAR_AXIS_DATA, axis.compile())

    def test_decompile(self):
        axis = Axis()
        axis.decompile(FVAR_AXIS_DATA)
        self.assertEqual("opsz", axis.axisTag)
        self.assertEqual(345, axis.axisNameID)
        self.assertEqual(-0.5, axis.minValue)
        self.assertAlmostEqual(1.3000031, axis.defaultValue)
        self.assertEqual(1.5, axis.maxValue)

    def test_toXML(self):
        font = MakeFont()
        axis = Axis()
        axis.decompile(FVAR_AXIS_DATA)
        AddName(font, "Optical Size").nameID = 256
        axis.axisNameID = 256
        axis.flags = 0xABC
        writer = XMLWriter(BytesIO())
        axis.toXML(writer, font)
        self.assertEqual(
            [
                "",
                "<!-- Optical Size -->",
                "<Axis>",
                "<AxisTag>opsz</AxisTag>",
                "<Flags>0xABC</Flags>",
                "<MinValue>-0.5</MinValue>",
                "<DefaultValue>1.3</DefaultValue>",
                "<MaxValue>1.5</MaxValue>",
                "<AxisNameID>256</AxisNameID>",
                "</Axis>",
            ],
            xml_lines(writer),
        )

    def test_fromXML(self):
        axis = Axis()
        for name, attrs, content in parseXML(
            "<Axis>"
            "    <AxisTag>wght</AxisTag>"
            "    <Flags>0x123ABC</Flags>"
            "    <MinValue>100</MinValue>"
            "    <DefaultValue>400</DefaultValue>"
            "    <MaxValue>900</MaxValue>"
            "    <AxisNameID>256</AxisNameID>"
            "</Axis>"
        ):
            axis.fromXML(name, attrs, content, ttFont=None)
        self.assertEqual("wght", axis.axisTag)
        self.assertEqual(0x123ABC, axis.flags)
        self.assertEqual(100, axis.minValue)
        self.assertEqual(400, axis.defaultValue)
        self.assertEqual(900, axis.maxValue)
        self.assertEqual(256, axis.axisNameID)


class NamedInstanceTest(unittest.TestCase):
    def assertDictAlmostEqual(self, dict1, dict2):
        self.assertEqual(set(dict1.keys()), set(dict2.keys()))
        for key in dict1:
            self.assertAlmostEqual(dict1[key], dict2[key])

    def test_compile_withPostScriptName(self):
        inst = NamedInstance()
        inst.subfamilyNameID = 345
        inst.postscriptNameID = 564
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        self.assertEqual(
            FVAR_INSTANCE_DATA_WITH_PSNAME, inst.compile(["wght", "wdth"], True)
        )

    def test_compile_withoutPostScriptName(self):
        inst = NamedInstance()
        inst.subfamilyNameID = 345
        inst.postscriptNameID = 564
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        self.assertEqual(
            FVAR_INSTANCE_DATA_WITHOUT_PSNAME, inst.compile(["wght", "wdth"], False)
        )

    def test_decompile_withPostScriptName(self):
        inst = NamedInstance()
        inst.decompile(FVAR_INSTANCE_DATA_WITH_PSNAME, ["wght", "wdth"])
        self.assertEqual(564, inst.postscriptNameID)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)

    def test_decompile_withoutPostScriptName(self):
        inst = NamedInstance()
        inst.decompile(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, ["wght", "wdth"])
        self.assertEqual(0xFFFF, inst.postscriptNameID)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)

    def test_toXML_withPostScriptName(self):
        font = MakeFont()
        inst = NamedInstance()
        inst.flags = 0xE9
        inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
        inst.postscriptNameID = AddName(font, "Test-LightCondensed").nameID
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        writer = XMLWriter(BytesIO())
        inst.toXML(writer, font)
        self.assertEqual(
            [
                "",
                "<!-- Light Condensed -->",
                "<!-- PostScript: Test-LightCondensed -->",
                '<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">'
                % (inst.postscriptNameID, inst.subfamilyNameID),
                '<coord axis="wght" value="0.7"/>',
                '<coord axis="wdth" value="0.5"/>',
                "</NamedInstance>",
            ],
            xml_lines(writer),
        )

    def test_toXML_withoutPostScriptName(self):
        font = MakeFont()
        inst = NamedInstance()
        inst.flags = 0xABC
        inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
        inst.coordinates = {"wght": 0.7, "wdth": 0.5}
        writer = XMLWriter(BytesIO())
        inst.toXML(writer, font)
        self.assertEqual(
            [
                "",
                "<!-- Light Condensed -->",
                '<NamedInstance flags="0xABC" subfamilyNameID="%s">'
                % inst.subfamilyNameID,
                '<coord axis="wght" value="0.7"/>',
                '<coord axis="wdth" value="0.5"/>',
                "</NamedInstance>",
            ],
            xml_lines(writer),
        )

    def test_fromXML_withPostScriptName(self):
        inst = NamedInstance()
        for name, attrs, content in parseXML(
            '<NamedInstance flags="0x0" postscriptNameID="257" subfamilyNameID="345">'
            '    <coord axis="wght" value="0.7"/>'
            '    <coord axis="wdth" value="0.5"/>'
            "</NamedInstance>"
        ):
            inst.fromXML(name, attrs, content, ttFont=MakeFont())
        self.assertEqual(257, inst.postscriptNameID)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)

    def test_fromXML_withoutPostScriptName(self):
        inst = NamedInstance()
        for name, attrs, content in parseXML(
            '<NamedInstance flags="0x123ABC" subfamilyNameID="345">'
            '    <coord axis="wght" value="0.7"/>'
            '    <coord axis="wdth" value="0.5"/>'
            "</NamedInstance>"
        ):
            inst.fromXML(name, attrs, content, ttFont=MakeFont())
        self.assertEqual(0x123ABC, inst.flags)
        self.assertEqual(345, inst.subfamilyNameID)
        self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)


if __name__ == "__main__":
    import sys

    sys.exit(unittest.main())
