import os
import shutil
import unittest
import tempfile
from io import open
from fontTools.ufoLib import UFOReader, UFOWriter
from fontTools.ufoLib import plistlib
from .testSupport import expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion


# the format version 1 lib.plist contains some data
# that these tests shouldn't be concerned about.
removeFromFormatVersion1Lib = [
    "org.robofab.opentype.classes",
    "org.robofab.opentype.features",
    "org.robofab.opentype.featureorder",
    "org.robofab.postScriptHintData",
]


class ConversionFunctionsTestCase(unittest.TestCase):
    def tearDown(self):
        path = self.getFontPath("TestFont1 (UFO1) converted.ufo")
        if os.path.exists(path):
            shutil.rmtree(path)
        path = self.getFontPath("TestFont1 (UFO2) converted.ufo")
        if os.path.exists(path):
            shutil.rmtree(path)

    def getFontPath(self, fileName):
        testdata = os.path.join(os.path.dirname(__file__), "testdata")
        return os.path.join(testdata, fileName)

    def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures):
        # result
        metainfoPath1 = os.path.join(path1, "metainfo.plist")
        fontinfoPath1 = os.path.join(path1, "fontinfo.plist")
        kerningPath1 = os.path.join(path1, "kerning.plist")
        groupsPath1 = os.path.join(path1, "groups.plist")
        libPath1 = os.path.join(path1, "lib.plist")
        featuresPath1 = os.path.join(path1, "features.plist")
        glyphsPath1 = os.path.join(path1, "glyphs")
        glyphsPath1_contents = os.path.join(glyphsPath1, "contents.plist")
        glyphsPath1_A = os.path.join(glyphsPath1, "A_.glif")
        glyphsPath1_B = os.path.join(glyphsPath1, "B_.glif")
        # expected result
        metainfoPath2 = os.path.join(path2, "metainfo.plist")
        fontinfoPath2 = os.path.join(path2, "fontinfo.plist")
        kerningPath2 = os.path.join(path2, "kerning.plist")
        groupsPath2 = os.path.join(path2, "groups.plist")
        libPath2 = os.path.join(path2, "lib.plist")
        featuresPath2 = os.path.join(path2, "features.plist")
        glyphsPath2 = os.path.join(path2, "glyphs")
        glyphsPath2_contents = os.path.join(glyphsPath2, "contents.plist")
        glyphsPath2_A = os.path.join(glyphsPath2, "A_.glif")
        glyphsPath2_B = os.path.join(glyphsPath2, "B_.glif")
        # look for existence
        self.assertEqual(os.path.exists(metainfoPath1), True)
        self.assertEqual(os.path.exists(fontinfoPath1), True)
        self.assertEqual(os.path.exists(kerningPath1), True)
        self.assertEqual(os.path.exists(groupsPath1), True)
        self.assertEqual(os.path.exists(libPath1), True)
        self.assertEqual(os.path.exists(glyphsPath1), True)
        self.assertEqual(os.path.exists(glyphsPath1_contents), True)
        self.assertEqual(os.path.exists(glyphsPath1_A), True)
        self.assertEqual(os.path.exists(glyphsPath1_B), True)
        if testFeatures:
            self.assertEqual(os.path.exists(featuresPath1), True)
        # look for aggrement
        with open(metainfoPath1, "rb") as f:
            data1 = plistlib.load(f)
        with open(metainfoPath2, "rb") as f:
            data2 = plistlib.load(f)
        self.assertEqual(data1, data2)
        with open(fontinfoPath1, "rb") as f:
            data1 = plistlib.load(f)
        self.assertEqual(sorted(data1.items()), sorted(expectedInfoData.items()))
        with open(kerningPath1, "rb") as f:
            data1 = plistlib.load(f)
        with open(kerningPath2, "rb") as f:
            data2 = plistlib.load(f)
        self.assertEqual(data1, data2)
        with open(groupsPath1, "rb") as f:
            data1 = plistlib.load(f)
        with open(groupsPath2, "rb") as f:
            data2 = plistlib.load(f)
        self.assertEqual(data1, data2)
        with open(libPath1, "rb") as f:
            data1 = plistlib.load(f)
        with open(libPath2, "rb") as f:
            data2 = plistlib.load(f)
        if "UFO1" in libPath1:
            for key in removeFromFormatVersion1Lib:
                if key in data1:
                    del data1[key]
        if "UFO1" in libPath2:
            for key in removeFromFormatVersion1Lib:
                if key in data2:
                    del data2[key]
        self.assertEqual(data1, data2)
        with open(glyphsPath1_contents, "rb") as f:
            data1 = plistlib.load(f)
        with open(glyphsPath2_contents, "rb") as f:
            data2 = plistlib.load(f)
        self.assertEqual(data1, data2)
        with open(glyphsPath1_A, "rb") as f:
            data1 = plistlib.load(f)
        with open(glyphsPath2_A, "rb") as f:
            data2 = plistlib.load(f)
        self.assertEqual(data1, data2)
        with open(glyphsPath1_B, "rb") as f:
            data1 = plistlib.load(f)
        with open(glyphsPath2_B, "rb") as f:
            data2 = plistlib.load(f)
        self.assertEqual(data1, data2)


