use std::sync::{Arc, LazyLock, Mutex};

use indexmap::IndexMap;

use super::Probe;

static NVML: LazyLock<Option<nvml_wrapper::Nvml>> =
    LazyLock::new(|| nvml_wrapper::Nvml::init().ok());

pub struct Nvml<'a> {
    devices: Vec<NvmlDevice<'a>>,
}

pub struct NvmlDevice<'a> {
    device: nvml_wrapper::Device<'a>,
    name: String,
    energy: u64,
}

impl<'a> Nvml<'a> {
    pub fn now() -> Option<Self> {
        let nvml = NVML.as_ref()?;
        let count = nvml.device_count().ok()?;
        let devices = (0..count).filter_map(NvmlDevice::new).collect();
        Some(Self { devices })
    }
}

impl<'a> Probe for Nvml<'a> {
    fn elapsed(&self) -> IndexMap<String, f32> {
        self.devices
            .iter()
            .map(|device| {
                let name = device.name.clone();
                let energy = device.elapsed();
                (name, energy)
            })
            .collect()
    }

    fn reset(&mut self) {
        self.devices.iter_mut().for_each(NvmlDevice::reset);
    }
}

impl<'a> NvmlDevice<'a> {
    fn new(index: u32) -> Option<Self> {
        let nvml = NVML.as_ref()?;
        let device = nvml.device_by_index(index).ok()?;
        let name = format!("GPU({}) {}", index, device.name().ok()?);
        let energy = device.total_energy_consumption().ok()?;
        Some(Self {
            device,
            name,
            energy,
        })
    }

    fn elapsed(&self) -> f32 {
        let prev = self.energy;
        let next = self.device.total_energy_consumption().unwrap();
        (next - prev) as f32 / 1000.0
    }

    fn reset(&mut self) {
        self.energy = self.device.total_energy_consumption().unwrap();
    }
}

pub fn nvml() -> Option<Arc<Mutex<dyn Probe>>> {
    Nvml::now().map(|x| Arc::new(Mutex::new(x)) as Arc<Mutex<dyn Probe>>)
}
