# Copyright 2025 Acute Technology Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
"""I3C Base Target Class."""

from enum import IntEnum
from secrets import randbits
from typing import TYPE_CHECKING, ClassVar

from aqpxlib.abstract import PxAbstractTarget
from aqpxlib.i3c.i3c_device_conf import ACUTE_DEFAULT_PID
from aqpxlib.message import I3C32BitRegisterTargetConfig as PxI3C32BitRegisterTargetConfig
from aqpxlib.message import (
    I3C32BitRegisterTargetConfigSubAddressType,
    I3CTargetHotJoin,
    I3CTargetHotJoinType,
    I3CTargetIbi,
    I3CTargetIbiType,
    PxDeviceOperation,
)
from aqpxlib.message import I3C32BitRegisterTargetState as PxI3C32BitRegisterTargetState

if TYPE_CHECKING:
    from aqpxlib.toplevel import AqProtocolExerciser


class PxI3C32BitRegisterTarget(PxAbstractTarget):
    """I3C Base Target Class."""

    # Used to identify the class
    identifier: str = "i3c_32bit_register_target"
    protobuf_key: str = "i3_c_32_bit_reg_target"
    valid_buses: ClassVar[list[str]] = ["i3c"]

    # All inherited devices shall add the event fields to the list they accept
    _protobuf_available_event_fields: ClassVar[list[str]] = [
        "i3_c_target_assigned_address",
        "i3_c_target_updated_event_state",
        "i3_c_target_assigned_group_address",
    ]

    class SubAddressType(IntEnum):
        """Sub-address type enumeration.

        This is intended to avoid exporting protobuf enum to the interface.
        """

        NONE = I3C32BitRegisterTargetConfigSubAddressType.SUB_ADDR_NONE
        REPEAT = I3C32BitRegisterTargetConfigSubAddressType.SUB_ADDR_8BIT_REPEAT
        IGNORE = I3C32BitRegisterTargetConfigSubAddressType.SUB_ADDR_8BIT_IGNORE
        INCREMENT = I3C32BitRegisterTargetConfigSubAddressType.SUB_ADDR_8BIT_INCREMENT
        INCREMENT_WITH_LOOP = I3C32BitRegisterTargetConfigSubAddressType.SUB_ADDR_8BIT_INCREMENT_WITH_LOOP

    def __init__(  # noqa: C901
        self,
        exerciser: "AqProtocolExerciser",
        *args,
        pid: int | None = None,
        bcr: int = 0x00,
        dcr: int = 0x00,
        config: PxI3C32BitRegisterTargetConfig | None = None,
        static_addr: int = 0,
        minimum_mrl: int | None = None,  # 0x10 by default
        minimum_mwl: int | None = None,  # 0x10 by default
        getcaps_std_value: bytes | None = None,
        getcaps_usr_value: list[tuple[int, bytes]] = [],
        sub_address_type: SubAddressType = SubAddressType.NONE,
        getstatus_std_value: bytes | None = None,
        getstatus_usr_value: list[tuple[int, bytes]] = [],
        getmxds_std_value: bytes | None = None,
        getmxds_usr_value: list[tuple[int, bytes]] = [],
        supported_group_address_count: int = 3,
        **kwargs,
    ) -> None:
        """Initialize an I3C 32-bit register target instance.

        Args:
          exerciser: The exerciser instance to use.
          pid: The 48-bit Provisioned ID (PID) of the target. On a given I3C bus
            there should be no Provisioned ID collision, otherwise the discovery
            mechanism may fail. If not provided, a default PID will be generated
            with Acute's manufacturer ID and a random 32-bit vendor value.
          bcr: The 8-bit Bus Characteristic Register (BCR) of the target.
            This register describes the device bus related capabilities.
          dcr: The 8-bit Device Characteristic Register (DCR) of the target.
            This register describes the functionalities provided by the device
          static_addr: The static address of the target.
          minimum_mrl: The minimum read length of the target.
          minimum_mwl: The minimum write length of the target.
          sub_address_type: The sub-address type of the target.
          getcaps_std_value: The default value for GETCAPS.
          getcaps_usr_value: The defining bytes and the corresponding value for GETCAPS.
            format: [(defining byte, getcaps value)]
          getstatus_std_value: set the default value for GETSTATUS.
          getstatus_usr_value: set the defining bytes and the corresponding value
            for GETSTATUS.
            format: [(first defining byte, first status value), (second defining byte, second status value)]
          getmxds_std_value: set the default value for GETMXDS.
          getmxds_usr_value: set the default bytes and the corresponding value for GETMXDS.
            format: [(defining byte, mxds value)]
          supported_group_address_count:
            set the count of group address that the target can support.
          config: The configuration of the device using protobuf structure.
          *args: Additional arguments to pass to the device constructor.
          **kwargs: Additional keyword arguments to pass to the device constructor.

        """
        if pid is None:
            pid = ACUTE_DEFAULT_PID | (0x01 << 32) | randbits(32)

        if getcaps_usr_value is not None and len(getcaps_usr_value) > 1:
            err_msg = "Multiple user defined getcaps values is not implemented yet"
            raise NotImplementedError(err_msg)

        if getstatus_usr_value is not None and len(getstatus_usr_value) > 2:
            err_msg = "Multiple user defined getstatus values is not implemented yet"
            raise NotImplementedError(err_msg)

        if getmxds_usr_value is not None and len(getmxds_usr_value) > 1:
            err_msg = "Multiple user defined getmxds value is not implemented yet"
            raise NotImplementedError(err_msg)

        # check whether Group Address information is legal
        if supported_group_address_count < 0 and supported_group_address_count > 3:
            err_msg = "The functionality for group addresses exceeding 3 has not yet been implemented."
            raise NotImplementedError(err_msg)
        # if getcaps_std_value had not been set by user, fit the getcaps value based on `supported_group_address_count`
        # as the default getcaps value of the target.
        if getcaps_std_value is None:
            getcaps_std_value = bytes([0x00, ((supported_group_address_count & 0x03) << 4) | 0x01, 0x00, 0x00])

        getcaps_usr0_value = (None, None)
        if getcaps_usr_value is not None and len(getcaps_usr_value) == 1:
            getcaps_usr0_value = getcaps_usr_value[0]

        # GETSTATUS
        getstatus_usr0_value = (None, None)
        getstatus_usr1_value = (None, None)
        if getstatus_usr_value:
            getstatus_usr0_value = getstatus_usr_value[0] if len(getstatus_usr_value) > 0 else (None, None)
            getstatus_usr1_value = getstatus_usr_value[1] if len(getstatus_usr_value) > 1 else (None, None)

        # GETMXDS
        getmxds_usr0_value = (None, None)
        if getmxds_usr_value is not None and len(getmxds_usr_value) == 1:
            getmxds_usr0_value = getmxds_usr_value[0]

        assert 0 <= bcr <= 0xFF, f"BCR must be between 0 and 0xFF, got {bcr}"
        assert 0 <= dcr <= 0xFF, f"DCR must be between 0 and 0xFF, got {dcr}"
        assert 0 <= pid <= 0xFFFFFFFFFFFF, f"PID must be between 0 and 0xFFFFFFFFFFFF, got {pid}"

        if config is None:
            config = PxI3C32BitRegisterTargetConfig(
                id_value=(pid << 16 | bcr << 8 | dcr),
                static_address=static_addr,
                sub_address_type=(I3C32BitRegisterTargetConfigSubAddressType)(sub_address_type.value),
                minimum_mrl=minimum_mrl,
                minimum_mwl=minimum_mwl,
                getcaps_std_value=getcaps_std_value,
                getcaps_usr0_defining_byte=getcaps_usr0_value[0],
                getcaps_usr0_value=getcaps_usr0_value[1],
                # GETSTATUS
                getstatus_std_value=getstatus_std_value,
                getstatus_usr0_defining_byte=getstatus_usr0_value[0],
                getstatus_usr0_value=getstatus_usr0_value[1],
                getstatus_usr1_defining_byte=getstatus_usr1_value[0],
                getstatus_usr1_value=getstatus_usr1_value[1],
                # GETMXDS
                getmxds_std_value=getmxds_std_value,
                getmxds_usr0_defining_byte=getmxds_usr0_value[0],
                getmxds_usr0_value=getmxds_usr0_value[1],
                # Group Address
                supported_group_address_count=supported_group_address_count,
            )
        elif config.id_value is None:
            assert isinstance(config, PxI3C32BitRegisterTargetConfig)
            config.id_value = pid << 16 | bcr << 8 | dcr

        super().__init__(exerciser, config=config, *args, **kwargs)

    @property
    def assigned_address(self) -> int:
        """Get the assigned address of the target."""
        state = self.state
        assert isinstance(state, PxI3C32BitRegisterTargetState)
        return state.assigned_address

    @property
    def event_state(self) -> int:
        """Get the event state of the target."""
        state = self.state
        assert isinstance(state, PxI3C32BitRegisterTargetState)
        return state.event_state

    @property
    def group_addresses(self) -> list[int]:
        """Get the group addresses assigned to the target."""
        state = self.state
        assert isinstance(state, PxI3C32BitRegisterTargetState)
        return state.group_address

    @property
    def mrl(self) -> int:
        """Get the maximum read length of the target."""
        state = self.state
        assert isinstance(state, PxI3C32BitRegisterTargetState)
        return state.mrl

    @property
    def mwl(self) -> int:
        """Get the maximum write length of the target."""
        state = self.state
        assert isinstance(state, PxI3C32BitRegisterTargetState)
        return state.mwl

    @property
    def register_value(self) -> int:
        """Get the current register value of the target."""
        state = self.state
        assert isinstance(state, PxI3C32BitRegisterTargetState)
        return state.reg_value

    @register_value.setter
    def register_value(self, value: int) -> None:
        """Set the register value of the target."""
        self._set_state(PxI3C32BitRegisterTargetState(reg_value=value))

    class IbiType(IntEnum):
        """IBI type enumeration.

        This is intended to avoid exporting protobuf enum to the interface.
        """

        IMMEDIATE = I3CTargetIbiType.ImmediateIBI
        ARBITRATED = I3CTargetIbiType.ArbitratedIBI

    def ibi_request(
        self,
        ibi_type: IbiType = IbiType.IMMEDIATE,
        mdb_value: bytes = b"",
        additional_data: bytes = b"",
        skip_bcr_check: bool = False,
    ) -> None:
        """Initiate an IBI request from the current target.

        Args:
            ibi_type: The type of IBI to request, either `Immediate` or `Arbitrated`.
            mdb_value: The mandatory data byte (MDB) value to send with the IBI.
            additional_data: Additional data to send along with the IBI payload.
            skip_bcr_check: If True, the BCR check will be dismissed and still
                            proceed to send and IBI request.

        Raises:
            ValueError: It the current target BCR does not allow IBI.
            ValueError: It the current target BCR does not allow IBI payload, but
                        the caller still tries to send an IBI payload.
            ValueError: If the `mdb_value` is empty but the `additional_data` is not.

        """
        state = self.state
        assert isinstance(state, PxI3C32BitRegisterTargetState)

        enec_enint = state.event_state & 0x01
        bcr = (self.config.id_value >> 8) & 0xFF
        bcr_allow_ibireq = (bcr >> 1) & 0x01
        bcr_allow_ibipayload = (bcr >> 2) & 0x01

        if not enec_enint:
            error_message = "This target is not able to send IBI."
            raise ValueError(error_message)
        if not skip_bcr_check and not bcr_allow_ibireq:
            error_message = (
                "This target's BCR disallow IBI. If you would like to send one anyway please set skip_bcr_check = True"
            )
            raise ValueError(error_message)
        if (mdb_value or additional_data) and not bcr_allow_ibipayload:
            error_message = "This target's BCR disallow sending IBI payload."
            raise ValueError(error_message)
        if mdb_value == b"" and additional_data != b"":
            error_message = "This target's BCR requires it to send a mandatory data byte (MDB) with IBI."
            raise ValueError(error_message)

        self.perform_operation(
            operation=PxDeviceOperation(
                i3_c_target_ibi=I3CTargetIbi(
                    ibi_type=(I3CTargetIbiType)(ibi_type.value),
                    ibi_data=mdb_value + additional_data if mdb_value else b"",
                ),
            ),
        )

    class HotJoinType(IntEnum):
        """IBI type enumeration.

        This is intended to avoid exporting protobuf enum to the interface.
        """

        IMMEDIATE = I3CTargetHotJoinType.Immediate
        ARBITRATED = I3CTargetHotJoinType.Arbitrated

    def send_hotjoin(
        self,
        hotjoin_type: HotJoinType = HotJoinType.IMMEDIATE,
    ) -> None:
        """Initiate an Hot-join request from the current target.

        Args:
            hotjoin_type: The type of Hot-join to request, either `Immediate` or
                          `Arbitrated`.

        """
        self.perform_operation(
            operation=PxDeviceOperation(
                i3_c_target_hotjoin=I3CTargetHotJoin(
                    hotjoin_type=(I3CTargetHotJoinType)(hotjoin_type.value),
                ),
            ),
        )


__all__ = [
    "PxI3C32BitRegisterTarget",
    "PxI3C32BitRegisterTargetConfig",
    "PxI3C32BitRegisterTargetState",
]
