///|
/// A datum label and its precedence in a datum reference frame.
pub struct DatumReference {
  label : String
  order : Int
} derive(Debug, Eq)

///|
/// Create a datum reference. Orders are normally one, two, and three.
pub fn DatumReference::new(label : String, order : Int) -> DatumReference {
  if order < 0 {
    abort("datum order must not be negative")
  }
  { label, order }
}

///|
/// A primary/secondary/tertiary datum reference frame.
pub struct DatumFrame {
  primary : DatumReference
  secondary : DatumReference
  tertiary : DatumReference
} derive(Debug, Eq)

///|
/// Construct a complete datum frame.
pub fn DatumFrame::new(
  primary : DatumReference,
  secondary : DatumReference,
  tertiary : DatumReference,
) -> DatumFrame {
  { primary, secondary, tertiary }
}

///|
/// Construct a frame containing only a primary datum.
pub fn DatumFrame::single(primary : DatumReference) -> DatumFrame {
  {
    primary,
    secondary: DatumReference::new("", 0),
    tertiary: DatumReference::new("", 0),
  }
}

///|
/// Return whether all three datum slots contain labels.
pub fn DatumFrame::is_complete(self : DatumFrame) -> Bool {
  self.primary.label != "" &&
  self.secondary.label != "" &&
  self.tertiary.label != ""
}

///|
/// Return a compact datum-frame label for reports.
pub fn DatumFrame::label(self : DatumFrame) -> String {
  if self.secondary.label == "" {
    self.primary.label
  } else if self.tertiary.label == "" {
    self.primary.label + "|" + self.secondary.label
  } else {
    self.primary.label + "|" + self.secondary.label + "|" + self.tertiary.label
  }
}

///|
/// A geometric characteristic attached to a datum frame.
pub struct FeatureControlFrame {
  mode : GeometricMode
  tolerance : Double
  datum_frame : DatumFrame
} derive(Debug, Eq)

///|
/// Create a position control frame.
pub fn FeatureControlFrame::position(
  tolerance : Double,
  datum_frame : DatumFrame,
) -> FeatureControlFrame {
  { mode: Position, tolerance, datum_frame }
}

///|
/// Create a concentricity control frame.
pub fn FeatureControlFrame::concentricity(
  tolerance : Double,
  datum_frame : DatumFrame,
) -> FeatureControlFrame {
  { mode: Concentricity, tolerance, datum_frame }
}

///|
/// Create a circular runout control frame.
pub fn FeatureControlFrame::runout(
  tolerance : Double,
  datum_frame : DatumFrame,
) -> FeatureControlFrame {
  { mode: Runout, tolerance, datum_frame }
}

///|
/// A measured feature center or radial reference.
pub struct FeatureMeasurement {
  name : String
  nominal : Vector2
  measured : Vector2
} derive(Debug, Eq)

///|
/// Create a measured feature record.
pub fn FeatureMeasurement::new(
  name : String,
  nominal : Vector2,
  measured : Vector2,
) -> FeatureMeasurement {
  { name, nominal, measured }
}

///|
/// Evaluation of one feature-control frame.
pub struct FeatureEvaluation {
  name : String
  status : ConstraintStatus
  deviation : Double
  margin : Double
  datum_label : String
} derive(Debug, Eq)

///|
/// Evaluate a measured feature against its control frame.
pub fn evaluate_feature(
  control : FeatureControlFrame,
  measurement : FeatureMeasurement,
) -> FeatureEvaluation {
  let tolerance = GeometricTolerance::new(control.mode, control.tolerance)
  let difference = measurement.measured.sub(measurement.nominal)
  let deviation = match control.mode {
    Position | Concentricity => difference.length()
    Runout =>
      (measurement.measured.length() - measurement.nominal.length()).abs()
  }
  let status = tolerance.evaluate(measurement.measured, measurement.nominal)
  {
    name: measurement.name,
    status,
    deviation,
    margin: control.tolerance - deviation,
    datum_label: control.datum_frame.label(),
  }
}

///|
/// Aggregate feature-control evaluations for a measurement batch.
pub struct FeatureEvaluationReport {
  total : Int
  passed : Int
  failed : Int
  evaluations : Array[FeatureEvaluation]
} derive(Debug, Eq)

///|
/// Evaluate corresponding controls and measurements in order.
pub fn evaluate_features(
  controls : Array[FeatureControlFrame],
  measurements : Array[FeatureMeasurement],
) -> FeatureEvaluationReport {
  if controls.length() != measurements.length() {
    abort("feature controls and measurements must have equal lengths")
  }
  let evaluations = []
  let mut passed = 0
  let mut failed = 0
  for index in 0.. passed += 1
      _ => failed += 1
    }
    evaluations.push(evaluation)
  }
  { total: evaluations.length(), passed, failed, evaluations }
}