///|
/// A simple stopwatch for measuring elapsed time in microseconds.
pub(all) struct Stopwatch {
  mut elapsed : Double
  mut running : Bool
  mut start_time : @bench.Timestamp
}

///|
/// Creates a new stopped Stopwatch with zero elapsed time.
pub fn Stopwatch::new() -> Stopwatch {
  { elapsed: 0.0, running: false, start_time: @bench.monotonic_clock_start() }
}

///|
/// Starts the stopwatch; no-op if already running.
pub fn Stopwatch::start(self : Stopwatch) -> Unit {
  if !self.running {
    self.running = true
    self.start_time = @bench.monotonic_clock_start()
  }
}

///|
/// Stops the stopwatch and accumulates the elapsed time; no-op if not running.
pub fn Stopwatch::stop(self : Stopwatch) -> Unit {
  if self.running {
    self.elapsed = self.elapsed + @bench.monotonic_clock_end(self.start_time)
    self.running = false
  }
}

///|
/// Resets the stopwatch to zero and stops it.
pub fn Stopwatch::reset(self : Stopwatch) -> Unit {
  self.elapsed = 0.0
  self.running = false
}

///|
/// Resets and immediately starts the stopwatch.
pub fn Stopwatch::restart(self : Stopwatch) -> Unit {
  self.reset()
  self.start()
}

///|
/// Returns the total elapsed time in microseconds (including current run if running).
pub fn Stopwatch::elapsed_micros(self : Stopwatch) -> Double {
  if self.running {
    self.elapsed + @bench.monotonic_clock_end(self.start_time)
  } else {
    self.elapsed
  }
}

///|
/// Returns the total elapsed time in milliseconds.
pub fn Stopwatch::elapsed_millis(self : Stopwatch) -> Double {
  self.elapsed_micros() / 1000.0
}

///|
/// Returns the total elapsed time in seconds.
pub fn Stopwatch::elapsed_secs(self : Stopwatch) -> Double {
  self.elapsed_micros() / 1000000.0
}

///|
/// Returns true if the stopwatch is currently running.
pub fn Stopwatch::is_running(self : Stopwatch) -> Bool {
  self.running
}

///|
/// Result of a benchmark run, containing timing statistics.
pub(all) struct BenchmarkResult {
  name : String
  iterations : Int
  total_time_us : Double
  min_time_us : Double
  max_time_us : Double
}

///|
/// Returns the benchmark name.
pub fn BenchmarkResult::name(self : BenchmarkResult) -> String {
  self.name
}

///|
/// Returns the number of iterations executed.
pub fn BenchmarkResult::iterations(self : BenchmarkResult) -> Int {
  self.iterations
}

///|
/// Returns the total time across all iterations in microseconds.
pub fn BenchmarkResult::total_time_us(self : BenchmarkResult) -> Double {
  self.total_time_us
}

///|
/// Returns the average time per iteration in microseconds.
pub fn BenchmarkResult::avg_time_us(self : BenchmarkResult) -> Double {
  self.total_time_us / self.iterations.to_double()
}

///|
/// Returns the minimum single-iteration time in microseconds.
pub fn BenchmarkResult::min_time_us(self : BenchmarkResult) -> Double {
  self.min_time_us
}

///|
/// Returns the maximum single-iteration time in microseconds.
pub fn BenchmarkResult::max_time_us(self : BenchmarkResult) -> Double {
  self.max_time_us
}

///|
/// Runs a function `iterations` times and returns timing statistics.
pub fn benchmark(
  name : String,
  iterations : Int,
  f : () -> Unit,
) -> BenchmarkResult {
  let mut total = 0.0
  let mut min_val = 999999999.0
  let mut max_val = 0.0
  let mut i = 0
  while i < iterations {
    let sw = Stopwatch::new()
    sw.start()
    f()
    sw.stop()
    let elapsed = sw.elapsed_micros()
    total = total + elapsed
    if elapsed < min_val {
      min_val = elapsed
    }
    if elapsed > max_val {
      max_val = elapsed
    }
    i = i + 1
  }
  {
    name,
    iterations,
    total_time_us: total,
    min_time_us: min_val,
    max_time_us: max_val,
  }
}

///|
/// Runs warmup iterations, then benchmarks and returns timing statistics.
pub fn benchmark_warmup(
  name : String,
  iterations : Int,
  warmup : Int,
  f : () -> Unit,
) -> BenchmarkResult {
  let mut i = 0
  while i < warmup {
    f()
    i = i + 1
  }
  benchmark(name, iterations, f)
}

