//! Compile-Budget-Benchmark (Instant-Compile-Anforderung, PLAN.md):
//! Projekt mit ~50.000 Zeilen < 1 s, einzelnes Modul (~500 Zeilen) < 50 ms
//! (Release-Build). Eigener Harness (kein Criterion — keine
//! Fremdabhängigkeit nötig); Lauf mit `cargo bench -p tb-vm`.

use std::fmt::Write as _;
use std::time::Instant;

/// Realistisch gemischtes Modul mit `n_blocks` Codeblöcken
/// (~10 Zeilen je Block) plus Prozeduren.
fn generate_module(n_blocks: usize, seed: usize) -> String {
    let mut src = String::new();
    let _ = writeln!(src, "' Generiertes Benchmark-Modul {seed}");
    let _ = writeln!(src, "DIM feld{seed}%(100)");
    for i in 0..n_blocks {
        let v = format!("v{seed}x{i}");
        let _ = writeln!(src, "{v}% = {i} MOD 100");
        let _ = writeln!(src, "{v}tot# = {v}% * 1.5 + SQR({v}% + 1)");
        let _ = writeln!(src, "IF {v}% > 50 THEN");
        let _ = writeln!(src, "  {v}s$ = \"gross\" + STR$({v}%)");
        let _ = writeln!(src, "ELSE");
        let _ = writeln!(src, "  {v}s$ = LEFT$(\"klein\", 3)");
        let _ = writeln!(src, "END IF");
        let _ = writeln!(src, "FOR {v}i% = 1 TO 10");
        let _ = writeln!(src, "  feld{seed}%({v}i% MOD 100) = {v}i%");
        let _ = writeln!(src, "NEXT");
    }
    // Prozeduren am Modulende
    for p in 0..(n_blocks / 20).max(1) {
        let _ = writeln!(src, "SUB Tu{seed}p{p} (a%, b#)");
        let _ = writeln!(src, "  b# = a% * 2 + b#");
        let _ = writeln!(src, "END SUB");
    }
    src
}

fn compile_all(sources: &[(String, String)]) -> usize {
    let units: Vec<_> = sources
        .iter()
        .map(|(name, src)| tb_frontend::source::SourceUnit::new(name, &format!("{name}.bas"), src))
        .collect();
    let project = tb_vm::compile_project("BENCH", &units, &Default::default(), &[])
        .expect("Benchmark-Projekt muss kompilieren");
    project.procs.iter().map(|p| p.code.len()).sum()
}

fn incremental(sources: &[(String, String)], budget: f64) {
    let mut units: Vec<_> = sources
        .iter()
        .map(|(n, s)| tb_frontend::source::SourceUnit::new(n, &format!("{n}.bas"), s))
        .collect();
    let mut compiler = tb_vm::project::ProjectCompiler::default();
    compiler
        .compile("BENCH", &units, &Default::default(), &[])
        .unwrap();
    let mut times = Vec::new();
    for i in 0..7 {
        // Private Rumpfänderung mit unverändertem öffentlichen Vertrag.
        units[0].segments[0].text = sources[0]
            .1
            .replace("a% * 2 + b#", &format!("a% * {} + b#", 3 + i));
        let start = Instant::now();
        let code = compiler
            .compile("BENCH", &units, &Default::default(), &[])
            .unwrap();
        std::hint::black_box(&code);
        times.push(start.elapsed().as_secs_f64());
        assert_eq!(compiler.stats.compiled, 1);
        assert_eq!(compiler.stats.reused, units.len() - 1);
    }
    times.sort_by(f64::total_cmp);
    let median = times[times.len() / 2];
    println!("  Cacheänderung: {} Module, Median {:.2} ms (7 Läufe; Invalidierung + Link), {} neu / {} wiederverwendet; Budget {:.0} ms {}",units.len(),median*1000.0,compiler.stats.compiled,compiler.stats.reused,budget*1000.0,if median<budget {"OK"}else{"VERFEHLT"});
    assert!(median < budget, "Cachebudget verfehlt");
}

fn main() {
    println!(
        "Hardware: {} / {} / {}",
        std::env::consts::OS,
        std::env::consts::ARCH,
        std::process::Command::new(if cfg!(target_os = "macos") {
            "sysctl"
        } else {
            "uname"
        })
        .args(if cfg!(target_os = "macos") {
            vec!["-n", "machdep.cpu.brand_string"]
        } else {
            vec!["-m"]
        })
        .output()
        .ok()
        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_owned())
        .unwrap_or_default()
    );
    println!(
        "Profil: Release / opt-level=3, eigener Harness; cargo bench -p tb-vm --bench compile"
    );
    // Einzelnes Modul: ~500 Zeilen (Budget < 50 ms).
    let single = generate_module(50, 0);
    let single_lines = single.lines().count();

    // Projekt: ~50.000 Zeilen über 20 Module (Budget < 1 s).
    let modules: Vec<(String, String)> = (0..20)
        .map(|i| (format!("MOD{i}"), generate_module(245, i)))
        .collect();
    let project_lines: usize = modules.iter().map(|(_, s)| s.lines().count()).sum();

    // Aufwärmen
    let _ = compile_all(&[("WARM".into(), single.clone())]);

    let t = Instant::now();
    let mut best_single = f64::MAX;
    for _ in 0..10 {
        let t1 = Instant::now();
        let _ = compile_all(&[("EINZEL".into(), single.clone())]);
        best_single = best_single.min(t1.elapsed().as_secs_f64());
    }
    let _ = t;

    let t2 = Instant::now();
    let instrs = compile_all(&modules);
    let project_secs = t2.elapsed().as_secs_f64();

    println!("Compile-Budget-Benchmark (Release):");
    println!(
        "  Einzelmodul: {single_lines} Zeilen in {:.2} ms  (Budget 50 ms) {}",
        best_single * 1000.0,
        if best_single < 0.050 {
            "OK"
        } else {
            "VERFEHLT"
        }
    );
    println!(
        "  Projekt:     {project_lines} Zeilen in {:.0} ms  (Budget 1000 ms) {} — {instrs} Instruktionen",
        project_secs * 1000.0,
        if project_secs < 1.0 { "OK" } else { "VERFEHLT" }
    );
    let lps = project_lines as f64 / project_secs;
    println!("  Durchsatz:   {:.0} Zeilen/s", lps);
    assert!(best_single < 0.050, "Einzelmodul-Budget verfehlt");
    assert!(project_secs < 1.0, "Projekt-Budget verfehlt");
    incremental(&[("EINZEL".into(), single)], 0.050);
    incremental(&modules, 1.0);
}
