from eth2spec.utils.ssz.ssz_impl import hash_tree_root, serialize
from eth2spec.utils.ssz.ssz_typing import (
    uint, boolean,
    Bitlist, Bitvector, Container, Vector, List, Union
)


def encode(value, include_hash_tree_roots=False):
    if isinstance(value, uint):
        # Larger uints are boxed and the class declares their byte length
        if value.__class__.type_byte_length() > 8:
            return str(int(value))
        return int(value)
    elif isinstance(value, boolean):
        return value == 1
    elif isinstance(value, (Bitlist, Bitvector)):
        return '0x' + serialize(value).hex()
    elif isinstance(value, list):  # normal python lists
        return [encode(element, include_hash_tree_roots) for element in value]
    elif isinstance(value, (List, Vector)):
        return [encode(element, include_hash_tree_roots) for element in value]
    elif isinstance(value, bytes):  # bytes, ByteList, ByteVector
        return '0x' + value.hex()
    elif isinstance(value, Container):
        ret = {}
        for field_name in value.fields().keys():
            field_value = getattr(value, field_name)
            ret[field_name] = encode(field_value, include_hash_tree_roots)
            if include_hash_tree_roots:
                ret[field_name + "_hash_tree_root"] = '0x' + hash_tree_root(field_value).hex()
        if include_hash_tree_roots:
            ret["hash_tree_root"] = '0x' + hash_tree_root(value).hex()
        return ret
    elif isinstance(value, Union):
        inner_value = value.value()
        return {
            'selector': int(value.selector()),
            'value': None if inner_value is None else encode(inner_value, include_hash_tree_roots)
        }
    else:
        raise Exception(f"Type not recognized: value={value}, typ={type(value)}")
