# Copyright 2009-2022 Joshua Bronson. All rights reserved.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

Test script for bidict.bidict:

    >>> from bidict import bidict
    >>> bi = bidict({1: 'one', 2: 'two', 3: 'three'})
    >>> bi
    bidict({1: 'one', 2: 'two', 3: 'three'})

Works like dict for getting and changing forward mappings:

    >>> bi[2]
    'two'
    >>> bi[2] = 'twain'
    >>> bi[2]
    'twain'
    >>> bi[4]
    Traceback (most recent call last):
      ...
    KeyError: 4
    >>> del bi[2]
    >>> bi.pop(3)
    'three'
    >>> bi.update({4: 'four'})
    >>> bi.popitem()
    (4, 'four')
    >>> bi
    bidict({1: 'one'})

``put`` can also be used to insert a mapping as long as its key and value
don't already exist:

    >>> bi.put(0, 'zero')
    >>> bi[0]
    'zero'
    >>> bi.put(1, 'aught')
    Traceback (most recent call last):
      ...
    bidict.KeyDuplicationError: 1
    >>> del bi[1]
    >>> bi.put(1, 'aught')
    >>> bi[1]
    'aught'
    >>> del bi[0]
    >>> bi
    bidict({1: 'aught'})

bidicts maintain references to their inverses via the ``inverse`` property,
which can also be used to access or modify them:

    >>> bi.inverse
    bidict({'aught': 1})
    >>> bi.inverse['aught']
    1
    >>> bi.inverse['aught'] = 'one'
    >>> bi
    bidict({'one': 'aught'})
    >>> bi.inverse.pop('aught')
    'one'
    >>> bi == bi.inverse == bidict()
    True
    >>> bi.inverse.update(one=1)
    >>> bi
    bidict({1: 'one'})
    >>> bi is bi.inverse.inverse
    True
    >>> bi.inverse is bi.inverse.inverse.inverse
    True

bidicts work with ``inverted`` as expected:

    >>> from bidict import inverted
    >>> biinv = bidict(inverted(bi))
    >>> biinv
    bidict({'one': 1})

This created a new object (equivalent but not identical):

    >>> biinv == bi.inverse
    True
    >>> biinv is bi.inverse
    False

Inverting the inverse should round-trip:

    >>> bi == bidict(inverted(inverted(bi)))
    True
    >>> bi = bi.inverse
    >>> bi == bidict(inverted(inverted(bi)))
    True

The rest of the ``MutableMapping`` interface is supported:

    >>> bi.get('one')
    1
    >>> bi.get('zero')
    >>> bi.get('zero', 'default')
    'default'
    >>> list(bi.keys())
    ['one']
    >>> list(bi.values())
    [1]
    >>> list(bi.items())
    [('one', 1)]
    >>> bi.setdefault('one', 2)
    1
    >>> bi.setdefault('two', 2)
    2
    >>> bi.pop('one')
    1
    >>> bi
    bidict({'two': 2})
    >>> bi.inverse
    bidict({2: 'two'})
    >>> bi.pop('no-such-key')
    Traceback (most recent call last):
      ...
    KeyError: 'no-such-key'
    >>> bi.pop('no-such-key', 'default')
    'default'
    >>> bi.pop('wrong', 'number', 'of', 'args')
    Traceback (most recent call last):
      ...
    TypeError: ...pop() takes from 2 to 3 positional arguments but 5 were given
    >>> bi.popitem()
    ('two', 2)
    >>> bi.popitem()
    Traceback (most recent call last):
      ...
    KeyError: 'popitem(): dictionary is empty'
    >>> bi.inverse.setdefault(3, 'three')
    'three'
    >>> bi
    bidict({'three': 3})
    >>> len(bi)  # calls __len__
    1
    >>> [key for key in bi]  # calls __iter__, returns keys like dict
    ['three']
    >>> 'three' in bi  # calls __contains__
    True
    >>> list(bi.keys())
    ['three']
    >>> list(bi.values())
    [3]
    >>> bi.update([('four', 4)])
    >>> bi.update({'five': 5}, six=6, seven=7)
    >>> sorted(bi.items(), key=lambda x: x[1])
    [('three', 3), ('four', 4), ('five', 5), ('six', 6), ('seven', 7)]
    >>> bi.clear()
    >>> bi
    bidict()

Empty updates are a no-op:

    >>> bi.update()
    >>> bi
    bidict()
    >>> bi.forceupdate()
    >>> bi
    bidict()

Initializing with wrong number of positional args is a TypeError:

    >>> bidict('wrong', 'number', 'of', 'args')
    Traceback (most recent call last):
      ...
    TypeError: Expected at most 1 positional argument, got 4

