///|
pub struct TracePoint {
  index_value : Int
  line_value : Int
  position_value : Position
  cumulative_distance_value : Double
  feed_value : Double?
  kind_value : MotionKind
} derive(Eq, Debug)

///|
pub fn TracePoint::index(self : TracePoint) -> Int {
  self.index_value
}

///|
pub fn TracePoint::line(self : TracePoint) -> Int {
  self.line_value
}

///|
pub fn TracePoint::position(self : TracePoint) -> Position {
  self.position_value
}

///|
pub fn TracePoint::cumulative_distance(self : TracePoint) -> Double {
  self.cumulative_distance_value
}

///|
pub fn TracePoint::feed(self : TracePoint) -> Double? {
  self.feed_value
}

///|
pub fn TracePoint::kind(self : TracePoint) -> MotionKind {
  self.kind_value
}

///|
pub struct ToolpathTrace {
  points_value : Array[TracePoint]
  total_distance_value : Double
} derive(Eq, Debug)

///|
pub fn ToolpathTrace::points(self : ToolpathTrace) -> Array[TracePoint] {
  self.points_value.copy()
}

///|
pub fn ToolpathTrace::length(self : ToolpathTrace) -> Int {
  self.points_value.length()
}

///|
pub fn ToolpathTrace::total_distance(self : ToolpathTrace) -> Double {
  self.total_distance_value
}

///|
pub fn ToolpathTrace::at(self : ToolpathTrace, index : Int) -> TracePoint? {
  if index < 0 || index >= self.points_value.length() {
    None
  } else {
    Some(self.points_value[index])
  }
}

///|
/// Build an immutable trace suitable for previews and downstream reports.
pub fn trace(program : GCodeProgram) -> ToolpathTrace {
  let points : Array[TracePoint] = []
  let mut cumulative = 0.0
  for index, motion in program.motions() {
    cumulative += motion_path_length(motion)
    points.push({
      index_value: index,
      line_value: motion.source_line(),
      position_value: motion.end(),
      cumulative_distance_value: cumulative,
      feed_value: motion.feed(),
      kind_value: motion.kind(),
    })
  }
  { points_value: points, total_distance_value: cumulative }
}

///|
pub fn trace_at_distance(
  trace_value : ToolpathTrace,
  distance : Double,
) -> TracePoint? {
  if distance < 0.0 {
    return None
  }
  for point in trace_value.points() {
    if point.cumulative_distance() >= distance {
      return Some(point)
    }
  }
  None
}

///|
pub fn render_trace_text(trace_value : ToolpathTrace) -> String {
  let mut output = "trace_points=" +
    trace_value.length().to_string() +
    " total=" +
    trace_value.total_distance().to_string() +
    "\n"
  for point in trace_value.points() {
    output = output +
      point.index().to_string() +
      " line=" +
      point.line().to_string() +
      " x=" +
      point.position().x().to_string() +
      " y=" +
      point.position().y().to_string() +
      "\n"
  }
  output
}