
import os
import pytest
import re
from fontTools.ttLib import TTFont
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
from fontTools.fontBuilder import FontBuilder
from fontTools.ttLib.tables.TupleVariation import TupleVariation
from fontTools.misc.psCharStrings import T2CharString


def getTestData(fileName, mode="r"):
    path = os.path.join(os.path.dirname(__file__), "data", fileName)
    with open(path, mode) as f:
        return f.read()


def strip_VariableItems(string):
    # ttlib changes with the fontTools version
    string = re.sub(' ttLibVersion=".*"', '', string)
    # head table checksum and creation and mod date changes with each save.
    string = re.sub('<checkSumAdjustment value="[^"]+"/>', '', string)
    string = re.sub('<modified value="[^"]+"/>', '', string)
    string = re.sub('<created value="[^"]+"/>', '', string)
    return string


def drawTestGlyph(pen):
    pen.moveTo((100, 100))
    pen.lineTo((100, 1000))
    pen.qCurveTo((200, 900), (400, 900), (500, 1000))
    pen.lineTo((500, 100))
    pen.closePath()


def _setupFontBuilder(isTTF, unitsPerEm=1024):
    fb = FontBuilder(unitsPerEm, isTTF=isTTF)
    fb.setupGlyphOrder([".notdef", ".null", "A", "a"])
    fb.setupCharacterMap({65: "A", 97: "a"})

    advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600}

    familyName = "HelloTestFont"
    styleName = "TotallyNormal"
    nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
                       styleName=dict(en="TotallyNormal", nl="TotaalNormaal"))
    nameStrings['psName'] = familyName + "-" + styleName

    return fb, advanceWidths, nameStrings


def _setupFontBuilderFvar(fb):
    assert 'name' in fb.font, 'Must run setupNameTable() first.'

    axes = [
        ('TEST', 0, 0, 100, "Test Axis"),
    ]
    instances = [
        dict(location=dict(TEST=0), stylename="TotallyNormal"),
        dict(location=dict(TEST=100), stylename="TotallyTested"),
    ]
    fb.setupFvar(axes, instances)

    return fb


def _setupFontBuilderCFF2(fb):
    assert 'fvar' in fb.font, 'Must run _setupFontBuilderFvar() first.'

    pen = T2CharStringPen(None, None, CFF2=True)
    drawTestGlyph(pen)
    charString = pen.getCharString()

    program = [
        200, 200, -200, -200, 2, "blend", "rmoveto",
        400, 400, 1, "blend", "hlineto",
        400, 400, 1, "blend", "vlineto",
        -400, -400, 1, "blend", "hlineto"
    ]
    charStringVariable = T2CharString(program=program)

    charStrings = {".notdef": charString, "A": charString,
                   "a": charStringVariable, ".null": charString}
    fb.setupCFF2(charStrings, regions=[{"TEST": (0, 1, 1)}])

    return fb


def _verifyOutput(outPath, tables=None):
    f = TTFont(outPath)
    f.saveXML(outPath + ".ttx", tables=tables)
    with open(outPath + ".ttx") as f:
        testData = strip_VariableItems(f.read())
    refData = strip_VariableItems(getTestData(os.path.basename(outPath) + ".ttx"))
    assert refData == testData


def test_build_ttf(tmpdir):
    outPath = os.path.join(str(tmpdir), "test.ttf")

    fb, advanceWidths, nameStrings = _setupFontBuilder(True)

    pen = TTGlyphPen(None)
    drawTestGlyph(pen)
    glyph = pen.glyph()
    glyphs = {".notdef": glyph, "A": glyph, "a": glyph, ".null": glyph}
    fb.setupGlyf(glyphs)
    metrics = {}
    glyphTable = fb.font["glyf"]
    for gn, advanceWidth in advanceWidths.items():
        metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
    fb.setupHorizontalMetrics(metrics)

    fb.setupHorizontalHeader(ascent=824, descent=200)
    fb.setupNameTable(nameStrings)
    fb.setupOS2()
    fb.addOpenTypeFeatures("feature salt { sub A by a; } salt;")
    fb.setupPost()
    fb.setupDummyDSIG()

    fb.save(outPath)

    _verifyOutput(outPath)


