use super::{CSocketInfo, SocketInfo, socket_info_list};
use std::{
    collections::HashMap,
    ffi::CStr,
    io,
    os::raw::{c_char, c_int},
    ptr,
};

#[repr(C)]
struct CSocketFile {
    kvaddr: usize,
    pid: libc::pid_t,
}

unsafe extern "C" {
    fn freebsd_fetch_tcp_sockets(list: *mut *mut CSocketInfo, nentries: *mut usize) -> c_int;
    fn freebsd_fetch_udp_sockets(list: *mut *mut CSocketInfo, nentries: *mut usize) -> c_int;
    fn freebsd_fetch_socket_files(list: *mut *mut CSocketFile, nentries: *mut usize) -> c_int;
    fn freebsd_fetch_process_name(pid: libc::pid_t) -> *mut c_char;
    fn freebsd_fetch_process_path(pid: libc::pid_t) -> *mut c_char;
}

pub(in crate::platform::bsd) fn get_tcp_sockets() -> io::Result<Vec<SocketInfo>> {
    let mut list: *mut CSocketInfo = ptr::null_mut();
    let mut nentries: usize = 0;

    if unsafe { freebsd_fetch_tcp_sockets(&raw mut list, &raw mut nentries) } != 0 {
        return Err(io::Error::last_os_error());
    }

    Ok(socket_info_list(list, nentries))
}

pub(in crate::platform::bsd) fn get_udp_sockets() -> io::Result<Vec<SocketInfo>> {
    let mut list: *mut CSocketInfo = ptr::null_mut();
    let mut nentries: usize = 0;

    if unsafe { freebsd_fetch_udp_sockets(&raw mut list, &raw mut nentries) } != 0 {
        return Err(io::Error::last_os_error());
    }

    Ok(socket_info_list(list, nentries))
}

pub(in crate::platform::bsd) fn get_kvaddr_to_pid_table() -> io::Result<HashMap<usize, i32>> {
    let mut list: *mut CSocketFile = ptr::null_mut();
    let mut nentries: usize = 0;

    if unsafe { freebsd_fetch_socket_files(&raw mut list, &raw mut nentries) } != 0 {
        return Err(io::Error::last_os_error());
    }

    let mut retval = HashMap::new();

    if nentries > 0 && !list.is_null() {
        unsafe {
            let c_files = std::slice::from_raw_parts(list, nentries);

            for c_file in c_files {
                retval.insert(c_file.kvaddr, c_file.pid);
            }

            libc::free(list.cast::<libc::c_void>());
        }
    }

    Ok(retval)
}

pub(in crate::platform::bsd) fn get_process_name(pid: i32) -> io::Result<String> {
    unsafe {
        let name_ptr = freebsd_fetch_process_name(pid);
        if name_ptr.is_null() {
            return Err(io::Error::last_os_error());
        }

        let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();

        libc::free(name_ptr.cast::<libc::c_void>());

        Ok(name)
    }
}

pub(in crate::platform::bsd) fn get_process_path(pid: i32) -> io::Result<String> {
    unsafe {
        let path_ptr = freebsd_fetch_process_path(pid);
        if path_ptr.is_null() {
            return Err(io::Error::last_os_error());
        }

        let path = CStr::from_ptr(path_ptr).to_string_lossy().into_owned();

        libc::free(path_ptr.cast::<libc::c_void>());

        Ok(path)
    }
}
