//
// Syd: rock-solid application kernel
// src/utils/syd-tck.rs: Measure command runtime in hardware ticks.
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    env,
    os::unix::process::ExitStatusExt,
    process::{Command, ExitCode},
    time::Instant,
};

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(coverage),
    not(feature = "prof"),
    not(target_os = "android"),
    target_page_size_4k,
    target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;

// Set global allocator to tcmalloc if profiling is enabled.
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;

#[cfg(target_arch = "x86_64")]
use tick_counter::x86_64_processor_id;
// Conditional compilation for architecture-specific features
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
use tick_counter::{frequency, precision_nanoseconds, TickCounter};

syd::main! {
    syd::set_sigpipe_dfl()?;

    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        help();
        return Ok(ExitCode::FAILURE);
    } else if args[1] == "-h" || args[1] == "--help" {
        help();
        return Ok(ExitCode::SUCCESS);
    }

    let arg0 = &args[1];
    let args = &args[2..];

    let start = Instant::now();
    let tick_start = current_tick();

    let status = Command::new(arg0).args(args).status()?;
    let tick_duration = elapsed_tick(&tick_start);
    let duration = start.elapsed();
    let code = status
        .code()
        .unwrap_or_else(|| 128 + status.signal().unwrap_or(127));

    let (freq, precision) = tick_info();
    let duration = duration.as_secs_f64();
    let extra_info = get_arch_specific_info();

    eprintln!("{arg0}\tcode:{code} total:{duration:.2}s td:{tick_duration} freq:{freq}Hz prec:{precision:.2}ns{extra_info}");
    Ok(ExitCode::from(code as u8))
}

#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
fn current_tick() -> TickCounter {
    TickCounter::current()
}

#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
fn current_tick() -> Instant {
    Instant::now()
}

#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
fn elapsed_tick(start_tick: &TickCounter) -> u64 {
    start_tick.elapsed()
}

#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
fn elapsed_tick(start_tick: &Instant) -> u64 {
    start_tick.elapsed().as_secs_f64() as u64
}

// XXX: false positive.
#[expect(clippy::needless_return)]
fn tick_info() -> (u64, f64) {
    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
    {
        let (freq, _) = frequency();
        let precision = precision_nanoseconds(freq);
        return (freq, precision);
    }
    #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
    {
        // Fallback to a simple estimation, not accurate
        return (1, 1_000_000_000.0);
    }
}

#[cfg(target_arch = "x86_64")]
fn get_arch_specific_info() -> String {
    let (tc, pid) = x86_64_processor_id();
    format!(" pid:{pid} tc:{tc}")
}

#[cfg(not(target_arch = "x86_64"))]
fn get_arch_specific_info() -> String {
    String::new() // Return an empty string for non-x86_64 architectures
}

fn help() {
    println!("Usage: syd-tck {{command [arg...]}}");
    println!("Given a command with optional arguments, measures runtime in hardware ticks.");
}
