//! System function call handlers

use abi::{
    error::Error,
    io::RawFd,
    process::{ExitCode, Signal, SignalEntryData},
    SyscallFunction,
};
use kernel_arch::task::TaskFrame;
use libk::{
    task::{
        process::{Process, ProcessIo},
        thread::Thread,
    },
    vfs::NodeRef,
};
use libk_util::sync::IrqSafeSpinlockGuard;

pub mod arg;
mod imp;

fn run_with_io<T, F: FnOnce(IrqSafeSpinlockGuard<ProcessIo>) -> T>(proc: &Process, f: F) -> T {
    let io = proc.io.lock();
    f(io)
}

fn at_fd(io: &ProcessIo, fd: Option<RawFd>) -> Result<Option<NodeRef>, Error> {
    if let Some(fd) = fd {
        let file = io.files.file(fd)?;
        file.node().ok_or(Error::InvalidFile).cloned().map(Some)
    } else {
        Ok(None)
    }
}

fn run_with_io_at<T, F: FnOnce(NodeRef, IrqSafeSpinlockGuard<ProcessIo>) -> Result<T, Error>>(
    proc: &Process,
    at: Option<RawFd>,
    f: F,
) -> Result<T, Error> {
    let mut io = proc.io.lock();
    let at = at
        .map(|fd| {
            io.files
                .file(fd)
                .and_then(|f| f.node().ok_or(Error::InvalidFile).cloned())
        })
        .transpose()?
        // at = None
        .unwrap_or_else(|| io.ioctx_mut().cwd().clone());

    f(at, io)
}

/// Handles "return from signal" syscall
///
/// # Safety
///
/// Unsafe: manipulates current thread's context
pub unsafe fn handle_signal_exit<F: TaskFrame>(frame: &mut F) {
    let saved_data: &SignalEntryData = match arg::ref_const(frame.argument() as _) {
        Ok(r) => r,
        Err(err) => {
            let thread = Thread::current();
            let process = thread.process();
            log::warn!("#{}: invalid SignalEntryData struct: {err:?}", process.id);
            thread.exit_process(ExitCode::BySignal(Ok(Signal::MemoryAccessViolation)));
        }
    };

    frame.restore(&saved_data.frame);
}

mod generated {
    #![allow(unreachable_code)]

    use abi::{
        process::{ExecveOptions, ProcessId, ThreadSpawnOptions},
        SyscallFunction,
    };
    use abi_lib::{SyscallFatRegister, SyscallRegister};

    use super::{
        arg,
        imp::{self, *},
    };

    include!(concat!(env!("OUT_DIR"), "/generated_dispatcher.rs"));
}

/// Entrypoint for system calls that takes raw argument values
pub fn raw_syscall_handler(func: usize, args: &[usize]) -> u64 {
    let Ok(func) = SyscallFunction::try_from(func) else {
        todo!("Undefined syscall: {}", func);
    };

    let result = generated::handle_syscall(func, args);
    if result.is_err() {
        log::info!("{func:?} ({args:x?}) -> {result:?}");
    }

    let value = match result {
        Ok(value) => value,
        Err(e) => (-(e as u32 as isize)) as usize,
    };

    value as _
}
