// bench_native.ce — compare VM vs native execution speed
//
// Usage:
//   cell --dev bench_native.ce <module.cm> [iterations]
//
// Compiles (if needed) and benchmarks a module via both VM and native dylib.
// Reports median/mean timing per benchmark + speedup ratio.

var os = use('internal/os')
var fd = use('fd')

if (length(args) < 1) {
  log.bench('usage: cell --dev bench_native.ce <module.cm> [iterations]')
  return
}

var file = args[0]
var name = file
if (ends_with(name, '.cm')) {
  name = text(name, 0, length(name) - 3)
}

var iterations = 11
if (length(args) > 1) {
  iterations = number(args[1])
}

def WARMUP = 3

var safe = replace(replace(name, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use'
var dylib_path = './' + file + '.dylib'

// --- Statistics ---

var stat_sort = function(arr) {
  return sort(arr)
}

var stat_median = function(arr) {
  if (length(arr) == 0) return 0
  var sorted = stat_sort(arr)
  var mid = floor(length(arr) / 2)
  if (length(arr) % 2 == 0) {
    return (sorted[mid - 1] + sorted[mid]) / 2
  }
  return sorted[mid]
}

var stat_mean = function(arr) {
  if (length(arr) == 0) return 0
  var sum = reduce(arr, function(a, b) { return a + b })
  return sum / length(arr)
}

var format_ns = function(ns) {
  if (ns < 1000) return text(round(ns)) + 'ns'
  if (ns < 1000000) return text(round(ns / 1000 * 100) / 100) + 'us'
  if (ns < 1000000000) return text(round(ns / 1000000 * 100) / 100) + 'ms'
  return text(round(ns / 1000000000 * 100) / 100) + 's'
}

// --- Collect benchmarks from module ---

var collect_benches = function(mod) {
  var benches = []
  var keys = null
  var i = 0
  var k = null
  if (is_function(mod)) {
    push(benches, {name: 'main', fn: mod})
  } else if (is_object(mod)) {
    keys = array(mod)
    i = 0
    while (i < length(keys)) {
      k = keys[i]
      if (is_function(mod[k])) {
        push(benches, {name: k, fn: mod[k]})
      }
      i = i + 1
    }
  }
  return benches
}

// --- Run one benchmark function ---

var run_bench = function(fn, label) {
  var samples = []
  var i = 0
  var t1 = 0
  var t2 = 0

  // warmup
  i = 0
  while (i < WARMUP) {
    fn(1)
    i = i + 1
  }

  // collect samples
  i = 0
  while (i < iterations) {
    t1 = os.now()
    fn(1)
    t2 = os.now()
    push(samples, t2 - t1)
    i = i + 1
  }

  return {
    label: label,
    median: stat_median(samples),
    mean: stat_mean(samples)
  }
}

// --- Load VM module ---

log.bench('loading VM module: ' + file)
var vm_mod = use(name)
var vm_benches = collect_benches(vm_mod)

if (length(vm_benches) == 0) {
  log.bench('no benchmarkable functions found in ' + file)
  return
}

// --- Load native module ---

var native_mod = null
var native_benches = []
var has_native = fd.is_file(dylib_path)
var lib = null

if (has_native) {
  log.bench('loading native module: ' + dylib_path)
  lib = os.dylib_open(dylib_path)
  native_mod = os.dylib_symbol(lib, symbol)
  native_benches = collect_benches(native_mod)
} else {
  log.bench('no ' + dylib_path + ' found -- VM-only benchmarking')
  log.bench('  hint: cell --dev compile.ce ' + file)
}

// --- Run benchmarks ---

log.bench('')
log.bench('samples: ' + text(iterations) + ' (warmup: ' + text(WARMUP) + ')')
log.bench('')

var pad = function(s, n) {
  var result = s
  while (length(result) < n) result = result + ' '
  return result
}

var i = 0
var b = null
var vm_result = null
var j = 0
var found = false
var nat_result = null
var speedup = 0
while (i < length(vm_benches)) {
  b = vm_benches[i]
  vm_result = run_bench(b.fn, 'vm')

  log.bench(pad(b.name, 20) + '  VM: ' + pad(format_ns(vm_result.median), 12) + ' (median)  ' + format_ns(vm_result.mean) + ' (mean)')

  // find matching native bench
  j = 0
  found = false
  while (j < length(native_benches)) {
    if (native_benches[j].name == b.name) {
      nat_result = run_bench(native_benches[j].fn, 'native')
      log.bench(pad('', 20) + '  NT: ' + pad(format_ns(nat_result.median), 12) + ' (median)  ' + format_ns(nat_result.mean) + ' (mean)')

      if (nat_result.median > 0) {
        speedup = vm_result.median / nat_result.median
        log.bench(pad('', 20) + '  speedup: ' + text(round(speedup * 100) / 100) + 'x')
      }
      found = true
    }
    j = j + 1
  }

  if (has_native && !found) {
    log.bench(pad('', 20) + '  NT: (no matching function)')
  }

  log.bench('')
  i = i + 1
}
