///|
priv struct Sample {
  index : Int32
  seed : Int32
  initial_points : Array[Array[Double]]
  points : Array[Array[Double]]
  stored_points : Array[Array[Double]]
  status : String
  error : String
  iterations : Int32
  variables : Int32
  equations : Int32
  local_dof : Int32
  max_tolerance_ratio : Double
  residual_ratios : Array[Double]
} derive(ToJson)

///|
// Independent per-case Park-Miller/Schrage seed, with no retry or rejection.
fn random(state : Array[Int32]) -> Double {
  let value = 16807 * (state[0] % 127773) - 2836 * (state[0] / 127773)
  state[0] = if value > 0 { value } else { value + 2147483647 }
  state[0].to_double() / 2147483647.0
}

///|
fn stored(
  s : @sketch.Sketch,
  ids : Array[@sketch.PointId],
) -> Array[Array[Double]] {
  ids.map(id => {
    let p = s.point(id).unwrap()
    [p.x, p.y]
  })
}

///|
fn sample(index : Int32) -> Sample {
  let seed = 20261022 + index * 97
  let state = [seed]
  for i = 0; i < 5; i = i + 1 {
    random(state) |> ignore
  }
  let s = @sketch.Sketch::new()
  // All six coordinates are sampled in (-10,10), not near a known solution.
  let ids = Array::makei(3, _ => {
    s.add_point(20.0 * random(state) - 10.0, 20.0 * random(state) - 10.0)
  })
  let initial_points = stored(s, ids)
  s.constrain(Distance(ids[0], ids[1], 3.0)) |> ignore
  s.constrain(Distance(ids[1], ids[2], 4.0)) |> ignore
  s.constrain(Distance(ids[0], ids[2], 5.0)) |> ignore
  // No anchors, direction constraints, exact-coordinate resets or restarts.
  // Translation, rotation and reflection are all valid for this experiment.
  let row : Sample = {
    index,
    seed,
    initial_points,
    points: [],
    stored_points: [],
    status: "Invalid",
    error: "",
    iterations: 0,
    variables: 0,
    equations: 0,
    local_dof: 0,
    max_tolerance_ratio: 0.0,
    residual_ratios: [],
  }
  match s.solve_and_apply() {
    Err(error) =>
      { ..row, error: error.message(), stored_points: stored(s, ids), }
    Ok(solution) => {
      let report = solution.report
      {
        ..row,
        points: ids.map(id => {
          let p = solution.point(id).unwrap()
          [p.x, p.y]
        }),
        stored_points: stored(s, ids),
        status: match report.status {
          Satisfied => "Satisfied"
          Stalled => "Stalled"
          IterationLimit => "IterationLimit"
        },
        iterations: report.iterations,
        variables: report.variables,
        equations: report.equations,
        local_dof: report.local_dof,
        max_tolerance_ratio: report.max_tolerance_ratio,
        residual_ratios: report.residuals.map(r => r.tolerance_ratio),
      }
    }
  }
}

///|
fn main {
  println(
    "{\"kind\":\"random_triangle\",\"schema\":1,\"cases\":1000,\"seed\":20261022,\"seed_step\":97,\"warmup\":5,\"coordinate_range\":[-10,10],\"sides\":[3,4,5],\"length_tolerance\":1e-7,\"max_iterations\":200}",
  )
  for index = 0; index < 1000; index = index + 1 {
    println(@json.to_json(sample(index)).stringify())
  }
}