///|
/// Summary of timestamp spacing in a force plate recording.
pub struct TimebaseSummary {
  sample_count : Int
  interval_count : Int
  positive_intervals : Int
  duration_s : Double
  mean_dt_s : Double
  median_dt_s : Double
  min_dt_s : Double
  max_dt_s : Double
  nominal_rate_hz : Double
  monotonic : Bool
} derive(Debug, Eq)

///|
/// Summarizes timestamp ordering and sampling interval statistics.
pub fn summarize_timebase(timestamps : Array[Double]) -> TimebaseSummary {
  let n = timestamps.length()
  if n == 0 {
    return {
      sample_count: 0,
      interval_count: 0,
      positive_intervals: 0,
      duration_s: 0.0,
      mean_dt_s: 0.0,
      median_dt_s: 0.0,
      min_dt_s: 0.0,
      max_dt_s: 0.0,
      nominal_rate_hz: 0.0,
      monotonic: false,
    }
  }
  let intervals : Array[Double] = Array::new(
    capacity=if n > 1 { n - 1 } else { 0 },
  )
  let mut monotonic = true
  let mut positive = 0
  for i = 1; i < n; i = i + 1 {
    let delta = timestamps[i] - timestamps[i - 1]
    intervals.push(delta)
    if delta <= 0.0 || delta.is_nan() || delta.is_inf() {
      monotonic = false
    } else {
      positive = positive + 1
    }
  }
  let duration = if n > 1 { timestamps[n - 1] - timestamps[0] } else { 0.0 }
  let mean_dt = if positive == 0 {
    0.0
  } else {
    let mut sum = 0.0
    for interval in intervals {
      if interval > 0.0 && !interval.is_nan() && !interval.is_inf() {
        sum = sum + interval
      }
    }
    sum / positive.to_double()
  }
  let median_dt = if positive == intervals.length() {
    median(intervals)
  } else {
    let positive_intervals : Array[Double] = Array::new(capacity=positive)
    for interval in intervals {
      if interval > 0.0 && !interval.is_nan() && !interval.is_inf() {
        positive_intervals.push(interval)
      }
    }
    median(positive_intervals)
  }
  let min_dt = if positive == 0 {
    0.0
  } else {
    let mut result = 1.0 / 0.0
    for interval in intervals {
      if interval > 0.0 && interval < result {
        result = interval
      }
    }
    result
  }
  let max_dt = if positive == 0 {
    0.0
  } else {
    let mut result = 0.0
    for interval in intervals {
      if interval > result && !interval.is_nan() && !interval.is_inf() {
        result = interval
      }
    }
    result
  }
  {
    sample_count: n,
    interval_count: intervals.length(),
    positive_intervals: positive,
    duration_s: duration,
    mean_dt_s: mean_dt,
    median_dt_s: median_dt,
    min_dt_s: min_dt,
    max_dt_s: max_dt,
    nominal_rate_hz: if monotonic && mean_dt > 0.0 {
      1.0 / mean_dt
    } else {
      0.0
    },
    monotonic,
  }
}

///|
/// Returns true when all positive intervals lie within an absolute tolerance.
pub fn timebase_is_regular(
  summary : TimebaseSummary,
  tolerance_s : Double,
) -> Bool {
  let tolerance = if tolerance_s < 0.0 { 0.0 } else { tolerance_s }
  summary.monotonic &&
  summary.interval_count > 0 &&
  summary.max_dt_s - summary.min_dt_s <= tolerance
}

///|
/// Estimates the sampling rate from a monotonic timestamp sequence.
pub fn estimate_sample_rate(timestamps : Array[Double]) -> Double {
  summarize_timebase(timestamps).nominal_rate_hz
}