//! `#[repr(C)]` wire-layout structs the kernel reads/writes through the
//! shared mmap regions. These are the io_uring ABI definitions translated
//! from `<linux/io_uring.h>`.

/// `struct io_sqring_offsets` — byte offsets of each SQ cursor inside the SQ
/// ring mapping, returned by `io_uring_setup`.
#[repr(C)]
#[derive(Default)]
pub struct IoSqringOffsets {
    pub head: u32,
    pub tail: u32,
    pub ring_mask: u32,
    pub ring_entries: u32,
    pub flags: u32,
    pub dropped: u32,
    pub array: u32,
    pub resv1: u32,
    pub resv2: u64,
}

/// `struct io_cqring_offsets` — byte offsets of each CQ cursor inside the CQ
/// ring mapping.
#[repr(C)]
#[derive(Default)]
pub struct IoCqringOffsets {
    pub head: u32,
    pub tail: u32,
    pub ring_mask: u32,
    pub ring_entries: u32,
    pub overflow: u32,
    pub cqes: u32,
    pub flags: u32,
    pub resv1: u32,
    pub resv2: u64,
}

/// `struct io_uring_params` — `io_uring_setup`'s in/out parameter.
#[repr(C)]
#[derive(Default)]
pub struct IoUringParams {
    pub sq_entries: u32,
    pub cq_entries: u32,
    pub flags: u32,
    pub sq_thread_cpu: u32,
    pub sq_thread_idle: u32,
    pub features: u32,
    pub wq_fd: u32,
    pub resv: [u32; 3],
    pub sq_off: IoSqringOffsets,
    pub cq_off: IoCqringOffsets,
}

/// `struct io_uring_sqe` — the 64-byte submission entry.
#[repr(C)]
pub struct IoUringSqe {
    pub opcode: u8,
    pub flags: u8,
    pub ioprio: u16,
    pub fd: i32,
    pub off: u64,
    pub addr: u64,
    pub len: u32,
    pub rw_flags: u32,
    pub user_data: u64,
    pub buf_index: u16,
    pub personality: u16,
    pub splice_fd_in: i32,
    pub addr3: u64,
    pub __pad2: u64,
}

impl IoUringSqe {
    /// A zeroed SQE with the common fields set. Op-specific fields (e.g.
    /// `rw_flags` for accept flags) are tweaked by the caller afterward.
    pub fn new(opcode: u8, fd: i32, addr: u64, len: u32, user_data: u64) -> IoUringSqe {
        IoUringSqe {
            opcode,
            flags: 0,
            ioprio: 0,
            fd,
            off: 0,
            addr,
            len,
            rw_flags: 0,
            user_data,
            buf_index: 0,
            personality: 0,
            splice_fd_in: 0,
            addr3: 0,
            __pad2: 0,
        }
    }
}

/// `struct __kernel_timespec` — the timeout payload an `IORING_OP_TIMEOUT`
/// SQE points at (always 64-bit fields, independent of the C `time_t` width).
#[repr(C)]
#[derive(Default)]
pub struct KernelTimespec {
    pub tv_sec: i64,
    pub tv_nsec: i64,
}

impl KernelTimespec {
    /// A relative timeout of `ms` milliseconds.
    pub fn from_millis(ms: u64) -> KernelTimespec {
        KernelTimespec {
            tv_sec: (ms / 1000) as i64,
            tv_nsec: ((ms % 1000) * 1_000_000) as i64,
        }
    }

    /// A relative timeout of `us` microseconds — for sub-ms nap timers
    /// (io_uring reactor's bounded nap; the previous `thread::sleep`
    /// version was wake-deaf and broke -c1 Rust-client latency).
    pub fn from_micros(us: u64) -> KernelTimespec {
        KernelTimespec {
            tv_sec: (us / 1_000_000) as i64,
            tv_nsec: ((us % 1_000_000) * 1_000) as i64,
        }
    }
}

/// `struct io_uring_buf_reg` — `io_uring_register(IORING_REGISTER_PBUF_RING,
/// …)`'s argument layout.
#[repr(C)]
pub struct IoUringBufReg {
    pub ring_addr: u64,
    pub ring_entries: u32,
    pub bgid: u16,
    pub pad: u16,
    pub resv: [u64; 3],
}

/// `struct io_uring_files_update` — `io_uring_register(IORING_REGISTER_FILES_UPDATE,
/// …)`'s argument layout. Updates a contiguous range of `len` slots starting
/// at `offset` in the registered files table; `fds` points to `len` i32 fds
/// (use -1 to clear a slot).
#[repr(C)]
pub struct IoUringFilesUpdate {
    pub offset: u32,
    pub resv: u32,
    pub fds: u64,
}

/// `struct io_uring_rsrc_update` — kernel `struct io_uring_rsrc_update`,
/// the argument layout for [`crate::ffi::IORING_REGISTER_RING_FDS`]. The
/// caller fills `data` with the raw ring fd to register; the kernel writes
/// the assigned index back into `offset`. `nr_args` in the syscall is the
/// number of entries in the array (we register one at a time).
#[repr(C)]
#[derive(Default)]
pub struct IoUringRsrcUpdate {
    pub offset: u32,
    pub resv: u32,
    pub data: u64,
}

/// `struct io_uring_rsrc_register` — `io_uring_register(IORING_REGISTER_FILES2,
/// …)`'s argument layout. With `flags = IORING_RSRC_REGISTER_SPARSE` and
/// `nr = slot_count` it registers an empty registered-files table of that
/// size. `data`/`tags` are unused for sparse registration. `nr_args` in the
/// syscall must equal `sizeof::<Self>()` = 32.
#[repr(C)]
#[derive(Default)]
pub struct IoUringRsrcRegister {
    pub nr: u32,
    pub flags: u32,
    pub resv2: u64,
    pub data: u64,
    pub tags: u64,
}

/// `IORING_RSRC_REGISTER_SPARSE` — tells the kernel "no initial fds, just
/// give me an empty table of size `nr`."
pub const IORING_RSRC_REGISTER_SPARSE: u32 = 1 << 0;
