/*
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::{
    clock::ClockControl,
    // interrupt::{self, Priority},
    peripherals::{Peripherals, TIMG0},
    prelude::*,
    timer::{Timer, Timer0, TimerGroup, TimerInterrupts},
};
use esp_println::println;

// Create a Global Variable for a GPIO Peripheral to pass around 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 around 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 = Peripherals::take();

    // Set up system clocks
    let system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // Instantiate Timer Group 0
    let timer_group0 = TimerGroup::new(
        peripherals.TIMG0,
        &clocks,
        Some(TimerInterrupts {
            timer0_t0: Some(tg0_t0_level),
            ..Default::default()
        }),
    );

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

    // Interrupt Configuration
    // Step 1: Configure timer to trigger an interrupt every second
    // Load count equivalent to 1 second
    timer0.load_alarm_value(40000000);
    // Enable Alarm to generate interrupts
    timer0.set_alarm_active(true);
    // Activate counter
    timer0.set_counter_active(true);
    // Step 2: Start listening for timer events
    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,
    };

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

    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
                );
            }
        });
    }
}