use crate::critical;
use crate::task::{self, mutex::BlockingMutex, BlockingOn, Task};
use core::fmt::Write;
use gba::prelude::*;
use gba_utils::gfx::{self, Font, GfxWriter};

type Writer = GfxWriter<gfx::Subpixel4x8>;

static mut TASK_0: Task = Task::new(task_0);
static mut TASK_1: Task = Task::new(task_1);
static WRITER: Mutex<Option<Writer>> = Mutex::new(None);

pub fn init() -> ! {
    {
        let mut opt_writer = WRITER.lock();
        let di = gfx::text_mode_setup(true);
        let mut writer = GfxWriter::new(0, 0, di);
        write!(writer, "init\n");
        opt_writer.replace(writer);
    }

    const FLAGS: InterruptFlags = InterruptFlags::new().with_timer1(true).with_timer0(true);

    const T0CTRL: TimerControl = TimerControl::new()
        .with_prescaler_selection(3)
        .with_irq_on_overflow(true)
        .with_enabled(true);

    const T1CTRL: TimerControl = TimerControl::new()
        .with_chained_counting(true)
        .with_irq_on_overflow(true)
        .with_enabled(true);

    TIMER0_RELOAD.write(u16::MAX / 2);
    TIMER0_CONTROL.write(T0CTRL);

    TIMER1_RELOAD.write(u16::MAX - 1);
    TIMER1_CONTROL.write(T1CTRL);

    task::register(unsafe { &mut TASK_0 });
    //task::register(unsafe { &mut TASK_1 });

    unsafe {
        IE.write(FLAGS);
        IME.write(true);
    }

    task::start();
}

pub fn timer_irq(timer_num: u16) {
    if timer_num == 0 {
        task::unblock_on(BlockingOn::Timer0);
    } else {
        task::unblock_on(BlockingOn::Timer1);
    }
}

fn with_writer(func: impl FnOnce(&mut Writer)) {
    critical!({
        let mut lock = WRITER.lock();
        let writer = lock.as_mut().unwrap();
        func(writer)
    });
}

extern "C" fn task_0() {
    with_writer(|w| {
        write!(w, "task 0 start\n").ok();
    });

    let mut n = 0;

    loop {
        task::block_on(BlockingOn::Timer0);
        with_writer(|w| {
            write!(w, "{}: timer 0\n", n).ok();
        });
        n += 1;
    }
}

extern "C" fn task_1() {
    with_writer(|w| {
        write!(w, "task 1 start\n").ok();
    });
    let mut n = 0;
    loop {
        task::block_on(BlockingOn::Timer1);
        with_writer(|w| {
            write!(w, "{}: timer 1\n", n).ok();
        });
        n += 1;
    }
}
