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

use crate::{Pid, Process};
use libc::{c_char, c_int, timeval};
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::ffi::CStr;
use std::mem;
use std::time::SystemTime;

/// This struct is used to switch between the "old" and "new" every time you use "get_mut".
#[derive(Debug)]
pub(crate) struct VecSwitcher<T> {
    v1: Vec<T>,
    v2: Vec<T>,
    first: bool,
}

impl<T: Clone> VecSwitcher<T> {
    pub fn new(v1: Vec<T>) -> Self {
        let v2 = v1.clone();

        Self {
            v1,
            v2,
            first: true,
        }
    }

    pub fn get_mut(&mut self) -> &mut [T] {
        self.first = !self.first;
        if self.first {
            // It means that `v2` will be the "new".
            &mut self.v2
        } else {
            // It means that `v1` will be the "new".
            &mut self.v1
        }
    }

    pub fn get_old(&self) -> &[T] {
        if self.first {
            &self.v1
        } else {
            &self.v2
        }
    }

    pub fn get_new(&self) -> &[T] {
        if self.first {
            &self.v2
        } else {
            &self.v1
        }
    }
}

#[inline]
pub unsafe fn init_mib(name: &[u8], mib: &mut [c_int]) {
    let mut len = mib.len();
    libc::sysctlnametomib(name.as_ptr() as _, mib.as_mut_ptr(), &mut len);
}

pub(crate) fn boot_time() -> u64 {
    let mut boot_time = timeval {
        tv_sec: 0,
        tv_usec: 0,
    };
    let mut len = std::mem::size_of::<timeval>();
    let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME];
    unsafe {
        if libc::sysctl(
            mib.as_mut_ptr(),
            mib.len() as _,
            &mut boot_time as *mut timeval as *mut _,
            &mut len,
            std::ptr::null_mut(),
            0,
        ) < 0
        {
            0
        } else {
            boot_time.tv_sec as _
        }
    }
}

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

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

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

pub(crate) fn c_buf_to_string(buf: &[libc::c_char]) -> Option<String> {
    c_buf_to_str(buf).map(|s| s.to_owned())
}

pub(crate) unsafe fn get_sys_value_str(mib: &[c_int], buf: &mut [libc::c_char]) -> Option<String> {
    let mut len = (mem::size_of::<libc::c_char>() * buf.len()) as libc::size_t;
    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;
    }
    c_buf_to_string(&buf[..len / mem::size_of::<libc::c_char>()])
}

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

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

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 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 c_char,
                buf.as_mut_ptr() as *mut _,
                &mut size,
                std::ptr::null_mut(),
                0,
            ) == 0
                && size > 0
            {
                c_buf_to_string(&buf)
            } else {
                // getting the system value failed
                None
            }
        } else {
            None
        }
    }
}

pub(crate) fn get_system_info(mib: &[c_int], default: Option<&str>) -> Option<String> {
    let mut size = 0;

    unsafe {
        // Call first to get size
        libc::sysctl(
            mib.as_ptr(),
            mib.len() as _,
            std::ptr::null_mut(),
            &mut size,
            std::ptr::null_mut(),
            0,
        );

        // exit early if we did not update the size
        if size == 0 {
            default.map(|s| s.to_owned())
        } else {
            // set the buffer to the correct size
            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 size,
                std::ptr::null_mut(),
                0,
            ) == -1
            {
                // If command fails return default
                default.map(|s| s.to_owned())
            } else {
                c_buf_to_string(&buf)
            }
        }
    }
}

pub(crate) unsafe fn from_cstr_array(ptr: *const *const c_char) -> Vec<String> {
    if ptr.is_null() {
        return Vec::new();
    }
    let mut max = 0;
    loop {
        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 {
        let p = ptr.add(pos);
        if let Ok(s) = CStr::from_ptr(*p).to_str() {
            ret.push(s.to_owned());
        }
    }
    ret
}

pub(crate) fn get_now() -> u64 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .map(|n| n.as_secs())
        .unwrap_or(0)
}

// All this is needed because `kinfo_proc` doesn't implement `Send` (because it contains pointers).
pub(crate) struct WrapMap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>);

unsafe impl<'a> Send for WrapMap<'a> {}
unsafe impl<'a> Sync for WrapMap<'a> {}

#[repr(transparent)]
pub(crate) struct KInfoProc(libc::kinfo_proc);
unsafe impl Send for KInfoProc {}
unsafe impl Sync for KInfoProc {}

impl std::ops::Deref for KInfoProc {
    type Target = libc::kinfo_proc;

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

pub(crate) unsafe fn get_frequency_for_cpu(cpu_nb: c_int) -> u64 {
    let mut frequency = 0;

    // The information can be missing if it's running inside a VM.
    if !get_sys_value_by_name(
        format!("dev.cpu.{cpu_nb}.freq\0").as_bytes(),
        &mut frequency,
    ) {
        frequency = 0;
    }
    frequency as _
}
