use core::{
    ffi::{c_char, c_int, c_void, CStr},
    mem::MaybeUninit,
    time::Duration,
};

use alloc::vec::Vec;
use yggdrasil_rt::{
    process::{ExitCode, Signal},
    sync::mutex::Mutex,
    sys as syscall,
};

use crate::{
    headers::sys_types::pid_t,
    io::{self, managed::stderr},
};

struct DsoDestructor {
    f: extern "C" fn(*mut c_void),
    arg: *mut c_void,
}

unsafe impl Sync for DsoDestructor {}

impl DsoDestructor {
    unsafe fn invoke(self) {
        (self.f)(self.arg)
    }
}

static AT_EXIT: Mutex<Vec<extern "C" fn()>> = Mutex::new(Vec::new());
// TODO this will be in the linker instead
static AT_DSO_EXIT: Mutex<Vec<DsoDestructor>> = Mutex::new(Vec::new());

pub fn getpid() -> pid_t {
    let pid = unsafe { syscall::get_pid() };
    pid.into_raw().try_into().unwrap()
}

pub fn getpgrp() -> pid_t {
    let pgid = unsafe { syscall::get_process_group_id() };
    pgid.into_raw().try_into().unwrap()
}

pub fn sleep(duration: Duration) -> Result<(), Duration> {
    let mut remaining = MaybeUninit::uninit();
    if unsafe { syscall::nanosleep(&duration, &mut remaining) }.is_ok() {
        Ok(())
    } else {
        Err(unsafe { remaining.assume_init() })
    }
}

pub fn abort() -> ! {
    let pid = unsafe { syscall::get_pid() };
    unsafe { syscall::send_signal(pid, Signal::Aborted).ok() };
    unreachable!()
}

pub fn c_exit_immediately(status: c_int) -> ! {
    let code = ExitCode::Exited(status);
    unsafe { syscall::exit_process(code) };
}

pub fn c_exit(status: c_int) -> ! {
    unsafe {
        pre_exit();
        c_exit_immediately(status)
    }
}

pub fn at_exit(f: extern "C" fn()) {
    AT_EXIT.lock().push(f);
}

pub fn at_dso_exit(f: extern "C" fn(*mut c_void), arg: *mut c_void, _dso_handle: *mut c_void) {
    // TODO no support for dlopen/dlclose, so at_dso_exit == at_exit right now
    AT_DSO_EXIT.lock().push(DsoDestructor { f, arg });
}

unsafe fn call_atexit() {
    for d in AT_DSO_EXIT.lock().drain(..) {
        d.invoke();
    }
    for f in AT_EXIT.lock().drain(..) {
        f();
    }
}

unsafe fn pre_exit() {
    call_atexit();
    io::cleanup();
}

#[no_mangle]
unsafe extern "C" fn __assert_fail(file: *const c_char, line: c_int, message: *const c_char) -> ! {
    use core::fmt::Write;

    let err = stderr.as_mut().unwrap();
    let file = match file.is_null() {
        false => CStr::from_ptr(file).to_str().ok(),
        true => None,
    }
    .unwrap_or("???");
    let expr = match message.is_null() {
        false => CStr::from_ptr(message).to_str().ok(),
        true => None,
    }
    .unwrap_or("???");

    writeln!(err, "Assertion failed: '{expr}' at {file}:{line}").ok();

    abort()
}
