// Take a look at the license at the top of the repository in the LICENSE file.

#[cfg(feature = "system")]
use std::ffi::{CStr, OsStr, OsString};
#[cfg(feature = "system")]
use std::os::unix::ffi::OsStrExt;

#[cfg(any(feature = "system", feature = "component"))]
pub(crate) unsafe fn get_sys_value<T: Sized>(mib: &[libc::c_int], value: &mut T) -> bool {
    let mut len = std::mem::size_of::<T>() as libc::size_t;
    unsafe {
        libc::sysctl(
            mib.as_ptr(),
            mib.len() as _,
            value as *mut _ as *mut _,
            &mut len,
            std::ptr::null_mut(),
            0,
        ) == 0
    }
}

#[cfg(feature = "system")]
pub(crate) unsafe fn get_sys_value_array<T: Sized>(mib: &[libc::c_int], value: &mut [T]) -> bool {
    let mut len = std::mem::size_of_val(value) as libc::size_t;
    unsafe {
        libc::sysctl(
            mib.as_ptr(),
            mib.len() as _,
            value.as_mut_ptr() as *mut _,
            &mut len as *mut _,
            std::ptr::null_mut(),
            0,
        ) == 0
    }
}

#[cfg(any(feature = "disk", feature = "system"))]
pub(crate) fn c_buf_to_utf8_str(buf: &[libc::c_char]) -> Option<&str> {
    unsafe {
        let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len());
        std::str::from_utf8(if let Some(pos) = buf.iter().position(|x| *x == 0) {
            // Shrink buffer to terminate the null bytes
            &buf[..pos]
        } else {
            buf
        })
        .ok()
    }
}

#[cfg(feature = "system")]
pub(crate) fn c_buf_to_utf8_string(buf: &[libc::c_char]) -> Option<String> {
    c_buf_to_utf8_str(buf).map(|s| s.to_owned())
}

#[cfg(feature = "system")]
pub(crate) fn c_buf_to_os_str(buf: &[libc::c_char]) -> &OsStr {
    unsafe {
        let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len());
        OsStr::from_bytes(if let Some(pos) = buf.iter().position(|x| *x == 0) {
            // Shrink buffer to terminate the null bytes
            &buf[..pos]
        } else {
            buf
        })
    }
}

#[cfg(feature = "system")]
pub(crate) fn c_buf_to_os_string(buf: &[libc::c_char]) -> OsString {
    c_buf_to_os_str(buf).to_owned()
}

#[cfg(feature = "disk")]
pub(crate) unsafe fn get_sysctl_raw(
    mib: &[libc::c_int],
    data: *mut (),
    len: &mut libc::size_t,
) -> bool {
    unsafe {
        libc::sysctl(
            mib.as_ptr(),
            mib.len() as _,
            data as _,
            len,
            std::ptr::null_mut(),
            0,
        ) == 0
    }
}

#[cfg(feature = "system")]
pub(crate) unsafe fn get_sys_value_osstr(
    mib: &[libc::c_int],
    buf: &mut [libc::c_char],
) -> Option<OsString> {
    let mut len = std::mem::size_of_val(buf) as libc::size_t;
    unsafe {
        if libc::sysctl(
            mib.as_ptr(),
            mib.len() as _,
            buf.as_mut_ptr() as *mut _,
            &mut len,
            std::ptr::null_mut(),
            0,
        ) != 0
        {
            return None;
        }
    }
    Some(c_buf_to_os_string(
        &buf[..len / std::mem::size_of::<libc::c_char>()],
    ))
}

#[cfg(feature = "system")]
pub(crate) unsafe fn get_sys_value_str(mib: &[libc::c_int]) -> Option<String> {
    let mut size = 0;

    unsafe {
        if libc::sysctl(
            mib.as_ptr(),
            mib.len() as _,
            std::ptr::null_mut(),
            &mut size,
            std::ptr::null_mut(),
            0,
        ) != 0
        {
            return None;
        }
        // now create a buffer with the size and get the real value
        let mut buf: Vec<libc::c_char> = vec![0; size as _];

        if libc::sysctl(
            mib.as_ptr(),
            mib.len() as _,
            buf.as_mut_ptr() as *mut _,
            &mut size,
            std::ptr::null_mut(),
            0,
        ) == 0
            && size > 0
        {
            c_buf_to_utf8_string(&buf)
        } else {
            None
        }
    }
}

