# Copyright 2025 Acute Technology Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
"""SPI Basic Bus Class."""

from typing import TYPE_CHECKING, ClassVar

import betterproto

import aqpxlib.message as pxmsg
from aqpxlib.abstract import PxAbstractBus, PxAbstractDevice
from aqpxlib.spi import PxSPI32BitRegisterTarget, PxSPIController

if TYPE_CHECKING:
    from aqpxlib.toplevel import AqProtocolExerciser


class PxSPIBus(PxAbstractBus):
    """SPI Basic Bus Class."""

    # Used to identify the class
    identifier: str = "spi"
    controller_class: type[PxAbstractDevice] = PxSPIController

    # A mapping of device identifiers to device classes
    _available_devices: ClassVar[list[type[PxAbstractDevice]]] = [
        PxSPIController,
        PxSPI32BitRegisterTarget,
    ]

    # Used to query the bus type from the protobuf message
    protobuf_key: str = "spi"

    def __init__(self, exerciser: "AqProtocolExerciser", *args, **kwargs) -> None:
        """Initialize an SPI bus."""
        super().__init__(exerciser)

        if "config" in kwargs:
            assert isinstance(kwargs["config"], pxmsg.PxHandle)
            spi_handle_config: pxmsg.PxHandle = kwargs["config"]
            self.clk = spi_handle_config.clk
            self.miso = spi_handle_config.miso
            self.mosi = spi_handle_config.mosi
            self.cs = spi_handle_config.cs
            if "handle_index" in kwargs:
                self._handle_index = kwargs["handle_index"]
                return
        else:
            self.clk = kwargs.get("clk", 0)
            self.miso = kwargs.get("miso", 1)
            self.mosi = kwargs.get("mosi", 2)
            self.cs = kwargs.get("cs", 3)

        handle_index = self._find_or_create_bus(
            clk=self.clk,
            miso=self.miso,
            mosi=self.mosi,
            cs=self.cs,
        )

        if handle_index == -1:
            err_msg = "Failed to create SPI bus"
            raise ValueError(err_msg)

        self.handle_index = handle_index

    def get_current_controller(self) -> PxSPIController | None:
        """Get the current controller on the bus.

        Get the controller interface from the exerciser.
        If there is no existing controller, returns an empty list.

        Returns:
            Optional[PxSPIController]: The current controller on the bus if found.
            Otherwise, retunrs empty list.

        """
        controllers = self.get_controllers()
        if len(controllers):
            return controllers[0]
        return None

    def _to_protobuf_handle_config(self, *args, **kwargs) -> pxmsg.PxHandle:
        """Convert the bus to a protobuf structure.

        This method is inherited from the abstract bus class.
        Here we expected to get the CLK, MISO, MOSI and CS pins from the kwargs, which
        are used to create a new protobuf structure.

        Args:
            *args: Additional arguments to pass the bus constructor
            **kwargs: Additional keyword arguments to pass to the bus constructor

        Returns:
            pxmsg.PxHandle: The protobuf structure of the bus

        Raises:
            ValueError: If the CLK, MISO, MOSI and CS pins are not provided

        """
        if "clk" not in kwargs or "miso" not in kwargs or "mosi" not in kwargs or "cs" not in kwargs:
            err_msg = "CLK, MISO, MOSI and CS pins are required to create a new SPI bus"
            raise ValueError(err_msg)

        clk: int = kwargs.get("clk", 0)
        miso: int = kwargs.get("miso", 1)
        mosi: int = kwargs.get("mosi", 2)
        cs: int = kwargs.get("cs", 3)

        return pxmsg.PxHandle(
            spi=pxmsg.SpiChannel(clk=clk, miso=miso, mosi=mosi, cs=cs),
        )

    def __eq__(self, other: object) -> bool:
        """Check if the bus is equal to another bus.

        This checks if the bus is equal to another bus or a protobuf structure of the bus.

        Args:
            other (Any): The other bus to compare with or a protobuf structure of the bus

        Returns:
            bool: True if the bus is equal to the other bus, False otherwise.

        """
        if isinstance(other, PxSPIBus):
            return self.clk == other.clk and self.miso == other.miso and self.mosi == other.mosi and self.cs == other.cs

        if isinstance(other, pxmsg.SpiChannel):
            return self.clk == other.clk and self.miso == other.miso and self.mosi == other.mosi and self.cs == other.cs

        if isinstance(other, pxmsg.PxHandle):
            _, channel = betterproto.which_one_of(other, "protocol")
            return self == channel

        return False

    def __str__(self) -> str:
        """Return string representation of the bus."""
        return f"SPI Bus (CLK: {self.clk}, MISO: {self.miso}, MOSI: {self.mosi}, CS: {self.cs})"

    def __repr__(self) -> str:
        """Representation of the bus."""
        return self.__str__()


__all__ = [
    "PxSPIBus",
]
