//! Typed views using temporary objects.
//!
//! This module defines the return types for [`AsFilelike::as_filelike_view`]
//! and [`AsSocketlike::as_socketlike_view`].
//!
//! [`AsSocketlike::as_socketlike_view`]: crate::AsSocketlike::as_socketlike_view

use crate::raw::{
    AsRawFilelike, AsRawSocketlike, FromRawFilelike, FromRawSocketlike, IntoRawFilelike,
    IntoRawSocketlike, RawFilelike, RawSocketlike,
};
#[cfg(any(unix, target_os = "wasi"))]
use crate::OwnedFd;
use crate::{
    AsFilelike, AsSocketlike, FromFilelike, FromSocketlike, IntoFilelike, IntoSocketlike,
    OwnedFilelike, OwnedSocketlike,
};
#[cfg(windows)]
use crate::{OwnedHandle, OwnedSocket};
use std::fmt;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ops::Deref;

/// Declare that a type is safe to use in a [`FilelikeView`].
///
/// # Safety
///
/// Types implementing this trait declare that if they are constructed with
/// [`FromFilelike`] and consumed with [`IntoFilelike`], their `IntoFilelike`
/// will return the same `OwnedFd` value that was passed to their
/// `FromFilelike`.
pub unsafe trait FilelikeViewType: FromFilelike + IntoFilelike {}

/// Declare that a type is safe to use in a [`SocketlikeView`].
///
/// # Safety
///
/// Types implementing this trait declare that if they are constructed with
/// [`FromSocketlike`] and consumed with [`IntoSocketlike`], their
/// `IntoSocketlike` will return the same `OwnedFd` value that was passed to
/// their `FromSocketlike`.
pub unsafe trait SocketlikeViewType: FromSocketlike + IntoSocketlike {}

/// A non-owning view of a resource which dereferences to a `&Target` or
/// `&mut Target`. These are returned by [`AsFilelike::as_filelike_view`].
pub struct FilelikeView<'filelike, Target: FilelikeViewType> {
    /// The value to dereference to. This is a `ManuallyDrop` so that we can
    /// consume it in our `Drop` impl.
    target: ManuallyDrop<Target>,

    /// `FilelikeViewType` implementors guarantee that their `Into<OwnedFd>`
    /// returns the same fd as their `From<OwnedFd>` gave them. This field
    /// allows us to verify this.
    #[cfg(debug_assertions)]
    orig: RawFilelike,

    /// This field exists because we don't otherwise explicitly use
    /// `'filelike`.
    _phantom: PhantomData<&'filelike OwnedFilelike>,
}

/// A non-owning view of a resource which dereferences to a `&Target` or
/// `&mut Target`. These are returned by [`AsSocketlike::as_socketlike_view`].
pub struct SocketlikeView<'socketlike, Target: SocketlikeViewType> {
    /// The value to dereference to. This is a `ManuallyDrop` so that we can
    /// consume it in our `Drop` impl.
    target: ManuallyDrop<Target>,

    /// `SocketlikeViewType` implementors guarantee that their `Into<OwnedFd>`
    /// returns the same fd as their `From<OwnedFd>` gave them. This field
    /// allows us to verify this.
    #[cfg(debug_assertions)]
    orig: RawSocketlike,

    /// This field exists because we don't otherwise explicitly use
    /// `'socketlike`.
    _phantom: PhantomData<&'socketlike OwnedSocketlike>,
}

impl<Target: FilelikeViewType> FilelikeView<'_, Target> {
    /// Construct a temporary `Target` and wrap it in a `FilelikeView` object.
    #[inline]
    pub(crate) fn new<T: AsFilelike>(filelike: &T) -> Self {
        // Safety: The returned `FilelikeView` is scoped to the lifetime of
        // `filelike`, which we've borrowed here, so the view won't outlive
        // the object it's borrowed from.
        unsafe { Self::view_raw(filelike.as_filelike().as_raw_filelike()) }
    }

    /// Construct a temporary `Target` from raw and wrap it in a `FilelikeView`
    /// object.
    ///
    /// # Safety
    ///
    /// `raw` must be a valid raw filelike referencing a resource that outlives
    /// the resulting view.
    #[inline]
    pub unsafe fn view_raw(raw: RawFilelike) -> Self {
        let owned = OwnedFilelike::from_raw_filelike(raw);
        Self {
            target: ManuallyDrop::new(Target::from_filelike(owned)),
            #[cfg(debug_assertions)]
            orig: raw,
            _phantom: PhantomData,
        }
    }
}

impl<Target: SocketlikeViewType> SocketlikeView<'_, Target> {
    /// Construct a temporary `Target` and wrap it in a `SocketlikeView`
    /// object.
    #[inline]
    pub(crate) fn new<T: AsSocketlike>(socketlike: &T) -> Self {
        // Safety: The returned `SocketlikeView` is scoped to the lifetime of
        // `socketlike`, which we've borrowed here, so the view won't outlive
        // the object it's borrowed from.
        unsafe { Self::view_raw(socketlike.as_socketlike().as_raw_socketlike()) }
    }

    /// Construct a temporary `Target` from raw and wrap it in a
    /// `SocketlikeView` object.
    ///
    /// # Safety
    ///
    /// `raw` must be a valid raw socketlike referencing a resource that
    /// outlives the resulting view.
    #[inline]
    pub unsafe fn view_raw(raw: RawSocketlike) -> Self {
        let owned = OwnedSocketlike::from_raw_socketlike(raw);
        Self {
            target: ManuallyDrop::new(Target::from_socketlike(owned)),
            #[cfg(debug_assertions)]
            orig: raw,
            _phantom: PhantomData,
        }
    }
}

