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

from typing import TYPE_CHECKING, ClassVar

import aqpxlib.message as pxmsg
from aqpxlib.abstract import PxAbstractController
from aqpxlib.message import SpiControllerConfig as PxSPIControllerConfig
from aqpxlib.message import SpiControllerState as PxSPIControllerState

if TYPE_CHECKING:
    from aqpxlib.toplevel import AqProtocolExerciser


class PxSPIController(PxAbstractController):
    """An SPI controller device class.

    This class provides a high-level interface, to an SPI controller with base SPI functionality.
    It does not automatically create an actual instance on the Protocol Exerciser until user call
    the `attach_to_bus` method.

    Typical usage example:

    ```python
    spi_bus = PxSPIBus(exerciser_session)
    spi_controller = PxSPIController(exerciser_session)
    spi_controller.attach_to_bus(spi_bus)

    ... # Do any operation from this controller

    spi_controller.detach_from_bus()
    ```
    """

    # Used to identify the class
    identifier: str = "spi_controller"
    valid_buses: ClassVar[list[str]] = ["spi"]

    protobuf_key: str = "spi_controller"

    # All inherited devices shall add the event fields to the list they accepts
    _protobuf_available_event_fields: ClassVar[list[str]] = []

    def __init__(
        self,
        exerciser: "AqProtocolExerciser",
        config: PxSPIControllerConfig | None = None,
        *args,
        **kwargs,
    ) -> None:
        """Initialize an SPI controller."""
        if config is None:
            config = PxSPIControllerConfig(
                enabled=1,
                temp_timing=pxmsg.SpiTempTimingConfiguration(
                    spi_sleep_high_pre=5,
                    spi_sleep_high_post=5,
                    spi_sleep_low_pre=5,
                    spi_sleep_low_post=5,
                ),
            )

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

    def send_data(
        self,
        data: bytes | list[int],
        mode: int = 0,
        dummy_count: int = 0,
        dummy_val: int = 0xFF,
        should_rise_cs: bool = True,
    ) -> bytes | None:
        """Perfom a sending data operation to SPI targets.

        This method sends data to SPI targets, and also read data from targets at the same time.

        Args:
            data: The data to write to the SPI targets. It can be a bytes object or a list of integers.
            mode: User defined transaction mode.
            dummy_count: The number of dummy bytes used to pad the transaction until the bytes \
                         that user expects to read appear.
            dummy_val: User defined dummy byte value.
            should_rise_cs: Determine whether pull CS high after exerciser send the data bytes.

        Returns:
            The data read from the targets.

        """
        transactions = [
            pxmsg.SpiTransaction(
                mode=mode,
                write_data=bytes(data),
                read_data=b"",
                dummy_count=dummy_count,
                dummy_val=dummy_val,
                should_rise_cs=should_rise_cs,
            ),
        ]

        resp = self.send_transactions(
            transactions,
        )

        return resp[0].read_data

    def send_transactions(
        self,
        transactions: list[pxmsg.SpiTransaction],
    ) -> list[pxmsg.SpiTransaction]:
        """Send SPI transactions.

        This methos enque a SPI transaction to be sent to the SPI bus.
        Each element in `transactions` is a `pxmsg.SpiSendTransaction` object, which contains:

        * mode
        * write_data
        * read_data
        * dummy_count
        * dummy_val
        * should_rise_cs

        The transactions are sent in the order they are provided.

        Args:
            transactions: A list of `pxmsg.SpiSendTransaction` object to be sent.

        Returns:
            PxError: If the transaction fails.

        """
        return self.perform_operation(
            operation=pxmsg.PxDeviceOperation(
                spi_controller_send_transaction=pxmsg.SpiSendTransaction(
                    transactions=transactions,
                ),
            ),
        ).spi_controller_send_transaction.transactions

    def check_param(self, config: PxSPIControllerConfig):
        """Verify the correctness of the config."""
        err_msg = ""
        if config.temp_timing.spi_sleep_high_pre < 0:
            err_msg = "Invalid timing: spi_sleep_high_pre"
        elif config.temp_timing.spi_sleep_high_post < 0:
            err_msg = "Invalid timing: spi_sleep_high_post"
        elif config.temp_timing.spi_sleep_low_pre < 0:
            err_msg = "Invalid timing: spi_sleep_low_pre"
        elif config.temp_timing.spi_sleep_low_post < 0:
            err_msg = "Invalid timing: spi_sleep_low_post"

        if err_msg != "":
            raise ValueError(err_msg)


__all__ = [
    "PxSPIController",
    "PxSPIControllerConfig",
    "PxSPIControllerState",
]
