# File generated from <stdin>, with the command:
#  /home/henrichataing/Projects/github/google/pdl/pdl-compiler/scripts/generate_python_backend.py
# /!\ Do not edit by hand.
from dataclasses import dataclass, field, fields
from typing import Optional, List, Tuple, Union
import enum
import inspect
import math

@dataclass
class Packet:
    payload: Optional[bytes] = field(repr=False, default_factory=bytes, compare=False)

    @classmethod
    def parse_all(cls, span: bytes) -> 'Packet':
        packet, remain = getattr(cls, 'parse')(span)
        if len(remain) > 0:
            raise Exception('Unexpected parsing remainder')
        return packet

    @property
    def size(self) -> int:
        pass

    def show(self, prefix: str = ''):
        print(f'{self.__class__.__name__}')

        def print_val(p: str, pp: str, name: str, align: int, typ, val):
            if name == 'payload':
                pass

            # Scalar fields.
            elif typ is int:
                print(f'{p}{name:{align}} = {val} (0x{val:x})')

            # Byte fields.
            elif typ is bytes:
                print(f'{p}{name:{align}} = [', end='')
                line = ''
                n_pp = ''
                for (idx, b) in enumerate(val):
                    if idx > 0 and idx % 8 == 0:
                        print(f'{n_pp}{line}')
                        line = ''
                        n_pp = pp + (' ' * (align + 4))
                    line += f' {b:02x}'
                print(f'{n_pp}{line} ]')

            # Enum fields.
            elif inspect.isclass(typ) and issubclass(typ, enum.IntEnum):
                print(f'{p}{name:{align}} = {typ.__name__}::{val.name} (0x{val:x})')

            # Struct fields.
            elif inspect.isclass(typ) and issubclass(typ, globals().get('Packet')):
                print(f'{p}{name:{align}} = ', end='')
                val.show(prefix=pp)

            # Array fields.
            elif getattr(typ, '__origin__', None) == list:
                print(f'{p}{name:{align}}')
                last = len(val) - 1
                align = 5
                for (idx, elt) in enumerate(val):
                    n_p  = pp + ('├── ' if idx != last else '└── ')
                    n_pp = pp + ('│   ' if idx != last else '    ')
                    print_val(n_p, n_pp, f'[{idx}]', align, typ.__args__[0], val[idx])

            # Custom fields.
            elif inspect.isclass(typ):
                print(f'{p}{name:{align}} = {repr(val)}')

            else:
                print(f'{p}{name:{align}} = ##{typ}##')

        last = len(fields(self)) - 1
        align = max(len(f.name) for f in fields(self) if f.name != 'payload')

        for (idx, f) in enumerate(fields(self)):
            p  = prefix + ('├── ' if idx != last else '└── ')
            pp = prefix + ('│   ' if idx != last else '    ')
            val = getattr(self, f.name)

            print_val(p, pp, f.name, align, f.type, val)

class Technology(enum.IntEnum):
    NFC_A = 0x0
    NFC_B = 0x1
    NFC_F = 0x2
    NFC_V = 0x3
    RAW = 0x7

    @staticmethod
    def from_int(v: int) -> Union[int, 'Technology']:
        try:
            return Technology(v)
        except ValueError as exn:
            raise exn


class Protocol(enum.IntEnum):
    UNDETERMINED = 0x0
    T1T = 0x1
    T2T = 0x2
    T3T = 0x3
    ISO_DEP = 0x4
    NFC_DEP = 0x5
    T5T = 0x6
    NDEF = 0x7

    @staticmethod
    def from_int(v: int) -> Union[int, 'Protocol']:
        try:
            return Protocol(v)
        except ValueError as exn:
            raise exn


