# coding: utf-8

from __future__ import print_function

import sys
import os
import string
import random
import pytest
py3 = sys.version_info >= (3,)
if not py3:
    import cPickle

from ruamel.ordereddict import ordereddict, sorteddict

all_lowercase = 'abcdefghijklmnopqrstuvwxyz'
all_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

class TestBase:
    def setup_method(self, method):
        self.x = ordereddict()
        self.x['a'] = 1
        self.x['b'] = 2
        self.x['c'] = 3
        self.x['d'] = 4
        self.z = ordereddict()
        for index, ch in enumerate(all_lowercase):
            self.z[ch] = index
        self.e = ordereddict()
        self.part = ordereddict((
            ('f', 5), ('g', 6), ('h', 7), ('i', 8), ('j', 9)))
        self.upperlower = ordereddict([('A', 1), ('a', 2), ('B', 4), ('b', 4)])


class TestOrderedDict(TestBase):
    def setup_method(self, method):
        self.x = ordereddict()
        self.x['a'] = 1
        self.x['b'] = 2
        self.x['c'] = 3
        self.x['d'] = 4
        self.z = ordereddict()
        for index, ch in enumerate(all_lowercase):
            self.z[ch] = index
        self.e = ordereddict()
        self.part = ordereddict((
            ('f', 5), ('g', 6), ('h', 7), ('i', 8), ('j', 9)))
        self.upperlower = ordereddict([('A', 1), ('a', 2), ('B', 4), ('b', 4)])

    def test_od_setitems(self):
        r = self.z
        r.setitems([('f', 5), ('g', 6), ('h', 7), ('i', 8), ('j', 9)])
        assert r == self.part

    def test_sd_init_from_seq(self):
        r = sorteddict((('j', 9), ('g', 6), ('i', 8), ('h', 7), ('f', 5)))
        assert r == self.part

    def test_sd_setitems(self):
        r = sorteddict()
        r['b'] = 1
        r['c'] = 2
        r['d'] = 3
        r['f'] = 5
        r['g'] = 6
        r['h'] = 7
        r['i'] = 8
        r['a'] = 0
        r['j'] = 9
        r['l'] = 11
        r['k'] = 10
        r['m'] = 12
        r['n'] = 13
        r['o'] = 14
        r['p'] = 15
        r['q'] = 16
        r['s'] = 18
        r['v'] = 21
        r['w'] = 22
        r['x'] = 23
        r['y'] = 24
        r['z'] = 25
        r['e'] = 4
        r['r'] = 17
        r['t'] = 19
        r['u'] = 20
        assert r == self.z

    def test_len(self):
        assert len(self.z) == len(all_lowercase)
        assert len(self.e) == 0

    def test_brackets(self):
        assert self.x['b'] == 2

    def test_del(self):
        self.x['1234'] = 1234
        del self.x['1234']
        assert self.x.get('1234') is None

    def test_clear(self):
        x = ordereddict()
        x['a'] = 1
        x['b'] = 2
        x['c'] = 3
        x['d'] = 4
        assert len(x) == 4
        x.clear()
        assert len(x) == 0

    def test_copy(self):
        x = self.x.copy()
        assert len(x) == 4
        assert x['c'] == 3
        x['c'] = 4
        assert self.x['c'] == 3

    def test_sd_copy(self):
        x1 = sorteddict(self.z)
        x = x1.copy()
        assert len(x) == 26
        assert x['c'] == 2
        x['c'] = 4
        assert self.x['c'] == 3
        assert x['c'] == 4

    def test_sd_lower(self):
        r = sorteddict(self.upperlower)
        assert r != self.upperlower
        assert r.index('a') == 2
        rl = sorteddict(self.upperlower,
                        key=str.lower if py3 else string.lower)
        assert rl == self.upperlower

    def test_in(self):
        assert 'c' in self.z

    def test_not_in(self):
        assert 'C' not in self.z

    def test_has_key(self):
        assert 'z' in self.z

    def test_items(self):
        "unlikely to function in a non-ordered dictionary"
        index = 0
        for index, y in enumerate(self.z.items()):
            assert all_lowercase[index] == y[0]
            assert index == y[1]

    def test_keys(self):
        "unlikely to function in a non-ordered dictionary"
        index = 0
        for y in self.z.keys():
            assert self.z[y] == index
            index += 1

    def test_update(self):
        y = ordereddict()
        y[1] = 'x'
        y['b'] = 'abc'
        y[3] = 'xx'
        y.update(self.x)
        assert len(y) == 6
        yval = [t for t in y.values()]
        xval = [t for t in self.x.values()]
        assert yval[1] == xval[1]
        assert yval[3] == xval[0]
        assert yval[4] == xval[2]
        assert yval[5] == xval[3]

    def test_sd_fromkeys(self):
        x = sorteddict.fromkeys([1, 2, 3, 4, 5, 6])
        assert len(x) == 6
        assert x[6] is None
        x = sorteddict.fromkeys((1, 2, 3, 4, 5), 'abc')
        assert len(x) == 5
        assert x[5] == 'abc'

    def test_fromkeys(self):
        x = ordereddict.fromkeys([1, 2, 3, 4, 5, 6])
        assert len(x) == 6
        assert x[6] is None
        x = ordereddict.fromkeys((1, 2, 3, 4, 5), 'abc')
        assert len(x) == 5
        assert x[5] == 'abc'
        for i, j  in enumerate( x.keys() ):
            assert i + 1 == j

    def test_values(self):
        "unlikely to function in a non-ordered dictionary"
        index = 0
        for y in self.z.values():
            assert y == index
            index += 1

    def test_values_rev(self):
        index = 25
        if py3:
            for y in reversed(list(self.z.values())):
                assert y == index
                index -= 1
        else:
            for y in self.z.values(reverse=True):
                assert y == index
                index -= 1

    def test_get1(self):
        assert self.x.get('b') == 2

    def test_get2(self):
        assert self.x.get('A') is None

    def test_get3(self):
        assert self.x.get('A', 'abc') == 'abc'

    def test_dict3(self):
        assert self.x.get('B', 'hello') == "hello"

    def test_setdefault(self):
        y = self.x.copy()
        res = y.setdefault('c', 42)
        assert res == 3
        assert y['c'] == 3
        res = y.setdefault('ab', 42)
        assert res == 42
        assert y['ab'] == 42

    def test_pop(self):
        y = self.x.copy()
        assert y.pop('b') == 2
        assert y.pop('y', 42) == 42
        assert len(y) == 3
        with pytest.raises(KeyError):
            y.pop('x')

    def test_popitem(self):
        y = self.x.copy()
        assert y.popitem() == ('d', 4)
        assert y.popitem(1) == ('b', 2)
        assert y.popitem(-2) == ('a', 1)
        with pytest.raises(KeyError):
            y.popitem(1)

