import math
import struct


class _StructBasedType(object):
    def __init__(self, fmt):
        self.struct = struct.Struct(fmt)

    def sizeof(self, v):
        return self.struct.size

    def marshal(self, v, f):
        f.write(self.struct.pack(v))
        return f

    def unmarshal(self, f, size):
        assert size == self.struct.size, \
               "{} != {}".format(size, self.struct.size)
        v = f.read(self.struct.size)
        b, = self.struct.unpack(v)
        return b


Bool = _StructBasedType('!?')

Int8 = _StructBasedType('!b')
Int16 = _StructBasedType('!h')
Int32 = _StructBasedType('!l')
Int64 = _StructBasedType('!q')

UInt8 = _StructBasedType('!B')
UInt16 = _StructBasedType('!H')
UInt32 = _StructBasedType('!L')
UInt64 = _StructBasedType('!Q')

Float32 = _StructBasedType('!f')
Float64 = _StructBasedType('!d')


class _UIntVar(object):
    @staticmethod
    def sizeof(v):
        # log128(v)
        # = log(v) / log(128)
        # = log(v) / log(2) / 7
        # = log2(v) / 7
        #
        # v.bit_length() = 1 + int(log2(abs(x))), so with some handwaving we get
        # the equation below.
        return (v.bit_length() - 1) // 7 + 1 if v != 0 else 1

    @staticmethod
    def marshal(v, f):
        while v > 0b01111111:
            UInt8.marshal(0b10000000 | (v & 0b01111111), f)
            v >>= 7
        UInt8.marshal(v, f)
        return f

    @staticmethod
    def _unmarshal_or_none(f):
        v = 0
        e = 0
        did_read = False

        while True:
            c = f.read(1)
            if not c:
                if not did_read:
                    return None
                else:
                    raise ValueError('failed to unmarshal uintvar')
            did_read = True
            b, = UInt8.struct.unpack(c)
            v += (b & 0b01111111) << e
            if b & 0b10000000 != 0:
                e += 7
            else:
                break

        return v

    @classmethod
    def unmarshal(cls, f, size=None):
        v = cls._unmarshal_or_none(f)
        if v is None:
            raise ValueError('failed to unmarshal uintvar')
        assert size is None or cls.sizeof(v) == size
        return v



class Nil(object):
    @staticmethod
    def sizeof(v):
        return 0

    @staticmethod
    def marshal(v, f):
        return f

    @staticmethod
    def unmarshal(f, size):
        assert size == 0, "{} != 0".format(size)
        return None


class String(object):
    @staticmethod
    def sizeof(v):
        return Bytes.sizeof(v.encode('utf-8'))

    @staticmethod
    def marshal(v, f):
        Bytes.marshal(v.encode('utf-8'), f)
        return f

    @staticmethod
    def unmarshal(f, size):
        return Bytes.unmarshal(f, size).decode('utf-8')


class Bytes(object):
    @staticmethod
    def sizeof(v):
        return len(v)

    @staticmethod
    def marshal(v, f):
        f.write(v)
        return f

    @staticmethod
    def unmarshal(f, size):
        return f.read(size)


class _Proxy(object):
    def __init__(self, thunk):
        object.__setattr__(self, '_thunk', lambda: self._make_trampoline(thunk))

    @classmethod
    def _make_trampoline(cls, thunk):
        v = thunk()
        while isinstance(v, cls):
            v = v._thunk()
        return v

    def __call__(self, *args, **kwargs):
        return self._thunk()(*args, **kwargs)

    def __getattr__(self, k):
        return getattr(self._thunk(), k)

    def __setattr__(self, k, v):
        setattr(self._thunk(), k, v)

    def __delattr__(self, k):
        delattr(self._thunk(), k)

    def __instancecheck__(self, v):
        return isinstance(v, self._thunk())

    def __subclasscheck__(self, c):
        return issubclass(c, self._thunk())

    def __repr__(self):
        return repr(self._thunk())


class _StaticCase(object):
    def __init__(self, type_name, name, number):
        self.type_name = type_name
        self.name = name
        self.number = number

    def sizeof(self):
        return _UIntVar.sizeof(self.number)

    def marshal(self, f):
        _UIntVar.marshal(self.number, f)
        return f

    def _unmarshal_body(self, f, size):
        Nil.unmarshal(f, size)
        return self

    def __repr__(self):
        return self.type_name + '.' + self.name
