
from typing import Optional
from abc import ABC, abstractmethod

from .protocol_declaration import PacketType, FIRST_ROBOT_PACKET_ID
from .protocol_utils import BinaryReader, BinaryWriter
from .util import hex_dump


class Struct(ABC):

    @abstractmethod
    def __len__(self):
        raise NotImplementedError

    @abstractmethod
    def __repr__(self):
        raise NotImplementedError

    @abstractmethod
    def to_bytes(self) -> bytes:
        raise NotImplementedError

    @abstractmethod
    def to_writer(self, writer: BinaryWriter) -> None:
        raise NotImplementedError

    @classmethod
    @abstractmethod
    def from_bytes(cls, buffer: bytes):
        raise NotImplementedError

    @classmethod
    @abstractmethod
    def from_reader(cls, reader: BinaryReader):
        raise NotImplementedError


class Packet(Struct, ABC):

    __slots__ = (
        "_type",
        "_id",
        "seq",
        "ack",
    )

    def __init__(self, packet_type: PacketType, packet_id: Optional[int] = None):
        self.type = packet_type
        self.id = packet_id
        self.seq = 0
        self.ack = 0

    @property
    def type(self) -> PacketType:
        return self._type

    @type.setter
    def type(self, value: PacketType):
        self._type = PacketType(value)

    @property
    def id(self) -> Optional[int]:
        return self._id

    @id.setter
    def id(self, value: Optional[int]):
        self._id = value

    def is_oob(self) -> bool:
        res = self.type.value >= PacketType.EVENT.value
        return res

    def is_from_robot(self) -> bool:
        if self.id is not None:
            res = self.id >= FIRST_ROBOT_PACKET_ID
        else:
            res = self.type == PacketType.CONNECT or (self.type == PacketType.PING and self.seq > 0)
        return res

    def is_from_engine(self) -> bool:
        return not self.is_from_robot()


class UnknownPacket(Packet):

    __slots__ = (
        "_data",
    )

    def __init__(self, packet_type: PacketType, data: bytes, packet_id: Optional[int] = None):
        super().__init__(packet_type, packet_id)
        self.data = data

    @property
    def data(self) -> bytes:
        return self._data

    @data.setter
    def data(self, value: bytes):
        self._data = bytes(value)

    def __len__(self):
        return len(self._data)

    def __repr__(self):
        return "{type_name}({type:02x}, {data})".format(
            type=self.type.value, type_name=type(self).__name__, data=hex_dump(data=self._data))

    def to_bytes(self):
        writer = BinaryWriter()
        self.to_writer(writer)
        return writer.dumps()

    def to_writer(self, writer):
        writer.write_farray(self._data, "B", len(self._data))

    @classmethod
    def from_bytes(cls, buffer):
        # The size is not known.
        raise NotImplementedError

    @classmethod
    def from_reader(cls, reader):
        # The size is not known.
        raise NotImplementedError


class UnknownCommand(UnknownPacket):

    def __init__(self, packet_id: int, data: bytes = b""):
        super().__init__(PacketType.COMMAND, data, packet_id=packet_id)

    @classmethod
    def from_bytes(cls, buffer):
        # The size is not known.
        raise NotImplementedError

    @classmethod
    def from_reader(cls, reader):
        # The size is not known.
        raise NotImplementedError

    def __repr__(self):
        return "{type}({id:02x}, {data})".format(
            id=self.id, type=type(self).__name__, data=hex_dump(data=self._data))


class UnknownEvent(UnknownPacket):

    def __init__(self, packet_id: int, data: bytes = b""):
        super().__init__(PacketType.EVENT, data, packet_id=packet_id)

    @classmethod
    def from_bytes(cls, buffer):
        # The size is not known.
        raise NotImplementedError

    @classmethod
    def from_reader(cls, reader):
        # The size is not known.
        raise NotImplementedError

    def __repr__(self):
        return "{type}({id:02x}, {data})".format(
            id=self.id, type=type(self).__name__, data=hex_dump(data=self._data))
