import random
import unittest

from TD_Dictionnaire.Dictionnaire import Dictionnaire


def random_Dictionnaire(n: int) -> tuple[list, Dictionnaire]:
    ensemble = set()
    while len(ensemble) < n:
        ensemble.add(random.randint(0, 10 * n))
    random_lst = [(str(i), random.random()) for i in ensemble]
    random.shuffle(random_lst)

    dico = Dictionnaire()
    for k, v in random_lst:
        dico[k] = v

    random_lst.sort()

    return random_lst, dico

def collision_Dictionnaire(n: int) -> tuple[list, Dictionnaire]:
    d = Dictionnaire()
    testlist = [(i, i) for i in range(0, n, 10)]

    for i in testlist:
        d[i[0]] = i[1]

    return testlist, d

def dictionnaire_to_list(dico: Dictionnaire) -> list:
    """
    Convertit un objet de type `Dictionnaire` en `list` Python
    :param dico: le `Dictionnaire` à convertir
    :return: la `list`
    """
    if dico is None:
        return None
    else:
        if not hasattr(dico, '__hashindex__'):
            dico.__hashindex__ = 0
            dico.__keyindex__ = 1
            dico.__valueindex__ = 2

        return sorted(
            [(i[Dictionnaire.__keyindex__], i[Dictionnaire.__valueindex__]) for i in dico._stockage if i is not None])


