//! Process management data types

use core::{mem::MaybeUninit, time::Duration};

pub use abi::process::{
    auxv, AuxValue, AuxValueIter, ExecveOptions, ExitCode, MutexOperation, ProcessGroupId,
    ProcessId, ProcessInfoElement, ProcessWait, ProgramArgumentInner, Signal, SignalEntryData,
    SpawnFlags, SpawnOption, SpawnOptions, StringArgIter, ThreadEvent, ThreadId,
    ThreadSpawnOptions, WaitFlags,
};
use abi::{
    error::Error,
    option::{OptionSizeHint, OptionValue},
};

use crate::sys;

pub mod signal;
pub mod thread;
pub mod thread_local;

pub use abi::process::options;

/// Makes the current thread wait until *at least* the amount of time specified in `duration`
/// passes.
///
/// If the underlying sleep syscall is interrupted by a signal, the function will resume the sleep.
///
/// # Panics
///
/// Will panic if sleep returns any error besides [crate::Error::Interrupted].
pub fn uninterruptible_sleep(mut duration: Duration) {
    let mut remaining = MaybeUninit::uninit();
    loop {
        match unsafe { sys::nanosleep(&duration, &mut remaining) } {
            Ok(()) => return,
            Err(Error::Interrupted) => {
                let remaining = unsafe { remaining.assume_init() };
                debug_assert!(remaining <= duration);
                duration = remaining;
            }
            Err(err) => {
                panic!("nanosleep syscall failed: {err:?}");
            }
        }
    }
}

/// Helper macro for [get_process_option].
pub macro get_process_option($process:expr, $variant_ty:ty) {{
    let mut buffer = [0; <$variant_ty as $crate::io::OptionSizeHint>::SIZE_HINT];
    $crate::process::get_process_option::<$variant_ty>($process, &mut buffer)
}}

/// Retrieves a process option value.
pub fn get_process_option<'de, T: OptionValue<'de>>(
    process: Option<ProcessId>,
    buffer: &'de mut [u8],
) -> Result<T::Value, Error> {
    let len = unsafe { crate::sys::get_process_option(process, T::VARIANT.into(), buffer) }?;
    T::load(&buffer[..len])
}

/// Helper macro for [get_thread_option].
pub macro get_thread_option($variant_ty:ty) {{
    let mut buffer = [0; <$variant_ty as $crate::io::OptionSizeHint>::SIZE_HINT];
    $crate::process::get_thread_option::<$variant_ty>(&mut buffer)
}}

/// Retrieves a thread option value.
pub fn get_thread_option<'de, T: OptionValue<'de>>(
    buffer: &'de mut [u8],
) -> Result<T::Value, Error> {
    let len = unsafe { crate::sys::get_thread_option(T::VARIANT.into(), buffer) }?;
    T::load(&buffer[..len])
}

/// Update a process option value. Requires `T`: [OptionSizeHint].
pub fn set_process_option<'de, T: OptionValue<'de> + OptionSizeHint>(
    process: Option<ProcessId>,
    value: &T::Value,
) -> Result<(), Error>
where
    [u8; T::SIZE_HINT]: Sized,
{
    let mut buffer = [0; T::SIZE_HINT];
    set_process_option_with::<T>(process, &mut buffer, value)
}

/// Update a process option value, using provided buffer for serialization.
pub fn set_process_option_with<'de, T: OptionValue<'de>>(
    process: Option<ProcessId>,
    buffer: &mut [u8],
    value: &T::Value,
) -> Result<(), Error> {
    let len = T::store(value, buffer)?;
    unsafe { crate::sys::set_process_option(process, T::VARIANT.into(), &buffer[..len]) }
}

/// Update a thread option value. Requires `T`: [OptionSizeHint].
pub fn set_thread_option<'de, T: OptionValue<'de> + OptionSizeHint>(
    value: &T::Value,
) -> Result<(), Error>
where
    [u8; T::SIZE_HINT]: Sized,
{
    let mut buffer = [0; T::SIZE_HINT];
    set_thread_option_with::<T>(&mut buffer, value)
}

/// Update a thread option value, using provided buffer for serialization.
pub fn set_thread_option_with<'de, T: OptionValue<'de>>(
    buffer: &mut [u8],
    value: &T::Value,
) -> Result<(), Error> {
    let len = T::store(value, buffer)?;
    unsafe { crate::sys::set_thread_option(T::VARIANT.into(), &buffer[..len]) }
}
