/*
Simplified Embedded Rust: ESP Core Library Edition
The Embassy Framework - LED Cycling Application Example
*/

#![no_std]
#![no_main]

use core::sync::atomic::Ordering;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::{
    gpio::{Input, Io, Level, Output, Pull},
    timer::timg::TimerGroup,
};
use portable_atomic::AtomicU32;

// Global Variable to Control LED Rotation Speed
static BLINK_DELAY: AtomicU32 = AtomicU32::new(200_u32);

type ButtonType =
    Mutex<CriticalSectionRawMutex, Option<Input<'static>>>;
static BUTTON: ButtonType = Mutex::new(None);

#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
    // Take Peripherals
    let peripherals =
        esp_hal::init(esp_hal::Config::default());

    // Initalize embassy executor
    let timg0 = TimerGroup::new(peripherals.TIMG0);
    esp_hal_embassy::init(timg0.timer0);

    // Acquire Handle to IO
    let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
    // Configure Delay Button to Pull Up input
    let del_but = Input::new(io.pins.gpio3, Pull::Up);
    // Inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the
    // Mutex is released
    {
        *(BUTTON.lock().await) = Some(del_but);
    }
    // Configure LED Array Pins to Output & Store in Array
    let mut leds: [Output; 10] = [
        Output::new(io.pins.gpio1, Level::Low),
        Output::new(io.pins.gpio10, Level::Low),
        Output::new(io.pins.gpio19, Level::Low),
        Output::new(io.pins.gpio18, Level::Low),
        Output::new(io.pins.gpio4, Level::Low),
        Output::new(io.pins.gpio5, Level::Low),
        Output::new(io.pins.gpio6, Level::Low),
        Output::new(io.pins.gpio7, Level::Low),
        Output::new(io.pins.gpio8, Level::Low),
        Output::new(io.pins.gpio9, Level::Low),
    ];

    // Spawn Button Press Task
    spawner.spawn(press_button(&BUTTON)).unwrap();

    // This line is for Wokwi only so that the console output is formatted correctly
    esp_println::print!("\x1b[20h");

    // Enter Application Loop Blinking on LED at a Time
    loop {
        for led in &mut leds {
            led.set_high();
            Timer::after(Duration::from_millis(
                BLINK_DELAY.load(Ordering::Relaxed) as u64,
            ))
            .await;
            led.set_low();
            Timer::after(Duration::from_millis(100)).await;
        }
    }
}

#[embassy_executor::task]
async fn press_button(button: &'static ButtonType) {
    loop {
        // Wait for Button Press
        {
            let mut button_unlocked = button.lock().await;
            if let Some(button_ref) =
                button_unlocked.as_mut()
            {
                button_ref.wait_for_rising_edge().await;
                esp_println::println!("Button Pressed!");
            }
        }
        // Retrieve Delay Global Variable
        let del = BLINK_DELAY.load(Ordering::Relaxed);
        // Adjust Delay Accordingly
        if del <= 50_u32 {
            BLINK_DELAY.store(200_u32, Ordering::Relaxed);
            esp_println::println!("Delay is now 200ms");
        } else {
            BLINK_DELAY
                .store(del - 50_u32, Ordering::Relaxed);
            esp_println::println!(
                "Delay is now {}ms",
                del - 50_u32
            );
        }
    }
}