class TestDictionnaire(unittest.TestCase):

    @unittest.skipIf(not hasattr(Dictionnaire, '__setitem__'), "Il manque la méthode ListeTriee.__setitem__()")
    @unittest.skipIf(not hasattr(Dictionnaire, '__getitem__'), "Il manque la méthode ListeTriee.__getitem__()")
    def test_TableauDictionnaire_setitem_sanscollision(self):
        for _ in range(100):
            d = Dictionnaire()
            maxkey = len(d._stockage)
            lst = [(i, random.randint(0, maxkey)) for i in range(maxkey)]
            random.shuffle(lst)
            for k, v in lst:
                d[k] = v
                self.assertEqual(d[k], v, f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]}")

            k = random.randint(2 * maxkey, 100 * maxkey)
            self.assertRaises(KeyError, d.__getitem__, k, "L'insertion dans un dictionnaire plein doit renvoyer une erreur")


    @unittest.skipIf(not hasattr(Dictionnaire, '__setitem__'), "Il manque la méthode ListeTriee.__setitem__()")
    @unittest.skipIf(not hasattr(Dictionnaire, '__getitem__'), "Il manque la méthode ListeTriee.__getitem__()")
    @unittest.skipIf(not hasattr(Dictionnaire, 'reallocation'), "Il manque la méthode ListeTriee.reallocation()")
    def test_TableauDictionnaire_setitem_aveccollision(self):
        for _ in range(1000):
            d = Dictionnaire()
            maxkey = 10
            lst = [(random.randint(0, maxkey), random.randint(0, maxkey)) for _ in range(200)]
            random.shuffle(lst)

            for k, v in lst:
                d[k] = v
                self.assertEqual(d[k], v, f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]}")
                k = random.randint(2 * maxkey, 100 * maxkey)
                self.assertRaises(KeyError, d.__getitem__, k)
                # assert k in d

        # On provoque des collisions de clés sans nécessiter une réallocation
        listes = [[(3, 'a'), (13, 'b'), (23, 'c')],
                  [(0, 'aa'), (10, 'bb'), (20, 'cc')],
                  [(9, 'aaa'), (19, 'bbb'), (29, 'ccc')]]

        for lst in listes:
            d = Dictionnaire()
            dictsize = len(d._stockage)

            for k, v in lst:
                d[k] = v
                self.assertEqual(d[k], v, f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]}")
                self.assertEqual(dictsize, len(d._stockage), "Le dictionnaire a inutliement augmenté de taille")

            for k, v in lst:
                self.assertEqual(d[k], v, f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]}")
                self.assertEqual(dictsize, len(d._stockage), "Le dictionnaire a inutliement augmenté de taille")

    @unittest.skipIf(not hasattr(Dictionnaire, '__setitem__'), "Il manque la méthode ListeTriee.__setitem__()")
    @unittest.skipIf(not hasattr(Dictionnaire, '__getitem__'), "Il manque la méthode ListeTriee.__getitem__()")
    @unittest.skipIf(not hasattr(Dictionnaire, 'reallocation'), "Il manque la méthode ListeTriee.reallocation()")
    def test_TableauDictionnaire_reallocation(self):
        for _ in range(100):
            d = Dictionnaire()
            maxkey = 10
            lst = [(i, random.randint(0, maxkey)) for i in range(9)]
            random.shuffle(lst)
            dictsize = len(d._stockage)

            # On ajoute des (clé, valeur) dans le dictionnaire et on vérifie qu'on les y retrouve bien
            for k, v in lst:
                d[k] = v
                self.assertEqual(d[k], v, f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]}")
                k = random.randint(2 * maxkey, 100 * maxkey)
                self.assertRaises(KeyError, d.__getitem__, k)
                # assert k in d

            # On opère une réallocation et on vérifie que les mêmes (clé, valeur) y sont toujours.
            d.reallocation()
            self.assertEqual(dictsize * 2, len(d._stockage),
                             "Le dictionnaire n'a pas doublé de taille après réallocation")
            for k, v in lst:
                self.assertEqual(d[k], v, f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]}")
                k = random.randint(2 * maxkey, 100 * maxkey)
                self.assertRaises(KeyError, d.__getitem__, k)

    @unittest.skipIf(not hasattr(Dictionnaire, '__delitem__'), "Il manque la méthode ListeTriee.__delitem__()")
    def test_TableauDictionnaire_delete(self):

        lst, d = collision_Dictionnaire(100)

        self.assertEqual(lst, dictionnaire_to_list(d))
        for i in lst:
            self.assertEqual(d[i[0]], i[1])

        random.shuffle(lst)
        while lst:
            self.assertEqual(sorted(lst), dictionnaire_to_list(d))
            k, v = lst.pop()
            self.assertEqual(d[k], v,
                             f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]} pour {dictionnaire_to_list(d)}")
            del (d[k])
            self.assertRaises(KeyError, d.__getitem__, k)
            self.assertEqual(sorted(lst), dictionnaire_to_list(d))

            for _k, _v in lst:
                self.assertNotEqual((k, v), (_k, _v))
                try:
                    self.assertEqual(d[_k], _v,
                                     f"d[{_k}] devrait être égal à {_v}, mais est égal à {d[_k]} pour {lst}")
                except KeyError:
                    print(f"d[{_k}] devrait être égal à {_v}, pour {lst}")
                    exit()

        for _ in range(100):
            n = random.randint(1, 9)
            lst, d = random_Dictionnaire(n)
            lend = len(d._stockage)
            self.assertEqual(lst, dictionnaire_to_list(d))

            for k, v in lst:
                self.assertEqual(d[k], v)

            while lst:
                self.assertEqual(lst, dictionnaire_to_list(d))
                k, v = lst.pop()

                self.assertEqual(d[k], v,
                                 f"d[{k}] devrait être égal à {v}, mais est égal à {d[k]} pour {dictionnaire_to_list(d)}")
                del (d[k])
                self.assertRaises(KeyError, d.__getitem__, k)
                dico_list = dictionnaire_to_list(d)
                self.assertEqual(lst, dico_list)

                for _k, _v in lst:
                    self.assertNotEqual((k, v), (_k, _v))
                    try:
                        self.assertEqual(d[_k], _v,
                                         f"d[{_k}] devrait être égal à {_v}, mais est égal à {d[_k]} pour {lst}")
                    except KeyError:
                        print(f"d[{_k}] devrait être égal à {_v}, pour {lst}")
                        exit()
    @unittest.skipIf(not hasattr(Dictionnaire, '__contains__'), "Il manque la méthode ListeTriee.__contains__()")
    def test_TableauDictionnaire_contains(self):

        # Test de dictionnaire vide
        d = Dictionnaire()
        self.assertFalse(str(random.randint(0,100)) in d, "Aucune clé ne devrait être détectée dans un dictionnaire vide")

        # Test de collision
        lst, d = collision_Dictionnaire(5)
        for k, _ in lst:
            self.assertTrue(k in d, f"La clé {k} devrait être dans {dictionnaire_to_list(d)}")

        # Test de dictionnaire saturé avec 100% de collisions
        lst, d = collision_Dictionnaire(10)
        for k, _ in lst:
            self.assertTrue(k in d, f"La clé {k} devrait être dans {dictionnaire_to_list(d)}")

        # Test quelconque
        lst_in, d = random_Dictionnaire(500)
        lst_out = [(i[0]+f'_{random.random()}', random.random()) for i in lst_in]

        final_lst = lst_out + lst_in
        random.shuffle(final_lst)
        lst_in = [e[0] for e in lst_in]
        for e in final_lst:
            self.assertEqual(e[0] in d, e[0] in lst_in,
                             f'{e} est incorrectement identifié comme {"absent" if {e[0] in d} else "présent"} de {dictionnaire_to_list(d)}')

if __name__ == '__main__':
    unittest.main(verbosity=2)
