"""Factory module for generating DSP objects."""

from __future__ import annotations

import logging

from sigmadsp.protocols.i2c import I2cProtocol
from sigmadsp.protocols.spi import SpiProtocol

from .adau1x0x import Adau1x0x
from .adau14xx import Adau14xx
from .common import ConfigurationError, Dsp, InputPin, OutputPin

logger = logging.getLogger(__name__)

ADAU_14XX = "adau14xx"
ADAU_1X0X = "adau1x0x"


def dsp_factory(dsp_type_name: str) -> type[Dsp]:
    """Creates Dsp objects, based on the name of the DSP.

    Args:
        dsp_type_name (str): The name of the DSP.

    Returns:
        type[Dsp]: The matching DSP class.
    """
    if dsp_type_name == ADAU_14XX:
        return Adau14xx

    if dsp_type_name == ADAU_1X0X:
        return Adau1x0x

    raise TypeError("DSP type {dsp_type} is not known.")


def dsp_from_config(config: dict) -> Dsp:
    """Parse a configuration dictionary and create a new DSP from it.

    Args:
        config (dict): The configuration dictionary.

    Returns:
        Dsp: The created DSP object.
    """
    dsp: Dsp

    try:
        dsp_protocol_name: str = config["dsp"]["protocol"].lower()
        dsp_type_name: str = config["dsp"]["type"].lower()
        bus = int(config["dsp"]["bus_number"])
        device = int(config["dsp"]["device_address"])

    except KeyError as e:
        logger.error("Key %s missing from the DSP configuration.", e.args[0])
        raise ConfigurationError from e

    else:
        # Generate the protocol first (e.g. SPI or I2C).
        dsp_protocol: SpiProtocol | I2cProtocol

        if dsp_protocol_name == "spi":
            dsp_protocol = SpiProtocol(bus=bus, device=device)

        elif dsp_protocol_name == "i2c":
            dsp_protocol = I2cProtocol(bus_id=bus, device_address=device)

        else:
            raise TypeError(f"Unknown DSP protocol {dsp_protocol_name}.")

        # Then, generate the dsp itself.
        dsp_type: type[Dsp] = dsp_factory(dsp_type_name)

        # Use safeload by default.
        dsp = dsp_type(True, dsp_protocol)

    try:
        # Parse the configuration for pin definitions.
        for pin_definition_key in config["dsp"]["pins"]:
            pin_definition = config["dsp"]["pins"][pin_definition_key]

            if pin_definition["mode"] == "output":
                output_pin = OutputPin(
                    pin_definition_key,
                    pin_definition["number"],
                    pin_definition["initial_state"],
                    pin_definition["active_high"],
                )

                dsp.add_pin(output_pin)

            elif pin_definition["mode"] == "input":
                input_pin = InputPin(
                    pin_definition_key,
                    pin_definition["number"],
                    pin_definition["pull_up"],
                    pin_definition["active_state"],
                    pin_definition["bounce_time"],
                )

                dsp.add_pin(input_pin)

    except (KeyError, TypeError):
        logger.info("No DSP pin definitions were found in the configuration file.")

    return dsp
