//! Adapter that runs the bloom-filter perf workload via the standard
//! `subms` harness. Reads stdin key=value params, emits JSON on stdout.
//!
//! ```sh
//! cat <<EOF | cargo run --release --example perf_main
//! entries=50000
//! warmup=5000
//! seed=0
//! EOF
//! ```

use std::collections::BTreeMap;
use std::io;
use std::process::ExitCode;
use std::time::Instant;

use subms::{read_stdin_kv, PerfHarness};
use subms_bloom_filter::BloomFilter;

struct Lcg(u64);
impl Lcg {
    fn new(seed: u64) -> Self { Lcg(seed | 1) }
    fn next(&mut self) -> u32 {
        self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        (self.0 >> 32) as u32
    }
    fn bounded(&mut self, n: u32) -> u32 { self.next() % n }
}

fn parse_usize(m: &BTreeMap<String, String>, key: &str, default: usize) -> usize {
    m.get(key).and_then(|v| v.parse::<usize>().ok()).unwrap_or(default)
}
fn parse_u64(m: &BTreeMap<String, String>, key: &str, default: u64) -> u64 {
    m.get(key).and_then(|v| v.parse::<u64>().ok()).unwrap_or(default)
}

fn main() -> ExitCode {
    let args = read_stdin_kv();
    let entries = parse_usize(&args, "entries", 50_000);
    let warmup  = parse_usize(&args, "warmup", 5_000);
    let seed    = parse_u64(&args, "seed", 0);

    let mut h = PerfHarness::new("bloom-filter", "rust");
    h.input("entries", &entries.to_string());
    h.input("warmup",  &warmup.to_string());
    h.input("seed",    &seed.to_string());

    let mut bf = BloomFilter::new(entries);

    // Warm-up so JIT-equivalent work (icache fill, branch prediction warm-up)
    // doesn't pollute the steady-state samples.
    for i in 0..warmup {
        bf.add(&format!("warm{i}"));
    }

    {
        let s = h.stage("add", entries);
        for i in 0..entries {
            let key = format!("key{i}");
            let t0 = Instant::now();
            bf.add(&key);
            s.record(t0.elapsed().as_nanos() as u64);
        }
    }

    {
        let s = h.stage("might_contain_hit", entries);
        let mut rng = Lcg::new(seed);
        for _ in 0..entries {
            let key = format!("key{}", rng.bounded(entries as u32));
            let t0 = Instant::now();
            let _ = bf.might_contain(&key);
            s.record(t0.elapsed().as_nanos() as u64);
        }
    }

    {
        let s = h.stage("might_contain_miss", entries);
        let mut rng = Lcg::new(seed.wrapping_add(1));
        for _ in 0..entries {
            let key = format!("absent{}", rng.bounded(entries as u32 * 10));
            let t0 = Instant::now();
            let _ = bf.might_contain(&key);
            s.record(t0.elapsed().as_nanos() as u64);
        }
    }

    h.meta("bits_per_key", "10");
    h.meta("k", "7");

    let mut out = io::stdout().lock();
    h.write_json(&mut out).expect("write json");
    ExitCode::SUCCESS
}
