"""Initializer routine for the CODLIR board"""
import logging
import time

from naludaq.backend.managers.io import BoardIoManager
from naludaq.board.initializers import Initializers
from naludaq.helpers.decorators import log_function
from naludaq.communication.spi import SPIConnection, SPIBUS_t
from naludaq.devices.ad5674r import AD5674R_SPI

LOGGER = logging.getLogger("naludaq.init_codlir")

VALID_BOARDS = ["codlir", "codlir_eval"]

DAC_1_map = {
    1: 12,
    3: 13,
    5: 14,
    7: 15,
    9: 4,
    11: 5,
    13: 6,
    15: 7,
    16: 11,
    18: 10,
    20: 9,
    22: 8,
    24: 3,
    26: 2,
    28: 1,
    30: 0,
}
DAC_2_map = {
    0: 12,
    2: 13,
    4: 14,
    6: 15,
    8: 4,
    10: 5,
    12: 6,
    14: 7,
    17: 11,
    19: 10,
    21: 9,
    23: 8,
    25: 3,
    27: 2,
    29: 1,
    31: 0,
}

DAC_3_map = {
    32: 11,
    34: 10,
    36: 9,
    38: 8,
    40: 3,
    42: 2,
    44: 1,
    46: 0,
    49: 12,
    51: 13,
    53: 14,
    55: 15,
    57: 4,
    59: 5,
    61: 6,
    63: 7,
}

DAC_4_map = {
    33: 11,
    35: 10,
    37: 9,
    39: 8,
    41: 3,
    43: 2,
    45: 1,
    47: 0,
    48: 12,
    50: 13,
    52: 14,
    54: 15,
    56: 4,
    58: 5,
    60: 6,
    62: 7,
}


