///|
/// A continuous sequence of force plate samples representing a single trial recording.
pub struct ForceTrial {
  metadata : TrialMetadata
  samples : Array[ForceSample]
} derive(Debug, Eq)

///|
pub fn ForceTrial::new(
  metadata : TrialMetadata,
  samples : Array[ForceSample],
) -> ForceTrial {
  { metadata, samples }
}

///|
pub fn ForceTrial::length(self : ForceTrial) -> Int {
  self.samples.length()
}

///|
pub fn ForceTrial::duration(self : ForceTrial) -> Double {
  if self.samples.length() < 2 {
    0.0
  } else {
    self.samples[self.samples.length() - 1].timestamp -
    self.samples[0].timestamp
  }
}

///|
pub fn ForceTrial::get_fz(self : ForceTrial) -> Array[Double] {
  let res : Array[Double] = Array::new(capacity=self.samples.length())
  for i = 0; i < self.samples.length(); i = i + 1 {
    res.push(self.samples[i].force.z)
  }
  res
}

///|
pub fn ForceTrial::get_fx(self : ForceTrial) -> Array[Double] {
  let res : Array[Double] = Array::new(capacity=self.samples.length())
  for i = 0; i < self.samples.length(); i = i + 1 {
    res.push(self.samples[i].force.x)
  }
  res
}

///|
pub fn ForceTrial::get_fy(self : ForceTrial) -> Array[Double] {
  let res : Array[Double] = Array::new(capacity=self.samples.length())
  for i = 0; i < self.samples.length(); i = i + 1 {
    res.push(self.samples[i].force.y)
  }
  res
}

///|
pub fn ForceTrial::get_timestamps(self : ForceTrial) -> Array[Double] {
  let res : Array[Double] = Array::new(capacity=self.samples.length())
  for i = 0; i < self.samples.length(); i = i + 1 {
    res.push(self.samples[i].timestamp)
  }
  res
}

///|
pub fn ForceTrial::get_cop_points(self : ForceTrial) -> Array[CoPPoint] {
  let res : Array[CoPPoint] = Array::new(capacity=self.samples.length())
  for i = 0; i < self.samples.length(); i = i + 1 {
    res.push(self.samples[i].cop)
  }
  res
}

///|
pub fn ForceTrial::slice(
  self : ForceTrial,
  start_idx : Int,
  end_idx : Int,
) -> ForceTrial {
  let start = if start_idx < 0 { 0 } else { start_idx }
  let end = if end_idx > self.samples.length() {
    self.samples.length()
  } else {
    end_idx
  }
  let sub : Array[ForceSample] = Array::new(capacity=end - start)
  for i = start; i < end; i = i + 1 {
    sub.push(self.samples[i])
  }
  { metadata: self.metadata, samples: sub }
}

///|
pub fn ForceTrial::apply_calibration(
  self : ForceTrial,
  cal : CalibrationParams,
) -> ForceTrial {
  let calibrated : Array[ForceSample] = Array::new(
    capacity=self.samples.length(),
  )
  for i = 0; i < self.samples.length(); i = i + 1 {
    let s = self.samples[i]
    let fx = (s.force.x - cal.offset_fx) * cal.gain_fx
    let fy = (s.force.y - cal.offset_fy) * cal.gain_fy
    let fz = (s.force.z - cal.offset_fz) * cal.gain_fz
    let cop_x = if fz.abs() > 0.0001 { -s.moment.y / fz } else { 0.0 }
    let cop_y = if fz.abs() > 0.0001 { s.moment.x / fz } else { 0.0 }
    calibrated.push({
      timestamp: s.timestamp,
      force: Vec3::new(fx, fy, fz),
      moment: s.moment,
      cop: CoPPoint::new(cop_x, cop_y),
    })
  }
  { metadata: self.metadata, samples: calibrated }
}

///|
pub fn ForceTrial::zero_tare(
  self : ForceTrial,
  baseline_samples_cnt : Int,
) -> ForceTrial {
  let cnt = if baseline_samples_cnt > self.samples.length() {
    self.samples.length()
  } else {
    baseline_samples_cnt
  }
  if cnt == 0 {
    return self
  }
  let mut sum_fx = 0.0
  let mut sum_fy = 0.0
  let mut sum_fz = 0.0
  for i = 0; i < cnt; i = i + 1 {
    sum_fx = sum_fx + self.samples[i].force.x
    sum_fy = sum_fy + self.samples[i].force.y
    sum_fz = sum_fz + self.samples[i].force.z
  }
  let off_x = sum_fx / cnt.to_double()
  let off_y = sum_fy / cnt.to_double()
  let off_z = sum_fz / cnt.to_double()

  let pared : Array[ForceSample] = Array::new(capacity=self.samples.length())
  for i = 0; i < self.samples.length(); i = i + 1 {
    let s = self.samples[i]
    pared.push({
      timestamp: s.timestamp,
      force: Vec3::new(s.force.x - off_x, s.force.y - off_y, s.force.z - off_z),
      moment: s.moment,
      cop: s.cop,
    })
  }
  { metadata: self.metadata, samples: pared }
}

///|
pub fn compute_mean(arr : Array[Double]) -> Double {
  if arr.length() == 0 {
    0.0
  } else {
    let mut sum = 0.0
    for i = 0; i < arr.length(); i = i + 1 {
      sum = sum + arr[i]
    }
    sum / arr.length().to_double()
  }
}

///|
pub fn compute_std_dev(arr : Array[Double]) -> Double {
  if arr.length() <= 1 {
    0.0
  } else {
    let mean = compute_mean(arr)
    let mut sq_sum = 0.0
    for i = 0; i < arr.length(); i = i + 1 {
      let diff = arr[i] - mean
      sq_sum = sq_sum + diff * diff
    }
    (sq_sum / (arr.length() - 1).to_double()).sqrt()
  }
}

///|
pub fn compute_max(arr : Array[Double]) -> Double {
  if arr.length() == 0 {
    0.0
  } else {
    let mut m = arr[0]
    for i = 1; i < arr.length(); i = i + 1 {
      if arr[i] > m {
        m = arr[i]
      }
    }
    m
  }
}

///|
pub fn compute_min(arr : Array[Double]) -> Double {
  if arr.length() == 0 {
    0.0
  } else {
    let mut m = arr[0]
    for i = 1; i < arr.length(); i = i + 1 {
      if arr[i] < m {
        m = arr[i]
      }
    }
    m
  }
}