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())