impl<Target: FilelikeViewType> Deref for FilelikeView<'_, Target> {
    type Target = Target;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.target
    }
}

impl<Target: SocketlikeViewType> Deref for SocketlikeView<'_, Target> {
    type Target = Target;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.target
    }
}

impl<Target: FilelikeViewType> Drop for FilelikeView<'_, Target> {
    #[inline]
    fn drop(&mut self) {
        // Use `Into*` to consume `self.target` without freeing its resource.
        //
        // Safety: Using `ManuallyDrop::take` requires us to ensure that
        // `self.target` is not used again. We don't use it again here, and
        // this is the `drop` function, so we know it's not used afterward.
        let _raw = unsafe { ManuallyDrop::take(&mut self.target) }
            .into_filelike()
            .into_raw_filelike();

        #[cfg(debug_assertions)]
        debug_assert_eq!(self.orig, _raw);
    }
}

impl<Target: SocketlikeViewType> Drop for SocketlikeView<'_, Target> {
    #[inline]
    fn drop(&mut self) {
        // Use `Into*` to consume `self.target` without freeing its resource.
        //
        // Safety: Using `ManuallyDrop::take` requires us to ensure that
        // `self.target` is not used again. We don't use it again here, and
        // this is the `drop` function, so we know it's not used afterward.
        let _raw = unsafe { ManuallyDrop::take(&mut self.target) }
            .into_socketlike()
            .into_raw_socketlike();

        #[cfg(debug_assertions)]
        debug_assert_eq!(self.orig, _raw);
    }
}

impl<Target: FilelikeViewType> fmt::Debug for FilelikeView<'_, Target> {
    #[allow(clippy::missing_inline_in_public_items)]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("FilelikeView")
            .field("target", &*self)
            .finish()
    }
}

impl<Target: SocketlikeViewType> fmt::Debug for SocketlikeView<'_, Target> {
    #[allow(clippy::missing_inline_in_public_items)]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("SocketlikeView")
            .field("target", &*self)
            .finish()
    }
}

#[cfg(any(unix, target_os = "wasi"))]
unsafe impl FilelikeViewType for OwnedFd {}
#[cfg(windows)]
unsafe impl FilelikeViewType for OwnedHandle {}
#[cfg(windows)]
unsafe impl SocketlikeViewType for OwnedSocket {}
unsafe impl FilelikeViewType for std::fs::File {}
unsafe impl SocketlikeViewType for std::net::TcpStream {}
unsafe impl SocketlikeViewType for std::net::TcpListener {}
unsafe impl SocketlikeViewType for std::net::UdpSocket {}
#[cfg(unix)]
unsafe impl SocketlikeViewType for std::os::unix::net::UnixStream {}
#[cfg(unix)]
unsafe impl SocketlikeViewType for std::os::unix::net::UnixListener {}

#[cfg(unix)]
unsafe impl SocketlikeViewType for std::os::unix::net::UnixDatagram {}
#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "os_pipe")]
unsafe impl FilelikeViewType for os_pipe::PipeWriter {}
#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "os_pipe")]
unsafe impl FilelikeViewType for os_pipe::PipeReader {}

#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "socket2")]
unsafe impl SocketlikeViewType for socket2::Socket {}

#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "async_std")]
unsafe impl SocketlikeViewType for async_std::net::TcpStream {}
#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "async_std")]
unsafe impl SocketlikeViewType for async_std::net::TcpListener {}
#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "async_std")]
unsafe impl SocketlikeViewType for async_std::net::UdpSocket {}
#[cfg(unix)]
#[cfg(feature = "async_std")]
unsafe impl SocketlikeViewType for async_std::os::unix::net::UnixStream {}
#[cfg(unix)]
#[cfg(feature = "async_std")]
unsafe impl SocketlikeViewType for async_std::os::unix::net::UnixListener {}
#[cfg(unix)]
#[cfg(feature = "async_std")]
unsafe impl SocketlikeViewType for async_std::os::unix::net::UnixDatagram {}

#[cfg(feature = "mio")]
unsafe impl SocketlikeViewType for mio::net::TcpStream {}
#[cfg(feature = "mio")]
unsafe impl SocketlikeViewType for mio::net::TcpListener {}
#[cfg(feature = "mio")]
unsafe impl SocketlikeViewType for mio::net::UdpSocket {}
#[cfg(unix)]
#[cfg(feature = "mio")]
unsafe impl SocketlikeViewType for mio::net::UnixDatagram {}
#[cfg(unix)]
#[cfg(feature = "mio")]
unsafe impl SocketlikeViewType for mio::net::UnixListener {}
#[cfg(unix)]
#[cfg(feature = "mio")]
unsafe impl SocketlikeViewType for mio::net::UnixStream {}
#[cfg(unix)]
#[cfg(feature = "mio")]
unsafe impl FilelikeViewType for mio::unix::pipe::Receiver {}
#[cfg(unix)]
#[cfg(feature = "mio")]
unsafe impl FilelikeViewType for mio::unix::pipe::Sender {}
