Documentation
use std::os::unix::ffi::OsStrExt;
use std::fs;
use std::io;
use std::str;
use std::ops;

#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};

#[cfg(feature = "serde")]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CpuFrequency {
    pub cpu_id: Option<u64>,
    pub current: Frequency,
    pub min: Option<Frequency>,
    pub max: Option<Frequency>,
}

#[cfg(not(feature = "serde"))]
#[derive(Debug, Default)]
pub struct CpuFrequency {
    pub cpu_id: Option<u64>,
    pub current: Frequency,
    pub min: Option<Frequency>,
    pub max: Option<Frequency>,
}

impl ops::Add<CpuFrequency> for CpuFrequency {
    type Output = CpuFrequency;

    fn add(self, rhs: CpuFrequency) -> CpuFrequency {
        let current = self.current + rhs.current;
        let min = match (self.min, rhs.min) {
            (Some(left), Some(right)) => Some(left + right),
            (Some(left), None) => Some(left),
            (None, Some(right)) => Some(right),
            (None, None) => None,
        };
        let max = match (self.max, rhs.max) {
            (Some(left), Some(right)) => Some(left + right),
            (Some(left), None) => Some(left),
            (None, Some(right)) => Some(right),
            (None, None) => None,
        };

        CpuFrequency {
            cpu_id: None,
            current,
            max,
            min,
        }
    }
}

#[cfg(not(feature = "serde"))]
#[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)]
pub struct Frequency(pub u64);

#[cfg(feature = "serde")]
#[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq, Serialize, Deserialize)]
pub struct Frequency(pub u64);

#[doc(hidden)]
impl Frequency {
    pub fn from_kilohertzs(value: u64) -> Self {
        Self(value * 1_000)
    }

    pub fn from_megahertzs(value: u64) -> Self {
        Self(value * 1_000_000)
    }
}

impl ops::Add<Frequency> for Frequency {
    type Output = Frequency;

    fn add(self, rhs: Frequency) -> Self::Output{
        Frequency(self.0 + rhs.0)
    }
}

pub fn frequency() -> io::Result<CpuFrequency> {
    let init = CpuFrequency::default();

    let freqs = frequencies()?;
    let len = freqs.len();

    let mut init = freqs.into_iter().fold(init, |init, f| {
        init + f
    });

    init.current = Frequency::from_kilohertzs(init.current.0 / len as u64);
    init.min = init.min.map(|v| Frequency::from_kilohertzs(v.0 / len as u64));
    init.max = init.max.map(|v| Frequency::from_kilohertzs(v.0 / len as u64));

    Ok(init)
}

pub fn frequencies() -> io::Result<Vec<CpuFrequency>> {
    let mut vec = vec![];

    let dirs = fs::read_dir("/sys/devices/system/cpu/")?;

    for dir in dirs {
        let dir = dir?;
        let path = dir.path();

        if let Some(file_name) = path.file_name() {
            if file_name.as_bytes().starts_with(b"cpu") {
                let core_id = unsafe { str::from_utf8_unchecked(&file_name.as_bytes()[3..]) };

                if let Ok(id) = core_id.parse::<u64>() {

                    let mut cpufreq = CpuFrequency::default();
                    // cpu id
                    cpufreq.cpu_id = Some(id);

                    let path = path.join("cpufreq");

                    // current
                    let path1 = path.join("scaling_cur_freq");
                    let content = fs::read_to_string(path1)?;
                    if let Ok(freq) = content.trim().parse::<u64>() {
                        cpufreq.current = Frequency::from_kilohertzs(freq);
                    }

                    // min
                    let path2 = path.join("scaling_min_freq");
                    if let Ok(content) = fs::read_to_string(path2) {
                        if let Ok(freq) = content.trim().parse::<u64>() {
                            cpufreq.min = Some(Frequency::from_kilohertzs(freq));
                        }
                    }

                    // max
                    let path2 = path.join("scaling_max_freq");
                    if let Ok(content) = fs::read_to_string(path2) {
                        if let Ok(freq) = content.trim().parse::<u64>() {
                            cpufreq.max = Some(Frequency::from_kilohertzs(freq));
                        }
                    }

                    vec.push(cpufreq)
                } 
            }
        }

    }

    Ok(vec)
}