/src/timer.rs
use core::cell::RefCell;

use crate::bsp;
use cortex_m::peripheral::SCB;
use cortex_m_rt::exception;
use critical_section::Mutex;

static TIMER: Mutex<RefCell<Option<Timer>>> = Mutex::new(RefCell::new(None));

pub type Ticks = u32;

#[derive(Debug)]
pub enum TimerError {
    TooManyTimers,
}

#[derive(Debug, Clone)]
enum TimerCallbackType {
    OneShot,
    Recurring(Ticks),
}

#[derive(Debug, Clone)]
struct TimerCallback {
    t0: Ticks,
    f: fn(),
    t_type: TimerCallbackType,
}

struct Timer {
    t: Ticks,
    callbacks: heapless::Vec<TimerCallback, 10>,
}

impl Timer {
    fn tick(&mut self) {
        self.t += 1;
    }

    pub fn gather_pending(&mut self) -> heapless::Vec<fn(), 10> {
        let mut pending = heapless::Vec::new();
        let mut i = 0;
        while i < self.callbacks.len() {
            if self.t >= self.callbacks[i].t0 {
                let f = match self.callbacks[i].t_type {
                    TimerCallbackType::OneShot => {
                        let tc = self.callbacks.swap_remove(i);
                        tc.f
                    }
                    TimerCallbackType::Recurring(d) => {
                        self.callbacks[i].t0 += d;
                        self.callbacks[i].f
                    }
                };
                pending.push(f).ok();
            } else {
                i += 1;
            }
        }
        pending
    }
}

pub fn init_timer(mut systick: bsp::pac::SYST) {
    critical_section::with(|cs| {
        systick.set_reload(bsp::pac::SYST::get_ticks_per_10ms() / 10);
        systick.clear_current();
        systick.enable_interrupt();
        systick.enable_counter();
        let mut timer = TIMER.borrow_ref_mut(cs);
        *timer = Some(Timer {
            t: 0,
            callbacks: heapless::Vec::new(),
        })
    })
}

pub fn schedule(d: Ticks, recurring: bool, f: fn()) -> Result<usize, TimerError> {
    critical_section::with(|cs| {
        let mut timer = TIMER.borrow_ref_mut(cs);
        if let Some(ref mut timer) = *timer {
            let t_type = if recurring {
                TimerCallbackType::Recurring(d)
            } else {
                TimerCallbackType::OneShot
            };
            match timer.callbacks.push(TimerCallback {
                t0: timer.t + d,
                f,
                t_type,
            }) {
                Ok(()) => Ok(timer.callbacks.len() - 1),
                Err(_) => Err(TimerError::TooManyTimers),
            }
        } else {
            panic!("Timer not initialized");
        }
    })
}

pub fn ticks() -> Ticks {
    critical_section::with(|cs| {
        let mut timer = TIMER.borrow_ref_mut(cs);
        if let Some(ref mut timer) = *timer {
            timer.t
        } else {
            panic!("Timer not initialized");
        }
    })
}

#[exception]
fn SysTick() {
    let pending = critical_section::with(|cs| {
        let mut timer = TIMER.borrow_ref_mut(cs);
        if let Some(ref mut timer) = *timer {
            timer.tick();
            timer.gather_pending()
        } else {
            panic!("Timer not initialized");
        }
    });

    for f in pending {
        (f)();
    }

    // Do a scheduling pass
    SCB::set_pendsv();
}