use core::ffi::{c_int, c_void};

use yggdrasil_rt::{
    process::{ExitCode, ProcessGroupId, ProcessId, ProcessWait, WaitFlags},
    sys as syscall,
};

use crate::error::{self, EResult, ResultExt};

use super::{
    errno::Errno,
    sys_resource::__ygg_rusage_t,
    sys_time::timeval,
    sys_types::{id_t, pid_t},
};

pub const WCONTINUED: c_int = 1 << 0;
pub const WNOHANG: c_int = 1 << 1;
pub const WUNTRACED: c_int = 1 << 2;

pub const WEXITED: c_int = 1 << 3;
pub const WNOWAIT: c_int = 1 << 4;
pub const WSTOPPED: c_int = 1 << 5;

pub type idtype_t = c_int;

pub const P_ALL: idtype_t = 0;
pub const P_PGID: idtype_t = 1;
pub const P_PID: idtype_t = 2;

pub struct WaitResult {
    pid: ProcessId,
    status: ExitCode,
}

fn wait_inner(what: ProcessWait, nonblocking: bool) -> EResult<WaitResult> {
    let mut status = ExitCode::Exited(0);
    let mut flags = WaitFlags::empty();
    if nonblocking {
        flags |= WaitFlags::NON_BLOCKING;
    }
    log::info!("wait_inner({what:?})");
    let pid = unsafe { syscall::wait_process(&what, &mut status, flags) }.e_map_err(Errno::from)?;
    log::info!(" -> {pid:?}");

    EResult::Ok(WaitResult { pid, status })
}

fn convert_wait_pid(pid: pid_t) -> ProcessWait {
    // TODO "any child whose process group ID is equal to that of the calling process"
    if pid > 0 {
        ProcessWait::Process(unsafe { ProcessId::from_raw(pid as _) })
    } else if pid == -1 || pid == 0 {
        ProcessWait::AnyChild
    } else {
        let pgrp = (-pid) as u32;
        ProcessWait::Group(unsafe { ProcessGroupId::from_raw(pgrp) })
    }
}

fn encode_exit_status(code: ExitCode) -> c_int {
    match code {
        ExitCode::Exited(status) => status & 0xFF,
        ExitCode::BySignal(Ok(sig)) => {
            let signum = sig as u32;
            (signum + 0x100) as c_int
        }
        ExitCode::BySignal(Err(signum)) => (signum + 0x100) as c_int,
    }
}

#[no_mangle]
unsafe extern "C" fn wait(status: *mut c_int) -> pid_t {
    let result = wait_inner(ProcessWait::AnyChild, false);
    match result {
        EResult::Ok(res) => {
            if let Some(status) = status.as_mut() {
                *status = encode_exit_status(res.status);
            }
            res.pid.bits() as pid_t
        }
        EResult::Err(error) => {
            error::set_errno(error);
            -1 as pid_t
        }
    }
}

// TODO siginfo_t
#[no_mangle]
unsafe extern "C" fn waitid(
    _idtype: idtype_t,
    _id: id_t,
    _siginfo: *mut c_void,
    _options: c_int,
) -> c_int {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn waitpid(_pid: pid_t, _status: *mut c_int, _options: c_int) -> pid_t {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn wait4(
    pid: pid_t,
    wstatus: *mut c_int,
    options: c_int,
    rusage: *mut __ygg_rusage_t,
) -> pid_t {
    let pid = convert_wait_pid(pid);
    let result = match wait_inner(pid, options & WNOHANG != 0) {
        EResult::Ok(result) => result,
        EResult::Err(err) => {
            error::set_errno(err);
            return -1;
        }
    };
    if let Some(wstatus) = wstatus.as_mut() {
        *wstatus = encode_exit_status(result.status);
    }
    if let Some(rusage) = rusage.as_mut() {
        // TODO not implemented on the kernel level
        rusage.ru_utime = timeval::zero();
        rusage.ru_stime = timeval::zero();
    }
    result.pid.bits() as pid_t
}
