//! Network socket wrappers.

use crate::sys::calls::*;
use crate::sys::constants::*;

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SockAddrIn {
    pub family: u16,
    pub port: u16, // network byte order (big-endian)
    pub addr: [u8; 4],
    pub zero: [u8; 8],
}

pub const AF_INET: u32 = 2;
pub const SOCK_STREAM: u32 = 1;
pub const SOCK_DGRAM: u32 = 2;

pub fn sys_socket(domain: u32, sock_type: u32, protocol: u32) -> i64 {
    unsafe { syscall3(SYS_SOCKET, domain as u64, sock_type as u64, protocol as u64) as i64 }
}

pub fn sys_bind(fd: u64, addr: &SockAddrIn) -> i64 {
    unsafe {
        syscall3(
            SYS_BIND,
            fd,
            addr as *const SockAddrIn as u64,
            core::mem::size_of::<SockAddrIn>() as u64,
        ) as i64
    }
}

pub fn sys_connect(fd: u64, addr: &SockAddrIn) -> i64 {
    unsafe {
        syscall3(
            SYS_CONNECT,
            fd,
            addr as *const SockAddrIn as u64,
            core::mem::size_of::<SockAddrIn>() as u64,
        ) as i64
    }
}

pub fn sys_listen(fd: u64, backlog: u32) -> i64 {
    unsafe { syscall2(SYS_LISTEN, fd, backlog as u64) as i64 }
}

pub fn sys_accept(fd: u64, addr: Option<&mut SockAddrIn>) -> i64 {
    let addr_ptr = match addr {
        Some(a) => a as *mut SockAddrIn as u64,
        None => 0,
    };
    let mut addr_len: u32 = core::mem::size_of::<SockAddrIn>() as u32;
    unsafe { syscall3(SYS_ACCEPT, fd, addr_ptr, &mut addr_len as *mut u32 as u64) as i64 }
}

pub fn sys_sendto(fd: u64, buf: &[u8], flags: u32, addr: Option<&SockAddrIn>) -> i64 {
    let (addr_ptr, addr_len) = match addr {
        Some(a) => (
            a as *const SockAddrIn as u64,
            core::mem::size_of::<SockAddrIn>() as u64,
        ),
        None => (0, 0),
    };
    unsafe {
        syscall6(
            SYS_SENDTO,
            fd,
            buf.as_ptr() as u64,
            buf.len() as u64,
            flags as u64,
            addr_ptr,
            addr_len,
        ) as i64
    }
}

pub fn sys_recvfrom(fd: u64, buf: &mut [u8], flags: u32, addr: Option<&mut SockAddrIn>) -> i64 {
    let addr_ptr = match addr {
        Some(a) => a as *mut SockAddrIn as u64,
        None => 0,
    };
    let mut addr_len: u32 = core::mem::size_of::<SockAddrIn>() as u32;
    unsafe {
        syscall6(
            SYS_RECVFROM,
            fd,
            buf.as_mut_ptr() as u64,
            buf.len() as u64,
            flags as u64,
            addr_ptr,
            &mut addr_len as *mut u32 as u64,
        ) as i64
    }
}

// Shutdown modes
pub const SHUT_RD: i32 = 0;
pub const SHUT_WR: i32 = 1;
pub const SHUT_RDWR: i32 = 2;

// Socket option levels and names
pub const SOL_SOCKET: i32 = 1;
pub const IPPROTO_IP: i32 = 0;
pub const IPPROTO_TCP: i32 = 6;
pub const IPPROTO_IPV6: i32 = 41;
pub const SO_RCVTIMEO: i32 = 20;
pub const SO_SNDTIMEO: i32 = 21;
pub const SO_LINGER: i32 = 13;
pub const SO_ERROR: i32 = 4;
pub const SO_REUSEADDR: i32 = 2;
pub const SO_BROADCAST: i32 = 6;
pub const TCP_NODELAY: i32 = 1;
pub const IP_TTL: i32 = 2;
pub const MSG_PEEK: i32 = 2;

pub fn sys_shutdown(fd: u64, how: i32) -> i64 {
    unsafe { syscall2(SYS_SHUTDOWN, fd, how as u64) as i64 }
}

pub fn sys_setsockopt(fd: u64, level: i32, optname: i32, val: *const u8, val_len: u32) -> i64 {
    unsafe {
        syscall5(
            SYS_SETSOCKOPT,
            fd,
            level as u64,
            optname as u64,
            val as u64,
            val_len as u64,
        ) as i64
    }
}

pub fn sys_getsockopt(fd: u64, level: i32, optname: i32, val: *mut u8, val_len: *mut u32) -> i64 {
    unsafe {
        syscall5(
            SYS_GETSOCKOPT,
            fd,
            level as u64,
            optname as u64,
            val as u64,
            val_len as u64,
        ) as i64
    }
}

pub fn sys_getpeername(fd: u64, addr: *mut SockAddrIn, addr_len: *mut u32) -> i64 {
    unsafe { syscall3(SYS_GETPEERNAME, fd, addr as u64, addr_len as u64) as i64 }
}

pub fn sys_getsockname(fd: u64, addr: *mut SockAddrIn, addr_len: *mut u32) -> i64 {
    unsafe { syscall3(SYS_GETSOCKNAME, fd, addr as u64, addr_len as u64) as i64 }
}

pub fn sys_ping(dst_ip: &[u8; 4], id: u16, seq: u16, timeout_ms: u64) -> u64 {
    unsafe {
        syscall4(
            SYS_PING,
            dst_ip as *const [u8; 4] as u64,
            id as u64,
            seq as u64,
            timeout_ms,
        )
    }
}
