#![no_std]
#![no_main]

use cortex_m::peripheral::syst::SystClkSource;
use cortex_m_rt::entry;
use panic_halt as _;
use tm4c123x_hal::{Peripherals, prelude::*};
use core::sync::atomic::{AtomicU32, Ordering};

static SYS_TICKS: AtomicU32 = AtomicU32::new(0);

#[cortex_m_rt::exception]
fn SysTick() {
    SYS_TICKS.fetch_add(1, Ordering::Relaxed);
}

fn millis() -> u32 {
    SYS_TICKS.load(Ordering::Relaxed)
}

fn busy_delay(ms: u32) {
    let start = millis();
    while millis() - start < ms {}
}

fn task1() { busy_delay(10_000); }
fn task2() { busy_delay(10_000); }
fn task3() { busy_delay(10_000); }
fn task4() { busy_delay(10_000); }
fn opt1() { busy_delay(3_000); }
fn opt2() { busy_delay(4_000); }

struct Task {
    name: &'static str,
    duration_ms: u32,
    active: bool,
    func: fn(),
}

static TASKS_MAIN: [Task; 4] = [
    Task { name: "T1", duration_ms: 10_000, active: true, func: task1 },
    Task { name: "T2", duration_ms: 10_000, active: true, func: task2 },
    Task { name: "T3", duration_ms: 10_000, active: true, func: task3 },
    Task { name: "T4", duration_ms: 10_000, active: true, func: task4 },
];

static TASKS_OPT: [Task; 2] = [
    Task { name: "Opt1", duration_ms: 3_000, active: true, func: opt1 },
    Task { name: "Opt2", duration_ms: 4_000, active: true, func: opt2 },
];

const MAJOR_FRAME_MS: u32 = 120_000;

fn run_optionals(tasks: &[Task], mut remaining: u32) {
    for t in tasks {
        if t.duration_ms <= remaining {
            (t.func)();
            remaining -= t.duration_ms;
        } else {
            break;
        }
    }
}

fn scheduler(tasks_main: &[Task], tasks_optional: &[Task]) {
    let start_time = millis();

    for t in tasks_main {
        (t.func)();
    }

    let elapsed = millis() - start_time;

    if elapsed < MAJOR_FRAME_MS {
        run_optionals(tasks_optional, MAJOR_FRAME_MS - elapsed);
    }

    // Wait out the frame so each cycle lasts exactly 120 s
    while millis() - start_time < MAJOR_FRAME_MS {}
}

#[entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();
    let p = Peripherals::take().unwrap();

    // System control and clock
    let mut sc = p.SYSCTL.constrain();
    let clocks = sc.clock_setup.freeze(); // default 16 MHz

    // SysTick 1 ms tick
    let mut syst = cp.SYST;
    syst.set_clock_source(SystClkSource::Core);
    syst.set_reload(16_000 - 1); // 16 MHz / 16_000 = 1 kHz
    syst.clear_current();
    syst.enable_counter();
    syst.enable_interrupt();

    loop {
        scheduler(&TASKS_MAIN, &TASKS_OPT);
    }
}
