import struct


class Unpacker(object):
    def __init__(self, buf):
        self._buffer = buf
        self._offset = 0
        self._typemethods = {'b': self.unpack_int8, 'B': self.unpack_uint8,
                             'h': self.unpack_int16, 'H': self.unpack_uint16,
                             'i': self.unpack_int32, 'I': self.unpack_uint32,
                             'q': self.unpack_int64, 'Q': self.unpack_uint64,
                             'f': self.unpack_float, 'd': self.unpack_double,
                             's': self.unpack_string, 'm': self.unpack_message,
                             }

    @property
    def offset(self):
        return self._offset

    def unpack_integer(self, fmt):
        value = struct.unpack_from(fmt, self._buffer, self._offset)
        self._offset = self._offset + struct.calcsize(fmt)
        return value[0]

    def unpack_int8(self):
        return self.unpack_integer('<b')

    def unpack_int16(self):
        return self.unpack_integer('<h')

    def unpack_int32(self):
        return self.unpack_integer('<l')

    def unpack_int64(self):
        return self.unpack_integer('<q')

    def unpack_uint8(self):
        return self.unpack_integer('<B')

    def unpack_uint16(self):
        return self.unpack_integer('<H')

    def unpack_uint32(self):
        return self.unpack_integer('<I')

    def unpack_uint64(self):
        return self.unpack_integer('<Q')

    def unpack_float(self):
        return float(self.unpack_string())

    def unpack_double(self):
        return float(self.unpack_string())

    def unpack_string(self):
        l = self.unpack_uint16()
        s = struct.unpack_from('%ds' % (l,), self._buffer, self._offset)
        self._offset = self._offset + l
        return s[0].decode('utf-8')

    def unpack_binary(self):
        l = self.unpack_uint32()
        s = struct.unpack_from('%ds' % (l,), self._buffer, self._offset)
        self._offset = self._offset + l
        return s[0].decode('utf-8')

    def unpack_message(self, msg):
        msg.unpack(self)

    def unpack_list(self, l):
        length = self.unpack_uint32()
        if l.typecode in l.typecodes[:-1]:
            for _ in range(0, length):
                value = self._typemethods[l.typecode]()
                l.append(value)
        elif l.typecode == l.typecodes[-1]:
            for _ in range(0, length):
                msg = l.type()
                self._typemethods[l.typecode](msg)
                l.append(msg)

    def unpack_set(self, s):
        length = self.unpack_uint32()
        if s.typecode in s.typecodes[:-1]:
            for _ in range(0, length):
                value = self._typemethods[s.typecode]()
                s.add(value)
        elif s.typecode == s.typecodes[-1]:
            for _ in range(0, length):
                msg = s.type()
                self._typemethods[s.typecode](msg)
                s.add(msg)

    def unpack_dict(self, d):
        length = self.unpack_uint32()
        for _ in range(0, length):
            # key
            key = self._typemethods[d.key_typecode]()
            # value
            if d.value_typecode in d.typecodes[:-1]:
                value = self._typemethods[d.value_typecode]()
            elif d.value_typecode == d.typecodes[-1]:
                value = d.value_type()
                self._typemethods[d.value_typecode](value)

            d[key] = value
