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

from typing import TYPE_CHECKING, ClassVar

import betterproto

import aqpxlib.message as pxmsg
from aqpxlib.abstract import PxAbstractBus, PxAbstractDevice
from aqpxlib.i2c import PxI2C32BitRegisterTarget, PxI2CController

if TYPE_CHECKING:
    from aqpxlib.toplevel import AqProtocolExerciser


class PxI2CBus(PxAbstractBus):
    """I2C Basic Bus Class."""

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

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

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

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

        if "config" in kwargs:
            assert isinstance(kwargs["config"], pxmsg.PxHandle)
            i2c_handle_config: pxmsg.PxHandle = kwargs["config"]
            self.scl = i2c_handle_config.i2_c.scl
            self.sda = i2c_handle_config.i2_c.sda
            if "handle_index" in kwargs:
                self._handle_index = kwargs["handle_index"]
                return
        else:
            self.scl = kwargs.get("scl", 0)
            self.sda = kwargs.get("sda", 1)

        handle_index = self._find_or_create_bus(scl=self.scl, sda=self.sda)
        if handle_index == -1:
            err_msg = "Failed to create I2C bus"
            raise ValueError(err_msg)

        self.handle_index = handle_index

    def get_current_controller(self) -> PxI2CController | 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.

        Retunrs:
            Optional[PxI2CController]: The current controller on the bus if found.
            Otherwise, returns empty list.
        """
        return self.get_controllers()

    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 SCL and SDA 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 SCL and SDA pins are not provided

        """
        if "scl" not in kwargs or "sda" not in kwargs:
            err_msg = "SCL and SDA pins are required to create a new I2C bus"
            raise ValueError(err_msg)

        scl: int = kwargs.get("scl", 0)
        sda: int = kwargs.get("sda", 1)

        return pxmsg.PxHandle(
            i2_c=pxmsg.I2CChannel(scl=scl, sda=sda),
        )

    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, PxI2CBus):
            return self.scl == other.scl and self.sda == other.sda

        if isinstance(other, pxmsg.I2CChannel):
            return self.scl == other.scl and self.sda == other.sda

        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"I2C Bus (SCL: {self.scl}, SDA: {self.sda})"

    def __repr__(self) -> str:
        """Representation of the bus."""
        return f"PxI2CBus(scl={self.scl}, sda={self.sda})"


__all__ = [
    "PxI2CBus",
]
