from collections import deque
from dataclasses import dataclass
from enum import IntEnum
from typing import ClassVar
from warnings import warn

from periphery import SPI


@dataclass
class ADC78H89:
    """The class for Texas Instruments ADC78H89 7-Channel, 500 KSPS,
    12-Bit A/D Converter.
    """

    class InputChannel(IntEnum):
        """The enum class for input channels."""

        AIN1: int = 0b000
        """The first (default) input channel."""
        AIN2: int = 0b001
        """The second input channel."""
        AIN3: int = 0b010
        """The third input channel."""
        AIN4: int = 0b011
        """The fourth input channel."""
        AIN5: int = 0b100
        """The fifth input channel."""
        AIN6: int = 0b101
        """The fifth input channel."""
        AIN7: int = 0b110
        """The seventh input channel."""
        GROUND: int = 0b111
        """The ground input channel."""

    SPI_MODES: ClassVar[tuple[int, int]] = 0b00, 0b11
    """The supported spi modes."""
    MIN_SPI_MAX_SPEED: ClassVar[float] = 5e4
    """The supported minimum spi maximum speed."""
    MAX_SPI_MAX_SPEED: ClassVar[float] = 8e6
    """The supported maximum spi maximum speed."""
    SPI_BIT_ORDER: ClassVar[str] = 'msb'
    """The supported spi bit order."""
    SPI_WORD_BIT_COUNT: ClassVar[int] = 8
    """The supported spi number of bits per word."""
    INPUT_CHANNEL_BITS_OFFSET: ClassVar[int] = 3
    """The input channel bits offset for control register bits."""
    REFERENCE_VOLTAGE: ClassVar[float] = 3.3
    """The reference voltage value (in volts)."""
    DIVISOR: ClassVar[int] = 4096
    """The lsb width for ADC78H89."""
    DEFAULT_INPUT_CHANNEL: ClassVar[InputChannel] = InputChannel(0)
    """The default input channel."""
    spi: SPI
    """The SPI for the ADC device."""

    def __post_init__(self) -> None:
        if self.spi.mode not in self.SPI_MODES:
            raise ValueError('unsupported spi mode')
        elif not (
                self.MIN_SPI_MAX_SPEED
                <= self.spi.max_speed
                <= self.MAX_SPI_MAX_SPEED
        ):
            raise ValueError('unsupported spi maximum speed')
        elif self.spi.bit_order != self.SPI_BIT_ORDER:
            raise ValueError('unsupported spi bit order')
        elif self.spi.bits_per_word != self.SPI_WORD_BIT_COUNT:
            raise ValueError('unsupported spi number of bits per word')

        if self.spi.extra_flags:
            warn(f'unknown spi extra flags {self.spi.extra_flags}')

    def sample(self, *input_channels: InputChannel) -> list[float]:
        """Sample the voltages of the input channels.

        :param input_channels: The input channels.
        :return: The sampled voltages.
        """
        transmitted_data = []

        for input_channel in input_channels:
            transmitted_data.append(
                input_channel << self.INPUT_CHANNEL_BITS_OFFSET,
            )
            transmitted_data.append(0)

        received_data = self.spi.transfer(transmitted_data)
        voltages = []

        for i in range(0, len(received_data), 2):
            raw_data = (
                received_data[i]
                << self.SPI_WORD_BIT_COUNT
                | received_data[i + 1]
            )

            voltages.append(self.REFERENCE_VOLTAGE * raw_data / self.DIVISOR)

        return voltages

    def sample_all(self) -> dict[InputChannel, float]:
        """Sample the voltages of all input channels.

        :return: The sampled voltages.
        """
        self.sample(self.InputChannel.GROUND)

        voltages = deque(self.sample(*self.InputChannel))

        voltages.rotate(-1)

        return dict(zip(self.InputChannel, voltages))
