#![no_std]
#![feature(start)]
#![feature(asm)]
#![feature(cell_update)]
#![feature(slice_as_chunks)]
#![feature(const_slice_from_raw_parts)]
#![feature(const_raw_ptr_deref)]
#![feature(isa_attribute)]
#![feature(const_fn_fn_ptr_basics)]
#![allow(dead_code)]
#![feature(const_ptr_offset)]

mod demo1;
mod demo2;
mod demo3;
mod demo4;
mod demo5;
mod error_handling;
mod flash;
mod macros;
mod queue;
mod serial;
mod task;

use crate::task::{_critical_enter, _critical_exit};
use core::sync::atomic::{compiler_fence, Ordering};
use gba::prelude::*;
use gba_utils::gfx;

pub struct GameFns {
    pub run: fn() -> !,
    pub draw: fn(),
    pub update: fn(),
    pub serial: fn(u16),
    pub timer: fn(u16),
}

const GAMEFNS: GameFns = GameFns {
    run: demo5::init,
    draw: task::vblank_irq,
    update: task::vcount_irq,
    serial: demo5::serial_irq,
    timer: demo5::timer_irq,
};

extern "C" {
    fn critical_enter_from_thumb() -> u32;
    fn critical_exit_from_thumb(psr: u32);
}

pub fn critical<T>(mut func: impl FnOnce() -> T) -> T {
    let psr = _critical_enter();
    // prevents the contents of the function from being reordered before IME is disabled.
    memory_write_hint(&mut func);

    let mut result = func();

    // prevents the contents of the function from being reordered after IME is re-enabled.
    memory_write_hint(&mut result);
    _critical_exit(psr);

    result
}

#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
    unsafe { USER_IRQ_HANDLER.write(Some(irq_handler_a32)) };
    (GAMEFNS.run)()
}

#[instruction_set(arm::a32)]
extern "C" fn irq_handler_a32() {
    irq_handler_t32()
}

/// Handles interrupt requests.
fn irq_handler_t32() {
    let state = SIOCNT.read();

    let to_handle = IRQ_PENDING.read() & IE.read();

    let mut intr_wait_flags = INTR_WAIT_ACKNOWLEDGE.read();

    compiler_fence(Ordering::SeqCst);

    if to_handle.vblank() {
        (GAMEFNS.draw)();
        intr_wait_flags.set_vblank(true);
    }

    if to_handle.vcount() {
        (GAMEFNS.update)();
        intr_wait_flags.set_vcount(true);
    }

    if to_handle.timer0() {
        (GAMEFNS.timer)(0);
        intr_wait_flags.set_timer0(true);
    }

    if to_handle.timer1() {
        (GAMEFNS.timer)(1);
        intr_wait_flags.set_timer1(true);
    }

    if to_handle.serial() {
        (GAMEFNS.serial)(state);
        intr_wait_flags.set_serial(true);
    }

    compiler_fence(Ordering::SeqCst);

    IRQ_ACKNOWLEDGE.write(to_handle);

    unsafe {
        INTR_WAIT_ACKNOWLEDGE.write(intr_wait_flags);
    }
}
