//! <div class="warning">
//!
//! This module is not intended to be used directly. It is only public for
//! documentation purposes.
//!
//! </div>

use std::io;
use std::os::fd::{AsFd as _, BorrowedFd};
use std::task::{Context, Poll, ready};

use tokio::io::Interest;

use crate::net::general::unix::{OwnedReadHalf, OwnedWriteHalf, TcpStream};

/// Marker trait indicating that the file descriptor can be "read" from.
///
/// <div class="warning">
///
/// This trait is exposed only to show implementations on foreign types or
/// implementors.
///
/// </div>
pub trait AsyncRead {
    #[doc(hidden)]
    /// Polls the file descriptor for readability and then executes the provided
    /// read operation `f` (i.e., `splice(2)`) when ready.
    fn poll_read_fn<F, R>(&self, _cx: &mut Context<'_>, _f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>;
}

impl<T> AsyncRead for &T
where
    T: AsyncRead,
{
    fn poll_read_fn<F, R>(&self, cx: &mut Context<'_>, f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        (*self).poll_read_fn(cx, f)
    }
}

impl AsyncRead for TcpStream {
    fn poll_read_fn<F, R>(&self, cx: &mut Context<'_>, mut f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        self.poll_read_fn(cx, |fd| f(fd.as_fd()))
    }
}

impl AsyncRead for OwnedReadHalf {
    fn poll_read_fn<F, R>(&self, cx: &mut Context<'_>, mut f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        self.poll_read_fn(cx, |fd| f(fd.as_fd()))
    }
}

impl AsyncRead for tokio::net::TcpStream {
    fn poll_read_fn<F, R>(&self, cx: &mut Context<'_>, mut f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        loop {
            ready!(self.poll_read_ready(cx))?;

            match self.try_io(Interest::READABLE, || f(self.as_fd())) {
                result @ Ok(_) => return Poll::Ready(result),
                Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
                e @ Err(_) => return Poll::Ready(e),
            }
        }
    }
}

impl AsyncRead for tokio::net::tcp::OwnedReadHalf {
    fn poll_read_fn<F, R>(&self, cx: &mut Context<'_>, f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        self.as_ref().poll_read_fn(cx, f)
    }
}

/// Marker trait indicating that the file descriptor can be "written" to.
///
/// <div class="warning">
///
/// This trait is exposed only to show implementations on foreign types or
/// implementors.
///
/// </div>
pub trait AsyncWrite {
    #[doc(hidden)]
    /// Polls the file descriptor for writability and then executes the provided
    /// write operation `f` (i.e., `splice(2)`) when ready.
    fn poll_write_fn<F, R>(&self, _cx: &mut Context<'_>, _f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>;
}

impl<T> AsyncWrite for &T
where
    T: AsyncWrite,
{
    fn poll_write_fn<F, R>(&self, cx: &mut Context<'_>, f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        (*self).poll_write_fn(cx, f)
    }
}

impl AsyncWrite for TcpStream {
    fn poll_write_fn<F, R>(&self, cx: &mut Context<'_>, mut f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        self.poll_write_fn(cx, |fd| f(fd.as_fd()))
    }
}

impl AsyncWrite for OwnedWriteHalf {
    fn poll_write_fn<F, R>(&self, cx: &mut Context<'_>, mut f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        self.poll_write_fn(cx, |fd| f(fd.as_fd()))
    }
}

impl AsyncWrite for tokio::net::TcpStream {
    fn poll_write_fn<F, R>(&self, cx: &mut Context<'_>, mut f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        loop {
            ready!(self.poll_write_ready(cx))?;

            match self.try_io(Interest::WRITABLE, || f(self.as_fd())) {
                result @ Ok(_) => return Poll::Ready(result),
                Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
                e @ Err(_) => return Poll::Ready(e),
            }
        }
    }
}

impl AsyncWrite for tokio::net::tcp::OwnedWriteHalf {
    fn poll_write_fn<F, R>(&self, cx: &mut Context<'_>, f: F) -> Poll<io::Result<R>>
    where
        F: FnMut(BorrowedFd<'_>) -> io::Result<R>,
    {
        self.as_ref().poll_write_fn(cx, f)
    }
}

/// Marker trait indicating that the file descriptor is a duplex "stream"-based
/// socket and can be split into a read half and a write half.
///
/// <div class="warning">
///
/// This trait is exposed only to show implementations on foreign types or
/// implementors.
///
/// </div>
pub trait AsyncStream {
    #[doc(hidden)]
    type ReadHalf: AsyncRead;
    #[doc(hidden)]
    type WriteHalf: AsyncWrite;

    #[doc(hidden)]
    fn split(self) -> (Self::ReadHalf, Self::WriteHalf);
}

impl AsyncStream for TcpStream {
    type ReadHalf = OwnedReadHalf;
    type WriteHalf = OwnedWriteHalf;

    fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
        self.split()
    }
}

impl AsyncStream for tokio::net::TcpStream {
    type ReadHalf = tokio::net::tcp::OwnedReadHalf;
    type WriteHalf = tokio::net::tcp::OwnedWriteHalf;

    fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
        self.into_split()
    }
}

/// Marker trait indicating that the file descriptor is a regular file.
///
/// <div class="warning">
///
/// This trait is exposed only to show implementations on foreign types or
/// implementors.
///
/// </div>
pub trait AsyncFile {}

impl AsyncFile for tokio::fs::File {}
