import collections.abc
import unittest
import tkinter
from tkinter import font
from test.support import requires, gc_collect, ALWAYS_EQ
from test.test_tkinter.support import setUpModule  # noqa: F401
from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest

requires('gui')

fontname = "TkDefaultFont"

class FontTest(AbstractTkTest, unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        AbstractTkTest.setUpClass.__func__(cls)
        try:
            cls.font = font.Font(root=cls.root, name=fontname, exists=True)
        except tkinter.TclError:
            cls.font = font.Font(root=cls.root, name=fontname, exists=False)

    def test_configure(self):
        self.assertEqual(self.font.config, self.font.configure)
        options = self.font.configure()
        self.assertGreaterEqual(set(options),
            {'family', 'size', 'weight', 'slant', 'underline', 'overstrike'})
        for key in options:
            self.assertEqual(self.font.cget(key), options[key])
            self.assertEqual(self.font[key], options[key])
        for key in 'family', 'weight', 'slant':
            self.assertIsInstance(options[key], str)
            self.assertIsInstance(self.font.cget(key), str)
            self.assertIsInstance(self.font[key], str)
        sizetype = int if self.wantobjects else str
        for key in 'size', 'underline', 'overstrike':
            self.assertIsInstance(options[key], sizetype)
            self.assertIsInstance(self.font.cget(key), sizetype)
            self.assertIsInstance(self.font[key], sizetype)
        self.assertRaisesRegex(tkinter.TclError, 'bad option "-spam"',
                               self.font.cget, 'spam')
        self.assertRaisesRegex(tkinter.TclError, 'bad option "-spam"',
                               self.font.configure, spam='x')
        self.assertRaises(TypeError, self.font.cget)
        self.assertRaises(TypeError, self.font.cget, 'size', 'weight')

    def test_create_from_named_font(self):
        # gh-143990: a font created from a named font copies its configured
        # options, preserving a size specified in pixels (a negative size).
        sizetype = int if self.wantobjects else str
        named = font.Font(root=self.root, name='my named font',  # name with spaces
                          family='Times', size=-20, weight='bold')
        # The source is the name of a named font or a Font representing one.
        for source in ['my named font', named]:
            with self.subTest(source=source):
                f = font.Font(root=self.root, font=source)
                self.assertEqual(f.cget('size'), sizetype(-20))
                self.assertEqual(f.actual('family'), named.actual('family'))
                self.assertEqual(f.actual('weight'), 'bold')

    def test_create_from_description(self):
        # gh-143990: a font created from a font description is resolved via
        # "font actual", so a size in pixels (negative) becomes a size in points.
        descriptions = [
            ('Times', -20),                     # tuple
            ('Times', -20, 'bold'),             # tuple with a style
            'Times -20',                        # string
            'Times -20 bold',                   # string with a style
            '{Times New Roman} -20',            # string, family with spaces
        ]
        for desc in descriptions:
            with self.subTest(font=desc):
                f = font.Font(root=self.root, font=desc)
                self.assertGreater(int(f.cget('size')), 0)  # pixels -> points

    def test_copy(self):
        # size=-20 (pixels): copy() copies the configured options, so the
        # size is preserved rather than resolved (gh-143990).
        f = font.Font(root=self.root, family='Times', size=-20, weight='bold')
        copied = f.copy()
        self.assertIsInstance(copied, font.Font)
        self.assertIsNot(copied, f)
        self.assertNotEqual(copied.name, f.name)
        self.assertEqual(copied.actual(), f.actual())
        sizetype = int if self.wantobjects else str
        self.assertEqual(copied.cget('size'), sizetype(-20))
        # The copy is independent of the original.
        copied.configure(size=20)
        self.assertEqual(f.cget('size'), sizetype(-20))
        self.assertEqual(copied.cget('size'), sizetype(20))
        self.assertRaises(TypeError, f.copy, 'x')

    def test_unicode_family(self):
        family = 'MS \u30b4\u30b7\u30c3\u30af'
        try:
            f = font.Font(root=self.root, family=family, exists=True)
        except tkinter.TclError:
            f = font.Font(root=self.root, family=family, exists=False)
        self.assertEqual(f.cget('family'), family)
        del f
        gc_collect()

    def test_actual(self):
        options = self.font.actual()
        self.assertGreaterEqual(set(options),
            {'family', 'size', 'weight', 'slant', 'underline', 'overstrike'})
        for key in options:
            self.assertEqual(self.font.actual(key), options[key])
        for key in 'family', 'weight', 'slant':
            self.assertIsInstance(options[key], str)
            self.assertIsInstance(self.font.actual(key), str)
        sizetype = int if self.wantobjects else str
        for key in 'size', 'underline', 'overstrike':
            self.assertIsInstance(options[key], sizetype)
            self.assertIsInstance(self.font.actual(key), sizetype)
        self.assertRaisesRegex(tkinter.TclError, 'bad option "-spam"',
                               self.font.actual, 'spam')
        self.assertRaises(TypeError, self.font.actual, 'size', 'weight', 'slant')

    def test_name(self):
        self.assertEqual(self.font.name, fontname)
        self.assertEqual(str(self.font), fontname)

    def test_equality(self):
        font1 = font.Font(root=self.root, name=fontname, exists=True)
        font2 = font.Font(root=self.root, name=fontname, exists=True)
        self.assertIsNot(font1, font2)
        self.assertEqual(font1, font2)
        self.assertNotEqual(font1, font1.copy())

        self.assertNotEqual(font1, 0)
        self.assertEqual(font1, ALWAYS_EQ)

        root2 = tkinter.Tk()
        self.addCleanup(root2.destroy)
        font3 = font.Font(root=root2, name=fontname, exists=True)
        self.assertEqual(str(font1), str(font3))
        self.assertNotEqual(font1, font3)

    def test_measure(self):
        self.assertIsInstance(self.font.measure('abc'), int)
        self.assertEqual(self.font.measure(''), 0)
        self.assertIsInstance(
            self.font.measure('abc', displayof=self.root), int)
        self.assertRaises(TypeError, self.font.measure)
        self.assertRaises(TypeError, self.font.measure, 'a', 'b', 'c')

    def test_metrics(self):
        metrics = self.font.metrics()
        self.assertGreaterEqual(set(metrics),
            {'ascent', 'descent', 'linespace', 'fixed'})
        for key in metrics:
            self.assertEqual(self.font.metrics(key), metrics[key])
            self.assertEqual(self.font.metrics(key, displayof=self.root),
                             metrics[key])
            self.assertIsInstance(metrics[key], int)
            self.assertIsInstance(self.font.metrics(key), int)
        self.assertRaisesRegex(tkinter.TclError, 'bad metric "-spam"',
                               self.font.metrics, 'spam')

    def test_families(self):
        families = font.families(self.root)
        self.assertIsInstance(families, tuple)
        self.assertTrue(families)
        for family in families:
            self.assertIsInstance(family, str)
            self.assertTrue(family)

    def test_names(self):
        names = font.names(self.root)
        self.assertIsInstance(names, tuple)
        self.assertTrue(names)
        for name in names:
            self.assertIsInstance(name, str)
            self.assertTrue(name)
        self.assertIn(fontname, names)

    def test_nametofont(self):
        testfont = font.nametofont(fontname, root=self.root)
        self.assertIsInstance(testfont, font.Font)
        self.assertEqual(testfont.name, fontname)

    def test_repr(self):
        self.assertEqual(
            repr(self.font), f'<tkinter.font.Font object {fontname!r}>'
        )

    def test_iterable_protocol(self):
        self.assertNotIsSubclass(font.Font, collections.abc.Iterable)
        self.assertNotIsSubclass(font.Font, collections.abc.Container)
        self.assertNotIsInstance(self.font, collections.abc.Iterable)
        self.assertNotIsInstance(self.font, collections.abc.Container)
        with self.assertRaisesRegex(TypeError, 'is not iterable'):
            iter(self.font)
        with self.assertRaisesRegex(TypeError, 'is not a container or iterable'):
            self.font in self.font


class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):

    def test_families(self):
        self.assertRaises(RuntimeError, font.families)
        root = tkinter.Tk()
        families = font.families()
        self.assertIsInstance(families, tuple)
        self.assertTrue(families)
        for family in families:
            self.assertIsInstance(family, str)
            self.assertTrue(family)
        root.destroy()
        tkinter.NoDefaultRoot()
        self.assertRaises(RuntimeError, font.families)

    def test_names(self):
        self.assertRaises(RuntimeError, font.names)
        root = tkinter.Tk()
        names = font.names()
        self.assertIsInstance(names, tuple)
        self.assertTrue(names)
        for name in names:
            self.assertIsInstance(name, str)
            self.assertTrue(name)
        self.assertIn(fontname, names)
        root.destroy()
        tkinter.NoDefaultRoot()
        self.assertRaises(RuntimeError, font.names)

    def test_nametofont(self):
        self.assertRaises(RuntimeError, font.nametofont, fontname)
        root = tkinter.Tk()
        testfont = font.nametofont(fontname)
        self.assertIsInstance(testfont, font.Font)
        self.assertEqual(testfont.name, fontname)
        root.destroy()
        tkinter.NoDefaultRoot()
        self.assertRaises(RuntimeError, font.nametofont, fontname)


class TestModule(unittest.TestCase):
    def test_deprecated__version__(self):
        with self.assertWarnsRegex(
            DeprecationWarning,
            "'__version__' is deprecated and slated for removal in Python 3.20",
        ) as cm:
            getattr(font, "__version__")
        self.assertEqual(cm.filename, __file__)


if __name__ == "__main__":
    unittest.main()
