import unittest

from immutables.map import Map as PyMap, map_bitcount


class CollisionKey:
    def __hash__(self):
        return 0


class Issue24Base:
    Map = None

    def test_issue24(self):
        keys = range(27)
        new_entries = dict.fromkeys(keys, True)
        m = self.Map(new_entries)
        self.assertTrue(17 in m)
        with m.mutate() as mm:
            for i in keys:
                del mm[i]
            self.assertEqual(len(mm), 0)

    def dump_check_node_kind(self, header, kind):
        header = header.strip()
        self.assertTrue(header.strip().startswith(kind))

    def dump_check_node_size(self, header, size):
        node_size = header.split('size=', 1)[1]
        node_size = int(node_size.split(maxsplit=1)[0])
        self.assertEqual(node_size, size)

    def dump_check_bitmap_count(self, header, count):
        header = header.split('bitmap=')[1]
        bitmap = int(header.split(maxsplit=1)[0], 0)
        self.assertEqual(map_bitcount(bitmap), count)

    def dump_check_bitmap_node_count(self, header, count):
        self.dump_check_node_kind(header, 'Bitmap')
        self.dump_check_node_size(header, count * 2)
        self.dump_check_bitmap_count(header, count)

    def dump_check_collision_node_count(self, header, count):
        self.dump_check_node_kind(header, 'Collision')
        self.dump_check_node_size(header, 2 * count)

    def test_bitmap_node_update_in_place_count(self):
        keys = range(7)
        new_entries = dict.fromkeys(keys, True)
        m = self.Map(new_entries)
        d = m.__dump__().splitlines()
        self.assertTrue(d)
        if d[0].startswith('HAMT'):
            header = d[1] # skip _map.Map.__dump__() header
        else:
            header = d[0]
        self.dump_check_bitmap_node_count(header, 7)

    def test_bitmap_node_delete_in_place_count(self):
        keys = range(7)
        new_entries = dict.fromkeys(keys, True)
        m = self.Map(new_entries)
        with m.mutate() as mm:
            del mm[0], mm[2], mm[3]
            m2 = mm.finish()
        d = m2.__dump__().splitlines()
        self.assertTrue(d)
        if d[0].startswith('HAMT'):
            header = d[1] # skip _map.Map.__dump__() header
        else:
            header = d[0]
        self.dump_check_bitmap_node_count(header, 4)

    def test_collision_node_update_in_place_count(self):
        keys = (CollisionKey() for i in range(7))
        new_entries = dict.fromkeys(keys, True)
        m = self.Map(new_entries)
        d = m.__dump__().splitlines()
        self.assertTrue(len(d) > 3)
        # get node headers
        if d[0].startswith('HAMT'):
            h1, h2 = d[1], d[3] # skip _map.Map.__dump__() header
        else:
            h1, h2 = d[0], d[2]
        self.dump_check_node_kind(h1, 'Bitmap')
        self.dump_check_collision_node_count(h2, 7)

    def test_collision_node_delete_in_place_count(self):
        keys = [CollisionKey() for i in range(7)]
        new_entries = dict.fromkeys(keys, True)
        m = self.Map(new_entries)
        with m.mutate() as mm:
            del mm[keys[0]], mm[keys[2]], mm[keys[3]]
            m2 = mm.finish()
        d = m2.__dump__().splitlines()
        self.assertTrue(len(d) > 3)
        # get node headers
        if d[0].startswith('HAMT'):
            h1, h2 = d[1], d[3] # skip _map.Map.__dump__() header
        else:
            h1, h2 = d[0], d[2]
        self.dump_check_node_kind(h1, 'Bitmap')
        self.dump_check_collision_node_count(h2, 4)

try:
    from immutables._map import Map as CMap
except ImportError:
    CMap = None


class Issue24PyTest(Issue24Base, unittest.TestCase):
    Map = PyMap


@unittest.skipIf(CMap is None, 'C Map is not available')
class Issue24CTest(Issue24Base, unittest.TestCase):
    Map = CMap

    def hamt_dump_check_first_return_second(self, m):
        d = m.__dump__().splitlines()
        self.assertTrue(len(d) > 2)
        self.assertTrue(d[0].startswith('HAMT'))
        return d[1]

    def test_array_node_update_in_place_count(self):
        keys = range(27)
        new_entries = dict.fromkeys(keys, True)
        m = self.Map(new_entries)
        header = self.hamt_dump_check_first_return_second(m)
        self.dump_check_node_kind(header, 'Array')
        for i in range(2, 18):
            m = m.delete(i)
        header = self.hamt_dump_check_first_return_second(m)
        self.dump_check_bitmap_node_count(header, 11)

    def test_array_node_delete_in_place_count(self):
        keys = range(27)
        new_entries = dict.fromkeys(keys, True)
        m = self.Map(new_entries)
        header = self.hamt_dump_check_first_return_second(m)
        self.dump_check_node_kind(header, 'Array')
        with m.mutate() as mm:
            for i in range(5):
                del mm[i]
            m2 = mm.finish()
        header = self.hamt_dump_check_first_return_second(m2)
        self.dump_check_node_kind(header, 'Array')
        for i in range(6, 17):
            m2 = m2.delete(i)
        header = self.hamt_dump_check_first_return_second(m2)
        self.dump_check_bitmap_node_count(header, 11)


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