///|
/// Returns a human-readable benchmark report string.
pub fn benchmark_report(result : BenchmarkResult) -> String {
  let sb = StringBuilder()
  sb.write_string("Benchmark: ")
  sb.write_string(result.name)
  sb.write_string("\n")
  sb.write_string("  Iterations: ")
  sb.write_string(result.iterations.to_string())
  sb.write_string("\n")
  sb.write_string("  Total time: ")
  sb.write_string(format_double(result.total_time_us, 2))
  sb.write_string(" us\n")
  sb.write_string("  Avg time:   ")
  sb.write_string(format_double(result.avg_time_us(), 2))
  sb.write_string(" us\n")
  sb.write_string("  Min time:   ")
  sb.write_string(format_double(result.min_time_us, 2))
  sb.write_string(" us\n")
  sb.write_string("  Max time:   ")
  sb.write_string(format_double(result.max_time_us, 2))
  sb.write_string(" us")
  sb.to_string()
}

///|
/// Formats the stopwatch's elapsed time as a human-readable string (us/ms/s).
pub fn Stopwatch::format_elapsed(self : Stopwatch) -> String {
  let us = self.elapsed_micros()
  if us < 1000.0 {
    format_double(us, 0) + " us"
  } else if us < 1000000.0 {
    format_double(us / 1000.0, 2) + " ms"
  } else {
    format_double(us / 1000000.0, 3) + " s"
  }
}

///|
/// Measures and returns the execution time of a function in microseconds.
pub fn measure_time(f : () -> Unit) -> Double {
  let sw = Stopwatch::new()
  sw.start()
  f()
  sw.stop()
  sw.elapsed_micros()
}

///|
/// Measures and returns the execution time of a function in milliseconds.
pub fn measure_time_ms(f : () -> Unit) -> Double {
  measure_time(f) / 1000.0
}

///|
/// Meter that counts operations and measures throughput (ops per second).
pub(all) struct ThroughputMeter {
  sw : Stopwatch
  mut count : Int64
}

///|
/// Creates a new ThroughputMeter with zero count and a stopped stopwatch.
pub fn ThroughputMeter::new() -> ThroughputMeter {
  { sw: Stopwatch::new(), count: 0L }
}

///|
/// Increments the operation count by one; starts the timer on first call.
pub fn ThroughputMeter::increment(self : ThroughputMeter) -> Unit {
  if self.count == 0L {
    self.sw.start()
  }
  self.count = self.count + 1L
}

///|
/// Records a batch of operations (adds n to count); starts the timer on first call.
pub fn ThroughputMeter::record_batch(self : ThroughputMeter, n : Int64) -> Unit {
  if self.count == 0L {
    self.sw.start()
  }
  self.count = self.count + n
}

///|
/// Stops the throughput measurement timer.
pub fn ThroughputMeter::stop(self : ThroughputMeter) -> Unit {
  self.sw.stop()
}

///|
/// Returns the measured throughput in operations per second.
pub fn ThroughputMeter::ops_per_second(self : ThroughputMeter) -> Double {
  let elapsed_us = self.sw.elapsed_micros()
  if elapsed_us > 0.0 {
    self.count.to_double() / (elapsed_us / 1000000.0)
  } else {
    0.0
  }
}

///|
/// Returns a human-readable throughput string (e.g. "1.23 K ops/s").
pub fn ThroughputMeter::format_throughput(self : ThroughputMeter) -> String {
  let ops = self.ops_per_second()
  if ops > 1000000.0 {
    format_double(ops / 1000000.0, 2) + " M ops/s"
  } else if ops > 1000.0 {
    format_double(ops / 1000.0, 2) + " K ops/s"
  } else {
    format_double(ops, 2) + " ops/s"
  }
}

///|
/// Returns the elapsed measurement time as a human-readable string.
pub fn ThroughputMeter::elapsed_str(self : ThroughputMeter) -> String {
  self.sw.format_elapsed()
}

///|
/// Runs two benchmarks and returns a side-by-side comparison report.
pub fn benchmark_compare(
  name_a : String,
  name_b : String,
  iterations : Int,
  f_a : () -> Unit,
  f_b : () -> Unit,
) -> String {
  let r_a = benchmark(name_a, iterations, f_a)
  let r_b = benchmark(name_b, iterations, f_b)
  let sb = StringBuilder()
  sb.write_string("Comparison: ")
  sb.write_string(name_a)
  sb.write_string(" vs ")
  sb.write_string(name_b)
  sb.write_string("\n")
  sb.write_string(benchmark_report(r_a))
  sb.write_string("\n")
  sb.write_string(benchmark_report(r_b))
  sb.write_string("\n")
  let avg_a = r_a.avg_time_us()
  let avg_b = r_b.avg_time_us()
  if avg_b > 0.0 {
    let ratio = avg_a / avg_b
    sb.write_string("Ratio: ")
    sb.write_string(format_double(ratio, 2))
    sb.write_string("x")
  }
  sb.to_string()
}

// Formats a double to the specified number of decimal places.

///|
fn format_double(v : Double, decimals : Int) -> String {
  let s = v.to_string()
  let dot_pos = s.find(".")
  match dot_pos {
    Some(pos) => {
      let end = pos + 1 + decimals
      if end < s.length() {
        s[:end].to_owned()
      } else {
        s
      }
    }
    None => s
  }
}