/src/svc.rs
use core::arch::global_asm;

use embedded_hal::digital::v2::OutputPin;
use usbd_hid::descriptor::KeyboardReport;

use crate::bsp::pac::SCB;
use crate::capabilities::CapType;
use crate::console::{self, ConsoleError};
use crate::kprintf;
use crate::peripherals::{with_peripherals, with_usb};
use crate::task::{current_task_has_capability, with_task_manager, TaskRegisters};
use crate::timer::ticks;

global_asm!(
    r#"
    .global SVCall
    .type SVCall,function
SVCall:
    ExceptionEntry
    bl svcall_handler   // call the handler
    ExceptionExit
"#
);

#[no_mangle]
unsafe fn svcall_handler(regs: Option<&mut TaskRegisters>) {
    let regs = regs.expect("SVCall in kernel mode!?");
    match regs.r0 {
        1 => {
            with_task_manager(|tm| {
                tm.sleep(regs.r1);
                SCB::set_pendsv();
            });
        }
        2 => {
            if !current_task_has_capability(CapType::ConsoleWrite) {
                return;
            }

            let bytes = core::slice::from_raw_parts(regs.r1 as *const u8, regs.r2 as usize);
            match console::write(bytes) {
                Ok(n) => regs.r0 = n as u32,
                Err(ConsoleError::Blocked) => {
                    with_task_manager(|tm| tm.block_current_task(CapType::ConsoleWrite));
                    SCB::set_pendsv();
                }
                Err(e) => panic!("write syscall error: {:?}", e),
            }
        }
        3 => {
            if !current_task_has_capability(CapType::ConsoleRead) {
                return;
            }

            let bytes = core::slice::from_raw_parts_mut(regs.r1 as *mut u8, regs.r2 as usize);
            match console::read(bytes) {
                Ok(n) => regs.r0 = n as u32,
                Err(ConsoleError::Blocked) => {
                    with_task_manager(|t| {
                        t.block_current_task(CapType::ConsoleRead);
                        SCB::set_pendsv();
                    });
                }
                Err(e) => panic!("read syscall error: {:?}", e),
            }
        }
        4 => {
            if !current_task_has_capability(CapType::Led) {
                return;
            }

            let on = regs.r1 != 0;
            with_peripherals(|p| {
                if on {
                    p.led().set_high().ok()
                } else {
                    p.led().set_low().ok()
                }
            });
        }
        100 => {
            with_task_manager(|tm| tm.exit_current_task());
            SCB::set_pendsv();
        }
        101 => {
            let entry = regs.r1 as *const ();
            let entry = core::mem::transmute(entry);
            let r = with_task_manager(|tm| tm.add_task("TODO", entry, &[], 10));
            match r {
                Ok(tid) => regs.r0 = tid,
                Err(_) => regs.r0 = 0xFFFFFFFF,
            }
        }
        102 => {
            with_task_manager(|tm| tm.print_tasks());
        }
        200 => {
            let p = 0x4000 as *const u8;
            let b = *p;
            regs.r0 = b as u32;
        }
        1000 => {
            // kb_read_matrix(bits: *mut u32)
            if !current_task_has_capability(CapType::Keyboard) {
                return;
            }

            let keys = core::slice::from_raw_parts_mut(regs.r1 as *mut u32, 3);
            let keybits = with_peripherals(|p| p.keyboard().scan());
            keys.copy_from_slice(&keybits);
        }
        1001 => {
            // kb_write_hid(arr: *const u8, len: usize) -> bool
            if !current_task_has_capability(CapType::Keyboard) {
                return;
            }

            if regs.r2 > 8 {
                return;
            }

            let raw_report = core::slice::from_raw_parts(regs.r1 as *const u8, regs.r2 as usize);
            let report = KeyboardReport {
                modifier: raw_report[0],
                reserved: 0,
                leds: 0,
                keycodes: raw_report[2..].try_into().unwrap(),
            };

            let r = with_usb(|u| u.keyboard().push_input(&report));
            regs.r0 = match r {
                Ok(_) => 1,
                Err(e) => {
                    kprintf!("{:?}\r\n", e);
                    0
                }
            };
        }
        _ => panic!("Unknown SVCall"),
    }
}

global_asm!(
    r#"
    .global PendSV
    .type PendSV,function
PendSV:
    ExceptionEntry
    bl pendsv_handler   // call the handler
    ExceptionExit
"#
);

#[no_mangle]
unsafe fn pendsv_handler(regs: Option<&mut TaskRegisters>) {
    let Some(regs) = regs else {
        // If we wind up here from handler mode; just bail. This should
        // only happen during debugging.
        return;
    };
    let t = ticks();
    with_task_manager(|tm| {
        tm.schedule(t);
        tm.task_swap(regs);
    });
    SCB::clear_pendsv();
}