from ssz_test_case import invalid_test_case, valid_test_case
from eth2spec.utils.ssz.ssz_typing import boolean, uint8, uint16, uint32, uint64, uint128, uint256, Vector, BasicView
from eth2spec.utils.ssz.ssz_impl import serialize
from random import Random
from typing import Dict, Type
from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object


def basic_vector_case_fn(rng: Random, mode: RandomizationMode, elem_type: Type[BasicView], length: int):
    return get_random_ssz_object(rng, Vector[elem_type, length],
                                 max_bytes_length=length * 8,
                                 max_list_length=length,
                                 mode=mode, chaos=False)


BASIC_TYPES: Dict[str, Type[BasicView]] = {
    'bool': boolean,
    'uint8': uint8,
    'uint16': uint16,
    'uint32': uint32,
    'uint64': uint64,
    'uint128': uint128,
    'uint256': uint256
}


def valid_cases():
    rng = Random(1234)
    for (name, typ) in BASIC_TYPES.items():
        random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max]
        if name != 'bool':
            random_modes.append(RandomizationMode.mode_random)
        for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]:
            for mode in random_modes:
                yield f'vec_{name}_{length}_{mode.to_name()}', \
                      valid_test_case(lambda: basic_vector_case_fn(rng, mode, typ, length))


def invalid_cases():
    # zero length vectors are illegal
    for (name, typ) in BASIC_TYPES.items():
        yield f'vec_{name}_0', invalid_test_case(lambda: b'')

    rng = Random(1234)
    for (name, typ) in BASIC_TYPES.items():
        random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max]
        if name != 'bool':
            random_modes.append(RandomizationMode.mode_random)
        for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]:
            yield f'vec_{name}_{length}_nil', invalid_test_case(lambda: b'')
            for mode in random_modes:
                if length == 1:
                    # empty bytes, no elements. It may seem valid, but empty fixed-size elements are not valid SSZ.
                    yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \
                          invalid_test_case(lambda: b"")
                else:
                    yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \
                          invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length - 1)))
                yield f'vec_{name}_{length}_{mode.to_name()}_one_more', \
                      invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length + 1)))
                yield f'vec_{name}_{length}_{mode.to_name()}_one_byte_less', \
                      invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length))[:-1])
                yield f'vec_{name}_{length}_{mode.to_name()}_one_byte_more', \
                      invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length))
                                        + serialize(basic_vector_case_fn(rng, mode, uint8, 1)))
