#![no_std]
#![no_main]


use core::cell::{Cell, RefCell};
use critical_section::Mutex;
use esp_backtrace as _;
use esp_println::println;
use esp_hal::
{
    delay::Delay,
    prelude::*, 
    gpio::{Event, Input, Pull, Io, Level, Output}
};

// Global Variable for a GPIO Peripheral to pass around between threads
/*****************************************************************************************************************
// Globabl shared variable created to access the GPIO peripheral
// This is because the the same peripheral instance needs to be accessed by two threads (main and the ISR)
// G_PIN starts with an Option that is wrapped in a RefCell, that is wrapped in a Mutex
// The Mutex makes sure that the peripheral can be safely shared among threads
// This means it would require a critical section everytime you want to access the peripheral 
// Critical sections make sure that no other thread would access the global variable in the same time
// The RefCell is used to be able to obtain a mutable reference to the peripheral
// The Option is used to allow for lazy initialization as we would not be able to initialize the variable 
// until after we have configured it
// Option allows us to place a placeholder of None until that is completed
******************************************************************************************************************/
static G_PIN: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));

/*** ISR Definition ***/
// The definition of an ISR begins by using the #[handler] attribute.
// Followed by a function definition, function name has to be consistent with whatever you use in main
// .clear_interrupt() is used to re-enable the ISR after it's done (doesn't have to be done in the ISR)
#[handler]
fn gpio()
{
    // Start Critical Section    // Obtain access to global pin and clear interrupt pending flag
    critical_section::with(|cs| {  G_PIN.borrow_ref_mut(cs).as_mut().unwrap().clear_interrupt(); });
}

#[entry]
fn main() -> ! 
{
    // Step 1: Take Peripherals & Configure Device
    let peripherals = esp_hal::init(esp_hal::Config::default());
    // Step 2: Create IO Driver
    let mut io = Io::new(peripherals.GPIO, peripherals.IO_MUX);

    /*** Interrupt Configuration: *******************************************************************/
    // Inside Main we need to configure and enable interrupt operation for GPIO peripheral input
    // This is such that when an even occurs on a GPIO input pin, execution switches over to the ISR
    /************************************************************************************************/
    // Step 1: Register interrupt handler
    // esp_hal::gpio::Io has a set_interrupt_handler function that accepts only the name of the ISR
    io.set_interrupt_handler(gpio);

    // Step 2: Configure pin direction
    let mut some_pin = Input::new(io.pins.gpio0, Pull::Up);

    // Step 3: Configure input to trigger an interrupt on the falling edge and start listening to events
    // listen takes one argument that is a esp_hal::gpio::Event enum defining the type of event
    some_pin.listen(Event::FallingEdge); 

    // Step 4: Now that pin is configured, move the pin to the global context
    // In critical section of code, preemption from interrrupts is disabled to ensure there are no race conditions
    // This is required because G_PIN is wrapped in a mutex
    // The closure passes a token cs that allows us to borrow a mutable reference to the global variable
    // and replace the Option inside of it with Some(Button).
    // From this point on in code, every time we want to access G_PIN we would need to introduce a critical section
    // using critical_section::with
    critical_section::with(|cs| G_PIN.borrow_ref_mut(cs).replace(some_pin));

    // Following application code
    loop 
    {
        //println!("Do some Bullshit");
    }
}