#[cfg(any(feature = "system", feature = "component"))]
pub(crate) unsafe fn get_sys_value_by_name<T: Sized>(name: &[u8], value: &mut T) -> bool {
    let mut len = std::mem::size_of::<T>() as libc::size_t;
    let original = len;

    unsafe {
        libc::sysctlbyname(
            name.as_ptr() as *const libc::c_char,
            value as *mut _ as *mut _,
            &mut len,
            std::ptr::null_mut(),
            0,
        ) == 0
            && original == len
    }
}

#[cfg(feature = "system")]
pub(crate) fn get_sys_value_str_by_name(name: &[u8]) -> Option<String> {
    let mut size = 0;

    unsafe {
        if libc::sysctlbyname(
            name.as_ptr() as *const libc::c_char,
            std::ptr::null_mut(),
            &mut size,
            std::ptr::null_mut(),
            0,
        ) == 0
            && size > 0
        {
            // now create a buffer with the size and get the real value
            let mut buf: Vec<libc::c_char> = vec![0; size as _];

            if libc::sysctlbyname(
                name.as_ptr() as *const libc::c_char,
                buf.as_mut_ptr() as *mut _,
                &mut size,
                std::ptr::null_mut(),
                0,
            ) == 0
                && size > 0
            {
                c_buf_to_utf8_string(&buf)
            } else {
                sysinfo_debug!(
                    "failed to get value for key {:?}",
                    std::str::from_utf8(name)
                );
                // getting the system value failed
                None
            }
        } else {
            sysinfo_debug!("no value for key {:?}", std::str::from_utf8(name));
            None
        }
    }
}

#[cfg(feature = "system")]
pub(crate) unsafe fn from_cstr_array(ptr: *const *const libc::c_char) -> Vec<OsString> {
    if ptr.is_null() {
        return Vec::new();
    }
    let mut max = 0;
    loop {
        unsafe {
            let ptr = ptr.add(max);
            if (*ptr).is_null() {
                break;
            }
        }
        max += 1;
    }
    if max == 0 {
        return Vec::new();
    }
    let mut ret = Vec::with_capacity(max);

    for pos in 0..max {
        unsafe {
            let p = ptr.add(pos);
            ret.push(OsStr::from_bytes(CStr::from_ptr(*p).to_bytes()).to_os_string());
        }
    }
    ret
}

#[cfg(any(feature = "system", feature = "component"))]
pub(crate) unsafe fn get_nb_cpus() -> usize {
    let mut nb_cpus: libc::c_int = 0;

    unsafe {
        if !get_sys_value(&[libc::CTL_HW, super::ffi::HW_NCPUONLINE], &mut nb_cpus) || nb_cpus < 1 {
            nb_cpus = 1;
        }
    }
    nb_cpus as usize
}

// All this is needed because `kinfo_proc2` doesn't implement `Send` (because it contains pointers).
#[cfg(feature = "system")]
pub(crate) struct WrapMap<'a>(
    pub std::cell::UnsafeCell<&'a mut std::collections::HashMap<crate::Pid, crate::Process>>,
);

#[cfg(feature = "system")]
unsafe impl Send for WrapMap<'_> {}
#[cfg(feature = "system")]
unsafe impl Sync for WrapMap<'_> {}

#[cfg(feature = "system")]
#[repr(transparent)]
pub(crate) struct KInfoProc(libc::kinfo_proc2);

#[cfg(feature = "system")]
unsafe impl Send for KInfoProc {}
#[cfg(feature = "system")]
unsafe impl Sync for KInfoProc {}

#[cfg(feature = "system")]
impl std::ops::Deref for KInfoProc {
    type Target = libc::kinfo_proc2;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
