#![cfg(windows)]

use crate::{Cvt, NtCvt, Sealed, WSACvt};
use std::{
    io::{self, Error},
    os::{
        raw::{c_int, c_long, c_uchar},
        windows::raw::HANDLE,
    },
};

macro_rules! impl_for_bool {
    ($(#[$doc:meta] $T:ty)+) => {
        $(
            impl Sealed for $T {
                type Output = ();
            }

            #[$doc]
            impl Cvt for $T {
                /// Returns [`Err(io::Error::last_os_error())`](Error::last_os_error)
                /// if `self` is zero, [`Ok(())`](Ok) otherwise.
                #[inline]
                fn cvt(self) -> io::Result<Self::Output> {
                    if self == 0 {
                        Err(Error::last_os_error())
                    } else {
                        Ok(())
                    }
                }
            }
        )+
    };
}

impl_for_bool! {
    /// Implementation for [`BOOL`](https://docs.rs/winapi/0.3.9/winapi/shared/minwindef/type.BOOL.html).
    c_int
    /// Implementation for [`BOOLEAN`](https://docs.rs/winapi/0.3.9/winapi/shared/ntdef/type.BOOLEAN.html).
    c_uchar
}

impl Sealed for HANDLE {
    type Output = Self;
}

impl Cvt for HANDLE {
    /// Returns [`Err(io::Error::last_os_error())`](Error::last_os_error)
    /// if `self` is [`NULL`](std::ptr::null_mut) or
    /// [`INVALID_HANDLE_VALUE`](https://docs.rs/winapi/0.3.9/winapi/um/handleapi/constant.INVALID_HANDLE_VALUE.html),
    /// [`Ok(self)`](Ok) otherwise.
    #[inline]
    fn cvt(self) -> io::Result<Self::Output> {
        if self.is_null() || self as isize == -1 {
            Err(Error::last_os_error())
        } else {
            Ok(self)
        }
    }
}

#[link(kind = "dylib", name = "ntdll")]
extern "system" {
    fn RtlNtStatusToDosError(status: i32) -> u32;
}

#[inline]
fn nt_status_to_error(status: i32) -> Error {
    #[allow(clippy::cast_possible_wrap)]
    Error::from_raw_os_error(unsafe { RtlNtStatusToDosError(status) } as _)
}

/// Implementation for [`NTSTATUS`](https://docs.rs/winapi/0.3.9/winapi/shared/ntdef/type.NTSTATUS.html).
impl NtCvt for c_long {
    /// Returns [`Err`] if [`NT_SUCCESS(self)`](https://docs.rs/winapi/0.3.9/winapi/shared/ntdef/fn.NT_SUCCESS.html),
    /// [`Ok(())`](Ok) otherwise.
    #[inline]
    fn nt_cvt(self) -> io::Result<Self::Output> {
        if self >= 0 {
            Ok(())
        } else {
            Err(nt_status_to_error(self))
        }
    }
}

// No need to `#[link]` here because libstd already links to `ws2_32.dll`.
extern "system" {
    fn WSAGetLastError() -> c_int;
}

#[inline]
fn wsa_last_error() -> Error {
    Error::from_raw_os_error(unsafe { WSAGetLastError() })
}

impl WSACvt for c_int {
    /// Returns [`Err`] if `self` is [`SOCKET_ERROR`](https://docs.rs/winapi/0.3.9/winapi/um/winsock2/constant.SOCKET_ERROR.html),
    /// [`Ok(())`](Ok) otherwise.
    #[inline]
    fn wsa_cvt(self) -> io::Result<Self::Output> {
        if self == -1 {
            Err(wsa_last_error())
        } else {
            Ok(())
        }
    }
}

impl Sealed for usize {
    type Output = Self;
}

/// Implementation for [`SOCKET`](https://docs.rs/winapi/0.3.9/winapi/um/winsock2/type.SOCKET.html).
impl WSACvt for usize {
    /// Returns [`Err`] if `self` is [`INVALID_SOCKET`](https://docs.rs/winapi/0.3.9/winapi/um/winsock2/constant.INVALID_SOCKET.html),
    /// [`Ok(self)`](Ok) otherwise.
    #[inline]
    fn wsa_cvt(self) -> io::Result<Self::Output> {
        if self == !0 {
            Err(wsa_last_error())
        } else {
            Ok(self)
        }
    }
}

impl WSACvt for HANDLE {
    /// Returns [`Err`] if `self` is [`NULL`](std::ptr::null_mut) or
    /// [`INVALID_HANDLE_VALUE`](https://docs.rs/winapi/0.3.9/winapi/um/handleapi/constant.INVALID_HANDLE_VALUE.html),
    /// [`Ok(self)`](Ok) otherwise.
    #[inline]
    fn wsa_cvt(self) -> io::Result<Self::Output> {
        if self.is_null() || self as isize == -1 {
            Err(wsa_last_error())
        } else {
            Ok(self)
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{Cvt, NtCvt, WSACvt};
    use std::{
        ffi::c_void,
        io::{Error, ErrorKind},
        os::{
            raw::{c_int, c_uchar},
            windows::raw::HANDLE,
        },
        ptr::null_mut,
    };

    #[test]
    fn cvt() {
        c_int::MAX.cvt().unwrap();
        c_uchar::MAX.cvt().unwrap();
        assert_eq!((1 as HANDLE).cvt().unwrap(), 1 as _);
        let code = Error::last_os_error().raw_os_error().unwrap();
        assert_eq!(
            c_int::default().cvt().unwrap_err().raw_os_error().unwrap(),
            code
        );
        assert_eq!(
            c_uchar::default()
                .cvt()
                .unwrap_err()
                .raw_os_error()
                .unwrap(),
            code
        );
        assert_eq!(
            null_mut::<c_void>()
                .cvt()
                .unwrap_err()
                .raw_os_error()
                .unwrap(),
            code
        );
        assert_eq!(
            (-1_isize as HANDLE)
                .cvt()
                .unwrap_err()
                .raw_os_error()
                .unwrap(),
            code
        );
    }

    #[allow(overflowing_literals)]
    const STATUS_INVALID_PARAMETER: i32 = 0xC000_000D;

    #[test]
    fn nt_status_to_error() {
        assert_eq!(
            super::nt_status_to_error(STATUS_INVALID_PARAMETER).kind(),
            ErrorKind::InvalidInput
        );
    }

    #[test]
    fn nt_cvt() {
        i32::MAX.nt_cvt().unwrap();
        0.nt_cvt().unwrap();
        (-1).nt_cvt().unwrap_err();
        assert_eq!(
            STATUS_INVALID_PARAMETER.nt_cvt().unwrap_err().kind(),
            ErrorKind::InvalidInput
        );
    }

    extern "system" {
        fn WSASetLastError(code: c_int);
    }

    #[test]
    fn wsa_last_error() {
        unsafe { WSASetLastError(-1) }
        assert_eq!(super::wsa_last_error().raw_os_error().unwrap(), -1);
    }

    #[test]
    fn wsa_cvt() {
        0.wsa_cvt().unwrap();
        assert_eq!(0_usize.wsa_cvt().unwrap(), 0);
        assert_eq!((1 as HANDLE).wsa_cvt().unwrap(), 1 as _);
        let code = Error::last_os_error().raw_os_error().unwrap();
        assert_eq!((-1).wsa_cvt().unwrap_err().raw_os_error().unwrap(), code);
        assert_eq!(
            (!0_usize).wsa_cvt().unwrap_err().raw_os_error().unwrap(),
            code
        );
        assert_eq!(
            null_mut::<c_void>()
                .wsa_cvt()
                .unwrap_err()
                .raw_os_error()
                .unwrap(),
            code
        );
        assert_eq!(
            (-1_isize as HANDLE)
                .cvt()
                .unwrap_err()
                .raw_os_error()
                .unwrap(),
            code
        );
    }
}
