import unittest
from bdflib import glyph_combining, model


class TestBuildUnicodeDecompositions(unittest.TestCase):
    def test_basic_functionality(self) -> None:
        """
        build_unicode_decompositions should run without crashing.
        """
        res = glyph_combining.build_unicode_decompositions()

        # It should return a dict.
        self.assertEqual(type(res), dict)

        # It should return a non-empty dict.
        self.assertNotEqual(res, {})

        # It should have a decompositions for simple accented letters.
        self.assertIn("\N{LATIN CAPITAL LETTER A WITH GRAVE}", res)

        # It should not have decompositions for complicated things like
        # superscripts.
        self.assertNotIn("\N{SUPERSCRIPT TWO}", res)

        # Decompositions should be iterables of (unicode char, int) tuples.
        for decomposable in res:
            for char, combining_class in res[decomposable]:
                self.assertEqual(type(char), type("a"))
                self.assertEqual(len(char), 1)
                self.assertEqual(type(combining_class), int)

    def test_base_dotless_i_and_j(self) -> None:
        """
        Accented "i" should be based on a dotless i.
        """
        res = glyph_combining.build_unicode_decompositions()

        # If there's an accent above the 'i', the base glyph should be
        # a dotless 'i'.
        components = res["\N{LATIN SMALL LETTER I WITH DIAERESIS}"]
        self.assertEqual(components[0], ("\N{LATIN SMALL LETTER DOTLESS I}", 0))

        # If the accent is elsewhere, the base glyph should be a dotted 'i'.
        components = res["\N{LATIN SMALL LETTER I WITH OGONEK}"]
        self.assertEqual(components[0], ("i", 0))

        # Likewise, a 'j' with an accent above should be based on dotless 'j'
        components = res["\N{LATIN SMALL LETTER J WITH CIRCUMFLEX}"]
        self.assertEqual(components[0], ("\N{LATIN SMALL LETTER DOTLESS J}", 0))

        # ...and a 'j' with any other accent should be dotted, but the only
        # Unicode characters that decompose to a 'j' and an accent have the
        # accent above, so there's no test we can test here.


class TestFontFiller(unittest.TestCase):
    def setUp(self) -> None:
        # Create a simple font with two characters
        self.font = model.Font(b"TestFont", 12, 100, 100)
        self.font.new_glyph_from_data(
            b"a", [0b10, 0b01], 0, 0, 2, 2, 3, ord("a")
        )
        self.font.new_glyph_from_data(
            b"b", [0b01, 0b10], 0, 0, 2, 2, 3, ord("b")
        )

        # Create a dictionary that says how to combine them.
        self.decompositions = {
            # A simple combination of basic characters.
            "c": [("a", 0), ("b", 0)],
            # A recursive definition
            "d": [("c", 0), ("a", 0), ("b", 0)],
            # A definition that can't be solved with the glyphs in this
            # font.
            "e": [("f", 0), ("g", 0)],
            # A definition that involves an unknown combining class
            "h": [("a", 0), ("b", 256)],
        }

        self.filler = glyph_combining.FontFiller(self.font, self.decompositions)

    def test_basic_functionality(self) -> None:
        """
        We succeed if we have a decomposition and the components.
        """
        added = self.filler.add_glyph_to_font("c")
        self.assertEqual(added, True)
        self.assertEqual(self.filler.missing_chars, {})
        self.assertEqual(self.filler.unknown_classes, {})

        glyph = self.font[ord("c")]

        print(glyph)
        self.assertEqual(str(glyph), "|#.#.\n" "#---#")

    def test_recursive_functionality(self) -> None:
        """
        We succeed even if we can only get the components recursively.
        """
        added = self.filler.add_glyph_to_font("d")
        self.assertEqual(added, True)
        self.assertEqual(self.filler.missing_chars, {})
        self.assertEqual(self.filler.unknown_classes, {})

        glyph = self.font[ord("d")]

        print(glyph)
        self.assertEqual(str(glyph), "|#.#...#.#.\n" "#---#-#---#")

    def test_character_present(self) -> None:
        """
        We succeed if the char is already in the font.
        """
        added = self.filler.add_glyph_to_font("a")
        self.assertEqual(self.filler.missing_chars, {})
        self.assertEqual(self.filler.unknown_classes, {})
        self.assertEqual(added, True)

    def test_missing_decomposition(self) -> None:
        """
        We fail if there's no decomposition for the given character.
        """
        added = self.filler.add_glyph_to_font("z")
        self.assertEqual(self.filler.missing_chars, {})
        self.assertEqual(self.filler.unknown_classes, {})
        self.assertEqual(added, False)

    def test_missing_recursive_decomposition(self) -> None:
        """
        We fail if there's a decomposition but no components for a character.
        """
        added = self.filler.add_glyph_to_font("e")
        self.assertEqual(self.filler.missing_chars, {"f": 1})
        self.assertEqual(self.filler.unknown_classes, {})
        self.assertEqual(added, False)

    def test_unknown_combining_class(self) -> None:
        """
        We fail if there's a decomposition but we don't know how to use it.
        """
        added = self.filler.add_glyph_to_font("h")
        self.assertEqual(self.filler.missing_chars, {})
        self.assertEqual(self.filler.unknown_classes, {256: 1})
        self.assertEqual(added, False)

    def test_add_decomposable_glyphs_to_font(self) -> None:
        """
        Add all the glyphs we can to the given font.
        """

        self.filler.add_decomposable_glyphs_to_font()

        self.assertIn(ord("c"), self.font)
        self.assertIn(ord("d"), self.font)
        self.assertEqual(self.filler.missing_chars, {"f": 1})
        self.assertEqual(self.filler.unknown_classes, {256: 1})


