"""
Tests for allmydata.util.dictutil.

Ported to Python 3.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from future.utils import PY2, PY3
if PY2:
    # dict omitted to match dictutil.py.
    from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, list, object, range, str, max, min  # noqa: F401

from unittest import skipIf

from twisted.trial import unittest

from allmydata.util import dictutil


class DictUtil(unittest.TestCase):
    def test_dict_of_sets(self):
        ds = dictutil.DictOfSets()
        ds.add(1, "a")
        ds.add(2, "b")
        ds.add(2, "b")
        ds.add(2, "c")
        self.failUnlessEqual(ds[1], set(["a"]))
        self.failUnlessEqual(ds[2], set(["b", "c"]))
        ds.discard(3, "d") # should not raise an exception
        ds.discard(2, "b")
        self.failUnlessEqual(ds[2], set(["c"]))
        ds.discard(2, "c")
        self.failIf(2 in ds)

        ds.add(3, "f")
        ds2 = dictutil.DictOfSets()
        ds2.add(3, "f")
        ds2.add(3, "g")
        ds2.add(4, "h")
        ds.update(ds2)
        self.failUnlessEqual(ds[1], set(["a"]))
        self.failUnlessEqual(ds[3], set(["f", "g"]))
        self.failUnlessEqual(ds[4], set(["h"]))

    def test_auxdict(self):
        d = dictutil.AuxValueDict()
        # we put the serialized form in the auxdata
        d.set_with_aux("key", ("filecap", "metadata"), "serialized")

        self.failUnlessEqual(list(d.keys()), ["key"])
        self.failUnlessEqual(d["key"], ("filecap", "metadata"))
        self.failUnlessEqual(d.get_aux("key"), "serialized")
        def _get_missing(key):
            return d[key]
        self.failUnlessRaises(KeyError, _get_missing, "nonkey")
        self.failUnlessEqual(d.get("nonkey"), None)
        self.failUnlessEqual(d.get("nonkey", "nonvalue"), "nonvalue")
        self.failUnlessEqual(d.get_aux("nonkey"), None)
        self.failUnlessEqual(d.get_aux("nonkey", "nonvalue"), "nonvalue")

        d["key"] = ("filecap2", "metadata2")
        self.failUnlessEqual(d["key"], ("filecap2", "metadata2"))
        self.failUnlessEqual(d.get_aux("key"), None)

        d.set_with_aux("key2", "value2", "aux2")
        self.failUnlessEqual(sorted(d.keys()), ["key", "key2"])
        del d["key2"]
        self.failUnlessEqual(list(d.keys()), ["key"])
        self.failIf("key2" in d)
        self.failUnlessRaises(KeyError, _get_missing, "key2")
        self.failUnlessEqual(d.get("key2"), None)
        self.failUnlessEqual(d.get_aux("key2"), None)
        d["key2"] = "newvalue2"
        self.failUnlessEqual(d.get("key2"), "newvalue2")
        self.failUnlessEqual(d.get_aux("key2"), None)

        d = dictutil.AuxValueDict({1:2,3:4})
        self.failUnlessEqual(sorted(d.keys()), [1,3])
        self.failUnlessEqual(d[1], 2)
        self.failUnlessEqual(d.get_aux(1), None)

        d = dictutil.AuxValueDict([ (1,2), (3,4) ])
        self.failUnlessEqual(sorted(d.keys()), [1,3])
        self.failUnlessEqual(d[1], 2)
        self.failUnlessEqual(d.get_aux(1), None)

        d = dictutil.AuxValueDict(one=1, two=2)
        self.failUnlessEqual(sorted(d.keys()), ["one","two"])
        self.failUnlessEqual(d["one"], 1)
        self.failUnlessEqual(d.get_aux("one"), None)


class TypedKeyDict(unittest.TestCase):
    """Tests for dictionaries that limit keys."""

    @skipIf(PY2, "Python 2 doesn't have issues mixing bytes and unicode.")
    def setUp(self):
        pass

    def test_bytes(self):
        """BytesKeyDict is limited to just byte keys."""
        self.assertRaises(TypeError, dictutil.BytesKeyDict, {u"hello": 123})
        d = dictutil.BytesKeyDict({b"123": 200})
        with self.assertRaises(TypeError):
            d[u"hello"] = "blah"
        with self.assertRaises(TypeError):
            d[u"hello"]
        with self.assertRaises(TypeError):
            del d[u"hello"]
        with self.assertRaises(TypeError):
            d.setdefault(u"hello", "123")
        with self.assertRaises(TypeError):
            d.get(u"xcd")

        # Byte keys are fine:
        self.assertEqual(d, {b"123": 200})
        d[b"456"] = 400
        self.assertEqual(d[b"456"], 400)
        del d[b"456"]
        self.assertEqual(d.get(b"456", 50), 50)
        self.assertEqual(d.setdefault(b"456", 300), 300)
        self.assertEqual(d[b"456"], 300)

    def test_unicode(self):
        """UnicodeKeyDict is limited to just unicode keys."""
        self.assertRaises(TypeError, dictutil.UnicodeKeyDict, {b"hello": 123})
        d = dictutil.UnicodeKeyDict({u"123": 200})
        with self.assertRaises(TypeError):
            d[b"hello"] = "blah"
        with self.assertRaises(TypeError):
            d[b"hello"]
        with self.assertRaises(TypeError):
            del d[b"hello"]
        with self.assertRaises(TypeError):
            d.setdefault(b"hello", "123")
        with self.assertRaises(TypeError):
            d.get(b"xcd")

        # Byte keys are fine:
        self.assertEqual(d, {u"123": 200})
        d[u"456"] = 400
        self.assertEqual(d[u"456"], 400)
        del d[u"456"]
        self.assertEqual(d.get(u"456", 50), 50)
        self.assertEqual(d.setdefault(u"456", 300), 300)
        self.assertEqual(d[u"456"], 300)


class TypedKeyDictPython2(unittest.TestCase):
    """Tests for dictionaries that limit keys on Python 2."""

    @skipIf(PY3, "Testing Python 2 behavior.")
    def test_python2(self):
        """
        On Python2, BytesKeyDict and UnicodeKeyDict are unnecessary, because
        dicts can mix both without problem so you don't get confusing behavior
        if you get the type wrong.

        Eventually in a Python 3-only world mixing bytes and unicode will be
        bad, thus the existence of these classes, but as we port there will be
        situations where it's mixed on Python 2, which again is fine.
        """
        self.assertIs(dictutil.UnicodeKeyDict, dict)
        self.assertIs(dictutil.BytesKeyDict, dict)
        # Demonstration of how bytes and unicode can be mixed:
        d = {u"abc": 1}
        self.assertEqual(d[b"abc"], 1)