###################

    def test_deepcopy(self):
        import copy
        y = self.x.copy()
        z = self.x.copy()
        y['r'] = z
        dc = copy.deepcopy(y)
        assert y['r']['d'] == 4
        y['r']['d'] = 5
        assert y['r']['d'] == 5
        assert dc['r']['d'] == 4

    def test_init1(self):
        y = ordereddict(self.x)
        y['b'] = 42
        assert self.x['b'] == 2
        assert y['c'] == 3
        assert y['b'] == 42

    def test_init2(self):
        a = {'a': 1}
        with pytest.raises(TypeError):
            y = ordereddict(a)

    def test_init3(self):
        y = ordereddict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
        assert y == self.x

    def test_compare_wrong_order(self):
        y = ordereddict([('a', 1), ('b', 2), ('d', 4), ('c', 3)])
        assert y != self.x

    def test_compare_wrong_value(self):
        y = ordereddict([('a', 1), ('b', 2), ('c', 4), ('d', 3)])
        assert y != self.x

    def test_compare(self):
        y = ordereddict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
        assert y == self.x

    def test_index(self):
        assert self.x.index('c') == 2
        with pytest.raises(ValueError):
            self.x.index('1')

###################

    def test_dict4(self):
        self.walk('b', 2)

    def test_dict5(self):
        with pytest.raises(KeyError):
            self.walk('ba', 999)

    def walk(self, key, val):
        for y in self.x:
            if y == key:
                assert self.x[y] == val
                break
        else:
            raise KeyError

    def test_walk_ordereddict(self):
        index = 0
        for y in self.z:
            assert self.z[y] == index
            index += 1

    def test_repr(self):
        d = ordereddict()
        assert repr(d) == 'ordereddict([])'
        d['a'] = 1
        assert repr(d) == "ordereddict([('a', 1)])"
        d[2] = 'b'
        assert repr(d) == "ordereddict([('a', 1), (2, 'b')])"

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_insert_newitem(self):
        r = self.x.copy()
        r.insert(3, 'ca', 8)
        assert r.index('ca') == 3
        assert r.get('ca') == 8
        assert len(r) == 5

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_insert_existing_key_sameplace(self):
        r = self.z
        pos = r.index('k')
        r.insert(pos, 'k', 42)
        assert r.index('k') == pos
        assert r.get('k') == 42
        assert len(r) == len(all_lowercase)

    #@pytest.mark.skipif(sys.version_info >= (3,),
    #                    reason="broken")
    def test_reverse(self):
        r = self.z
        r.reverse()
        res = []
        for index, ch in enumerate(all_lowercase):
            assert r[ch] == index
            res.insert(0, ch)
        if py3:
            assert res == [x for x in r.keys()]
        else:
            assert res == r.keys()

    def test_consequitive_slice(self):
        r = self.z
        assert r[5:10] == self.part
        assert r[-2:] == ordereddict([('y', 24), ('z', 25)])
        assert r[-4:-2] == ordereddict([('w', 22), ('x', 23)])
        assert r[:] == self.z

    def test_slice(self):
        r = self.z
        rp = self.part.copy()
        rp.reverse()
        assert r[9:4:-1] == rp
        assert r[5:25:5] == ordereddict(
            [('f', 5), ('k', 10), ('p', 15), ('u', 20)])

    def test_del_consequitive_slice(self):
        r = self.z
        del r[3:24]
        assert r == ordereddict(
            [('a', 0), ('b', 1), ('c', 2), ('y', 24), ('z', 25)])

    def test_del_non_consequitive_slice(self):
        r = self.z
        del r[4:24:2]
        t = ordereddict()
        for index, ch in enumerate(all_lowercase):
            if ch not in 'abcdfhjlnprtvxyz':
                continue
            t[ch] = index
        # print(r)
        # print(t)
        assert r == t

    def test_sd_slice(self):
        r = sorteddict(self.z)
        # print(r)
        y = r[3:6]
        # print(y)
        assert y['d'] == 3
        assert y['e'] == 4
        assert y['f'] == 5

    def test_sd_del_consequitive_slice(self):
        r = sorteddict(self.z)
        with pytest.raises(TypeError):
            del r[3:24]

    def test_sd_del_non_consequitive_slice(self):
        r = sorteddict(self.z)
        with pytest.raises(TypeError):
            del r[4:24:2]

    def test_del_rev_non_consequitive_slice(self):
        r = self.z
        del r[22:3:-2]
        t = ordereddict()
        for index, ch in enumerate(all_lowercase):
            if ch not in 'abcdfhjlnprtvxyz':
                continue
            t[ch] = index
        # print(r)
        # print(t)
        assert r == t

    def test_rename(self):
        if py3:
            print(type(self.x))
        self.x.rename('c', 'caaa')
        assert self.x == ordereddict(
            [('a', 1), ('b', 2), ('caaa', 3), ('d', 4)])

    def test_sd_rename(self):
        x = sorteddict(self.x)
        with pytest.raises(TypeError):
            x.rename('c', 'caaa')

    def test_setvalues(self):
        r1 = self.z[:5]
        r2 = self.z[:5]
        for k in r1:
            r1[k] = r1[k] + 100
        r2.setvalues([100, 101, 102, 103, 104])
        assert r1 == r2
        with pytest.raises(ValueError):
            r2.setvalues([100, 101, 102, 103])
        with pytest.raises(ValueError):
            r2.setvalues([100, 101, 102, 103, 104, 105])
        # we don't know length upfront
        r2.setvalues((x for x in [100, 101, 102, 103, 104]))
        with pytest.raises(ValueError):
            r2.setvalues((x for x in [100, 101, 102, 103]))
        with pytest.raises(ValueError):
            r2.setvalues((x for x in [100, 101, 102, 103, 104, 105]))

    def test_setkeys(self):
        self.x[42] = 'abc'
        r1 = self.x.copy()
        r2 = ordereddict(
            [('d', 4), ('c', 3), ('a', 1), (42, 'abc'), ('b', 2), ])
        r1.setkeys(('d', 'c', 'a', 42, 'b',))
        assert r1 == r2
        with pytest.raises(KeyError):
            r1.setkeys(('d', 'e', 'a', 42, 'b',))
        with pytest.raises(KeyError):
            r1.setkeys(('d', 42, 'a', 42, 'b',))
        with pytest.raises(ValueError):
            r1.setkeys(('d', 'c', 'a', 42, 'b', 'a',))
        with pytest.raises(ValueError):
            r1.setkeys(('g', 'c', 'a', 42,))

    def test_sd_setkeys(self):
        x = sorteddict.fromkeys((1, 2, 3, 4, 5), 'abc')
        with pytest.raises(TypeError):
            x.setkeys((5, 3, 1, 2, 4,))

    def test_setitems(self):
        r = self.z
        r.setitems([('f', 5), ('g', 6), ('h', 7), ('i', 8), ('j', 9)])
        assert r == self.part

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_pickle(self):
        if py3:
            return
        fname = 'tmpdata.pkl'
        r = self.z.copy()
        r[(1, 2)] = self.x
        fp = open(fname, 'wb')
        cPickle.dump(r, fp)
        fp.close()
        s = cPickle.load(open(fname, 'rb'))
        assert s == r
        os.remove(fname)

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_pickle_kvio(self):
        fname = 'tmpdata.pkl'
        r = ordereddict(self.z, kvio=True)
        fp = open(fname, 'wb')
        cPickle.dump(r, fp)
        fp.close()
        s = cPickle.load(open(fname, 'rb'))
        assert s == r
        r['k'] = 42
        s['k'] = 42
        assert s == r
        os.remove(fname)

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_sd_pickle(self):
        fname = 'tmpdata.pkl'
        r = sorteddict(self.z)
        fp = open(fname, 'wb')
        cPickle.dump(r, fp)
        fp.close()
        s = cPickle.load(open(fname, 'rb'))
        assert s == r
        s['k'] = 10
        assert s == self.z
        os.remove(fname)

    def test_kvio1(self):
        r = ordereddict(kvio=True)
        r.update(self.x)
        r['b'] = 42
        assert r == ordereddict([('a', 1), ('c', 3), ('d', 4), ('b', 42)])

    def test_kvio2(self):
        r = ordereddict(kvio=True)
        r.update(self.x)
        r['b'] = 2
        assert r != self.x
        r.update([('c', 3), ('d', 4)])
        assert r == self.x

    def test_kvio_copy(self):
        r = ordereddict(kvio=True)
        r.update(self.x)
        r['b'] = 2
        r1 = r.copy()
        assert r1 != self.x
        r1.update([('c', 3), ('d', 4)])
        assert r1 == self.x

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_relax(self):
        nd = dict(z=1, y=2, w=3, v=4, x=5)
        with pytest.raises(TypeError):
            r = ordereddict(nd)
        r = ordereddict(nd, relax=True)
        assert nd.keys() == r.keys()
        assert nd.values() == r.values()

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_relax_class(self):
        class relaxed_ordereddict(ordereddict):
            def __init__(self, *args, **kw):
                kw['relax'] = True
                ordereddict.__init__(self, *args, **kw)

        nd = dict(z=1, y=2, w=3, v=4, x=5)
        r = relaxed_ordereddict(nd)
        assert nd.keys() == r.keys()
        assert nd.values() == r.values()

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_relax_update(self):
        d = ordereddict()
        nd = dict(z=1, y=2, w=3, v=4, x=5)
        with pytest.raises(TypeError):
            d.update(nd)
        d.update(nd, relax=True)
        assert len(d) == 5

    def _test_order(self):
        nd = dict(z=1, y=2, w=3, v=4, x=5)
        r = ordereddict(nd, key=True)
        assert nd.keys() == r.keys()
        assert nd.values() == r.values()

    @pytest.mark.skipif(sys.version_info >= (3,),
                        reason="broken")
    def test_subclass_sorted(self):  # found thanks to Sam Pointon
        class SD(sorteddict):
            pass
        s = SD({0: 'foo', 2: 'bar'})
        s[1] = 'abc'
        # print(s.items())
        assert s.items() == [(0, 'foo'), (1, 'abc'), (2, 'bar')]

    def test_subclass_sorted_repr(self):  # found thanks to Sam Pointon
        class SD(sorteddict):
            pass
        s = SD({0: 'foo', 2: 'bar'})
        x = repr(s)
        assert x == "sorteddict([(0, 'foo'), (2, 'bar')])"

    def test_deepcopy2(self):  # found thanks to Alexandre Andrade
        import copy
        import gc
        d = ordereddict([(1, 2)])
        gc.collect()
        none_refcount = sys.getrefcount(None)
        if py3:
            for i in range(10):
                copy.deepcopy(d)
                gc.collect()
        else:
            for i in xrange(10):
                copy.deepcopy(d)
                gc.collect()
        assert none_refcount == sys.getrefcount(None)