class TestFontFillerCombiningAbove(unittest.TestCase):
    def _build_composing_above_font(
        self, set_cap_height: bool = False
    ) -> model.Font:
        """
        Return a font with glyphs useful for testing COMBINING ABOVE.
        """
        font = model.Font(b"TestFont", 12, 100, 100)

        # Some glyphs for testing accent placement
        font.new_glyph_from_data(b"space", [], 0, 0, 0, 0, 4, ord(" "))
        font.new_glyph_from_data(
            b"O",
            [
                0b010,
                0b101,
                0b101,
                0b010,
            ],
            0,
            0,
            3,
            4,
            4,
            ord("O"),
        )
        font.new_glyph_from_data(
            b"o",
            [
                0b010,
                0b101,
                0b010,
            ],
            0,
            0,
            3,
            3,
            4,
            ord("o"),
        )
        font.new_glyph_from_data(
            b"macron",
            [0b1111],
            0,
            5,
            4,
            1,
            4,
            ord("\N{COMBINING MACRON}"),
        )
        font.new_glyph_from_data(
            b"caron",
            [
                0b101,
                0b010,
            ],
            0,
            5,
            3,
            2,
            4,
            ord("\N{COMBINING CARON}"),
        )

        if set_cap_height:
            font[b"CAP_HEIGHT"] = 4

        decompositions = {
            # Test combining an odd-width base-character with an even-width
            # accent.
            "I": [("O", 0), ("\N{COMBINING MACRON}", glyph_combining.CC_A)],
            "i": [("o", 0), ("\N{COMBINING MACRON}", glyph_combining.CC_A)],
            # Test combining an odd-width base-character with an odd-width
            # accent.
            "J": [("O", 0), ("\N{COMBINING CARON}", glyph_combining.CC_A)],
            "j": [("o", 0), ("\N{COMBINING CARON}", glyph_combining.CC_A)],
            "\N{MACRON}": [
                (" ", 0),
                ("\N{COMBINING MACRON}", glyph_combining.CC_A),
            ],
        }

        glyph_combining.FontFiller(
            font,
            decompositions,
        ).add_decomposable_glyphs_to_font()

        return font

    def test_composing_even_above_odd(self) -> None:
        font = self._build_composing_above_font()

        O_macron = font[ord("I")]
        print(O_macron)
        self.assertEqual(
            str(O_macron),
            "####\n" "|...\n" "|#..\n" "#.#.\n" "#.#.\n" "+#--",
        )

    def test_composing_odd_above_odd(self) -> None:
        font = self._build_composing_above_font()

        O_caron = font[ord("J")]
        print(O_caron)
        self.assertEqual(
            str(O_caron), "|#.\n" "#.#\n" "|..\n" "|#.\n" "#.#\n" "#.#\n" "+#-"
        )

    def test_composing_above_without_CAP_HEIGHT(self) -> None:
        # Build the font without CAP_HEIGHT set.
        font = self._build_composing_above_font(False)

        # Upper case and lower-case should have the accent at the same place.
        O_caron = font[ord("J")]
        print(O_caron)
        self.assertEqual(
            str(O_caron), "|#.\n" "#.#\n" "|..\n" "|#.\n" "#.#\n" "#.#\n" "+#-"
        )

        o_caron = font[ord("j")]
        print(o_caron)
        self.assertEqual(
            str(o_caron), "|#.\n" "#.#\n" "|..\n" "|..\n" "|#.\n" "#.#\n" "+#-"
        )

    def test_composing_above_with_CAP_HEIGHT(self) -> None:
        # Build the font with CAP_HEIGHT set.
        font = self._build_composing_above_font(True)

        # Upper case should be the same.
        O_caron = font[ord("J")]
        print(O_caron)
        self.assertEqual(
            str(O_caron), "|#.\n" "#.#\n" "|..\n" "|#.\n" "#.#\n" "#.#\n" "+#-"
        )

        # The accent should be as high above lowercase as it is above
        # upper-case.

        o_caron = font[ord("j")]
        print(o_caron)
        self.assertEqual(
            str(o_caron), "|#.\n" "#.#\n" "|..\n" "|#.\n" "#.#\n" "+#-"
        )

    def test_composing_above_blank_base(self) -> None:
        """
        Composing on top of a blank base char should work.
        """
        # Build the font with CAP_HEIGHT set.
        font = self._build_composing_above_font(True)

        # Macron should be drawn in the usual place.
        macron = font[ord("\N{MACRON}")]
        print(macron)
        self.assertEqual(
            str(macron), "####\n" "|...\n" "|...\n" "|...\n" "|...\n" "+---"
        )

        # Without CAP_HEIGHT set, the same should happen.
        font = self._build_composing_above_font(False)

        # Macron should be drawn in the usual place.
        macron = font[ord("\N{MACRON}")]
        print(macron)
        self.assertEqual(
            str(macron), "####\n" "|...\n" "|...\n" "|...\n" "|...\n" "+---"
        )


