(** CEK benchmark — measures throughput of the CEK evaluator on tight loops.

    Usage:
      dune exec bin/bench_cek.exe
      dune exec bin/bench_cek.exe -- 5      (5 runs each)
*)

open Sx_types
open Sx_parser

let parse_one s =
  let exprs = parse_all s in
  match exprs with
  | e :: _ -> e
  | [] -> failwith "empty parse"

let parse_many s = parse_all s

let bench_run name setup expr iters =
  let env = Sx_types.make_env () in
  (* Run setup forms in env *)
  List.iter (fun e -> ignore (Sx_ref.eval_expr e (Env env))) setup;
  let times = ref [] in
  for _ = 1 to iters do
    Gc.full_major ();
    let t0 = Unix.gettimeofday () in
    let _r = Sx_ref.eval_expr expr (Env env) in
    let t1 = Unix.gettimeofday () in
    times := (t1 -. t0) :: !times
  done;
  let sorted = List.sort compare !times in
  let median = List.nth sorted (iters / 2) in
  let min_t = List.nth sorted 0 in
  let max_t = List.nth sorted (iters - 1) in
  Printf.printf "  %-22s  min=%8.2fms  median=%8.2fms  max=%8.2fms\n%!"
    name (min_t *. 1000.0) (median *. 1000.0) (max_t *. 1000.0);
  median

let () =
  let iters =
    if Array.length Sys.argv > 1
    then int_of_string Sys.argv.(1)
    else 5
  in
  Printf.printf "CEK benchmark (%d runs each, taking median)\n%!" iters;
  Printf.printf "==========================================\n%!";

  (* fib 18 — recursive function call benchmark, smallish *)
  let fib_setup = parse_many "(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))" in
  let fib_expr = parse_one "(fib 18)" in
  let _ = bench_run "fib(18)" fib_setup fib_expr iters in

  (* loop 5000 — tight let loop *)
  let loop_setup = parse_many "(define (loop n acc) (if (= n 0) acc (loop (- n 1) (+ acc 1))))" in
  let loop_expr = parse_one "(loop 5000 0)" in
  let _ = bench_run "loop(5000)" loop_setup loop_expr iters in

  (* map+square over 1000 elem list *)
  let map_setup = parse_many "(define (range-list n) (let loop ((i 0) (acc (list))) (if (= i n) acc (loop (+ i 1) (cons i acc))))) (define xs (range-list 1000))" in
  let map_expr = parse_one "(map (fn (x) (* x x)) xs)" in
  let _ = bench_run "map sq xs(1000)" map_setup map_expr iters in

  (* reduce + over 2000 elem list *)
  let red_setup = parse_many "(define (range-list n) (let loop ((i 0) (acc (list))) (if (= i n) acc (loop (+ i 1) (cons i acc))))) (define ys (range-list 2000))" in
  let red_expr = parse_one "(reduce + 0 ys)" in
  let _ = bench_run "reduce + ys(2000)" red_setup red_expr iters in

  (* let-heavy: many bindings + if *)
  let lh_setup = parse_many "(define (lh n) (let ((a 1) (b 2) (c 3) (d 4)) (if (= n 0) (+ a b c d) (lh (- n 1)))))" in
  let lh_expr = parse_one "(lh 2000)" in
  let _ = bench_run "let-heavy(2000)" lh_setup lh_expr iters in

  Printf.printf "\nDone.\n%!"
