//
// Syd: rock-solid application kernel
// src/syd-sys.rs: Calculate the memory usage of a given process or the parent process.
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::process::ExitCode;

use nix::{errno::Errno, libc::pid_t, unistd::Pid};
use procfs::process::MMapPath;
use syd::{
    err::SydResult,
    human_size,
    proc::{proc_smaps, proc_statm},
};

fn main() -> SydResult<ExitCode> {
    use lexopt::prelude::*;

    syd::set_sigpipe_dfl()?;

    // Configure syd::proc.
    syd::config::proc_init()?;

    // Parse CLI options.
    let mut opt_human = false; // -H
    let mut opt_is_vm = false; // -V
    let mut opt_pid = None;

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Short('H') => {
                opt_human = true;
            }
            Short('V') => {
                opt_is_vm = true;
            }
            Value(pid) if opt_pid.is_none() => {
                opt_pid = Some(pid.parse::<pid_t>()?);
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    let pid = match opt_pid {
        None => Pid::parent().as_raw(),
        Some(pid) => pid,
    };

    let size = if opt_is_vm {
        match proc_statm(Pid::from_raw(pid)) {
            Ok(statm) => statm.size.saturating_mul(*syd::config::PAGE_SIZE),
            Err(error) => {
                eprintln!("syd-mem: {error}");
                return Ok(ExitCode::FAILURE);
            }
        }
    } else {
        match proc_mem(Pid::from_raw(pid)) {
            Ok(size) => size,
            Err(error) => {
                eprintln!("syd-mem: {error}");
                return Ok(ExitCode::FAILURE);
            }
        }
    };

    if opt_human {
        println!("{}", human_size(size as usize));
    } else {
        println!("{size}");
    }

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-mem [-HV] [pid]");
    println!("Calculate the memory usage of a given process or the parent process and exit.");
    println!("-H    Print human-formatted size");
    println!("-V    Print virtual memory size");
}

/// Calculates process memory usage.
///
/// This function uses the `procfs` crate to obtain detailed memory maps
/// from `/proc/[pid]/smaps`. It sums multiple memory usage values reported in these maps
/// to calculate a more comprehensive total memory usage.
///
/// # Arguments
///
/// * `process` - `Process` instance representing the process.
///
/// # Returns
///
/// This function returns a `Result<u64, Errno>`.
///
/// # Errors
///
/// This function returns an error if it fails to retrieve the process's memory maps,
/// typically due to insufficient permissions or an invalid process ID.
fn proc_mem(pid: Pid) -> Result<u64, Errno> {
    proc_smaps(pid).map(|maps| {
        let mut total_size: u64 = 0;
        for map in &maps {
            match &map.0.pathname {
                MMapPath::Path(_) | MMapPath::Anonymous | MMapPath::Stack | MMapPath::Other(_) => {
                    let pss = map.0.extension.map.get("Pss").copied().unwrap_or(0);
                    let private_dirty = map
                        .0
                        .extension
                        .map
                        .get("Private_Dirty")
                        .copied()
                        .unwrap_or(0);
                    let shared_dirty = map
                        .0
                        .extension
                        .map
                        .get("Shared_Dirty")
                        .copied()
                        .unwrap_or(0);

                    total_size = total_size.saturating_add(
                        pss.saturating_add(private_dirty)
                            .saturating_add(shared_dirty),
                    );
                }
                _ => (),
            }
        }

        total_size
    })
}
