use abi::{
    error::Error,
    io::RawFd,
    net::{MessageHeader, MessageHeaderMut, SocketShutdown, SocketType},
};
use libk::{
    task::thread::Thread,
    vfs::{File, FileRef, SocketWrapper},
};
use ygg_driver_net_core::socket::{
    LocalPacketSocket, NetConfigSocket, RawSocket, TcpSocket, UdpSocket,
};

use crate::syscall::run_with_io;

fn get_socket(fd: RawFd) -> Result<FileRef, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd).cloned())
}

pub(crate) fn create_socket(ty: SocketType) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let socket = match ty {
            SocketType::RawPacket => SocketWrapper::from_inner(RawSocket::new()),
            SocketType::TcpStream => SocketWrapper::from_inner(TcpSocket::new()),
            SocketType::UdpPacket => SocketWrapper::from_inner(UdpSocket::new()),
            SocketType::LocalPacket => SocketWrapper::from_inner(LocalPacketSocket::new()),
            SocketType::NetConfig => SocketWrapper::from_inner(NetConfigSocket::new()),
        };
        let file = File::from_socket(socket);
        let fd = io.files.place_file(file, true)?;

        Ok(fd)
    })
}

pub(crate) fn listen(sock_fd: RawFd) -> Result<(), Error> {
    let file = get_socket(sock_fd)?;
    file.as_socket()?.listen()
}

pub(crate) fn bind(sock_fd: RawFd, local: &[u8]) -> Result<(), Error> {
    let file = get_socket(sock_fd)?;
    file.as_socket()?.bind(local)
}

pub(crate) fn connect(sock_fd: RawFd, remote: &[u8]) -> Result<(), Error> {
    let file = get_socket(sock_fd)?;
    file.as_socket()?.connect(remote)
}

pub(crate) fn accept(sock_fd: RawFd, remote: Option<&mut [u8]>) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let listener = io.files.file(sock_fd)?;
        let non_blocking = listener.is_non_blocking();
        let listener = listener.as_socket()?;

        let stream_socket = listener.accept(remote, non_blocking)?;
        let stream_fd = io
            .files
            .place_file(File::from_socket(stream_socket), true)?;

        Ok(stream_fd)
    })
}

pub(crate) fn shutdown(sock_fd: RawFd, how: SocketShutdown) -> Result<(), Error> {
    let file = get_socket(sock_fd)?;
    file.as_socket()?.shutdown(how)
}

pub(crate) fn send_to(
    sock_fd: RawFd,
    data: &[u8],
    remote: Option<&[u8]>, // remote: &Option<SocketAddr>,
) -> Result<usize, Error> {
    let header = MessageHeader {
        destination: remote,
        payload: data,
        ancillary: &[],
    };
    send_message(sock_fd, &header)
}

pub(crate) fn receive_from(
    sock_fd: RawFd,
    data: &mut [u8],
    remote: Option<&mut [u8]>,
) -> Result<usize, Error> {
    let mut header = MessageHeaderMut {
        source: remote,
        payload: data,
        ancillary: None,
        ancillary_len: 0,
    };
    receive_message(sock_fd, &mut header)
}

pub(crate) fn send_message(sock_fd: RawFd, message: &MessageHeader) -> Result<usize, Error> {
    let file = get_socket(sock_fd)?;
    let non_blocking = file.is_non_blocking();
    file.as_socket()?.send_message(message, non_blocking)
}

pub(crate) fn receive_message(
    sock_fd: RawFd,
    message: &mut MessageHeaderMut,
) -> Result<usize, Error> {
    let file = get_socket(sock_fd)?;
    let non_blocking = file.is_non_blocking();
    file.as_socket()?.receive_message(message, non_blocking)
}

pub(crate) fn get_socket_option(
    sock_fd: RawFd,
    option: u32,
    value: &mut [u8],
) -> Result<usize, Error> {
    let file = get_socket(sock_fd)?;
    file.as_socket()?.get_option(option, value)
}

pub(crate) fn set_socket_option(sock_fd: RawFd, option: u32, value: &[u8]) -> Result<(), Error> {
    let file = get_socket(sock_fd)?;
    file.as_socket()?.set_option(option, value)
}