#############################
    def _test_alloc_many(self):
        res = []
        times = 1000
        while times > 0:
            times -= 1
            td = ordereddict()
            count = 100000
            while count > 0:
                td['ab%08d' % count] = dict(abcdef='%09d' % (count))
                count -= 1
            count = 100000
            while count > 0:
                del td['ab%08d' % count]
                count -= 1
            res.append(td)

@pytest.mark.skipif(sys.version_info >= (3,),
                    reason="broken")
class TestInsertDelete(TestBase):
    def test_insert_existing_key_before(self):
        r = self.z
        pos = r.index('k')
        r.insert(pos-3, 'k', 42)
        assert r.index('k') == pos - 3
        assert r.get('k') == 42
        assert len(r) == len(all_lowercase)

    def test_insert_existing_key_after(self):
        r = self.z
        pos = r.index('k')
        r.insert(pos+3, 'k', 42)
        assert r.index('k') == pos + 3
        assert r.get('k') == 42
        assert len(r) == len(all_lowercase)

    def test_insert_existing_non_last_key_beyond(self):
        r = self.z
        pos = r.index('y')
        pos += 1
        r.insert(pos, 'y', 42)
        assert r.index('y') == pos
        assert r.get('y') == 42
        assert len(r) == len(all_lowercase)

    def test_insert_existing_last_key_at_end(self):
        r = self.z
        pos = r.index('z')
        r.insert(pos, 'z', 42)
        assert r.index('z') == pos
        assert r.get('z') == 42
        assert len(r) == len(all_lowercase)

    def test_insert_existing_last_key_beyond_end(self):
        r = self.z
        pos = r.index('z')
        r.insert(pos + 1, 'z', 42)
        assert r.index('z') == pos
        assert r.get('z') == 42
        assert len(r) == len(all_lowercase)

    def test_insert_range(self):
        r = ordereddict()
        r['c'] = 3
        r.insert(0, 'b', 2)
        r.insert(-10, 'a', 1)
        r.insert(3, 'd', 4)
        r.insert(10, 'e', 5)
        self.x['e'] = 5
        assert r == self.x

    # this failed
    def test_multiple_inserts_then_deletes(self):
        d = ordereddict()

        for i in range(1, 6):
            d.insert(i-1, i, 'a')

        for i in range(1, 6):
            del d[i]

        d.insert(0, 0, 'will this crash?')
        #print d.items()

    # this failed
    def test_repeated_add_delete(self):
        d = ordereddict()
        for el in range(0, 9):
            d[el] = el
            del d[el]