def test_build_otf(tmpdir):
    outPath = os.path.join(str(tmpdir), "test.otf")

    fb, advanceWidths, nameStrings = _setupFontBuilder(False)

    pen = T2CharStringPen(600, None)
    drawTestGlyph(pen)
    charString = pen.getCharString()
    charStrings = {".notdef": charString, "A": charString, "a": charString, ".null": charString}
    fb.setupCFF(nameStrings['psName'], {"FullName": nameStrings['psName']}, charStrings, {})

    lsb = {gn: cs.calcBounds(None)[0] for gn, cs in charStrings.items()}
    metrics = {}
    for gn, advanceWidth in advanceWidths.items():
        metrics[gn] = (advanceWidth, lsb[gn])
    fb.setupHorizontalMetrics(metrics)

    fb.setupHorizontalHeader(ascent=824, descent=200)
    fb.setupNameTable(nameStrings)
    fb.setupOS2()
    fb.addOpenTypeFeatures("feature kern { pos A a -50; } kern;")
    fb.setupPost()
    fb.setupDummyDSIG()

    fb.save(outPath)

    _verifyOutput(outPath)


def test_build_var(tmpdir):
    outPath = os.path.join(str(tmpdir), "test_var.ttf")

    fb, advanceWidths, nameStrings = _setupFontBuilder(True)

    pen = TTGlyphPen(None)
    pen.moveTo((100, 0))
    pen.lineTo((100, 400))
    pen.lineTo((500, 400))
    pen.lineTo((500, 000))
    pen.closePath()
    glyph1 = pen.glyph()

    pen = TTGlyphPen(None)
    pen.moveTo((50, 0))
    pen.lineTo((50, 200))
    pen.lineTo((250, 200))
    pen.lineTo((250, 0))
    pen.closePath()
    glyph2 = pen.glyph()

    pen = TTGlyphPen(None)
    emptyGlyph = pen.glyph()

    glyphs = {".notdef": emptyGlyph, "A": glyph1, "a": glyph2, ".null": emptyGlyph}
    fb.setupGlyf(glyphs)
    metrics = {}
    glyphTable = fb.font["glyf"]
    for gn, advanceWidth in advanceWidths.items():
        metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
    fb.setupHorizontalMetrics(metrics)

    fb.setupHorizontalHeader(ascent=824, descent=200)
    fb.setupNameTable(nameStrings)

    axes = [
        ('LEFT', 0, 0, 100, "Left"),
        ('RGHT', 0, 0, 100, "Right"),
        ('UPPP', 0, 0, 100, "Up"),
        ('DOWN', 0, 0, 100, "Down"),
    ]
    instances = [
        dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"),
        dict(location=dict(LEFT=0, RGHT=100, UPPP=100, DOWN=0), stylename="Right Up"),
    ]
    fb.setupFvar(axes, instances)
    variations = {}
    # Four (x, y) pairs and four phantom points:
    leftDeltas = [(-200, 0), (-200, 0), (0, 0), (0, 0), None, None, None, None]
    rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None]
    upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None]
    downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None]
    variations['a'] = [
        TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas),
        TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas),
        TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas),
        TupleVariation(dict(DOWN=(0, 1, 1)), downDeltas),
    ]
    fb.setupGvar(variations)

    fb.addFeatureVariations(
        [
            (
                [
                    {"LEFT": (0.8, 1), "DOWN": (0.8, 1)},
                    {"RGHT": (0.8, 1), "UPPP": (0.8, 1)},
                  ],
                {"A": "a"}
            )
        ],
        featureTag="rclt",
    )

    statAxes = []
    for tag, minVal, defaultVal, maxVal, name in axes:
        values = [dict(name="Neutral", value=defaultVal, flags=0x2),
                  dict(name=name, value=maxVal)]
        statAxes.append(dict(tag=tag, name=name, values=values))
    fb.setupStat(statAxes)

    fb.setupOS2()
    fb.setupPost()
    fb.setupDummyDSIG()

    fb.save(outPath)

    _verifyOutput(outPath)


def test_build_cff2(tmpdir):
    outPath = os.path.join(str(tmpdir), "test_var.otf")

    fb, advanceWidths, nameStrings = _setupFontBuilder(False, 1000)
    fb.setupNameTable(nameStrings)
    fb = _setupFontBuilderFvar(fb)
    fb = _setupFontBuilderCFF2(fb)

    metrics = {gn: (advanceWidth, 0) for gn, advanceWidth in advanceWidths.items()}
    fb.setupHorizontalMetrics(metrics)

    fb.setupHorizontalHeader(ascent=824, descent=200)
    fb.setupOS2(sTypoAscender=825, sTypoDescender=200, usWinAscent=824, usWinDescent=200)
    fb.setupPost()

    fb.save(outPath)

    _verifyOutput(outPath)


