///|
/// Performance harness for `moonphys`.
///
/// Three scenes cover the shapes real games throw at a 2D engine: a tall stack
/// (many resting contacts), a dense crowd (many broadphase pairs) and a chain
/// (long constraint chains). Each scene is stepped `STEPS` times at a fixed
/// `DT`, and the wall clock is reported per step together with the body count,
/// the arbiter count and a bit-exact state hash.
///
/// Run it with:
///
/// ```bash
/// moon run bench --target js
/// moon run bench --target wasm-gc
/// ```
///
/// The hash is the determinism check: the same scene must print the same hash
/// on every target, because the engine never touches randomness or hash order.
const DT : Double = 1.0 / 60.0

///|
const STEPS : Int32 = 600

///|
fn fixed_ground(world : @moonphys.World, half_width : Double) -> Unit {
  ignore(
    world.add_body(
      @moonphys.Body::fixed(@moonphys.Shape::box(half_width, 0.5)).at(0.0, -0.5),
    ),
  )
}

///|
/// Ten layers of ten boxes: the worst case for resting contacts.
fn stack_scene() -> @moonphys.World {
  let world = @moonphys.World::new()
  fixed_ground(world, 40.0)
  for row in 0..<10 {
    for col in 0..<10 {
      ignore(
        world.add_body(
          @moonphys.Body::box(0.4, 0.4)
          .at(col.to_double() * 0.85 - 4.0, 0.45 + row.to_double() * 0.85)
          .with_friction(0.6),
        ),
      )
    }
  }
  world
}

///|
/// Five hundred circles packed shoulder to shoulder: the broadphase stress test.
fn crowd_scene() -> @moonphys.World {
  let world = @moonphys.World::new()
  fixed_ground(world, 60.0)
  for row in 0..<20 {
    for col in 0..<25 {
      ignore(
        world.add_body(
          @moonphys.Body::ball(0.35)
          .at(col.to_double() * 0.69 - 8.3, 0.4 + row.to_double() * 0.69)
          .with_friction(0.5),
        ),
      )
    }
  }
  world
}

///|
/// Twenty links hanging off a static mount: the constraint chain stress test.
fn chain_scene() -> @moonphys.World {
  let world = @moonphys.World::new()
  fixed_ground(world, 40.0)
  let mut previous = world.add_body(
    @moonphys.Body::fixed(@moonphys.Shape::box(0.2, 0.05)).at(0.0, 12.0),
  )
  for i in 0..<20 {
    let link = world.add_body(
      @moonphys.Body::box(0.25, 0.05).at(0.25 + i.to_double() * 0.5, 12.0),
    )
    world.add_revolve_joint_local(
      previous,
      link,
      @moonphys.Vec2::new(0.25, 0.0),
      @moonphys.Vec2::new(-0.25, 0.0),
    )
    previous = link
  }
  world
}

///|
fn round3(value : Double) -> Double {
  (value * 1000.0).round() / 1000.0
}

///|
fn report(name : String, world : @moonphys.World) -> Unit {
  let start = @clock.monotonic_clock_start()
  world.run(STEPS, DT)
  let micros = @clock.monotonic_clock_end(start)
  let per_step = round3(micros / 1000.0 / STEPS.to_double())
  println(
    name +
    " | bodies=" +
    world.body_count().to_string() +
    " arbiters=" +
    world.arbiter_count().to_string() +
    " ms/step=" +
    per_step.to_string() +
    " hash=" +
    world.state_hash().to_string(),
  )
}

///|
fn main {
  println(
    "moonphys bench: dt=" + DT.to_string() + " steps=" + STEPS.to_string(),
  )
  report("stack(100 boxes)", stack_scene())
  report("crowd(500 circles)", crowd_scene())
  report("chain(20 links)", chain_scene())
}