@pytest.mark.skipif(sys.version_info >= (3,),
                   reason="broken")
class TestSlice(TestBase):
    def test_ass_non_consequitive_slice_wrong_size(self):
        r = self.z
        with pytest.raises(ValueError):
            r[10:20:2] = ordereddict([(1, 0), (2, 1), (3, 2), ])

    def test_ass_non_consequitive_slice_wrong_type(self):
        r = self.z
        with pytest.raises(TypeError):
            r[10:20:2] = dict([(1, 0), (2, 1), (3, 2),  (4, 24), (5, 25)])

    def test_ass_non_consequitive_slice(self):
        r = self.z
        r[10:15:2] = ordereddict([(1, 0), (2, 1), (3, 2), ])
        #print(r[9:16])
        #print(ordereddict([('j', 9), (1, 0), ('l', 11), (2, 1),
        #                               ('n', 13), (3, 2), ('p', 15)]))
        assert r[9:16] == ordereddict([('j', 9), (1, 0), ('l', 11), (2, 1),
                                       ('n', 13), (3, 2), ('p', 15)])

    def test_sd_ass_non_consequitive_slice(self):
        r = sorteddict(self.z)
        with pytest.raises(TypeError):
            r[10:15:2] = ordereddict([(1, 0), (2, 1), (3, 2), ])

    def test_ass_reverse_non_consequitive_slice(self):
        r = self.z
        r[14:9:-2] = ordereddict([(3, 2), (2, 1), (1, 0), ])
        assert len(r) == 26
        #print(r[9:16])
        #print(ordereddict([('j', 9), (1, 0), ('l', 11), (2, 1),
        #                               ('n', 13), (3, 2), ('p', 15)]))
        assert r[9:16] == ordereddict([('j', 9), (1, 0), ('l', 11), (2, 1),
                                       ('n', 13), (3, 2), ('p', 15)])

    def test_ass_consequitive_slice_wrong_size(self):
        r = self.z
        with pytest.raises(ValueError):
            r[10:15] = ordereddict([(1, 0), (2, 1), (3, 2), ])

    def test_ass_consequitive_slice_wrong_type(self):
        r = self.z
        with pytest.raises(TypeError):
            r[10:15] = dict([(1, 0), (2, 1), (3, 2),  (4, 24), (5, 25)])

    def test_ass_consequitive_slice(self):
        r = self.z
        r[10:15] = ordereddict([(1, 0), (2, 1), (3, 2), (4, 24), (5, 25)])
        assert r[9:16] == ordereddict([('j', 9), (1, 0), (2, 1), (3, 2),
                                       (4, 24), (5, 25), ('p', 15)])

    def test_sd_ass_consequitive_slice(self):
        r = sorteddict(self.z)
        with pytest.raises(TypeError):
            r[10:15] = ordereddict([(1, 0), (2, 1), (3, 2), (4, 24), (5, 25)])