def test_build_cff_to_cff2(tmpdir):
    fb, _, _ = _setupFontBuilder(False, 1000)

    pen = T2CharStringPen(600, None)
    drawTestGlyph(pen)
    charString = pen.getCharString()
    charStrings = {".notdef": charString, "A": charString, "a": charString, ".null": charString}
    fb.setupCFF("TestFont", {}, charStrings, {})

    from fontTools.varLib.cff import convertCFFtoCFF2
    convertCFFtoCFF2(fb.font)


def test_setupNameTable_no_mac():
    fb, _, nameStrings = _setupFontBuilder(True)
    fb.setupNameTable(nameStrings, mac=False)

    assert all(n for n in fb.font["name"].names if n.platformID == 3)
    assert not any(n for n in fb.font["name"].names if n.platformID == 1)


def test_setupNameTable_no_windows():
    fb, _, nameStrings = _setupFontBuilder(True)
    fb.setupNameTable(nameStrings, windows=False)

    assert all(n for n in fb.font["name"].names if n.platformID == 1)
    assert not any(n for n in fb.font["name"].names if n.platformID == 3)


@pytest.mark.parametrize('is_ttf, keep_glyph_names, make_cff2, post_format', [
    (True, True, False, 2),    # TTF with post table format 2.0
    (True, False, False, 3),   # TTF with post table format 3.0
    (False, True, False, 3),   # CFF with post table format 3.0
    (False, False, False, 3),  # CFF with post table format 3.0
    (False, True, True, 2),    # CFF2 with post table format 2.0
    (False, False, True, 3),   # CFF2 with post table format 3.0
])
def test_setupPost(is_ttf, keep_glyph_names, make_cff2, post_format):
    fb, _, nameStrings = _setupFontBuilder(is_ttf)

    if make_cff2:
        fb.setupNameTable(nameStrings)
        fb = _setupFontBuilderCFF2(_setupFontBuilderFvar(fb))

    if keep_glyph_names:
        fb.setupPost()
    else:
        fb.setupPost(keepGlyphNames=keep_glyph_names)

    assert fb.isTTF is is_ttf
    assert ('CFF2' in fb.font) is make_cff2
    assert fb.font["post"].formatType == post_format


def test_unicodeVariationSequences(tmpdir):
    familyName = "UVSTestFont"
    styleName = "Regular"
    nameStrings = dict(familyName=familyName, styleName=styleName)
    nameStrings['psName'] = familyName + "-" + styleName
    glyphOrder = [".notdef", "space", "zero", "zero.slash"]
    cmap = {ord(" "): "space", ord("0"): "zero"}
    uvs = [
        (0x0030, 0xFE00, "zero.slash"),
        (0x0030, 0xFE01, None),  # not an official sequence, just testing
    ]
    metrics = {gn: (600, 0) for gn in glyphOrder}
    pen = TTGlyphPen(None)
    glyph = pen.glyph()  # empty placeholder
    glyphs = {gn: glyph for gn in glyphOrder}

    fb = FontBuilder(1024, isTTF=True)
    fb.setupGlyphOrder(glyphOrder)
    fb.setupCharacterMap(cmap, uvs)
    fb.setupGlyf(glyphs)
    fb.setupHorizontalMetrics(metrics)
    fb.setupHorizontalHeader(ascent=824, descent=200)
    fb.setupNameTable(nameStrings)
    fb.setupOS2()
    fb.setupPost()

    outPath = os.path.join(str(tmpdir), "test_uvs.ttf")
    fb.save(outPath)
    _verifyOutput(outPath, tables=["cmap"])

    uvs = [
        (0x0030, 0xFE00, "zero.slash"),
        (0x0030, 0xFE01, "zero"),  # should result in the exact same subtable data, due to cmap[0x0030] == "zero"
    ]
    fb.setupCharacterMap(cmap, uvs)
    fb.save(outPath)
    _verifyOutput(outPath, tables=["cmap"])
