use core::{
    ffi::{c_int, c_void},
    net::SocketAddr,
};

use crate::{error::EResult, headers::errno::Errno, util::PointerExt};

use super::{
    netinet_in::{sockaddr_in, sockaddr_in6},
    sys_uio::__ygg_iovec_t,
};

mod io;
mod option;
mod socket;

pub type socklen_t = usize;
pub type sa_family_t = u16;

pub const __SS_SIZE: usize = 256;
// __SS_SIZE - sizeof(sa_samily_t)
pub const __SOCKADDR_LEN: usize = __SS_SIZE - 2;
// __SS_SIZE - sizeof(sa_family_t) - sizeof(__ss_aligntype)
pub const __SS_PADSIZE: usize = __SS_SIZE - 6;

pub type __ss_aligntype = u32;

#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct sockaddr {
    pub sa_family: sa_family_t,
    pub sa_data: [u8; __SOCKADDR_LEN],
}

// TODO struct sockaddr_storage

#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct msghdr {
    pub msg_name: *mut c_void,
    pub msg_namelen: socklen_t,
    pub msg_iov: *mut __ygg_iovec_t,
    pub msg_iovlen: c_int,
    pub msg_control: *mut c_void,
    pub msg_controllen: socklen_t,
    pub msg_flags: c_int,
}

// TODO struct cmsghdr

// socket() parameters
pub const SOCK_DGRAM: c_int = 1;
pub const SOCK_RAW: c_int = 2;
pub const SOCK_SEQPACKET: c_int = 3;
pub const SOCK_STREAM: c_int = 4;

// setsockopt() parameters
pub const SOL_SOCKET: c_int = 1 << 24;
pub const SOL_SOCKOPT_TCP: c_int = 2 << 24;
pub const SOL_SOCKOPT_IPV4: c_int = 4 << 24;
pub const SOL_SOCKOPT_IPV6: c_int = 6 << 24;

pub const SO_ACCEPTCONN: c_int = 1 | SOL_SOCKET;
pub const SO_BROADCAST: c_int = 2 | SOL_SOCKET;
pub const SO_DEBUG: c_int = 3 | SOL_SOCKET;
pub const SO_DONTROUTE: c_int = 4 | SOL_SOCKET;
pub const SO_ERROR: c_int = 5 | SOL_SOCKET;
pub const SO_KEEPALIVE: c_int = 6 | SOL_SOCKET;
pub const SO_LINGER: c_int = 7 | SOL_SOCKET;
pub const SO_OOBINLINE: c_int = 8 | SOL_SOCKET;
pub const SO_RCVBUF: c_int = 9 | SOL_SOCKET;
pub const SO_RCVLOWAT: c_int = 10 | SOL_SOCKET;
pub const SO_RCVTIMEO: c_int = 11 | SOL_SOCKET;
pub const SO_REUSEADDR: c_int = 12 | SOL_SOCKET;
pub const SO_SNDBUF: c_int = 13 | SOL_SOCKET;
pub const SO_SNDLOWAT: c_int = 14 | SOL_SOCKET;
pub const SO_SNDTIMEO: c_int = 15 | SOL_SOCKET;
pub const SO_TYPE: c_int = 16 | SOL_SOCKET;

pub const SOMAXCONN: usize = 64;

// TODO msg_flags values

pub const AF_INET: c_int = 1;
pub const AF_INET6: c_int = 2;
pub const AF_UNIX: c_int = 3;
pub const AF_UNSPEC: c_int = 0;

pub const SHUT_RD: c_int = 1 << 0;
pub const SHUT_WR: c_int = 1 << 1;
pub const SHUT_RDWR: c_int = SHUT_RD | SHUT_WR;

pub(crate) trait SocketAddrExt<T> {
    fn to_c(&self, storage: *mut T, len: socklen_t) -> EResult<socklen_t>;
}

impl sockaddr {
    pub unsafe fn to_rust(self: *const Self, len: socklen_t) -> EResult<SocketAddr> {
        let family = self.ensure().sa_family as c_int;
        match family {
            AF_INET if len == size_of::<sockaddr_in>() => {
                self.cast::<sockaddr_in>().to_rust().map(SocketAddr::V4)
            }
            AF_INET6 if len == size_of::<sockaddr_in6>() => {
                self.cast::<sockaddr_in6>().to_rust().map(SocketAddr::V6)
            }
            _ => EResult::Err(Errno::ENOTSUPP),
        }
    }
}

impl SocketAddrExt<sockaddr> for SocketAddr {
    fn to_c(&self, storage: *mut sockaddr, len: socklen_t) -> EResult<socklen_t> {
        match self {
            Self::V4(v4) if len >= size_of::<sockaddr_in>() => v4.to_c(storage.cast(), len),
            Self::V6(v6) if len >= size_of::<sockaddr_in6>() => v6.to_c(storage.cast(), len),
            _ => EResult::Err(Errno::EINVAL),
        }
    }
}
