/*
Simplified Embedded Rust: ESP Core Library Edition
Programming Timers & Counters - Real-time Timer Application Example
*/

#![no_std]
#![no_main]

use core::cell::{Cell, RefCell};
use critical_section::Mutex;
use esp_backtrace as _;
use esp_hal::{
    delay::MicrosDurationU64,
    peripherals::TIMG0,
    prelude::*,
    timer::timg::{Timer, Timer0, TimerGroup},
};
use esp_println::println;

// Create a Global Variable for timer to pass between threads.
static G_TIMER: Mutex<
    RefCell<
        Option<Timer<Timer0<TIMG0>, esp_hal::Blocking>>,
    >,
> = Mutex::new(RefCell::new(None));
// Create a Global Variable for a FLAG to pass between threads.
static G_FLAG: Mutex<Cell<bool>> =
    Mutex::new(Cell::new(false));

struct Time {
    seconds: u32,
    minutes: u32,
    hours: u32,
}

// ISR Definition
#[handler]
fn tg0_t0_level() {
    // Start a Critical Section
    critical_section::with(|cs| {
        // Clear Timer Interrupt Pending Flag
        G_TIMER
            .borrow_ref_mut(cs)
            .as_mut()
            .unwrap()
            .clear_interrupt();
        // Re-activate Timer Alarm For Interrupts to Occur again
        G_TIMER
            .borrow_ref_mut(cs)
            .as_mut()
            .unwrap()
            .set_alarm_active(true);
        // Assert G_FLAG indicating a press button happened
        G_FLAG.borrow(cs).set(true);
    });
}

#[entry]
fn main() -> ! {
    // Take Peripherals
    let peripherals =
        esp_hal::init(esp_hal::Config::default());

    // Instantiate Timer Group 0
    let timer_group0 = TimerGroup::new(peripherals.TIMG0);

    // Instantiate Timer0 in Timer Group 0
    let timer0 = timer_group0.timer0;

    // Interrupt Configuration
    // Step 1: Configure timer to trigger an interrupt every second
    // Load count equivalent to 1 second
    timer0
        .load_value(MicrosDurationU64::micros(1_000_000))
        .unwrap();
    // Enable Alarm to generate interrupts
    timer0.set_alarm_active(true);
    // Activate counter
    timer0.set_counter_active(true);
    // Step 2: Attach Interrupt and Start listening for timer events
    timer0.set_interrupt_handler(tg0_t0_level);
    timer0.listen();
    // Step 3: Now that input is configured, move the input pin to the global context
    critical_section::with(|cs| {
        G_TIMER.borrow_ref_mut(cs).replace(timer0)
    });

    // Set up a Time struct to keep track of time
    let mut time = Time {
        seconds: 0_u32,
        minutes: 0_u32,
        hours: 0_u32,
    };

    loop {
        critical_section::with(|cs| {
            if G_FLAG.borrow(cs).get() {
                // Clear global flag
                G_FLAG.borrow(cs).set(false);
                // Update Time struct and print
                time.seconds = time.seconds.wrapping_add(1);
                if time.seconds > 59 {
                    time.minutes += 1;
                }
                if time.minutes > 59 {
                    time.hours += 1;
                }
                if time.hours > 23 {
                    time.seconds = 0;
                    time.minutes = 0;
                    time.hours = 0;
                }
                println!(
                    "Elapsed Time {:0>2}:{:0>2}:{:0>2}",
                    time.hours, time.minutes, time.seconds
                );
            }
        });
    }
}