///|
/// Higher-level operations for 2D rails and masks.
pub struct Path2DMetric2 {
  length : Double
  bounds : Bounds2D
  samples : Array[Point2]
  average_speed : Double
  chord_error : Double
} derive(Debug)

///|
pub fn Path2DMetric2::length(self : Path2DMetric2) -> Double {
  self.length
}

///|
pub fn Path2DMetric2::bounds(self : Path2DMetric2) -> Bounds2D {
  self.bounds
}

///|
pub fn Path2DMetric2::samples(self : Path2DMetric2) -> Array[Point2] {
  self.samples.copy()
}

///|
pub fn Path2DMetric2::average_speed(self : Path2DMetric2) -> Double {
  self.average_speed
}

///|
pub fn Path2DMetric2::chord_error(self : Path2DMetric2) -> Double {
  self.chord_error
}

///|
pub fn analyze_path2d(path : Path2D, count : Int) -> Path2DMetric2 {
  let actual = count.max(2)
  let samples = path.polyline(actual)
  let mut chord_error = 0.0
  for index in 1.. Array[Point2] {
  let result : Array[Point2] = []
  for distance in distances {
    result.push(path.sample_by_distance(distance))
  }
  result
}

///|
pub fn path2d_tangent_batch(
  path : Path2D,
  parameters : Array[Double],
) -> Array[Point2] {
  let result : Array[Point2] = []
  for parameter in parameters {
    result.push(point2_normalized(path.tangent(parameter)))
  }
  result
}

///|
pub fn path2d_normals(path : Path2D, count : Int) -> Array[Point2] {
  let result : Array[Point2] = []
  let actual = count.max(1)
  for index in 0.. Array[Point2] {
  let result : Array[Point2] = []
  let actual = count.max(1)
  for index in 0.. Array[Double] {
  let actual = count.max(2)
  let result : Array[Double] = []
  let step = path.total_length() / (actual - 1).to_double()
  for index in 0.. Path2DMetric2 {
  analyze_path2d(path, count)
}

///|
pub fn path2d_distance_to_segment(
  path : Path2D,
  target : Point2,
  samples : Int,
) -> Double {
  path.project(target, samples_per_segment=samples.max(1)).distance()
}

///|
pub fn path2d_closest_parameter(
  path : Path2D,
  target : Point2,
  samples : Int,
) -> Double {
  path.project(target, samples_per_segment=samples.max(1)).parameter()
}

///|
pub fn path2d_split_samples(
  path : Path2D,
  parameters : Array[Double],
) -> Array[Point2] {
  let result : Array[Point2] = []
  for parameter in parameters {
    result.push(path.sample(parameter))
  }
  result
}

///|
pub fn path2d_motion_length(
  path : Path2D,
  start : Double,
  end : Double,
  samples : Int,
) -> Double {
  let actual = samples.max(1)
  let mut total = 0.0
  let mut previous = path.sample(start)
  for index in 1..<=actual {
    let current = path.sample(
      start + (end - start) * index.to_double() / actual.to_double(),
    )
    total = total + previous.distance(current)
    previous = current
  }
  total
}

///|
pub fn path2d_curvature_estimate(
  path : Path2D,
  parameter : Double,
  epsilon? : Double = 0.001,
) -> Double {
  let step = epsilon.max(0.000000000001)
  let left = point2_normalized(path.tangent((parameter - step).max(0.0)))
  let right = point2_normalized(path.tangent((parameter + step).min(1.0)))
  @math.hypot(right.x() - left.x(), right.y() - left.y()) / (2.0 * step)
}

///|
pub fn path2d_curvature_profile(path : Path2D, count : Int) -> Array[Double] {
  let result : Array[Double] = []
  let actual = count.max(1)
  for index in 0.. Bool {
  path.total_length() <= tolerance.abs()
}