use std::path::PathBuf;

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DpmClockType {
    SCLK,
    MCLK,
    FCLK,
    SOCCLK,
    VCLK,
    VCLK1,
    DCLK,
    DCLK1,
}

impl DpmClockType {
    const fn sysfs_name(&self) -> &str {
        match self {
            Self::SCLK => "pp_dpm_sclk",
            Self::MCLK => "pp_dpm_mclk",
            Self::FCLK => "pp_dpm_fclk",
            Self::SOCCLK => "pp_dpm_socclk",
            Self::VCLK => "pp_dpm_vclk",
            Self::VCLK1 => "pp_dpm_vclk1",
            Self::DCLK => "pp_dpm_dclk",
            Self::DCLK1 => "pp_dpm_dclk1",
        }
    }
}

#[derive(Clone, Debug)]
pub struct DpmClockRange {
    pub clk_type: DpmClockType,
    pub current_mhz: u32,
    pub min_mhz: u32,
    pub max_mhz: u32,
}

impl DpmClockRange {
    fn parse_mhz(s: &str) -> Option<u32> {
        // For SMU v13.0.6, the line might start with an "S:".
        // ref: https://www.kernel.org/doc/html/v6.16-rc6/gpu/amdgpu/thermal.html#pp-dpm
        let mut chars = s.chars().skip(1);
        let mhz_pos = chars.position(|c| c.is_ascii_alphabetic())?;
        let s = s.get(3..mhz_pos+1)?;
        s.parse::<u32>().ok()
    }

    fn parse_fine_grained_dpm(clk_type: DpmClockType, lines: Vec<&str>) -> Option<Self> {
        let mut clks = [0u32; 2];
        let mut cur_index = 0usize;

        for (i, (l, clk)) in lines.iter().zip(clks.iter_mut()).enumerate() {
            if l.ends_with("*") {
                cur_index = i;
            }
            let u = Self::parse_mhz(l)?;
            *clk = u;
        }

        let last_mhz = Self::parse_mhz(lines.last()?)?;

        Some(Self {
            clk_type,
            current_mhz: clks.get(cur_index).copied().unwrap_or(last_mhz),
            min_mhz: std::cmp::min(clks[0], last_mhz),
            max_mhz: std::cmp::max(clks[0], last_mhz),
        })
    }

    pub fn from_sysfs<P: Into<PathBuf>>(clk_type: DpmClockType, sysfs: P) -> Option<Self> {
        let sysfs = sysfs.into();
        let path = sysfs.join(clk_type.sysfs_name());
        let s = std::fs::read_to_string(&path).ok()?;
        let lines: Vec<&str> = s.lines().collect();
        let len = lines.len();

        if len == 2 || len == 3 {
            return Self::parse_fine_grained_dpm(clk_type, lines);
        }
        
        let [first, last] = [lines.first()?, lines.last()?].map(|s| Self::parse_mhz(s));
        let [first, last] = [first?, last?];

        let current_mhz = lines
            .iter()
            .find(|&s| s.ends_with("*"))
            .and_then(|s| Self::parse_mhz(s))
            .unwrap_or(last);

        Some(Self {
            clk_type,
            current_mhz,
            min_mhz: std::cmp::min(first, last),
            max_mhz: std::cmp::max(first, last),
        })
    }
}

pub(crate) fn get_min_max_from_dpm<
    T: std::cmp::Ord + std::marker::Copy,
    P: Into<PathBuf>
>(
    sysfs_path: P,
    parse: fn(&str) -> Option<T>,
) -> Option<[T; 2]> {
    let sysfs_path = sysfs_path.into();
    let s = std::fs::read_to_string(sysfs_path).ok()?;
    let mut lines = s.lines();

    let first = parse(lines.next()?)?;
    let last = match lines.last() {
        Some(last) => parse(last)?,
        None => return Some([first; 2]),
    };

    Some([
        std::cmp::min(first, last),
        std::cmp::max(first, last),
    ])
}
