"""
A simulator for the SRv4 Power Board.

Provides a message parser that simulates the behavior of a power board.

Based on the Power Board v4.4.2 firmware.
"""
from __future__ import annotations

import logging

from sbot_interface.devices.led import BaseLed
from sbot_interface.devices.power import BaseButton, BaseBuzzer, Output

LOGGER = logging.getLogger(__name__)

NUM_OUTPUTS = 7  # 6 12V outputs, 1 5V output
SYS_OUTPUT = 4  # L2 output for the brain

RUN_LED = 0
ERR_LED = 1


class PowerBoard:
    """
    A simulator for the SRv4 Power Board.

    :param outputs: A list of simulated outputs connected to the power board.
                        The list is indexed by the output number.
    :param buzzer: A simulated buzzer connected to the power board.
    :param button: A simulated button connected to the power board.
    :param leds: A tuple of simulated LEDs connected to the power board.
    :param asset_tag: The asset tag to report for the power board.
    :param software_version: The software version to report for the power board.
    """

    def __init__(
        self,
        outputs: list[Output],
        buzzer: BaseBuzzer,
        button: BaseButton,
        leds: tuple[BaseLed, BaseLed],
        asset_tag: str,
        software_version: str = '4.4.2',
    ):
        self.outputs = outputs
        self.buzzer = buzzer
        self.button = button
        self.leds = leds
        self.asset_tag = asset_tag
        self.software_version = software_version
        self.temp = 25
        self.battery_voltage = 12000

    def handle_command(self, command: str) -> str:
        """
        Process a command string and return the response.

        Executes the appropriate action on any specified outputs, LEDs,
        or the buzzer automatically.

        :param command: The command string to process.
        :return: The response to the command.
        """
        args = command.split(':')
        if args[0] == '*IDN?':
            return f'Student Robotics:PBv4B:{self.asset_tag}:{self.software_version}'
        elif args[0] == '*STATUS?':
            # Output faults are unsupported, fan is always off
            return f"0,0,0,0,0,0,0:{self.temp}:0:5000"
        elif args[0] == '*RESET':
            LOGGER.info(f'Resetting power board {self.asset_tag}')
            for output in self.outputs:
                output.set_output(False)
            self.buzzer.set_note(0, 0)
            self.leds[RUN_LED].set_colour(0)
            self.leds[ERR_LED].set_colour(0)
            return 'ACK'
        elif args[0] == 'BTN':
            if len(args) < 2:
                return 'NACK:Missing button command'
            if args[1] == 'START' and args[2] == 'GET?':
                return f'{self.button.get_state():d}:0'
            else:
                return 'NACK:Unknown button command'
        elif args[0] == 'OUT':
            if len(args) < 2:
                return 'NACK:Missing output number'
            try:
                output_number = int(args[1])
            except ValueError:
                return 'NACK:Invalid output number'
            if not (0 <= output_number < NUM_OUTPUTS):
                return 'NACK:Invalid output number'

            if len(args) < 3:
                return 'NACK:Missing output command'
            if args[2] == 'SET':
                if output_number == SYS_OUTPUT:
                    return 'NACK:Brain output cannot be controlled'
                if len(args) < 4:
                    return 'NACK:Missing output state'
                try:
                    state = int(args[3])
                except ValueError:
                    return 'NACK:Invalid output state'
                if state not in [0, 1]:
                    return 'NACK:Invalid output state'
                LOGGER.info(
                    f'Setting output {output_number} on board {self.asset_tag} to {state}'
                )
                self.outputs[output_number].set_output(bool(state))
                return 'ACK'
            elif args[2] == 'GET?':
                return '1' if self.outputs[output_number].get_output() else '0'
            elif args[2] == 'I?':
                return str(self.outputs[output_number].get_current())
            else:
                return 'NACK:Unknown output command'
        elif args[0] == 'BATT':
            if len(args) < 2:
                return 'NACK:Missing battery command'
            if args[1] == 'V?':
                return str(self.battery_voltage)
            elif args[1] == 'I?':
                return str(self.current())
            else:
                return 'NACK:Unknown battery command'
        elif args[0] == 'LED':
            if len(args) < 3:
                return 'NACK:Missing LED command'
            if args[1] not in ['RUN', 'ERR']:
                return 'NACK:Invalid LED type'

            led_type = RUN_LED if args[1] == 'RUN' else ERR_LED

            if args[2] == 'SET':
                if len(args) < 4:
                    return 'NACK:Missing LED state'
                if args[3] in ['0', '1', 'F']:
                    LOGGER.info(
                        f'Setting {args[1]} LED on board {self.asset_tag} to {args[3]}'
                    )
                    if args[3] == 'F':
                        self.leds[led_type].set_colour(1)
                    else:
                        self.leds[led_type].set_colour(int(args[3]))
                    return 'ACK'
                else:
                    return 'NACK:Invalid LED state'
            elif args[2] == 'GET?':
                return str(self.leds[led_type].get_colour())
            else:
                return 'NACK:Invalid LED command'
        elif args[0] == 'NOTE':
            if len(args) < 2:
                return 'NACK:Missing note command'
            if args[1] == 'GET?':
                return ':'.join(map(str, self.buzzer.get_note()))
            else:
                if len(args) < 3:
                    return 'NACK:Missing note frequency'
                try:
                    freq = int(args[1])
                except ValueError:
                    return 'NACK:Invalid note frequency'
                if not (0 <= freq < 10000):
                    return 'NACK:Invalid note frequency'

                try:
                    dur = int(args[2])
                except ValueError:
                    return 'NACK:Invalid note duration'
                if dur < 0:
                    return 'NACK:Invalid note duration'

                LOGGER.info(
                    f'Setting buzzer on board {self.asset_tag} to {freq}Hz for {dur}ms'
                )
                self.buzzer.set_note(freq, dur)
                return 'ACK'
        else:
            return f'NACK:Unknown command {command.strip()}'
        return 'NACK:Command failed'

    def current(self) -> int:
        """
        Get the total current draw of all outputs.

        :return: The total current draw of all outputs in mA.
        """
        return sum(output.get_current() for output in self.outputs)
