#![cfg_attr(feature = "once_cell_try", feature(once_cell_try))]
#![warn(missing_docs)]
#[cfg(all(
    target_os = "linux",
    not(feature = "io-uring"),
    not(feature = "polling")
))]
compile_error!("You must choose one of these features: [\"io-uring\", \"polling\"]");
use std::{io, task::Poll, time::Duration};
use compio_buf::BufResult;
use slab::Slab;
pub mod op;
#[cfg(unix)]
mod unix;
cfg_if::cfg_if! {
    if #[cfg(target_os = "windows")] {
        #[path = "iocp/mod.rs"]
        mod sys;
    } else if #[cfg(all(target_os = "linux", feature = "io-uring"))] {
        #[path = "iour/mod.rs"]
        mod sys;
    } else if #[cfg(unix)] {
        #[path = "poll/mod.rs"]
        mod sys;
    }
}
pub use sys::*;
#[cfg(target_os = "windows")]
#[macro_export]
#[doc(hidden)]
macro_rules! syscall {
    ($fn: ident ( $($arg: expr),* $(,)* ), $op: tt $rhs: expr) => {{
        #[allow(unused_unsafe)]
        let res = unsafe { $fn($($arg, )*) };
        if res $op $rhs {
            Err(::std::io::Error::last_os_error())
        } else {
            Ok(res)
        }
    }};
    (BOOL, $fn: ident ( $($arg: expr),* $(,)* )) => {
        $crate::syscall!($fn($($arg, )*), == 0)
    };
    (SOCKET, $fn: ident ( $($arg: expr),* $(,)* )) => {
        $crate::syscall!($fn($($arg, )*), != 0)
    };
    (HANDLE, $fn: ident ( $($arg: expr),* $(,)* )) => {
        $crate::syscall!($fn($($arg, )*), == ::windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE)
    };
}
#[cfg(unix)]
#[macro_export]
#[doc(hidden)]
macro_rules! syscall {
    ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{
        #[allow(unused_unsafe)]
        let res = unsafe { ::libc::$fn($($arg, )*) };
        if res == -1 {
            Err(::std::io::Error::last_os_error())
        } else {
            Ok(res)
        }
    }};
    (break $fn: ident ( $($arg: expr),* $(,)* )) => {
        match $crate::syscall!( $fn ( $($arg, )* )) {
            Ok(fd) => ::std::task::Poll::Ready(Ok(fd as usize)),
            Err(e) if e.kind() == ::std::io::ErrorKind::WouldBlock || e.raw_os_error() == Some(::libc::EINPROGRESS)
                   => ::std::task::Poll::Pending,
            Err(e) => ::std::task::Poll::Ready(Err(e)),
        }
    };
    ($fn: ident ( $($arg: expr),* $(,)* ) or $f:ident($fd:expr)) => {
        match $crate::syscall!( break $fn ( $($arg, )* )) {
            ::std::task::Poll::Pending => Ok($crate::Decision::$f($fd)),
            ::std::task::Poll::Ready(Ok(res)) => Ok($crate::Decision::Completed(res)),
            ::std::task::Poll::Ready(Err(e)) => Err(e),
        }
    };
}
#[macro_export]
#[doc(hidden)]
macro_rules! impl_raw_fd {
    ($t:ty, $inner:ident) => {
        impl $crate::AsRawFd for $t {
            fn as_raw_fd(&self) -> $crate::RawFd {
                self.$inner.as_raw_fd()
            }
        }
        impl $crate::FromRawFd for $t {
            unsafe fn from_raw_fd(fd: $crate::RawFd) -> Self {
                Self {
                    $inner: $crate::FromRawFd::from_raw_fd(fd),
                }
            }
        }
        impl $crate::IntoRawFd for $t {
            fn into_raw_fd(self) -> $crate::RawFd {
                self.$inner.into_raw_fd()
            }
        }
    };
}
pub enum PushEntry<K, R> {
    Pending(K),
    Ready(R),
}
impl<K, R> PushEntry<K, R> {
    pub fn map_pending<L>(self, f: impl FnOnce(K) -> L) -> PushEntry<L, R> {
        match self {
            Self::Pending(k) => PushEntry::Pending(f(k)),
            Self::Ready(r) => PushEntry::Ready(r),
        }
    }
    pub fn map_ready<S>(self, f: impl FnOnce(R) -> S) -> PushEntry<K, S> {
        match self {
            Self::Pending(k) => PushEntry::Pending(k),
            Self::Ready(r) => PushEntry::Ready(f(r)),
        }
    }
}
pub struct Proactor {
    driver: Driver,
    ops: Slab<RawOp>,
}
impl Proactor {
    pub fn new() -> io::Result<Self> {
        Self::with_entries(1024)
    }
    pub fn with_entries(entries: u32) -> io::Result<Self> {
        Ok(Self {
            driver: Driver::new(entries)?,
            ops: Slab::with_capacity(entries as _),
        })
    }
    pub fn attach(&mut self, fd: RawFd) -> io::Result<()> {
        self.driver.attach(fd)
    }
    pub fn cancel(&mut self, user_data: usize) {
        self.driver.cancel(user_data, &mut self.ops);
    }
    pub fn push<T: OpCode + 'static>(&mut self, op: T) -> PushEntry<usize, BufResult<usize, T>> {
        let entry = self.ops.vacant_entry();
        let user_data = entry.key();
        let op = RawOp::new(user_data, op);
        let op = entry.insert(op);
        match self.driver.push(user_data, op) {
            Poll::Pending => PushEntry::Pending(user_data),
            Poll::Ready(res) => {
                let op = self.ops.remove(user_data);
                PushEntry::Ready(BufResult(res, unsafe { op.into_inner::<T>() }))
            }
        }
    }
    pub fn poll(
        &mut self,
        timeout: Option<Duration>,
        entries: &mut impl Extend<Entry>,
    ) -> io::Result<()> {
        unsafe {
            self.driver.poll(timeout, entries, &mut self.ops)?;
        }
        Ok(())
    }
    pub fn pop<'a>(
        &'a mut self,
        entries: &'a mut impl Iterator<Item = Entry>,
    ) -> impl Iterator<Item = BufResult<usize, Operation>> + 'a {
        std::iter::from_fn(|| {
            entries.next().map(|entry| {
                let op = self
                    .ops
                    .try_remove(entry.user_data())
                    .expect("the entry should be valid");
                let op = Operation::new(op, entry.user_data());
                BufResult(entry.into_result(), op)
            })
        })
    }
}
impl AsRawFd for Proactor {
    fn as_raw_fd(&self) -> RawFd {
        self.driver.as_raw_fd()
    }
}
pub struct Operation {
    op: RawOp,
    user_data: usize,
}
impl Operation {
    pub(crate) fn new(op: RawOp, user_data: usize) -> Self {
        Self { op, user_data }
    }
    #[doc(hidden)]
    pub fn into_inner(self) -> RawOp {
        self.op
    }
    pub unsafe fn into_op<T: OpCode>(self) -> T {
        self.into_inner().into_inner()
    }
    pub fn user_data(&self) -> usize {
        self.user_data
    }
}
#[derive(Debug)]
pub struct Entry {
    user_data: usize,
    result: io::Result<usize>,
}
impl Entry {
    pub(crate) fn new(user_data: usize, result: io::Result<usize>) -> Self {
        Self { user_data, result }
    }
    pub fn user_data(&self) -> usize {
        self.user_data
    }
    pub fn into_result(self) -> io::Result<usize> {
        self.result
    }
}