class InitCODLIR(Initializers):
    def __init__(self, board):
        """Initializer for the aardvarcv3.

        Args:
            board (Board): the board to initialize.
        """
        super().__init__(board, VALID_BOARDS)
        self.power_sequence = []

    def run(self, init_clock: bool = True) -> bool:
        """Run the init sequence"""
        has_dacs = True if self.board.features["ext_dac"] else False

        # 1. Write control registers
        self._fpga_init()
        self._i2c_startup()

        # 2. Power up ASICS
        # self._power_toggle(False)
        # self._power_toggle(True)
        time.sleep(0.25)
        # 3. Toggle reset
        # self._system_reset()
        # Program the clock

        # Program the clock
        if init_clock:
            self._init_clock()

        self._system_reset()
        # ControlRegisters(self.board).write("clkrst", 1)
        # time.sleep(0.01)
        # ControlRegisters(self.board).write("clkrst", 0)
        self._set_serial_mode(True)
        time.sleep(0.1)
        self.set_loopback_enabled(False)

        print("Programming ASIC registers")
        self._analog_startup()
        self._digital_startup()

        self._init_spi_connection()
        if has_dacs:
            self._init_dacs()

        return True

    def _init_spi_connection(self):
        """Initialize the SPI connection for the board."""
        self.board.spi_connection = SPIConnection(self.board)
        self.board.spi_connection.add_bus(
            bus_id=0b00,
            bus_type=SPIBUS_t.DAISY_CHAIN,
        )
        self._populate_spi_connection()
        self.board.spi_connection.init()

    def _populate_spi_connection(self):
        """Populate the SPI connection with the devices.

        The devices are connected according to the schematic:
        320-0065-110

        """

        device_map = {
            0: {
                "device_id": 0,
                "bus_id": 0b00,
                "channel_map": DAC_1_map,
            },
            16: {
                "device_id": 1,
                "bus_id": 0b00,
                "channel_map": DAC_2_map,
            },
            32: {
                "device_id": 3,
                "bus_id": 0b00,
                "channel_map": DAC_3_map,
            },
            48: {
                "device_id": 2,
                "bus_id": 0b00,
                "channel_map": DAC_4_map,
            },
        }

        for channel, values in device_map.items():
            bus_id = values["bus_id"]
            device_id = values["device_id"]
            self.board.spi_connection[bus_id][device_id] = AD5674R_SPI(
                spi_connection=self.board.spi_connection,
                bus_id=values["bus_id"],
                device_id=values["device_id"],
                channelmap=values["channel_map"],
                daisy_chained=True,
            )
            # bus_id=values["bus_id"],
            # device_id=values["device_id"],
            # )

    @log_function(LOGGER)
    def _fpga_init(self):
        """Write all FPGA registers to their default values."""
        self.control_registers.write_all()

    @log_function(LOGGER)
    def _i2c_startup(self):
        """Initialize the I2C interface."""
        self.i2c_registers.write("i2c_en", True)

    @log_function(LOGGER)
    def _power_toggle(self, state):
        """Toggles the power rails defined in `power_sequence` attribute."""
        for register in self.power_sequence:
            self.control_write(register, state)

    @log_function(LOGGER)
    def _system_reset(self):
        """Toggle the sysrst pin high, then low"""
        time.sleep(0.01)
        self.control_write("sysrst", True)
        time.sleep(0.01)
        self.control_write("regclr", True)
        time.sleep(0.01)
        self.control_write("sysrst", False)
        time.sleep(0.01)
        self.control_write("regclr", False)

    @log_function(LOGGER)
    def _init_clock(self):
        """Program the clock with the clockfile from the params"""
        LOGGER.info("Programming clock")
        clockfile = self.board.params["clock_file"]
        self.board.clock.program(clockfile)
        # time.sleep(0.25)
        # self.control_write("clk_oeb", False)

    @log_function(LOGGER)
    def _digital_startup(self):
        """Startup the digital side of the chip by programming all registers."""
        self.digital_registers.write_all()

    @log_function(LOGGER)
    def _analog_startup(self):
        """Start the analog side of the chip by programming all registers."""
        self._set_scvbias()
        self.analog_registers.write_all()
        self._dll_startup()

    def _set_scvbias(self):
        """Set the SCVBIAS value for both sides to default values.

        The SCVBias values are set to the values in NaluConfigs.
        """
        time.sleep(0.05)
        self.analog_write("scvbias_left")
        time.sleep(0.05)
        self.analog_write("scvbias_right")
        time.sleep(0.05)

    @log_function(LOGGER)
    def _dll_startup(self):
        """Starting the delay line on the analog side.

        Sets and unsets vanbuff to get the dll going and
        changes the vadjp values to ensure proper SST duty cycle once locked.
        """

        # DLL startup
        for _side in ["left", "right"]:
            self.analog_write(f"qbias_{_side}", 0)
            time.sleep(0.1)
            self.analog_write(f"vadjn_{_side}")  # Use vadjn from yaml
            time.sleep(0.1)
            self.analog_write(f"vanbuf_{_side}", 2000)
            time.sleep(0.5)
            self.analog_write(f"qbias_{_side}", 2950)
            time.sleep(0.1)
            self.analog_write(f"vanbuf_{_side}", 0)
            time.sleep(0.1)

    @log_function(LOGGER)
    def _init_dacs(self):
        """Program the default values to left and right side DACs"""
        channels = list(range(self.board.channels))
        self.board.ext_bias.set_dacs(channels=channels)

    @log_function(LOGGER)
    def _set_serial_mode(self, enabled: bool):
        """Turn on or off serial mode."""
        self.board.control.set_serial_mode(enabled)

    def set_loopback_enabled(self, enabled: bool):
        """Set whether serial loopback is enabled.

        Loopback can safely be disabled during most of the operations with the board.
        Loopback **must** be disabled when communicating over the serial interface.
        If serial communication with the ASIC is intended then this should run during startup and only be enabled as needed.

        Args:
            enabled (bool): True to enable loopback.

        Raises:
            TypeError if enabled is not a bool.
        """
        if not isinstance(enabled, bool):
            raise TypeError("Argument must be bool")
        OFF = "B0900002"
        ON = "B0900003"
        cmd = ON if enabled else OFF
        self._send_command(cmd)

    def _send_command(self, cmd: str):
        """Sends a raw command to the board, handling backend differences."""
        if self.board.using_new_backend:
            BoardIoManager(self.board).write(cmd)
        else:
            self.board.connection.send(cmd)
