import ctypes
import unittest

from immutables.map import map_hash, map_mask, Map as PyMap
from immutables._testutils import HashKey


none_hash = map_hash(None)
assert none_hash != 1
assert none_hash.bit_length() <= 32

none_hash_u = ctypes.c_size_t(none_hash).value
not_collision = 0xffffffff & (~none_hash_u)

mask = 0x7ffffffff
none_collisions = [none_hash_u & (mask >> shift)
                   for shift in reversed(range(0, 32, 5))]
assert len(none_collisions) == 7
none_collisions = [
    ctypes.c_ssize_t(h | (not_collision & (mask << shift))).value
    for shift, h in zip(range(5, 37, 5), none_collisions)
]


class NoneCollision(HashKey):
    def __init__(self, name, level):
        if name is None:
            raise ValueError("Can't have a NoneCollision with a None value")
        super().__init__(none_collisions[level], name)

    def __eq__(self, other):
        if other is None:
            return False
        return super().__eq__(other)

    __hash__ = HashKey.__hash__


class BaseNoneTest:
    Map = None

    def test_none_collisions(self):
        collisions = [NoneCollision('a', level) for level in range(7)]
        indices = [map_mask(none_hash, shift) for shift in range(0, 32, 5)]

        for i, c in enumerate(collisions[:-1], 1):
            self.assertNotEqual(c, None)
            c_hash = map_hash(c)
            self.assertNotEqual(c_hash, none_hash)
            for j, idx in enumerate(indices[:i]):
                self.assertEqual(map_mask(c_hash, j*5), idx)
            for j, idx in enumerate(indices[i:], i):
                self.assertNotEqual(map_mask(c_hash, j*5), idx)

        c = collisions[-1]
        self.assertNotEqual(c, None)
        c_hash = map_hash(c)
        self.assertEqual(c_hash, none_hash)
        for i, idx in enumerate(indices):
            self.assertEqual(map_mask(c_hash, i*5), idx)

    def test_none_as_key(self):
        m = self.Map({None: 1})

        self.assertEqual(len(m), 1)
        self.assertTrue(None in m)
        self.assertEqual(m[None], 1)
        self.assertEqual(repr(m), 'immutables.Map({None: 1})')

        for level in range(7):
            key = NoneCollision('a', level)
            self.assertFalse(key in m)
            with self.assertRaises(KeyError):
                m.delete(key)

        m = m.delete(None)
        self.assertEqual(len(m), 0)
        self.assertFalse(None in m)
        self.assertEqual(repr(m), 'immutables.Map({})')

        self.assertEqual(m, self.Map())

        with self.assertRaises(KeyError):
            m.delete(None)

    def test_none_set(self):
        m = self.Map().set(None, 2)

        self.assertEqual(len(m), 1)
        self.assertTrue(None in m)
        self.assertEqual(m[None], 2)

        m = m.set(None, 1)

        self.assertEqual(len(m), 1)
        self.assertTrue(None in m)
        self.assertEqual(m[None], 1)

        m = m.delete(None)

        self.assertEqual(len(m), 0)
        self.assertEqual(m, self.Map())
        self.assertFalse(None in m)

        with self.assertRaises(KeyError):
            m.delete(None)

    def test_none_collision_1(self):
        for level in range(7):
            key = NoneCollision('a', level)
            m = self.Map({None: 1, key: 2})

            self.assertEqual(len(m), 2)
            self.assertTrue(None in m)
            self.assertEqual(m[None], 1)
            self.assertTrue(key in m)
            self.assertEqual(m[key], 2)

            m2 = m.delete(None)
            self.assertEqual(len(m2), 1)
            self.assertTrue(key in m2)
            self.assertEqual(m2[key], 2)
            self.assertFalse(None in m2)
            with self.assertRaises(KeyError):
                m2.delete(None)

            m3 = m2.delete(key)
            self.assertEqual(len(m3), 0)
            self.assertFalse(None in m3)
            self.assertFalse(key in m3)
            self.assertEqual(m3, self.Map())
            self.assertEqual(repr(m3), 'immutables.Map({})')
            with self.assertRaises(KeyError):
                m3.delete(None)
            with self.assertRaises(KeyError):
                m3.delete(key)

            m2 = m.delete(key)
            self.assertEqual(len(m2), 1)
            self.assertTrue(None in m2)
            self.assertEqual(m2[None], 1)
            self.assertFalse(key in m2)
            with self.assertRaises(KeyError):
                m2.delete(key)

            m4 = m2.delete(None)
            self.assertEqual(len(m4), 0)
            self.assertFalse(None in m4)
            self.assertFalse(key in m4)
            self.assertEqual(m4, self.Map())
            self.assertEqual(repr(m4), 'immutables.Map({})')
            with self.assertRaises(KeyError):
                m4.delete(None)
            with self.assertRaises(KeyError):
                m4.delete(key)

            self.assertEqual(m3, m4)

    def test_none_collision_2(self):
        key = HashKey(not_collision, 'a')
        m = self.Map().set(None, 1).set(key, 2)

        self.assertEqual(len(m), 2)
        self.assertTrue(key in m)
        self.assertTrue(None in m)
        self.assertEqual(m[key], 2)
        self.assertEqual

        m = m.set(None, 0)
        self.assertEqual(len(m), 2)
        self.assertTrue(key in m)
        self.assertTrue(None in m)

        for level in range(7):
            key2 = NoneCollision('b', level)
            self.assertFalse(key2 in m)
            m2 = m.set(key2, 1)

            self.assertEqual(len(m2), 3)
            self.assertTrue(key in m2)
            self.assertTrue(None in m2)
            self.assertTrue(key2 in m2)
            self.assertEqual(m2[key], 2)
            self.assertEqual(m2[None], 0)
            self.assertEqual(m2[key2], 1)

            m2 = m2.set(None, 1)
            self.assertEqual(len(m2), 3)
            self.assertTrue(key in m2)
            self.assertTrue(None in m2)
            self.assertTrue(key2 in m2)
            self.assertEqual(m2[key], 2)
            self.assertEqual(m2[None], 1)
            self.assertEqual(m2[key2], 1)

            m2 = m2.set(None, 2)
            self.assertEqual(len(m2), 3)
            self.assertTrue(key in m2)
            self.assertTrue(None in m2)
            self.assertTrue(key2 in m2)
            self.assertEqual(m2[key], 2)
            self.assertEqual(m2[None], 2)
            self.assertEqual(m2[key2], 1)

            m3 = m2.delete(key)
            self.assertEqual(len(m3), 2)
            self.assertTrue(None in m3)
            self.assertTrue(key2 in m3)
            self.assertFalse(key in m3)
            self.assertEqual(m3[None], 2)
            self.assertEqual(m3[key2], 1)
            with self.assertRaises(KeyError):
                m3.delete(key)

            m3 = m2.delete(key2)
            self.assertEqual(len(m3), 2)
            self.assertTrue(None in m3)
            self.assertTrue(key in m3)
            self.assertFalse(key2 in m3)
            self.assertEqual(m3[None], 2)
            self.assertEqual(m3[key], 2)
            with self.assertRaises(KeyError):
                m3.delete(key2)

            m3 = m2.delete(None)
            self.assertEqual(len(m3), 2)
            self.assertTrue(key in m3)
            self.assertTrue(key2 in m3)
            self.assertFalse(None in m3)
            self.assertEqual(m3[key], 2)
            self.assertEqual(m3[key2], 1)
            with self.assertRaises(KeyError):
                m3.delete(None)

        m2 = m.delete(None)
        self.assertEqual(len(m2), 1)
        self.assertFalse(None in m2)
        self.assertTrue(key in m2)
        self.assertEqual(m2[key], 2)
        with self.assertRaises(KeyError):
            m2.delete(None)

        m2 = m.delete(key)
        self.assertEqual(len(m2), 1)
        self.assertFalse(key in m2)
        self.assertTrue(None in m2)
        self.assertEqual(m2[None], 0)
        with self.assertRaises(KeyError):
            m2.delete(key)

    def test_none_collision_3(self):
        for level in range(7):
            key = NoneCollision('a', level)
            m = self.Map({key: 2})

            self.assertEqual(len(m), 1)
            self.assertFalse(None in m)
            self.assertTrue(key in m)
            self.assertEqual(m[key], 2)
            with self.assertRaises(KeyError):
                m.delete(None)

            m = m.set(None, 1)
            self.assertEqual(len(m), 2)
            self.assertTrue(key in m)
            self.assertEqual(m[key], 2)
            self.assertTrue(None in m)
            self.assertEqual(m[None], 1)

            m = m.set(None, 0)
            self.assertEqual(len(m), 2)
            self.assertTrue(key in m)
            self.assertEqual(m[key], 2)
            self.assertTrue(None in m)
            self.assertEqual(m[None], 0)

            m2 = m.delete(key)
            self.assertEqual(len(m2), 1)
            self.assertTrue(None in m2)
            self.assertEqual(m2[None], 0)
            self.assertFalse(key in m2)
            with self.assertRaises(KeyError):
                m2.delete(key)

            m2 = m.delete(None)
            self.assertEqual(len(m2), 1)
            self.assertTrue(key in m2)
            self.assertEqual(m2[key], 2)
            self.assertFalse(None in m2)
            with self.assertRaises(KeyError):
                m2.delete(None)

    def test_collision_4(self):
        key2 = NoneCollision('a', 2)
        key4 = NoneCollision('b', 4)
        m = self.Map({key2: 2, key4: 4})

        self.assertEqual(len(m), 2)
        self.assertTrue(key2 in m)
        self.assertTrue(key4 in m)
        self.assertEqual(m[key2], 2)
        self.assertEqual(m[key4], 4)
        self.assertFalse(None in m)

        m2 = m.set(None, 9)

        self.assertEqual(len(m2), 3)
        self.assertTrue(key2 in m2)
        self.assertTrue(key4 in m2)
        self.assertTrue(None in m2)
        self.assertEqual(m2[key2], 2)
        self.assertEqual(m2[key4], 4)
        self.assertEqual(m2[None], 9)

        m3 = m2.set(None, 0)
        self.assertEqual(len(m3), 3)
        self.assertTrue(key2 in m3)
        self.assertTrue(key4 in m3)
        self.assertTrue(None in m3)
        self.assertEqual(m3[key2], 2)
        self.assertEqual(m3[key4], 4)
        self.assertEqual(m3[None], 0)

        m3 = m2.set(key2, 0)
        self.assertEqual(len(m3), 3)
        self.assertTrue(key2 in m3)
        self.assertTrue(key4 in m3)
        self.assertTrue(None in m3)
        self.assertEqual(m3[key2], 0)
        self.assertEqual(m3[key4], 4)
        self.assertEqual(m3[None], 9)

        m3 = m2.set(key4, 0)
        self.assertEqual(len(m3), 3)
        self.assertTrue(key2 in m3)
        self.assertTrue(key4 in m3)
        self.assertTrue(None in m3)
        self.assertEqual(m3[key2], 2)
        self.assertEqual(m3[key4], 0)
        self.assertEqual(m3[None], 9)

        m3 = m2.delete(None)
        self.assertEqual(m3, m)
        self.assertEqual(len(m3), 2)
        self.assertTrue(key2 in m3)
        self.assertTrue(key4 in m3)
        self.assertEqual(m3[key2], 2)
        self.assertEqual(m3[key4], 4)
        self.assertFalse(None in m3)
        with self.assertRaises(KeyError):
            m3.delete(None)

        m3 = m2.delete(key2)
        self.assertEqual(len(m3), 2)
        self.assertTrue(None in m3)
        self.assertTrue(key4 in m3)
        self.assertEqual(m3[None], 9)
        self.assertEqual(m3[key4], 4)
        self.assertFalse(key2 in m3)
        with self.assertRaises(KeyError):
            m3.delete(key2)

        m3 = m2.delete(key4)
        self.assertEqual(len(m3), 2)
        self.assertTrue(None in m3)
        self.assertTrue(key2 in m3)
        self.assertEqual(m3[None], 9)
        self.assertEqual(m3[key2], 2)
        self.assertFalse(key4 in m3)
        with self.assertRaises(KeyError):
            m3.delete(key4)

    def test_none_mutation(self):
        key2 = NoneCollision('a', 2)
        key4 = NoneCollision('b', 4)
        key = NoneCollision('c', -1)
        m = self.Map({key: -1, key2: 2, key4: 4, None: 9})

        with m.mutate() as mm:
            self.assertEqual(len(mm), 4)
            self.assertTrue(key in mm)
            self.assertTrue(key2 in mm)
            self.assertTrue(key4 in mm)
            self.assertTrue(None in mm)
            self.assertEqual(mm[key2], 2)
            self.assertEqual(mm[key4], 4)
            self.assertEqual(mm[key], -1)
            self.assertEqual(mm[None], 9)

            for k in m:
                mm[k] = -mm[k]

            self.assertEqual(len(mm), 4)
            self.assertTrue(key in mm)
            self.assertTrue(key2 in mm)
            self.assertTrue(key4 in mm)
            self.assertTrue(None in mm)
            self.assertEqual(mm[key2], -2)
            self.assertEqual(mm[key4], -4)
            self.assertEqual(mm[key], 1)
            self.assertEqual(mm[None], -9)

            for k in m:
                del mm[k]
                self.assertEqual(len(mm), 3)
                self.assertFalse(k in mm)
                for n in m:
                    if n != k:
                        self.assertTrue(n in mm)
                        self.assertEqual(mm[n], -m[n])
                with self.assertRaises(KeyError):
                    del mm[k]
                mm[k] = -m[k]
                self.assertEqual(len(mm), 4)
                self.assertTrue(k in mm)
                self.assertEqual(mm[k], -m[k])

            for k in m:
                mm[k] = -mm[k]

            self.assertEqual(len(mm), 4)
            self.assertTrue(key in mm)
            self.assertTrue(key2 in mm)
            self.assertTrue(key4 in mm)
            self.assertTrue(None in mm)
            self.assertEqual(mm[key2], 2)
            self.assertEqual(mm[key4], 4)
            self.assertEqual(mm[key], -1)
            self.assertEqual(mm[None], 9)

            for k in m:
                mm[k] = -mm[k]

            self.assertEqual(len(mm), 4)
            self.assertTrue(key in mm)
            self.assertTrue(key2 in mm)
            self.assertTrue(key4 in mm)
            self.assertTrue(None in mm)
            self.assertEqual(mm[key2], -2)
            self.assertEqual(mm[key4], -4)
            self.assertEqual(mm[key], 1)
            self.assertEqual(mm[None], -9)

            m2 = mm.finish()

        self.assertEqual(set(m), set(m2))
        self.assertEqual(len(m2), 4)
        self.assertTrue(key in m2)
        self.assertTrue(key2 in m2)
        self.assertTrue(key4 in m2)
        self.assertTrue(None in m2)
        self.assertEqual(m2[key2], -2)
        self.assertEqual(m2[key4], -4)
        self.assertEqual(m2[key], 1)
        self.assertEqual(m2[None], -9)

        for k, v in m.items():
            self.assertTrue(k in m2)
            self.assertEqual(m2[k], -v)

    def test_iterators(self):
        key2 = NoneCollision('a', 2)
        key4 = NoneCollision('b', 4)
        key = NoneCollision('c', -1)
        m = self.Map({key: -1, key2: 2, key4: 4, None: 9})

        self.assertEqual(len(m), 4)
        self.assertTrue(key in m)
        self.assertTrue(key2 in m)
        self.assertTrue(key4 in m)
        self.assertTrue(None in m)
        self.assertEqual(m[key2], 2)
        self.assertEqual(m[key4], 4)
        self.assertEqual(m[key], -1)
        self.assertEqual(m[None], 9)

        s = set(m)
        self.assertEqual(len(s), 4)
        self.assertEqual(s, set([None, key, key2, key4]))

        sk = set(m.keys())
        self.assertEqual(s, sk)

        sv = set(m.values())
        self.assertEqual(len(sv), 4)
        self.assertEqual(sv, set([-1, 2, 4, 9]))

        si = set(m.items())
        self.assertEqual(len(si), 4)
        self.assertEqual(si,
                         set([(key, -1), (key2, 2), (key4, 4), (None, 9)]))

        d = {key: -1, key2: 2, key4: 4, None: 9}
        self.assertEqual(dict(m.items()), d)


class PyMapNoneTest(BaseNoneTest, unittest.TestCase):

    Map = PyMap


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


@unittest.skipIf(CMap is None, 'C Map is not available')
class CMapNoneTest(BaseNoneTest, unittest.TestCase):

    Map = CMap


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