import enum

import zigpy.types


def deserialize(data, schema):
    result = []
    for type_ in schema:
        value, data = type_.deserialize(data)
        result.append(value)
    return result, data


def serialize(data, schema):
    return b''.join(t(v).serialize() for t, v in zip(schema, data))


class Bytes(bytes):
    def serialize(self):
        return self

    @classmethod
    def deserialize(cls, data):
        return cls(data), b''


class ATCommand(Bytes):
    @classmethod
    def deserialize(cls, data):
        return cls(data[:2]), data[2:]


class int_t(int):
    _signed = True

    def serialize(self):
        return self.to_bytes(self._size, 'big', signed=self._signed)

    @classmethod
    def deserialize(cls, data):
        # Work around https://bugs.python.org/issue23640
        r = cls(int.from_bytes(data[:cls._size], 'big', signed=cls._signed))
        data = data[cls._size:]
        return r, data


class int8s(int_t):
    _size = 1


class int16s(int_t):
    _size = 2


class int24s(int_t):
    _size = 3


class int32s(int_t):
    _size = 4


class int40s(int_t):
    _size = 5


class int48s(int_t):
    _size = 6


class int56s(int_t):
    _size = 7


class int64s(int_t):
    _size = 8


class uint_t(int_t):
    _signed = False


class uint8_t(uint_t):
    _size = 1


class uint16_t(uint_t):
    _size = 2


class uint24_t(uint_t):
    _size = 3


class uint32_t(uint_t):
    _size = 4


class uint40_t(uint_t):
    _size = 5


class uint48_t(uint_t):
    _size = 6


class uint56_t(uint_t):
    _size = 7


class uint64_t(uint_t):
    _size = 8


class Bool(uint8_t, enum.Enum):
    # Boolean type with values true and false.

    false = 0x00  # An alias for zero, used for clarity.
    true = 0x01  # An alias for one, used for clarity.


class EUI64(zigpy.types.EUI64):
    @classmethod
    def deserialize(cls, data):
        r, data = super().deserialize(data)
        return cls(r[::-1]), data

    def serialize(self):
        assert self._length == len(self)
        return b''.join([i.serialize() for i in self])


class UndefinedEnumMeta(enum.EnumMeta):
    def __call__(cls, value=None, *args, **kwargs):
        if value is None:
            # the 1st enum member is default
            return next(iter(cls))

        try:
            return super().__call__(value, *args, **kwargs)
        except ValueError as exc:
            try:
                return super().__call__(cls._UNDEFINED)
            except AttributeError:
                raise exc


class UndefinedEnum(enum.Enum, metaclass=UndefinedEnumMeta):
    pass


class NWK(uint16_t):
    def __repr__(self):
        return '0x{:04x}'.format(self)

    def __str__(self):
        return '0x{:04x}'.format(self)