# ---------------------
# kerning up conversion
# ---------------------


class TestInfoObject:
    pass


class KerningUpConversionTestCase(unittest.TestCase):
    expectedKerning = {
        ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
        ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
        ("public.kern1.BGroup", "A"): 5,
        ("public.kern1.BGroup", "B"): 6,
        ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
        ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
        ("public.kern1.CGroup", "A"): 9,
        ("public.kern1.CGroup", "B"): 10,
        ("A", "public.kern2.CGroup"): 3,
        ("A", "public.kern2.DGroup"): 4,
        ("A", "A"): 1,
        ("A", "B"): 2,
        ("X", "A"): 13,
        ("X", "public.kern2.CGroup"): 14,
    }

    expectedGroups = {
        "BGroup": ["B"],
        "CGroup": ["C", "Ccedilla"],
        "DGroup": ["D"],
        "public.kern1.BGroup": ["B"],
        "public.kern1.CGroup": ["C", "Ccedilla"],
        "public.kern2.CGroup": ["C", "Ccedilla"],
        "public.kern2.DGroup": ["D"],
        "Not A Kerning Group": ["A"],
        "X": ["X", "X.sc"],
    }

    def setUp(self):
        self.tempDir = tempfile.mktemp()
        os.mkdir(self.tempDir)
        self.ufoPath = os.path.join(self.tempDir, "test.ufo")

    def tearDown(self):
        shutil.rmtree(self.tempDir)

    def makeUFO(self, formatVersion):
        self.clearUFO()
        if not os.path.exists(self.ufoPath):
            os.mkdir(self.ufoPath)

        # glyphs
        glyphsPath = os.path.join(self.ufoPath, "glyphs")
        if not os.path.exists(glyphsPath):
            os.mkdir(glyphsPath)
        glyphFile = "X_.glif"
        glyphsContents = dict(X=glyphFile)
        path = os.path.join(glyphsPath, "contents.plist")
        with open(path, "wb") as f:
            plistlib.dump(glyphsContents, f)
        path = os.path.join(glyphsPath, glyphFile)
        with open(path, "w") as f:
            f.write('<?xml version="1.0" encoding="UTF-8"?>\n')

        # metainfo.plist
        metaInfo = dict(creator="test", formatVersion=formatVersion)
        path = os.path.join(self.ufoPath, "metainfo.plist")
        with open(path, "wb") as f:
            plistlib.dump(metaInfo, f)
        # kerning
        kerning = {
            "A": {"A": 1, "B": 2, "CGroup": 3, "DGroup": 4},
            "BGroup": {"A": 5, "B": 6, "CGroup": 7, "DGroup": 8},
            "CGroup": {"A": 9, "B": 10, "CGroup": 11, "DGroup": 12},
            "X": {"A": 13, "CGroup": 14},
        }
        path = os.path.join(self.ufoPath, "kerning.plist")
        with open(path, "wb") as f:
            plistlib.dump(kerning, f)
        # groups
        groups = {
            "BGroup": ["B"],
            "CGroup": ["C", "Ccedilla"],
            "DGroup": ["D"],
            "Not A Kerning Group": ["A"],
            "X": ["X", "X.sc"],  # a group with a name that is also a glyph name
        }
        path = os.path.join(self.ufoPath, "groups.plist")
        with open(path, "wb") as f:
            plistlib.dump(groups, f)
        # font info
        fontInfo = {"familyName": "Test"}
        path = os.path.join(self.ufoPath, "fontinfo.plist")
        with open(path, "wb") as f:
            plistlib.dump(fontInfo, f)

    def clearUFO(self):
        if os.path.exists(self.ufoPath):
            shutil.rmtree(self.ufoPath)

    def testUFO1(self):
        self.makeUFO(formatVersion=2)
        reader = UFOReader(self.ufoPath, validate=True)
        kerning = reader.readKerning()
        self.assertEqual(self.expectedKerning, kerning)
        groups = reader.readGroups()
        self.assertEqual(self.expectedGroups, groups)
        info = TestInfoObject()
        reader.readInfo(info)

    def testUFO2(self):
        self.makeUFO(formatVersion=2)
        reader = UFOReader(self.ufoPath, validate=True)
        kerning = reader.readKerning()
        self.assertEqual(self.expectedKerning, kerning)
        groups = reader.readGroups()
        self.assertEqual(self.expectedGroups, groups)
        info = TestInfoObject()
        reader.readInfo(info)


