/* src/processes/stat.rs
 *
 * Copyright 2025 Mission Center Developers
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

use std::fmt::Write;

use arrayvec::ArrayString;
use magpie_platform::processes::ProcessState;

use super::MAX_U32_LEN;

const PROC_PID_STAT_TCOMM: usize = 1;
const PROC_PID_STAT_STATE: usize = 2;
const PROC_PID_STAT_PPID: usize = 3;
const PROC_PID_STAT_UTIME: usize = 13;
const PROC_PID_STAT_STIME: usize = 14;

pub fn open(pid: u32) -> Option<std::fs::File> {
    const MAX_PATH_LEN: usize = "/proc/".len() + "/stat".len() + MAX_U32_LEN;

    let mut path: ArrayString<MAX_PATH_LEN> = ArrayString::new();
    let _ = write!(path, "/proc/{}/stat", pid);

    let stat_file = match std::fs::OpenOptions::new().read(true).open(path) {
        Ok(f) => f,
        Err(e) => {
            log::warn!(
                "Failed to open `stat` file for process {}, skipping: {}",
                pid,
                e,
            );
            return None;
        }
    };

    Some(stat_file)
}

pub fn parse_stat_file(stat_file_content: &str) -> Option<[&str; 52]> {
    let mut output = [""; 52];
    let mut part_index = 0;

    let mut split = stat_file_content.split('(').filter(|x| !x.is_empty());
    output[part_index] = match split.next() {
        Some(x) => x,
        None => return None,
    };
    part_index += 1;

    let mut split = match split.next() {
        Some(x) => x.split(')').filter(|x| !x.is_empty()),
        None => return None,
    };

    output[part_index] = match split.next() {
        Some(x) => x,
        None => return None,
    };
    part_index += 1;

    let split = match split.next() {
        Some(x) => x,
        None => return None,
    };

    for entry in split.split_whitespace() {
        output[part_index] = entry;
        part_index += 1;
    }

    Some(output)
}

pub fn name<'a>(stat: &[&'a str; 52]) -> &'a str {
    stat[PROC_PID_STAT_TCOMM]
}

pub fn state(stat: &[&str; 52]) -> ProcessState {
    match stat[PROC_PID_STAT_STATE] {
        "R" => ProcessState::Running,
        "S" => ProcessState::Sleeping,
        "D" => ProcessState::SleepingUninterruptible,
        "Z" => ProcessState::Zombie,
        "T" => ProcessState::Stopped,
        "t" => ProcessState::Tracing,
        "X" | "x" => ProcessState::Dead,
        "K" => ProcessState::WakeKill,
        "W" => ProcessState::Waking,
        "P" => ProcessState::Parked,
        _ => ProcessState::Unknown,
    }
}

pub fn parent_pid(stat: &[&str; 52]) -> u32 {
    stat[PROC_PID_STAT_PPID].parse::<u32>().unwrap_or(0)
}

pub fn user_mode_jiffies(stat: &[&str; 52]) -> u64 {
    stat[PROC_PID_STAT_UTIME].parse::<u64>().unwrap_or(0)
}

pub fn kernel_mode_jiffies(stat: &[&str; 52]) -> u64 {
    stat[PROC_PID_STAT_STIME].parse::<u64>().unwrap_or(0)
}
