use std::{io, time::Duration};

use io_uring::{CompletionQueue, IoUring, Submitter, opcode, types};

use crate::driver::wire;

pub struct Ring {
    inner: IoUring,
}

impl Ring {
    pub(crate) fn new(uring: IoUring) -> Self {
        Self { inner: uring }
    }

    pub(crate) fn submitter<'a>(&'a self) -> Submitter<'a> {
        self.inner.submitter()
    }

    pub(crate) fn completion<'a>(&'a mut self) -> CompletionQueue<'a> {
        self.inner.completion()
    }

    pub(in crate::driver) fn submission_len(&mut self) -> usize {
        self.inner.submission().len()
    }

    pub(in crate::driver) fn is_full(&mut self) -> bool {
        self.inner.submission().is_full()
    }

    pub(in crate::driver) fn has_submission(&mut self) -> bool {
        !self.inner.submission().is_empty()
    }

    pub(in crate::driver) fn has_completion(&mut self) -> bool {
        !self.inner.completion().is_empty()
    }

    pub(in crate::driver) fn submit_and_wait(&mut self, min_complete: u32) -> io::Result<()> {
        self.inner.submit_and_wait(min_complete as usize)?;
        Ok(())
    }

    pub(in crate::driver) fn submit(&mut self) -> io::Result<usize> {
        let before = self.submission_len();
        self.inner
            .submit()
            .map(|_| before.saturating_sub(self.submission_len()))
    }

    pub(in crate::driver) fn submit_with_timeout(
        &mut self,
        min_complete: u32,
        duration: Duration,
    ) -> io::Result<()> {
        let ts = types::Timespec::from(duration);
        let args = types::SubmitArgs::new().timespec(&ts);
        let want = min_complete as usize;
        match self.inner.submitter().submit_with_args(want, &args) {
            Ok(_) => Ok(()),
            Err(e) if e.raw_os_error() == Some(libc::ETIME) => Ok(()),
            Err(e) => Err(e),
        }
    }

    pub(in crate::driver) fn try_push_arm(&mut self, sqe: io_uring::squeue::Entry) -> bool {
        unsafe {
            let mut sq = self.inner.submission();
            sq.push(&sqe).is_ok()
        }
    }

    pub(in crate::driver) fn push(&mut self, sqe: &io_uring::squeue::Entry) -> bool {
        unsafe {
            let mut sq = self.inner.submission();
            sq.push(sqe).is_ok()
        }
    }

    pub(in crate::driver) fn submit_cancel(&mut self, target_user_data: u64) {
        let sqe = opcode::AsyncCancel::new(target_user_data)
            .build()
            .user_data(wire::ignore());

        unsafe {
            let mut sq = self.inner.submission();
            if sq.push(&sqe).is_ok() {
                return;
            }
        }
        if let Err(e) = self.submit() {
            panic!("dope invariant violated: cancel submit failed: {e}");
        }
        unsafe {
            let mut sq = self.inner.submission();
            if sq.push(&sqe).is_err() {
                panic!("dope invariant violated: cancel SQE enqueue failed after submit");
            }
        }
    }
}
