use std::io;
use std::net::SocketAddrV4;
use std::net::SocketAddrV6;
use std::os::fd::RawFd;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::net::UnixDatagram;
use std::os::unix::net::UnixListener;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::ptr::null_mut;
use libc::c_int;
use libc::in6_addr;
use libc::in_addr;
use libc::msghdr;
use libc::sa_family_t;
use libc::sendmsg;
use libc::sockaddr_in;
use libc::sockaddr_in6;
use libc::ssize_t;
use libc::AF_INET;
use libc::AF_INET6;
use libc::MSG_NOSIGNAL;
use libc::SOCK_CLOEXEC;
use libc::SOCK_STREAM;
use crate::descriptor::AsRawDescriptor;
use crate::descriptor::FromRawDescriptor;
use crate::unix::net::socket;
use crate::unix::net::socketpair;
use crate::unix::net::sun_path_offset;
use crate::unix::net::InetVersion;
use crate::unix::net::TcpSocket;
use crate::SafeDescriptor;
use crate::ScmSocket;
use crate::UnixSeqpacket;
use crate::UnixSeqpacketListener;
pub(in crate::sys) unsafe fn sendmsg_nosignal(
    fd: RawFd,
    msg: *const msghdr,
    flags: c_int,
) -> ssize_t {
    sendmsg(fd, msg, flags | MSG_NOSIGNAL)
}
pub(in crate::sys) fn sockaddrv4_to_lib_c(s: &SocketAddrV4) -> sockaddr_in {
    sockaddr_in {
        sin_family: AF_INET as sa_family_t,
        sin_port: s.port().to_be(),
        sin_addr: in_addr {
            s_addr: u32::from_ne_bytes(s.ip().octets()),
        },
        sin_zero: [0; 8],
    }
}
pub(in crate::sys) fn sockaddrv6_to_lib_c(s: &SocketAddrV6) -> sockaddr_in6 {
    sockaddr_in6 {
        sin6_family: AF_INET6 as sa_family_t,
        sin6_port: s.port().to_be(),
        sin6_flowinfo: 0,
        sin6_addr: in6_addr {
            s6_addr: s.ip().octets(),
        },
        sin6_scope_id: 0,
    }
}
pub(in crate::sys) fn sockaddr_un<P: AsRef<Path>>(
    path: P,
) -> io::Result<(libc::sockaddr_un, libc::socklen_t)> {
    let mut addr = libc::sockaddr_un {
        sun_family: libc::AF_UNIX as libc::sa_family_t,
        sun_path: std::array::from_fn(|_| 0),
    };
    let bytes = path.as_ref().as_os_str().as_bytes();
    if bytes.len() >= addr.sun_path.len() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "Input path size should be less than the length of sun_path.",
        ));
    };
    for (dst, src) in addr.sun_path.iter_mut().zip(bytes) {
        *dst = *src as libc::c_char;
    }
    let len = sun_path_offset() + bytes.len() + 1;
    Ok((addr, len as libc::socklen_t))
}
impl TcpSocket {
    pub fn new(inet_version: InetVersion) -> io::Result<Self> {
        Ok(TcpSocket {
            inet_version,
            descriptor: socket(
                Into::<sa_family_t>::into(inet_version) as libc::c_int,
                SOCK_STREAM | SOCK_CLOEXEC,
                0,
            )?,
        })
    }
}
impl UnixSeqpacket {
    pub fn pair() -> io::Result<(UnixSeqpacket, UnixSeqpacket)> {
        socketpair(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0)
            .map(|(s0, s1)| (UnixSeqpacket::from(s0), UnixSeqpacket::from(s1)))
    }
}
impl UnixSeqpacketListener {
    pub fn accept(&self) -> io::Result<UnixSeqpacket> {
        match unsafe {
            libc::accept4(
                self.as_raw_descriptor(),
                null_mut(),
                null_mut(),
                SOCK_CLOEXEC,
            )
        } {
            -1 => Err(io::Error::last_os_error()),
            fd => {
                Ok(UnixSeqpacket::from(
                    unsafe { SafeDescriptor::from_raw_descriptor(fd) },
                ))
            }
        }
    }
}
macro_rules! ScmSocketTryFrom {
    ($name:ident) => {
        impl TryFrom<$name> for ScmSocket<$name> {
            type Error = io::Error;
            fn try_from(socket: $name) -> io::Result<Self> {
                Ok(ScmSocket { socket })
            }
        }
    };
}
ScmSocketTryFrom!(UnixDatagram);
ScmSocketTryFrom!(UnixListener);
ScmSocketTryFrom!(UnixSeqpacket);
ScmSocketTryFrom!(UnixStream);