// Benchmark Driver for moon-frequency-sketch
// ============================================
// Exercises all core data structures with realistic workloads.
// Run with: moon run bench

///|
fn main {
  println("=== moon-frequency-sketch Benchmarks ===")
  println("")
  bench_cms()
  bench_topk()
  bench_bloom()
  bench_countsketch()
  bench_window()
  bench_telemetry()
  println("")
  println("All benchmarks complete.")
}

///|
/// Simulate a Zipf-like distribution: rank k gets N/(k * H_distinct) events.
fn zipf_workload(n : Int, distinct : Int) -> Array[(String, Int64)] {
  let result = Array::make(distinct, ("", 0L))
  let mut i = 0
  while i < distinct {
    let rank = i + 1
    let count = (n / rank).to_int64()
    result[i] = ("key_" + rank.to_string(), if count < 1L { 1L } else { count })
    i = i + 1
  }
  result
}

///|
/// Count-Min Sketch benchmark.
fn bench_cms() -> Unit {
  println("[CMS] Building workload (100k insertions, 1k distinct keys)...")
  let workload = zipf_workload(100000, 1000)
  let cms = @cms.CountMinSketch::new(5, 2000)
  let mut i = 0
  while i < workload.length() {
    let (key, count) = workload[i]
    cms.add_str(key, count)
    i = i + 1
  }
  println("[CMS] Querying top-10 keys...")
  i = 0
  while i < 10 {
    let (key, true_count) = workload[i]
    let est = cms.estimate_str(key)
    let over = est - true_count
    println(
      "  " +
      key +
      " true=" +
      true_count.to_string() +
      " est=" +
      est.to_string() +
      " over=" +
      over.to_string(),
    )
    i = i + 1
  }
  println("[CMS] " + cms.summary())
  println("")
}

///|
/// Top-K Heavy Hitters benchmark.
fn bench_topk() -> Unit {
  println("[TopK] Space-Saving benchmark (500 events, 50 distinct)...")
  let ss = @topk.SpaceSaving::new(100)
  let mut i = 0
  while i < 500 {
    ss.add("event_" + (i % 50).to_string())
    i = i + 1
  }
  let top = ss.top_k(5)
  println("  Top-5 results:")
  i = 0
  while i < top.length() {
    println(
      "  " +
      (i + 1).to_string() +
      ". " +
      top[i].key +
      " count=" +
      top[i].count.to_string(),
    )
    i = i + 1
  }
  println("")
  println("[TopK] Misra-Gries benchmark (500 events, 30 distinct)...")
  let mg = @topk.MisraGries::new(50)
  i = 0
  while i < 500 {
    mg.add("item_" + (i % 30).to_string())
    i = i + 1
  }
  let cands = mg.candidates()
  println("  Active candidates: " + cands.length().to_string())
  println("")
}

///|
/// Bloom Filter benchmark.
fn bench_bloom() -> Unit {
  println("[Bloom] Standard Bloom Filter (10k insertions, ~1% FP rate)...")
  let bf = @bloomfilter.BloomFilter::from_params(10000, 0.01)
  let mut i = 0
  while i < 10000 {
    bf.add_str("item_" + i.to_string())
    i = i + 1
  }
  let mut fp_count = 0
  i = 10000
  while i < 20000 {
    if bf.contains_str("item_" + i.to_string()) {
      fp_count = fp_count + 1
    }
    i = i + 1
  }
  let fp_rate = fp_count.to_double() / 10000.0
  println(
    "  FP count: " + fp_count.to_string() + ", rate: " + fp_rate.to_string(),
  )
  println("  Memory: " + bf.memory_bytes().to_string() + " bytes")
  println("")
  println("[Bloom] Counting Bloom Filter (1k insertions + 500 removals)...")
  let cb = @bloomfilter.CountingBloom::from_params(1000, 0.01)
  i = 0
  while i < 1000 {
    cb.add_str("session_" + i.to_string())
    i = i + 1
  }
  i = 0
  while i < 500 {
    let _ = cb.remove_str("session_" + i.to_string())
    i = i + 1
  }
  println(
    "  After 500 removals, approx count: " + cb.approx_count().to_string(),
  )
  println("")
}

///|
/// Count Sketch benchmark.
fn bench_countsketch() -> Unit {
  println("[CountSketch] 5k events, 100 distinct keys (50 each)...")
  let cs = @countsketch.CountSketch::new(7, 1000)
  let mut i = 0
  while i < 5000 {
    cs.add_str("key_" + (i % 100).to_string(), 1L)
    i = i + 1
  }
  let e0 = cs.estimate_str("key_0")
  let e1 = cs.estimate_str("key_1")
  println("  key_0 estimate: " + e0.to_string() + " (expected ~50)")
  println("  key_1 estimate: " + e1.to_string() + " (expected ~50)")
  println("")
}

///|
/// Sliding Window benchmark.
fn bench_window() -> Unit {
  println("[Window] 10-bucket sliding window, 100 ticks...")
  let sw = @window.SlidingWindow::new(10, 4, 200)
  let mut i = 0
  while i < 100 {
    sw.add_str("hot_key", 10L)
    sw.add_str("cold_key", 1L)
    if i % 10 == 9 {
      sw.tick()
    }
    i = i + 1
  }
  println(
    "  hot_key window estimate: " + sw.estimate_str("hot_key").to_string(),
  )
  println(
    "  cold_key window estimate: " + sw.estimate_str("cold_key").to_string(),
  )
  println("  Total window count: " + sw.total_count().to_string())
  println("")
}

///|
/// Deterministic end-to-end API telemetry benchmark with exact reference values.
fn bench_telemetry() -> Unit {
  let event_count = 10000
  let monitor = @telemetry.Monitor::new(20, 10, 5, 1000, 12, 100).unwrap()
  let start = @env.now()
  for i in 0.. value
    None => 0.0
  }
  let p99 = match report.latency_p99 {
    Some(value) => value
    None => 0.0
  }
  println("[Telemetry] Deterministic API stream benchmark")
  println("  events=" + event_count.to_string() + " distinct_entities=1000")
  println(
    "  measured_ms=" +
    measured_ms.to_string() +
    " throughput_events_per_sec=" +
    throughput.to_string(),
  )
  println(
    "  unique_estimate=" +
    report.unique_entities.to_string() +
    " relative_error=" +
    unique_error.to_string(),
  )
  println(
    "  latency_p95_estimate=" +
    p95.to_string() +
    " exact=104.0 latency_p99_estimate=" +
    p99.to_string() +
    " exact=108.0",
  )
  println(
    "  error_events=" +
    report.error_events.to_string() +
    " error_rate=" +
    report.error_rate.to_string(),
  )
  println("")
}