class RfPacketType(enum.IntEnum):
    DATA = 0x0
    POLL_COMMAND = 0x1
    POLL_RESPONSE = 0x2
    SELECT_COMMAND = 0x3
    SELECT_RESPONSE = 0x4
    DEACTIVATE_NOTIFICATION = 0x5

    @staticmethod
    def from_int(v: int) -> Union[int, 'RfPacketType']:
        try:
            return RfPacketType(v)
        except ValueError as exn:
            raise exn


@dataclass
class RfPacket(Packet):
    sender: int = field(kw_only=True, default=0)
    receiver: int = field(kw_only=True, default=0)
    technology: Technology = field(kw_only=True, default=Technology.NFC_A)
    protocol: Protocol = field(kw_only=True, default=Protocol.UNDETERMINED)
    packet_type: RfPacketType = field(kw_only=True, default=RfPacketType.DATA)

    def __post_init__(self):
        pass

    @staticmethod
    def parse(span: bytes) -> Tuple['RfPacket', bytes]:
        fields = {'payload': None}
        if len(span) < 7:
            raise Exception('Invalid packet size')
        value_ = int.from_bytes(span[0:2], byteorder='little')
        fields['sender'] = value_
        value_ = int.from_bytes(span[2:4], byteorder='little')
        fields['receiver'] = value_
        fields['technology'] = Technology.from_int(span[4])
        fields['protocol'] = Protocol.from_int(span[5])
        fields['packet_type'] = RfPacketType.from_int(span[6])
        span = span[7:]
        payload = span
        span = bytes([])
        fields['payload'] = payload
        try:
            return PollCommand.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return NfcAPollResponse.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return T4ATSelectCommand.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return T4ATSelectResponse.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return NfcDepSelectCommand.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return NfcDepSelectResponse.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return SelectCommand.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return DeactivateNotification.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        try:
            return Data.parse(fields.copy(), payload)
        except Exception as exn:
            pass
        return RfPacket(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        if self.sender > 65535:
            print(f"Invalid value for field RfPacket::sender: {self.sender} > 65535; the value will be truncated")
            self.sender &= 65535
        _span.extend(int.to_bytes((self.sender << 0), length=2, byteorder='little'))
        if self.receiver > 65535:
            print(f"Invalid value for field RfPacket::receiver: {self.receiver} > 65535; the value will be truncated")
            self.receiver &= 65535
        _span.extend(int.to_bytes((self.receiver << 0), length=2, byteorder='little'))
        _span.append((self.technology << 0))
        _span.append((self.protocol << 0))
        _span.append((self.packet_type << 0))
        _span.extend(payload or self.payload or [])
        return bytes(_span)

    @property
    def size(self) -> int:
        return len(self.payload) + 7

@dataclass
class PollCommand(RfPacket):
    data : bytearray = field(kw_only=True, default_factory=bytearray)

    def __post_init__(self):
        self.packet_type = RfPacketType.POLL_COMMAND

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['PollCommand', bytes]:
        if fields['packet_type'] != RfPacketType.POLL_COMMAND:
            raise Exception("Invalid constraint field values")
        data = span[0:]
        fields['data'] = data
        return PollCommand(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        _span.extend(self.data)
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return len(self.data)

@dataclass
class NfcAPollResponse(RfPacket):
    nfcid1: bytearray = field(kw_only=True, default_factory=bytearray)
    int_protocol: int = field(kw_only=True, default=0)
    bit_frame_sdd: int = field(kw_only=True, default=0)

    def __post_init__(self):
        self.technology = Technology.NFC_A
        self.packet_type = RfPacketType.POLL_RESPONSE

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['NfcAPollResponse', bytes]:
        if fields['technology'] != Technology.NFC_A or fields['packet_type'] != RfPacketType.POLL_RESPONSE:
            raise Exception("Invalid constraint field values")
        if len(span) < 1:
            raise Exception('Invalid packet size')
        nfcid1_size = span[0]
        span = span[1:]
        if len(span) < nfcid1_size:
            raise Exception('Invalid packet size')
        fields['nfcid1'] = list(span[:nfcid1_size])
        span = span[nfcid1_size:]
        if len(span) < 2:
            raise Exception('Invalid packet size')
        fields['int_protocol'] = (span[0] >> 0) & 0x3
        fields['bit_frame_sdd'] = span[1]
        span = span[2:]
        return NfcAPollResponse(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        _span.append(((len(self.nfcid1) * 1) << 0))
        _span.extend(self.nfcid1)
        if self.int_protocol > 3:
            print(f"Invalid value for field NfcAPollResponse::int_protocol: {self.int_protocol} > 3; the value will be truncated")
            self.int_protocol &= 3
        _span.append((self.int_protocol << 0))
        if self.bit_frame_sdd > 255:
            print(f"Invalid value for field NfcAPollResponse::bit_frame_sdd: {self.bit_frame_sdd} > 255; the value will be truncated")
            self.bit_frame_sdd &= 255
        _span.append((self.bit_frame_sdd << 0))
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return len(self.nfcid1) * 1 + 3

@dataclass
class T4ATSelectCommand(RfPacket):
    param: int = field(kw_only=True, default=0)

    def __post_init__(self):
        self.technology = Technology.NFC_A
        self.protocol = Protocol.ISO_DEP
        self.packet_type = RfPacketType.SELECT_COMMAND

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['T4ATSelectCommand', bytes]:
        if fields['technology'] != Technology.NFC_A or fields['protocol'] != Protocol.ISO_DEP or fields['packet_type'] != RfPacketType.SELECT_COMMAND:
            raise Exception("Invalid constraint field values")
        if len(span) < 1:
            raise Exception('Invalid packet size')
        fields['param'] = span[0]
        span = span[1:]
        return T4ATSelectCommand(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        if self.param > 255:
            print(f"Invalid value for field T4ATSelectCommand::param: {self.param} > 255; the value will be truncated")
            self.param &= 255
        _span.append((self.param << 0))
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return 1

@dataclass
class T4ATSelectResponse(RfPacket):
    rats_response: bytearray = field(kw_only=True, default_factory=bytearray)

    def __post_init__(self):
        self.technology = Technology.NFC_A
        self.protocol = Protocol.ISO_DEP
        self.packet_type = RfPacketType.SELECT_RESPONSE

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['T4ATSelectResponse', bytes]:
        if fields['technology'] != Technology.NFC_A or fields['protocol'] != Protocol.ISO_DEP or fields['packet_type'] != RfPacketType.SELECT_RESPONSE:
            raise Exception("Invalid constraint field values")
        if len(span) < 1:
            raise Exception('Invalid packet size')
        rats_response_size = span[0]
        span = span[1:]
        if len(span) < rats_response_size:
            raise Exception('Invalid packet size')
        fields['rats_response'] = list(span[:rats_response_size])
        span = span[rats_response_size:]
        return T4ATSelectResponse(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        _span.append(((len(self.rats_response) * 1) << 0))
        _span.extend(self.rats_response)
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return len(self.rats_response) * 1 + 1

@dataclass
class NfcDepSelectCommand(RfPacket):
    lr: int = field(kw_only=True, default=0)

    def __post_init__(self):
        self.protocol = Protocol.NFC_DEP
        self.packet_type = RfPacketType.SELECT_COMMAND

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['NfcDepSelectCommand', bytes]:
        if fields['protocol'] != Protocol.NFC_DEP or fields['packet_type'] != RfPacketType.SELECT_COMMAND:
            raise Exception("Invalid constraint field values")
        if len(span) < 1:
            raise Exception('Invalid packet size')
        fields['lr'] = (span[0] >> 0) & 0x3
        span = span[1:]
        return NfcDepSelectCommand(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        if self.lr > 3:
            print(f"Invalid value for field NfcDepSelectCommand::lr: {self.lr} > 3; the value will be truncated")
            self.lr &= 3
        _span.append((self.lr << 0))
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return 1

@dataclass
class NfcDepSelectResponse(RfPacket):
    atr_response: bytearray = field(kw_only=True, default_factory=bytearray)

    def __post_init__(self):
        self.protocol = Protocol.NFC_DEP
        self.packet_type = RfPacketType.SELECT_RESPONSE

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['NfcDepSelectResponse', bytes]:
        if fields['protocol'] != Protocol.NFC_DEP or fields['packet_type'] != RfPacketType.SELECT_RESPONSE:
            raise Exception("Invalid constraint field values")
        if len(span) < 1:
            raise Exception('Invalid packet size')
        atr_response_size = span[0]
        span = span[1:]
        if len(span) < atr_response_size:
            raise Exception('Invalid packet size')
        fields['atr_response'] = list(span[:atr_response_size])
        span = span[atr_response_size:]
        return NfcDepSelectResponse(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        _span.append(((len(self.atr_response) * 1) << 0))
        _span.extend(self.atr_response)
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return len(self.atr_response) * 1 + 1

@dataclass
class SelectCommand(RfPacket):


    def __post_init__(self):
        self.packet_type = RfPacketType.SELECT_COMMAND

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['SelectCommand', bytes]:
        if fields['packet_type'] != RfPacketType.SELECT_COMMAND:
            raise Exception("Invalid constraint field values")
        return SelectCommand(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return 0

class DeactivateType(enum.IntEnum):
    IDLE_MODE = 0x0
    SLEEP_MODE = 0x1
    SLEEP_AF_MODE = 0x2
    DISCOVERY = 0x3

    @staticmethod
    def from_int(v: int) -> Union[int, 'DeactivateType']:
        try:
            return DeactivateType(v)
        except ValueError as exn:
            raise exn


class DeactivateReason(enum.IntEnum):
    DH_REQUEST = 0x0
    ENDPOINT_REQUEST = 0x1
    RF_LINK_LOSS = 0x2
    NFC_B_BAD_AFI = 0x3
    DH_REQUEST_FAILED = 0x4

    @staticmethod
    def from_int(v: int) -> Union[int, 'DeactivateReason']:
        try:
            return DeactivateReason(v)
        except ValueError as exn:
            raise exn


@dataclass
class DeactivateNotification(RfPacket):
    type_: DeactivateType = field(kw_only=True, default=DeactivateType.IDLE_MODE)
    reason: DeactivateReason = field(kw_only=True, default=DeactivateReason.DH_REQUEST)

    def __post_init__(self):
        self.packet_type = RfPacketType.DEACTIVATE_NOTIFICATION

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['DeactivateNotification', bytes]:
        if fields['packet_type'] != RfPacketType.DEACTIVATE_NOTIFICATION:
            raise Exception("Invalid constraint field values")
        if len(span) < 2:
            raise Exception('Invalid packet size')
        fields['type_'] = DeactivateType.from_int(span[0])
        fields['reason'] = DeactivateReason.from_int(span[1])
        span = span[2:]
        return DeactivateNotification(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        _span.append((self.type_ << 0))
        _span.append((self.reason << 0))
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return 2

@dataclass
class Data(RfPacket):
    data: bytearray = field(kw_only=True, default_factory=bytearray)

    def __post_init__(self):
        self.packet_type = RfPacketType.DATA

    @staticmethod
    def parse(fields: dict, span: bytes) -> Tuple['Data', bytes]:
        if fields['packet_type'] != RfPacketType.DATA:
            raise Exception("Invalid constraint field values")
        fields['data'] = list(span)
        span = bytes()
        return Data(**fields), span

    def serialize(self, payload: bytes = None) -> bytes:
        _span = bytearray()
        _span.extend(self.data)
        return RfPacket.serialize(self, payload = bytes(_span))

    @property
    def size(self) -> int:
        return len(self.data) * 1
