import pytest

from project import get_coin_to_flip, get_parameters, get_parity, is_power_of_two, solve


def main():
    test_get_coin_to_flip()
    test_get_parameters()
    test_get_parity()
    test_is_power_of_two()
    test_solve()


def test_get_coin_to_flip():
    for i in (4, 8, 16):
        initial_state, key_position, _ = get_parameters(board_side=i)
        c = get_coin_to_flip(initial_state=initial_state, key_position=key_position)
        assert 0 <= c < i**2
        final_state = solve(initial_state, c)
        assert get_parity(final_state) == key_position


def test_get_parameters():
    for i in (4, 8, 16):
        initial_state, key_position, coin_to_flip = get_parameters(board_side=i)
        assert len(initial_state) == i ** 2
        assert 0 <= key_position <= 2**i - 1
        assert 0 <= coin_to_flip <= 2**i - 1
    for i in range(3, 5, 7):
        with pytest.raises(ValueError):
            get_parameters(board_side=i)


def test_get_parity():
    assert get_parity('0100') == 1
    assert get_parity('0010') == 2
    assert get_parity('0' * 64) == 0
    assert get_parity('1' * 64) == 0
    assert get_parity('0000'
                      '0010'
                      '0000'
                      '0000') == 6
    assert get_parity('00111011'
                      '00001001'
                      '10010101'
                      '11011011'
                      '01001110'
                      '01110000'
                      '10001101'
                      '11101001') == 17


def test_is_power_of_two():
    assert not is_power_of_two(0)
    for i in range(1, 13):
        assert is_power_of_two(2**i)
        assert not is_power_of_two(2 ** i + 3)


def test_solve():
    for i in (4, 8, 16):
        initial_state, _, coin_to_flip = get_parameters(board_side=i)
        final_state = solve(initial_state=initial_state, coin_to_flip=coin_to_flip)
        for j, (c0, c1) in enumerate(zip(initial_state, final_state)):
            assert j == coin_to_flip or c0 == c1


if __name__ == '__main__':
    main()
