//! The following is derived from Rust's
//! library/std/src/os/windows/io/socket.rs
//! at revision
//! 4f9b394c8a24803e57ba892fa00e539742ebafc0.
//!
//! All code in this file is licensed MIT or Apache 2.0 at your option.

use super::raw::*;
use crate::backend::c;
use crate::backend::fd::LibcFd as LibcSocket;
use core::fmt;
use core::marker::PhantomData;
use core::mem::forget;

/// A borrowed socket.
///
/// This has a lifetime parameter to tie it to the lifetime of something that
/// owns the socket.
///
/// This uses `repr(transparent)` and has the representation of a host socket,
/// so it can be used in FFI in places where a socket is passed as an argument,
/// it is not captured or consumed, and it never has the value
/// `INVALID_SOCKET`.
///
/// This type's `.to_owned()` implementation returns another `BorrowedSocket`
/// rather than an `OwnedSocket`. It just makes a trivial copy of the raw
/// socket, which is then borrowed under the same lifetime.
#[derive(Copy, Clone)]
#[repr(transparent)]
#[cfg_attr(staged_api, rustc_layout_scalar_valid_range_start(0))]
// This is -2, in two's complement. -1 is `INVALID_SOCKET`.
#[cfg_attr(
    all(staged_api, target_pointer_width = "32"),
    rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)
)]
#[cfg_attr(
    all(staged_api, target_pointer_width = "64"),
    rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FF_FF_FF_FF_FE)
)]
#[cfg_attr(staged_api, rustc_nonnull_optimization_guaranteed)]
#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
pub struct BorrowedSocket<'socket> {
    socket: RawSocket,
    _phantom: PhantomData<&'socket OwnedSocket>,
}

/// An owned socket.
///
/// This closes the socket on drop.
///
/// This uses `repr(transparent)` and has the representation of a host socket,
/// so it can be used in FFI in places where a socket is passed as a consumed
/// argument or returned as an owned value, and it never has the value
/// `INVALID_SOCKET`.
#[repr(transparent)]
#[cfg_attr(staged_api, rustc_layout_scalar_valid_range_start(0))]
// This is -2, in two's complement. -1 is `INVALID_SOCKET`.
#[cfg_attr(
    all(staged_api, target_pointer_width = "32"),
    rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)
)]
#[cfg_attr(
    all(staged_api, target_pointer_width = "64"),
    rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FF_FF_FF_FF_FE)
)]
#[cfg_attr(staged_api, rustc_nonnull_optimization_guaranteed)]
#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
pub struct OwnedSocket {
    socket: RawSocket,
}

impl BorrowedSocket<'_> {
    /// Return a `BorrowedSocket` holding the given raw socket.
    ///
    /// # Safety
    ///
    /// The resource pointed to by `raw` must remain open for the duration of
    /// the returned `BorrowedSocket`, and it must not have the value
    /// `INVALID_SOCKET`.
    #[inline]
    #[cfg_attr(
        staged_api,
        rustc_const_stable(feature = "io_safety", since = "1.63.0")
    )]
    #[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
    pub const unsafe fn borrow_raw(socket: RawSocket) -> Self {
        assert!(socket != c::INVALID_SOCKET as RawSocket);
        Self {
            socket,
            _phantom: PhantomData,
        }
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl AsRawSocket for BorrowedSocket<'_> {
    #[inline]
    fn as_raw_socket(&self) -> RawSocket {
        self.socket
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl AsRawSocket for OwnedSocket {
    #[inline]
    fn as_raw_socket(&self) -> RawSocket {
        self.socket
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl IntoRawSocket for OwnedSocket {
    #[inline]
    fn into_raw_socket(self) -> RawSocket {
        let socket = self.socket;
        forget(self);
        socket
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl FromRawSocket for OwnedSocket {
    #[inline]
    unsafe fn from_raw_socket(socket: RawSocket) -> Self {
        debug_assert_ne!(socket, c::INVALID_SOCKET as RawSocket);
        Self { socket }
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl Drop for OwnedSocket {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            let _ = c::closesocket(self.socket as LibcSocket);
        }
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl fmt::Debug for BorrowedSocket<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("BorrowedSocket")
            .field("socket", &self.socket)
            .finish()
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl fmt::Debug for OwnedSocket {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("OwnedSocket")
            .field("socket", &self.socket)
            .finish()
    }
}

/// A trait to borrow the socket from an underlying object.
#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
pub trait AsSocket {
    /// Borrows the socket.
    #[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
    fn as_socket(&self) -> BorrowedSocket<'_>;
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl<T: AsSocket> AsSocket for &T {
    #[inline]
    fn as_socket(&self) -> BorrowedSocket<'_> {
        T::as_socket(self)
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl<T: AsSocket> AsSocket for &mut T {
    #[inline]
    fn as_socket(&self) -> BorrowedSocket<'_> {
        T::as_socket(self)
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl AsSocket for BorrowedSocket<'_> {
    #[inline]
    fn as_socket(&self) -> BorrowedSocket<'_> {
        *self
    }
}

#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
impl AsSocket for OwnedSocket {
    #[inline]
    fn as_socket(&self) -> BorrowedSocket<'_> {
        // Safety: `OwnedSocket` and `BorrowedSocket` have the same validity
        // invariants, and the `BorrowdSocket` is bounded by the lifetime
        // of `&self`.
        unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) }
    }
}
