from trezor.utils import obj_eq, obj_repr

from apps.monero.xmr.serialize.base_types import XmrType
from apps.monero.xmr.serialize.int_serialize import (
    dump_uint,
    dump_uvarint,
    load_uint,
    load_uvarint,
)


class UnicodeType(XmrType):
    """
    Unicode data in UTF-8 encoding.
    """

    @staticmethod
    def dump(writer, s):
        dump_uvarint(writer, len(s))
        writer.write(bytes(s))

    @staticmethod
    def load(reader):
        ivalue = load_uvarint(reader)
        fvalue = bytearray(ivalue)
        reader.readinto(fvalue)
        return str(fvalue)


class BlobType(XmrType):
    """
    Binary data, represented as bytearray.  BlobType is only a scheme
    descriptor.  Behaves in the same way as primitive types.
    """

    FIX_SIZE = 0
    SIZE = 0

    @classmethod
    def dump(cls, writer, elem: bytes):
        if cls.FIX_SIZE:
            if cls.SIZE != len(elem):
                raise ValueError("Size mismatch")
        else:
            dump_uvarint(writer, len(elem))
        writer.write(elem)

    @classmethod
    def load(cls, reader) -> bytearray:
        if cls.FIX_SIZE:
            size = cls.SIZE
        else:
            size = load_uvarint(reader)
        elem = bytearray(size)
        reader.readinto(elem)
        return elem


class ContainerType(XmrType):
    """
    Array of elements, represented as a list of items.  ContainerType is only a
    scheme descriptor.
    """

    FIX_SIZE = 0
    SIZE = 0
    ELEM_TYPE = None

    @classmethod
    def dump(cls, writer, elems, elem_type=None):
        if elem_type is None:
            elem_type = cls.ELEM_TYPE
        if cls.FIX_SIZE:
            if cls.SIZE != len(elems):
                raise ValueError("Size mismatch")
        else:
            dump_uvarint(writer, len(elems))
        for elem in elems:
            elem_type.dump(writer, elem)

    @classmethod
    def load(cls, reader, elem_type=None):
        if elem_type is None:
            elem_type = cls.ELEM_TYPE
        if cls.FIX_SIZE:
            size = cls.SIZE
        else:
            size = load_uvarint(reader)
        elems = []
        for _ in range(size):
            elem = elem_type.load(reader)
            elems.append(elem)
        return elems


class VariantType(XmrType):
    """
    Union of types, differentiated by variant tags. VariantType is only a scheme
    descriptor.
    """

    @classmethod
    def dump(cls, writer, elem):
        for field in cls.f_specs():
            ftype = field[1]
            if isinstance(elem, ftype):
                break
        else:
            raise ValueError("Unrecognized variant: %s" % elem)

        dump_uint(writer, ftype.VARIANT_CODE, 1)
        ftype.dump(writer, elem)

    @classmethod
    def load(cls, reader):
        tag = load_uint(reader, 1)
        for field in cls.f_specs():
            ftype = field[1]
            if ftype.VARIANT_CODE == tag:
                fvalue = ftype.load(reader)
                break
        else:
            raise ValueError("Unknown tag: %s" % tag)
        return fvalue

    @classmethod
    def f_specs(cls):
        return ()


class MessageType(XmrType):
    """
    Message composed of fields with specific types.
    """

    def __init__(self, **kwargs):
        for kw in kwargs:
            setattr(self, kw, kwargs[kw])

    __eq__ = obj_eq
    __repr__ = obj_repr

    @classmethod
    def dump(cls, writer, msg):
        defs = cls.f_specs()
        for field in defs:
            fname, ftype, *fparams = field
            fvalue = getattr(msg, fname, None)
            ftype.dump(writer, fvalue, *fparams)

    @classmethod
    def load(cls, reader):
        msg = cls()
        defs = cls.f_specs()
        for field in defs:
            fname, ftype, *fparams = field
            fvalue = ftype.load(reader, *fparams)
            setattr(msg, fname, fvalue)
        return msg

    @classmethod
    def f_specs(cls):
        return ()
