#[allow(unused_imports)]
use crate::error::Status;

use std::{ffi::c_ulong, ptr, sync::LazyLock, thread::available_parallelism};

use singe_core::string_from_c_chars;
use singe_nvml_sys as sys;

use crate::{
    device::Device,
    error::Result,
    try_ffi,
    types::{ClockRangeI32, ProcessInfo, UtilizationCounter},
};

pub fn option_u64_from_not_available(value: u64) -> Option<u64> {
    if value == sys::NVML_VALUE_NOT_AVAILABLE as u64 {
        None
    } else {
        Some(value)
    }
}

pub const fn struct_version<T>(version: u32) -> u32 {
    (size_of::<T>() as u32) | (version << 24)
}

static ULONG_BITMASK_COUNT: LazyLock<usize> = LazyLock::new(|| {
    let cpu_count = available_parallelism()
        .map(|count| count.get())
        .unwrap_or(64);
    let bits_per_ulong = c_ulong::BITS as usize;
    cpu_count.div_ceil(bits_per_ulong)
});

pub fn ulong_bitmask_count() -> usize {
    *ULONG_BITMASK_COUNT
}

pub fn mask255_bits(mask: sys::nvmlMask255_t) -> Vec<u32> {
    mask.mask
        .into_iter()
        .enumerate()
        .flat_map(|(word_index, word)| {
            (0..u32::BITS).filter_map(move |bit_index| {
                let bit = 1u32 << bit_index;
                (word & bit != 0).then_some((word_index as u32) * u32::BITS + bit_index)
            })
        })
        .collect()
}

pub fn device_string_query(
    device: Device,
    length: usize,
    function: unsafe extern "C" fn(sys::nvmlDevice_t, *mut i8, u32) -> sys::nvmlReturn_t,
) -> Result<String> {
    let mut buffer = vec![0i8; length];
    unsafe {
        try_ffi!(function(
            device.as_raw(),
            buffer.as_mut_ptr(),
            buffer.len() as u32
        ))?;
    }
    Ok(string_from_c_chars(&buffer))
}

pub fn query_u32_list(
    function: impl Fn(*mut u32, *mut u32) -> sys::nvmlReturn_t,
) -> Result<Vec<u32>> {
    let mut count = 0;
    let status = function(&raw mut count, ptr::null_mut());
    if status == sys::nvmlReturn_t::NVML_SUCCESS && count == 0 {
        return Ok(Vec::new());
    }
    if status != sys::nvmlReturn_t::NVML_ERROR_INSUFFICIENT_SIZE {
        return Err(status.into());
    }

    let mut values = vec![0u32; count as usize];
    try_ffi!(function(&raw mut count, values.as_mut_ptr()))?;
    values.truncate(count as usize);
    Ok(values)
}

pub fn query_sized_raw(
    function: impl Fn(*mut u32, *mut u8) -> sys::nvmlReturn_t,
) -> Result<Vec<u8>> {
    let mut size = 0;
    let status = function(&raw mut size, ptr::null_mut());
    if status == sys::nvmlReturn_t::NVML_SUCCESS && size == 0 {
        return Ok(Vec::new());
    }
    if status != sys::nvmlReturn_t::NVML_ERROR_INSUFFICIENT_SIZE {
        return Err(status.into());
    }

    let mut buffer = vec![0u8; size as usize];
    try_ffi!(function(&raw mut size, buffer.as_mut_ptr()))?;
    buffer.truncate(size as usize);
    Ok(buffer)
}

pub fn query_process_info_list(
    device: Device,
    function: unsafe extern "C" fn(
        sys::nvmlDevice_t,
        *mut u32,
        *mut sys::nvmlProcessInfo_t,
    ) -> sys::nvmlReturn_t,
) -> Result<Vec<ProcessInfo>> {
    let mut count = 0;
    let status = unsafe { function(device.as_raw(), &raw mut count, ptr::null_mut()) };
    if status == sys::nvmlReturn_t::NVML_SUCCESS && count == 0 {
        return Ok(Vec::new());
    }
    if status != sys::nvmlReturn_t::NVML_ERROR_INSUFFICIENT_SIZE {
        return Err(status.into());
    }

    let mut infos = vec![sys::nvmlProcessInfo_t::default(); count as usize];
    unsafe {
        try_ffi!(function(
            device.as_raw(),
            &raw mut count,
            infos.as_mut_ptr()
        ))?;
    }
    infos.truncate(count as usize);
    Ok(infos.into_iter().map(Into::into).collect())
}

pub fn device_utilization_counter(
    device: Device,
    function: unsafe extern "C" fn(sys::nvmlDevice_t, *mut u32, *mut u32) -> sys::nvmlReturn_t,
) -> Result<UtilizationCounter> {
    let mut utilization = 0;
    let mut sampling_period_us = 0;
    unsafe {
        try_ffi!(function(
            device.as_raw(),
            &raw mut utilization,
            &raw mut sampling_period_us,
        ))?;
    }
    Ok(UtilizationCounter {
        utilization,
        sampling_period_us,
    })
}

pub fn device_ulong_bitmask_list(
    function: impl Fn(u32, *mut c_ulong) -> sys::nvmlReturn_t,
) -> Result<Vec<u64>> {
    let count = ulong_bitmask_count();
    let mut values = vec![0 as c_ulong; count];
    try_ffi!(function(count as u32, values.as_mut_ptr()))?;
    Ok(values.into_iter().collect())
}

pub fn device_clock_offset_range(
    device: Device,
    function: unsafe extern "C" fn(sys::nvmlDevice_t, *mut i32, *mut i32) -> sys::nvmlReturn_t,
) -> Result<ClockRangeI32> {
    let mut min = 0;
    let mut max = 0;
    unsafe {
        try_ffi!(function(device.as_raw(), &raw mut min, &raw mut max))?;
    }
    Ok(ClockRangeI32 { min, max })
}
