import random

import pytest
from clarabot_nano_common.utility.algorithm import dict_nested_get

from clarabot_nano.utility.algorithm import (
    chain_call,
    multi_islice,
    encode_interleaved_int,
    decode_interleaved_int,
)


def test_chain_call_single():
    def x(a):
        return a + 1

    def y(a):
        return a - 2

    assert chain_call([x, x, x], 5) == 8
    assert chain_call([y, y, y], 5) == -1
    assert chain_call([x, y, x], 5) == 5
    assert chain_call([y, x, y], 5) == 2


def test_chain_call_multi():
    def x(a, b):
        return a + 1, a + b

    def y(a, b):
        return a - 2, a + b

    assert chain_call([x, x, x], 5, 10) == (8, 28)
    assert chain_call([y, y, y], 5, 10) == (-1, 19)
    assert chain_call([x, y, x], 5, 10) == (5, 25)
    assert chain_call([y, x, y], 5, 10) == (2, 22)


def test_multi_islice():
    x = list(range(100, 120))

    def _mi(*args):
        return [list(s) for s in multi_islice(*args)]

    assert _mi(x, [], 0, 0) == []
    assert _mi(x, [], 0, 1) == [x]
    assert _mi(x, [], 0, 2) == [x]

    assert _mi(x, [5], 0, 0) == []
    assert _mi(x, [5], 0, 1) == [x[:5]]
    assert _mi(x, [5], 0, 2) == [x[:5], x[5:]]
    assert _mi(x, [5], 0, 3) == [x[:5], x[5:]]
    assert _mi(x, [5], 1, 1) == []
    assert _mi(x, [5], 1, 2) == [x[5:]]
    assert _mi(x, [5], 1, 3) == [x[5:]]

    assert _mi(x, [5, 8], 0, 0) == []
    assert _mi(x, [5, 8], 0, 1) == [x[:5]]
    assert _mi(x, [5, 8], 0, 2) == [x[:5], x[5:8]]
    assert _mi(x, [5, 8], 0, 3) == [x[:5], x[5:8], x[8:]]
    assert _mi(x, [5, 8], 0, 4) == [x[:5], x[5:8], x[8:]]
    assert _mi(x, [5, 8], 1, 1) == []
    assert _mi(x, [5, 8], 1, 2) == [x[5:8]]
    assert _mi(x, [5, 8], 1, 3) == [x[5:8], x[8:]]
    assert _mi(x, [5, 8], 1, 4) == [x[5:8], x[8:]]
    assert _mi(x, [5, 8], 2, 2) == []
    assert _mi(x, [5, 8], 2, 3) == [x[8:]]
    assert _mi(x, [5, 8], 2, 4) == [x[8:]]


def test_dict_nested_get():
    assert dict_nested_get({"s": 2}, "s") == 2
    with pytest.raises(KeyError):
        dict_nested_get({"s": 2}, "d")
    assert dict_nested_get({"s": {8: "t"}}, "s") == {8: "t"}
    assert dict_nested_get({"s": {8: "t"}}, "s", 8) == "t"
    with pytest.raises(KeyError):
        dict_nested_get({"s": {8: "t"}}, "s", 9)
    with pytest.raises(KeyError):
        dict_nested_get({"s": {8: "t"}}, "s", 8, "5")


def test_interleaved_int():
    # NOTE: python impl can handle arbitrary int size, but 15 bits is safe for sure for both web (JavaScript) and
    # irrelevant from signed or unsigned.
    bit_max = 2**15 - 1
    safe_max = 2**30 - 1
    for a, b, i in [(0, 0, 0), (bit_max, bit_max, safe_max)]:
        assert encode_interleaved_int(a, b) == i
        assert decode_interleaved_int(i) == (a, b)
    for _ in range(10000):
        a = random.randint(0, bit_max)
        b = random.randint(0, bit_max)
        i = encode_interleaved_int(a, b)
        assert 0 <= i <= safe_max
        if a == b:
            assert encode_interleaved_int(b, a) == i
        else:
            j = encode_interleaved_int(b, a)
            assert j != i
            assert decode_interleaved_int(j) == (b, a)
        assert decode_interleaved_int(i) == (a, b)
