import io
import os
import re
from fontTools import ttLib
from fontTools.fontBuilder import FontBuilder
import unittest
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable, table__c_m_a_p

CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
DATA_DIR = os.path.join(CURR_DIR, "data")
CMAP_FORMAT_14_TTX = os.path.join(DATA_DIR, "_c_m_a_p_format_14.ttx")
CMAP_FORMAT_14_BW_COMPAT_TTX = os.path.join(
    DATA_DIR, "_c_m_a_p_format_14_bw_compat.ttx"
)


def strip_VariableItems(string):
    # ttlib changes with the fontTools version
    string = re.sub(' ttLibVersion=".*"', "", string)
    return string


class CmapSubtableTest(unittest.TestCase):
    def makeSubtable(self, cmapFormat, platformID, platEncID, langID):
        subtable = CmapSubtable.newSubtable(cmapFormat)
        subtable.platformID, subtable.platEncID, subtable.language = (
            platformID,
            platEncID,
            langID,
        )
        return subtable

    def test_toUnicode_utf16be(self):
        subtable = self.makeSubtable(4, 0, 2, 7)
        self.assertEqual("utf_16_be", subtable.getEncoding())
        self.assertEqual(True, subtable.isUnicode())

    def test_toUnicode_macroman(self):
        subtable = self.makeSubtable(4, 1, 0, 7)  # MacRoman
        self.assertEqual("mac_roman", subtable.getEncoding())
        self.assertEqual(False, subtable.isUnicode())

    def test_toUnicode_macromanian(self):
        subtable = self.makeSubtable(4, 1, 0, 37)  # Mac Romanian
        self.assertNotEqual(None, subtable.getEncoding())
        self.assertEqual(False, subtable.isUnicode())

    def test_extended_mac_encodings(self):
        subtable = self.makeSubtable(4, 1, 1, 0)  # Mac Japanese
        self.assertNotEqual(None, subtable.getEncoding())
        self.assertEqual(False, subtable.isUnicode())

    def test_extended_unknown(self):
        subtable = self.makeSubtable(4, 10, 11, 12)
        self.assertEqual(subtable.getEncoding(), None)
        self.assertEqual(subtable.getEncoding("ascii"), "ascii")
        self.assertEqual(subtable.getEncoding(default="xyz"), "xyz")

    def test_compile_2(self):
        subtable = self.makeSubtable(2, 1, 2, 0)
        subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)}
        font = ttLib.TTFont()
        font.setGlyphOrder([".notdef"] + list(subtable.cmap.values()))
        data = subtable.compile(font)

        subtable2 = CmapSubtable.newSubtable(2)
        subtable2.decompile(data, font)
        self.assertEqual(subtable2.cmap, subtable.cmap)

    def test_compile_2_rebuild_rev_glyph_order(self):
        for fmt in [2, 4, 12]:
            subtable = self.makeSubtable(fmt, 1, 2, 0)
            subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)}
            font = ttLib.TTFont()
            font.setGlyphOrder([".notdef"] + list(subtable.cmap.values()))
            font._reverseGlyphOrderDict = (
                {}
            )  # force first KeyError branch in subtable.compile()
            data = subtable.compile(font)
            subtable2 = CmapSubtable.newSubtable(fmt)
            subtable2.decompile(data, font)
            self.assertEqual(subtable2.cmap, subtable.cmap, str(fmt))

    def test_compile_2_gids(self):
        for fmt in [2, 4, 12]:
            subtable = self.makeSubtable(fmt, 1, 3, 0)
            subtable.cmap = {0x0041: "gid001", 0x0042: "gid002"}
            font = ttLib.TTFont()
            font.setGlyphOrder([".notdef"])
            data = subtable.compile(font)

    def test_compile_decompile_4_empty(self):
        subtable = self.makeSubtable(4, 3, 1, 0)
        subtable.cmap = {}
        font = ttLib.TTFont()
        font.setGlyphOrder([])
        data = subtable.compile(font)
        subtable2 = CmapSubtable.newSubtable(4)
        subtable2.decompile(data, font)
        self.assertEqual(subtable2.cmap, {})

    def test_decompile_4(self):
        subtable = CmapSubtable.newSubtable(4)
        font = ttLib.TTFont()
        font.setGlyphOrder([])
        subtable.decompile(b"\0" * 3 + b"\x10" + b"\0" * 12, font)

    def test_decompile_12(self):
        subtable = CmapSubtable.newSubtable(12)
        font = ttLib.TTFont()
        font.setGlyphOrder([])
        subtable.decompile(b"\0" * 7 + b"\x10" + b"\0" * 8, font)

    def test_buildReversed(self):
        c4 = self.makeSubtable(4, 3, 1, 0)
        c4.cmap = {0x0041: "A", 0x0391: "A"}
        c12 = self.makeSubtable(12, 3, 10, 0)
        c12.cmap = {0x10314: "u10314"}
        cmap = table__c_m_a_p()
        cmap.tables = [c4, c12]
        self.assertEqual(
            cmap.buildReversed(), {"A": {0x0041, 0x0391}, "u10314": {0x10314}}
        )

    def test_getBestCmap(self):
        c4 = self.makeSubtable(4, 3, 1, 0)
        c4.cmap = {0x0041: "A", 0x0391: "A"}
        c12 = self.makeSubtable(12, 3, 10, 0)
        c12.cmap = {0x10314: "u10314"}
        cmap = table__c_m_a_p()
        cmap.tables = [c4, c12]
        self.assertEqual(cmap.getBestCmap(), {0x10314: "u10314"})
        self.assertEqual(
            cmap.getBestCmap(cmapPreferences=[(3, 1)]), {0x0041: "A", 0x0391: "A"}
        )
        self.assertEqual(cmap.getBestCmap(cmapPreferences=[(0, 4)]), None)

    def test_font_getBestCmap(self):
        c4 = self.makeSubtable(4, 3, 1, 0)
        c4.cmap = {0x0041: "A", 0x0391: "A"}
        c12 = self.makeSubtable(12, 3, 10, 0)
        c12.cmap = {0x10314: "u10314"}
        cmap = table__c_m_a_p()
        cmap.tables = [c4, c12]
        font = ttLib.TTFont()
        font["cmap"] = cmap
        self.assertEqual(font.getBestCmap(), {0x10314: "u10314"})
        self.assertEqual(
            font.getBestCmap(cmapPreferences=[(3, 1)]), {0x0041: "A", 0x0391: "A"}
        )
        self.assertEqual(font.getBestCmap(cmapPreferences=[(0, 4)]), None)

    def test_format_14(self):
        subtable = self.makeSubtable(14, 0, 5, 0)
        subtable.cmap = {}  # dummy
        subtable.uvsDict = {
            0xFE00: [(0x0030, "zero.slash")],
            0xFE01: [(0x0030, None)],
        }
        fb = FontBuilder(1024, isTTF=True)
        font = fb.font
        fb.setupGlyphOrder([".notdef", "zero.slash"])
        fb.setupMaxp()
        fb.setupPost()
        cmap = table__c_m_a_p()
        cmap.tableVersion = 0
        cmap.tables = [subtable]
        font["cmap"] = cmap
        f = io.BytesIO()
        font.save(f)
        f.seek(0)
        font = ttLib.TTFont(f)
        self.assertEqual(font["cmap"].getcmap(0, 5).uvsDict, subtable.uvsDict)
        f = io.StringIO(newline=None)
        font.saveXML(f, tables=["cmap"])
        ttx = strip_VariableItems(f.getvalue())
        with open(CMAP_FORMAT_14_TTX) as f:
            expected = strip_VariableItems(f.read())
        self.assertEqual(ttx, expected)
        with open(CMAP_FORMAT_14_BW_COMPAT_TTX) as f:
            font.importXML(f)
        self.assertEqual(font["cmap"].getcmap(0, 5).uvsDict, subtable.uvsDict)

    def test_sort_subtables_with_duplicate_keys(self):
        # https://github.com/fonttools/fonttools/issues/4035
        # Sorting subtables that share (platformID, platEncID, language) but
        # differ in format should not raise TypeError from comparing dicts.
        subtable4 = self.makeSubtable(4, 3, 1, 0)
        subtable4.cmap = {0x41: "A"}
        subtable12 = self.makeSubtable(12, 3, 1, 0)
        subtable12.cmap = {0x41: "A"}
        # This used to raise: TypeError: '<' not supported between
        # instances of 'dict' and 'dict'
        tables = sorted([subtable12, subtable4])
        # Both have the same key, so stable sort preserves original order
        self.assertEqual(len(tables), 2)

    def test_compile_raises_on_duplicate_subtable_keys(self):
        # https://github.com/fonttools/fonttools/issues/4035
        # The OpenType spec requires each (platformID, platEncID, language)
        # combination to be unique; compile should raise a clear error.
        cmap = table__c_m_a_p()
        cmap.tableVersion = 0
        subtable4 = self.makeSubtable(4, 3, 1, 0)
        subtable4.cmap = {0x41: "A"}
        subtable12 = self.makeSubtable(12, 3, 1, 0)
        subtable12.cmap = {0x41: "A"}
        cmap.tables = [subtable4, subtable12]
        with self.assertRaisesRegex(ValueError, "duplicate"):
            cmap.compile(ttFont=None)


if __name__ == "__main__":
    import sys

    sys.exit(unittest.main())
