//! Data structures for process management

use core::{ffi::CStr, fmt, marker::PhantomData, time::Duration};

use crate::io::{FileMode, RawFd};

mod exit;
pub mod options;
pub mod thread;

pub use crate::generated::{
    ExecveOptions, ProcessGroupId, ProcessId, Signal, SignalEntryData, SpawnFlags, SpawnOptions,
    ThreadId, ThreadSpawnOptions, WaitFlags,
};
use abi_serde::{
    des::{DeserializeError, Deserializer},
    ser::Serializer,
    Deserialize, Serialize,
};
pub use exit::{ExitCode, ThreadEvent};

// TODO this is ugly
pub mod auxv {
    #![allow(missing_docs)]
    //! Auxiliary values passed to the executable

    pub const TLS_MASTER_COPY: u64 = 0x80;
    pub const TLS_FULL_SIZE: u64 = 0x81;
    pub const TLS_DATA_SIZE: u64 = 0x82;
    pub const TLS_ALIGN: u64 = 0x83;

    pub const TLS_MODULE_ID: u64 = 0x84;
    pub const TLS_MODULE_OFFSET: u64 = 0x85;

    pub const TLS_ALREADY_INITIALIZED: u64 = 0x86;

    pub const NULL: u64 = 0x00;
}

/// Represents a process/group object to wait on
#[derive(Debug)]
pub enum ProcessWait {
    /// Wait for a specific process
    Process(ProcessId),
    /// Wait for any process in a group
    Group(ProcessGroupId),
    /// Wait for any child process
    AnyChild,
}

/// Defines an optional argument for controlling process creation
#[derive(Debug)]
pub enum SpawnOption {
    /// Copy a file descriptor into the child process and close it in the parent
    MoveFile {
        /// Parent file descriptor
        source: RawFd,
        /// Child file descriptor
        child: RawFd,
    },
    /// Copy a file descriptor into the child process
    CopyFile {
        /// Parent file descriptor
        source: RawFd,
        /// Child file descriptor
        child: RawFd,
    },
    /// The new process should be placed in the specified group
    SetProcessGroup(ProcessGroupId),
    /// Gain terminal control for the given FD
    GainTerminal(RawFd),
    /// Attach tracing to the spawned process. This is equivalent to issuing a `debug_control` with
    /// `DebugControl::Attach` to the newly spawned process.
    /// The newly spawned process will have its main thread paused.
    AttachTrace,
}

/// Describes a single mutex operation
#[derive(Clone, Debug)]
#[repr(C)]
pub enum MutexOperation {
    /// Waits on the mutex object until it is different from "compare value"
    WaitWhileEqual(u32, Option<Duration>),
    /// Waits on the mutex object until it becomes equal to the "compare value"
    WaitUntilEqual(u32, Option<Duration>),
    /// Waits on the mutex object until `mutex & A == 0`
    WaitWhileSet(u32, Option<Duration>),
    /// Waits on the mutex object until `mutex & A != 0`
    WaitUntilSet(u32, Option<Duration>),
    /// Wakes at most N mutex-waiting threads
    Wake(u32),
}

/// Provides some amount of process-related information
#[derive(Clone, Debug)]
pub enum ProcessInfoElement {
    /// Mask applied to file modes when creating new files/directories
    Umask(FileMode),
}

/// Process auxiliary value
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct AuxValue {
    /// Tag part
    pub tag: u64,
    /// Value part
    pub val: u64,
}

/// An iterator over string-like arguments
#[derive(Clone, Copy)]
pub struct StringArgIter<'a> {
    ptr: *const *const u8,
    _pd: PhantomData<&'a ProgramArgumentInner>,
}

/// An iterator over auxiliary values
#[derive(Clone, Copy)]
pub struct AuxValueIter<'a> {
    ptr: *const AuxValue,
    _pd: PhantomData<&'a ProgramArgumentInner>,
}

/// An argument passed from the kernel/loader to the executable
#[repr(C)]
pub struct ProgramArgumentInner {
    /// Argument list
    pub args: *const *const u8,
    /// Environment variable list
    pub envs: *const *const u8,
    /// Auxiliary value list
    pub auxv: *const AuxValue,
    /// Real program path
    pub real_program: *const u8,
}

impl ProcessId {
    /// Zero PID
    pub const ZERO: Self = Self(0);
}

impl fmt::Display for ProcessId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl fmt::Display for ProcessGroupId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<Process group {}>", self.0)
    }
}

impl<'a> Iterator for StringArgIter<'a> {
    type Item = &'a CStr;

    fn next(&mut self) -> Option<Self::Item> {
        if self.ptr.is_null() {
            return None;
        }
        let entry = unsafe { self.ptr.read() };
        if entry.is_null() {
            return None;
        }
        self.ptr = unsafe { self.ptr.add(1) };
        let entry = unsafe { CStr::from_ptr(entry.cast()) };
        Some(entry)
    }
}

impl<'a> Iterator for AuxValueIter<'a> {
    type Item = &'a AuxValue;

    fn next(&mut self) -> Option<Self::Item> {
        if self.ptr.is_null() {
            return None;
        }
        let entry = unsafe { &*self.ptr };
        if entry.tag == auxv::NULL {
            return None;
        }
        self.ptr = unsafe { self.ptr.add(1) };
        Some(entry)
    }
}

impl ProgramArgumentInner {
    /// Returns an iterator over the arguments
    pub fn args(&self) -> impl Iterator<Item = &CStr> {
        StringArgIter {
            ptr: self.args,
            _pd: PhantomData,
        }
    }

    /// Returns an iterator over the environment variables
    pub fn envs(&self) -> impl Iterator<Item = &CStr> {
        StringArgIter {
            ptr: self.envs,
            _pd: PhantomData,
        }
    }

    /// Returns an iterator over the auxiliary values
    pub fn auxv(&self) -> impl Iterator<Item = &AuxValue> {
        AuxValueIter {
            ptr: self.auxv,
            _pd: PhantomData,
        }
    }

    /// Returns the real program name provided, if any
    pub fn real_program(&self) -> Option<&CStr> {
        if self.real_program.is_null() {
            return None;
        }
        Some(unsafe { CStr::from_ptr(self.real_program.cast()) })
    }
}

impl Signal {
    /// Returns `true` if the signal is delivered in synchronous fashion
    pub fn is_synchronous(&self) -> bool {
        matches!(self, Self::MemoryAccessViolation)
    }

    /// Returns `true` if the signal can be handled by an userspace process
    pub fn is_handleable(&self) -> bool {
        !matches!(self, Self::Killed)
    }
}

abi_serde::impl_newtype_serde!(ProcessGroupId);
abi_serde::impl_newtype_serde!(ProcessId);

impl Serialize for Signal {
    fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
        serializer.write_u32(self.into_raw())
    }
}

impl<'de> Deserialize<'de> for Signal {
    fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
        Self::try_from(deserializer.read_u32()?).map_err(|_| D::Error::INVALID_ENUM_VARIANT)
    }
}

impl Serialize for ThreadId {
    fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
        self.into_raw().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for ThreadId {
    fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
        Deserialize::deserialize(deserializer).map(|r| unsafe { Self::from_raw(r) })
    }
}