Initializing with different keys mapping to the same value fails:

    >>> bidict([(1, 1), (2, 1)])
    Traceback (most recent call last):
      ...
    bidict.ValueDuplicationError: 1

Adding a new key associated with an existing value fails:

    >>> b = bidict({1: 1})
    >>> b[2] = 1
    Traceback (most recent call last):
      ...
    bidict.ValueDuplicationError: 1
    >>> b.update({2: 1})
    Traceback (most recent call last):
      ...
    bidict.ValueDuplicationError: 1

``forceput`` and ``forceupdate`` can be used instead:

    >>> b.forceput(2, 1)
    >>> b
    bidict({2: 1})
    >>> b.forceupdate({1: 1})
    >>> b
    bidict({1: 1})

Trying to insert an existing mapping does not raise, and is a no-op:

    >>> b = bidict({1: 'one'})
    >>> b[1] = 'one'
    >>> b[1]
    'one'
    >>> b.inverse['one'] = 1
    >>> b.inverse['one']
    1

The following case does not half-succeed,
i.e. the bidict is not in an inconsistent state after:

    >>> b = bidict(one=1, two=2)
    >>> b['one'] = 2
    Traceback (most recent call last):
      ...
    bidict.KeyAndValueDuplicationError: ('one', 2)
    >>> len(b) == len(b.inverse)
    True

``put`` and ``putall`` allow you to have
per-call control over duplication behavior
(see doctests in ``../docs/unique-values.rst.inc``).

Even with RAISE duplication behavior,
inserting existing items is a no-op (i.e. it doesn't raise):

    >>> from bidict import RAISE, OnDup
    >>> b.putall(
    ...     [('three', 3), ('one', 1)],
    ...     OnDup(key=RAISE, val=RAISE)
    ... )  # does not raise an error because these items were already contained
    >>> b0 = b.copy()
    >>> b.putall([])  # no-op
    >>> b == b0
    True

Make sure copy.copy and copy.deepcopy create shallow and deep copies, respectively:

    >>> from copy import copy, deepcopy
    >>> from bidict import frozenbidict
    >>> b = frozenbidict({1: frozenbidict()})
    >>> c = copy(b)
    >>> d = deepcopy(b)
    >>> b == c == d
    True
    >>> b[1] is c[1]
    True
    >>> b[1] is d[1]
    False

Bidicts support PEP 584-style dict merge operators:

    >>> b = bidict({'one': 1})
    >>> b | {}
    bidict({'one': 1})
    >>> b | {'one': 1}
    bidict({'one': 1})
    >>> b | {'two': 2}
    bidict({'one': 1, 'two': 2})
    >>> b | {'two': 2} | {'three': 3}
    bidict({'one': 1, 'two': 2, 'three': 3})

    >>> b | {'one': 111}  # duplicate key -> succeeds, last one wins (as with dict.__or__)
    bidict({'one': 111})
    >>> {'one': 111} | b  # ditto
    bidict({'one': 1})

    >>> b | {'uno': 1}  # duplicate value -> ValueDuplicationError
    Traceback (most recent call last):
      ...
    bidict.ValueDuplicationError: 1

    >>> {'uno': 1} | b  # ditto
    Traceback (most recent call last):
      ...
    bidict.ValueDuplicationError: 1

    >>> b = bidict({'one': 1, 'two': 2})
    >>> b | {'one': 2}
    Traceback (most recent call last):
      ...
    bidict.KeyAndValueDuplicationError: ('one', 2)

    >>> b |= {'three': 3}  # in-place merge
    >>> b
    bidict({'one': 1, 'two': 2, 'three': 3})

    >>> b |= {'one': 111}  # in-place merge, duplicate key -> succeeds, last one wins
    >>> b
    bidict({'one': 111, 'two': 2, 'three': 3})

    >>> b |= {'dos': 2}  # duplicate value -> ValueDuplicationError
    Traceback (most recent call last):
      ...
    bidict.ValueDuplicationError: 2

    >>> b
    bidict({'one': 111, 'two': 2, 'three': 3})
    >>> b |= {'one': 3}
    Traceback (most recent call last):
      ...
    bidict.KeyAndValueDuplicationError: ('one', 3)

    >>> b | 1
    Traceback (most recent call last):
      ...
    TypeError: unsupported operand type(s) for |: 'bidict' and 'int'
    >>> 1 | b
    Traceback (most recent call last):
      ...
    TypeError: unsupported operand type(s) for |: 'int' and 'bidict'
