#![allow(missing_docs)]

pub mod poll {
    pub use abi::io::PollControl;
}

pub use abi::io::{
    options, AccessMode, DirectoryEntry, FileAttr, FileMetadataUpdate, FileMetadataUpdateMode,
    FileMode, FileSync, FileTimesUpdate, FileType, OpenOptions, RawFd, RemoveFlags, Rename,
    SeekFrom, TimerOptions,
};
pub use abi::option::OptionSizeHint;

pub mod device;
pub mod filesystem;
pub mod paths;
pub mod terminal;
pub use paths::*;

use core::mem::MaybeUninit;

use abi::{error::Error, option::OptionValue, process::ProcessId};

pub fn read_pid_fd(fd: RawFd) -> Result<(ProcessId, i32), Error> {
    let mut buffer = [0; size_of::<u32>() + size_of::<i32>()];
    let len = unsafe { crate::sys::read(fd, &mut buffer) }?;
    assert_eq!(len, buffer.len());
    let mut word = [0; size_of::<u32>()];
    word.copy_from_slice(&buffer[0..4]);
    let pid = unsafe { ProcessId::from_raw(u32::from_ne_bytes(word)) };
    word.copy_from_slice(&buffer[4..8]);
    let status = i32::from_ne_bytes(word);
    Ok((pid, status))
}

pub fn remove_file(at: Option<RawFd>, path: &str) -> Result<(), Error> {
    unsafe { crate::sys::remove(at, path, RemoveFlags::empty()) }
}

pub fn remove_directory(at: Option<RawFd>, path: &str) -> Result<(), Error> {
    unsafe { crate::sys::remove(at, path, RemoveFlags::DIRECTORY_ONLY) }
}

pub fn remove_directory_recursive(_at: Option<RawFd>, _path: &str) -> Result<(), Error> {
    todo!()
}

fn configure_pipe(
    read: RawFd,
    write: RawFd,
    read_nonblocking: bool,
    write_nonblocking: bool,
) -> Result<(), Error> {
    if read_nonblocking {
        set_file_option::<options::NonBlocking>(read, &true)?;
    }
    if write_nonblocking {
        set_file_option::<options::NonBlocking>(write, &true)?;
    }
    Ok(())
}

pub fn create_pipe_pair(
    read_nonblocking: bool,
    write_nonblocking: bool,
) -> Result<(RawFd, RawFd), Error> {
    let mut fds = [MaybeUninit::uninit(); 2];
    unsafe { crate::sys::create_pipe(&mut fds) }?;
    let fds = unsafe { MaybeUninit::array_assume_init(fds) };
    let read = fds[0];
    let write = fds[1];

    match configure_pipe(read, write, read_nonblocking, write_nonblocking) {
        Ok(()) => Ok((read, write)),
        Err(error) => {
            unsafe {
                crate::sys::close(read).ok();
                crate::sys::close(write).ok();
            }
            Err(error)
        }
    }
}

pub macro get_file_option($fd:expr, $variant_ty:ty) {{
    let mut buffer = [0; <$variant_ty as $crate::io::OptionSizeHint>::SIZE_HINT];
    $crate::io::get_file_option::<$variant_ty>($fd, &mut buffer)
}}

pub fn get_file_option<'de, T: OptionValue<'de>>(
    fd: RawFd,
    buffer: &'de mut [u8],
) -> Result<T::Value, Error> {
    let len = unsafe { crate::sys::get_file_option(fd, T::VARIANT.into(), buffer) }?;
    T::load(&buffer[..len])
}

pub fn set_file_option<'de, T: OptionValue<'de> + OptionSizeHint>(
    fd: RawFd,
    value: &T::Value,
) -> Result<(), Error>
where
    [u8; T::SIZE_HINT]: Sized,
{
    let mut buffer = [0; T::SIZE_HINT];
    set_file_option_with::<T>(fd, &mut buffer, value)
}

pub fn set_file_option_with<'de, T: OptionValue<'de>>(
    fd: RawFd,
    buffer: &mut [u8],
    value: &T::Value,
) -> Result<(), Error> {
    let len = T::store(value, buffer)?;
    unsafe { crate::sys::set_file_option(fd, T::VARIANT.into(), &buffer[..len]) }
}

pub fn set_nonblocking(fd: RawFd, nonblocking: bool) -> Result<(), Error> {
    set_file_option::<options::NonBlocking>(fd, &nonblocking)
}