class TestFontFillerCombiningBelow(unittest.TestCase):
    def _build_composing_below_font(self) -> model.Font:
        """
        Return a font with glyphs useful for testing COMBINING BELOW.
        """
        font = model.Font(b"TestFont", 12, 100, 100)

        # Some glyphs for testing accent placement
        font.new_glyph_from_data(b"space", [], 0, 0, 0, 0, 4, ord(" "))
        font.new_glyph_from_data(
            b"Y",
            [
                0b010,
                0b010,
                0b101,
                0b101,
            ],
            0,
            0,
            3,
            4,
            4,
            ord("Y"),
        )
        font.new_glyph_from_data(
            b"y",
            [
                0b110,
                0b001,
                0b011,
                0b101,
                0b101,
            ],
            0,
            -2,
            3,
            5,
            4,
            ord("y"),
        )
        font.new_glyph_from_data(
            b"macron_below",
            [0b1111],
            0,
            -2,
            4,
            1,
            4,
            ord("\N{COMBINING MACRON BELOW}"),
        )
        font.new_glyph_from_data(
            b"dot_below",
            [0b1],
            0,
            -2,
            1,
            1,
            1,
            ord("\N{COMBINING DOT BELOW}"),
        )

        decompositions = {
            # Test combining an odd-width base-character with an even-width
            # accent.
            "I": [
                ("Y", 0),
                ("\N{COMBINING MACRON BELOW}", glyph_combining.CC_B),
            ],
            "i": [
                ("y", 0),
                ("\N{COMBINING MACRON BELOW}", glyph_combining.CC_B),
            ],
            # Test combining an odd-width base-character with an odd-width
            # accent.
            "J": [
                ("Y", 0),
                ("\N{COMBINING DOT BELOW}", glyph_combining.CC_A),
            ],
            "j": [
                ("y", 0),
                ("\N{COMBINING DOT BELOW}", glyph_combining.CC_B),
            ],
            "_": [
                (" ", 0),
                ("\N{COMBINING MACRON BELOW}", glyph_combining.CC_B),
            ],
        }

        glyph_combining.FontFiller(
            font,
            decompositions,
        ).add_decomposable_glyphs_to_font()

        return font

    def test_composing_even_below_odd(self) -> None:
        font = self._build_composing_below_font()

        Y_macron = font[ord("I")]
        print(Y_macron)
        self.assertEqual(
            str(Y_macron), "#.#.\n" "#.#.\n" "|#..\n" "+#--\n" "|...\n" "####"
        )

    def test_composing_odd_below_odd(self) -> None:
        font = self._build_composing_below_font()

        Y_dot = font[ord("J")]
        print(Y_dot)
        self.assertEqual(
            str(Y_dot), "#.#\n" "#.#\n" "|#.\n" "+#-\n" "|..\n" "|#."
        )

    def test_composing_below_clears_descenders(self) -> None:
        font = self._build_composing_below_font()

        # Upper case and lower-case should have the accent at the same place.
        Y_dot = font[ord("J")]
        print(Y_dot)
        self.assertEqual(
            str(Y_dot), "#.#\n" "#.#\n" "|#.\n" "+#-\n" "|..\n" "|#."
        )

        y_dot = font[ord("j")]
        print(y_dot)
        self.assertEqual(
            str(y_dot), "#.#\n" "#.#\n" "+##\n" "|.#\n" "##.\n" "|..\n" "|#."
        )

    def test_composing_below_blank_base(self) -> None:
        """
        Composing on top of a blank base char should work.
        """
        font = self._build_composing_below_font()

        # Underscore should be drawn in the usual place.
        underscore = font[ord("_")]
        print(underscore)
        self.assertEqual(str(underscore), "+---\n" "|...\n" "####")