class KerningDownConversionTestCase(unittest.TestCase):
    expectedKerning = {
        ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
        ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
        ("public.kern1.BGroup", "A"): 5,
        ("public.kern1.BGroup", "B"): 6,
        ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
        ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
        ("public.kern1.CGroup", "A"): 9,
        ("public.kern1.CGroup", "B"): 10,
        ("A", "public.kern2.CGroup"): 3,
        ("A", "public.kern2.DGroup"): 4,
        ("A", "A"): 1,
        ("A", "B"): 2,
    }

    groups = {
        "BGroup": ["B"],
        "CGroup": ["C"],
        "DGroup": ["D"],
        "public.kern1.BGroup": ["B"],
        "public.kern1.CGroup": ["C", "Ccedilla"],
        "public.kern2.CGroup": ["C", "Ccedilla"],
        "public.kern2.DGroup": ["D"],
        "Not A Kerning Group": ["A"],
    }
    expectedWrittenGroups = {
        "BGroup": ["B"],
        "CGroup": ["C", "Ccedilla"],
        "DGroup": ["D"],
        "Not A Kerning Group": ["A"],
    }

    kerning = {
        ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
        ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
        ("public.kern1.BGroup", "A"): 5,
        ("public.kern1.BGroup", "B"): 6,
        ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
        ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
        ("public.kern1.CGroup", "A"): 9,
        ("public.kern1.CGroup", "B"): 10,
        ("A", "public.kern2.CGroup"): 3,
        ("A", "public.kern2.DGroup"): 4,
        ("A", "A"): 1,
        ("A", "B"): 2,
    }
    expectedWrittenKerning = {
        "BGroup": {"CGroup": 7, "DGroup": 8, "A": 5, "B": 6},
        "CGroup": {"CGroup": 11, "DGroup": 12, "A": 9, "B": 10},
        "A": {"CGroup": 3, "DGroup": 4, "A": 1, "B": 2},
    }

    downConversionMapping = {
        "side1": {"BGroup": "public.kern1.BGroup", "CGroup": "public.kern1.CGroup"},
        "side2": {"CGroup": "public.kern2.CGroup", "DGroup": "public.kern2.DGroup"},
    }

    def setUp(self):
        self.tempDir = tempfile.mktemp()
        os.mkdir(self.tempDir)
        self.dstDir = os.path.join(self.tempDir, "test.ufo")

    def tearDown(self):
        shutil.rmtree(self.tempDir)

    def tearDownUFO(self):
        shutil.rmtree(self.dstDir)

    def testWrite(self):
        writer = UFOWriter(self.dstDir, formatVersion=2)
        writer.setKerningGroupConversionRenameMaps(self.downConversionMapping)
        writer.writeKerning(self.kerning)
        writer.writeGroups(self.groups)
        # test groups
        path = os.path.join(self.dstDir, "groups.plist")
        with open(path, "rb") as f:
            writtenGroups = plistlib.load(f)
        self.assertEqual(writtenGroups, self.expectedWrittenGroups)
        # test kerning
        path = os.path.join(self.dstDir, "kerning.plist")
        with open(path, "rb") as f:
            writtenKerning = plistlib.load(f)
        self.assertEqual(writtenKerning, self.expectedWrittenKerning)
        self.tearDownUFO()
