//! Project: CMtec CMDR Keyboard 42
//! Date:    2025-03-09
//! Author:  Christoffer Martinsson
//! Email:   cm@cmtec.se
//! License: Please refer to LICENSE in root directory

use rp2040_hal::{
    gpio::AnyPin,
    pio::{PIOExt, StateMachineIndex, UninitStateMachine, PIO},
};
use smart_leds::{SmartLedsWrite, RGB8};
use ws2812_pio::Ws2812Direct;

/// Status LED modes
///
/// * OFF = Syatem offline
/// * NORMAL = All system Ok
/// * ACTIVITY = System activity
/// * OTHER = Other activity
/// * WARNING = Warning
/// * ERROR = Error
/// * BOOTLOADER = Bootloader active
#[allow(dead_code)]
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum StatusMode {
    Off = 0,
    Normal = 1,
    NormalFlash = 2,
    Activity = 3,
    ActivityFlash = 4,
    Other = 5,
    OtherFlash = 6,
    Warning = 7,
    Error = 8,
    Bootloader = 9,
}
#[warn(dead_code)]
/// This driver uses the PIO state machine to drive a WS2812 LED
///
///  # Example
///
///  ```
/// let mut status_led = Ws2812StatusLed::new(
///     pins.neopixel.into_mode(),
///     &mut pio,
///     sm0,
///     clocks.peripheral_clock.freq(),
/// );
///  ```
pub struct Ws2812StatusLed<P, SM, I>
where
    I: AnyPin<Function = P::PinFunction>,
    P: PIOExt,
    SM: StateMachineIndex,
{
    ws2812_direct: Ws2812Direct<P, SM, I>,
    state: bool,
    mode: StatusMode,
}

impl<P, SM, I> Ws2812StatusLed<P, SM, I>
where
    I: AnyPin<Function = P::PinFunction>,
    P: PIOExt,
    SM: StateMachineIndex,
{
    /// Creates a new instance of this driver.
    ///
    /// # Arguments
    ///
    /// * `pin` - PIO pin
    /// * `pio` - PIO instance
    /// * `sm` - PIO state machine
    /// * `clock_freq` - PIO clock frequency
    pub fn new(
        pin: I,
        pio: &mut PIO<P>,
        sm: UninitStateMachine<(P, SM)>,
        clock_freq: fugit::HertzU32,
    ) -> Self {
        // prepare the PIO program
        let ws2812_direct = Ws2812Direct::new(pin, pio, sm, clock_freq);
        let state = false;
        let mode = StatusMode::Off;
        Self {
            ws2812_direct,
            state,
            mode,
        }
    }

    /// Get current status mode
    #[allow(dead_code)]
    pub fn get_mode(&self) -> StatusMode {
        self.mode
    }
    #[warn(dead_code)]
    /// Update status LED
    /// Depending on the mode, the LED will be set to a different colour
    ///
    /// * OFF = off
    /// * NORMAL = green
    /// * NORMALFLASH = green (flashing)
    /// * ACTIVITY = blue
    /// * ACTIVITYFLASH = blue (flashing)
    /// * OTHER = orange
    /// * OTHERFLASH = orange (flashing)
    /// * WARNING = red (flashing)
    /// * ERROR = red
    /// * BOOTLOADER = purple
    ///
    /// Make sure to call this function regularly to keep the LED flashing
    pub fn update(&mut self, mode: StatusMode) {
        let colors: [RGB8; 10] = [
            (0, 0, 0).into(),   // Off
            (10, 7, 0).into(),  // Green
            (10, 7, 0).into(),  // Green
            (10, 4, 10).into(), // Blue
            (10, 4, 10).into(), // Blue
            (5, 10, 0).into(),  // Orange
            (5, 10, 0).into(),  // Orange
            (2, 20, 0).into(),  // Red
            (2, 20, 0).into(),  // Red
            (0, 10, 10).into(), // Purple
        ];

        if mode == StatusMode::Warning
            || mode == StatusMode::NormalFlash
            || mode == StatusMode::ActivityFlash
            || mode == StatusMode::OtherFlash
            || mode != self.mode
        {
            self.mode = mode;
        } else {
            return;
        }

        if (mode == StatusMode::Warning
            || mode == StatusMode::NormalFlash
            || mode == StatusMode::ActivityFlash
            || mode == StatusMode::OtherFlash)
            && !self.state
        {
            self.ws2812_direct
                .write([colors[mode as usize]].iter().copied())
                .unwrap();
            self.state = true;
        } else if mode == StatusMode::Warning
            || mode == StatusMode::NormalFlash
            || mode == StatusMode::ActivityFlash
            || mode == StatusMode::OtherFlash
            || mode == StatusMode::Off
        {
            self.ws2812_direct
                .write([colors[0]].iter().copied())
                .unwrap();
            self.state = false;
        } else {
            self.ws2812_direct
                .write([colors[mode as usize]].iter().copied())
                .unwrap();
            self.state = true;
        }
    }
}
