///|
/// Structural validation for a complete force plate trial.
pub struct TrialShapeReport {
  sample_count : Int
  finite_force_samples : Int
  finite_cop_samples : Int
  monotonic_timestamps : Bool
  finite_timestamps : Int
  duration_s : Double
  sampling_rate_hz : Double
  first_bad_timestamp_index : Int
  is_usable : Bool
} derive(Debug, Eq)

///|
fn finite_vec(force : Vec3) -> Bool {
  !force.x.is_nan() &&
  !force.x.is_inf() &&
  !force.y.is_nan() &&
  !force.y.is_inf() &&
  !force.z.is_nan() &&
  !force.z.is_inf()
}

///|
fn finite_cop_value(cop : CoPPoint) -> Bool {
  !cop.x.is_nan() && !cop.x.is_inf() && !cop.y.is_nan() && !cop.y.is_inf()
}

///|
/// Validates timestamp order and finite coverage without changing the trial.
pub fn validate_trial_shape(trial : ForceTrial) -> TrialShapeReport {
  let mut finite_force = 0
  let mut finite_cop = 0
  let mut finite_times = 0
  let mut monotonic = true
  let mut first_bad = -1
  for i = 0; i < trial.samples.length(); i = i + 1 {
    let sample = trial.samples[i]
    if finite_vec(sample.force) {
      finite_force = finite_force + 1
    }
    if finite_cop_value(sample.cop) {
      finite_cop = finite_cop + 1
    }
    if !sample.timestamp.is_nan() && !sample.timestamp.is_inf() {
      finite_times = finite_times + 1
    } else if first_bad < 0 {
      first_bad = i
    }
    if i > 0 && sample.timestamp <= trial.samples[i - 1].timestamp {
      monotonic = false
      if first_bad < 0 {
        first_bad = i
      }
    }
  }
  let duration = if trial.samples.length() > 1 {
    trial.samples[trial.samples.length() - 1].timestamp -
    trial.samples[0].timestamp
  } else {
    0.0
  }
  let rate = estimate_sample_rate(trial.get_timestamps())
  {
    sample_count: trial.samples.length(),
    finite_force_samples: finite_force,
    finite_cop_samples: finite_cop,
    monotonic_timestamps: monotonic && finite_times == trial.samples.length(),
    finite_timestamps: finite_times,
    duration_s: if duration > 0.0 {
      duration
    } else {
      0.0
    },
    sampling_rate_hz: rate,
    first_bad_timestamp_index: first_bad,
    is_usable: trial.samples.length() >= 2 &&
    finite_force == trial.samples.length() &&
    finite_cop == trial.samples.length() &&
    monotonic &&
    finite_times == trial.samples.length() &&
    duration > 0.0,
  }
}