///|
/// Arrival picking methods exposed by the high-level API.
pub enum PickMethod {
  AmplitudeThreshold
  EnergyRatio
  AkaikeInformationCriterion
  KurtosisChange
}

///|
/// Return pick method constructors.
pub fn pick_by_amplitude() -> PickMethod {
  AmplitudeThreshold
}

///|
pub fn pick_by_energy_ratio() -> PickMethod {
  EnergyRatio
}

///|
pub fn pick_by_aic() -> PickMethod {
  AkaikeInformationCriterion
}

///|
pub fn pick_by_kurtosis() -> PickMethod {
  KurtosisChange
}

///|
/// A phase pick with method and confidence metadata.
pub struct ArrivalPick {
  index : Int
  time : Double
  strategy : PickMethod
  confidence : Double
  characteristic_value : Double
}

///|
/// Compute a running kurtosis characteristic function.
pub fn running_kurtosis(
  data : Array[Double],
  window_size : Int,
) -> Array[Double] {
  let result = Array::make(data.length(), 0.0)
  if data.is_empty() || window_size <= 1 {
    return result
  }
  for index = 0; index < data.length(); index = index + 1 {
    let start = if index + 1 < window_size {
      0
    } else {
      index + 1 - window_size
    }
    let values = slice_samples(data, start, index + 1)
    let mean = mean_samples(values)
    let deviation = standard_deviation(values)
    if deviation > 0.0 {
      let mut fourth = 0.0
      for value in values {
        let normalized = (value - mean) / deviation
        fourth += normalized * normalized * normalized * normalized
      }
      result[index] = fourth / values.length().to_double() - 3.0
    }
  }
  result
}

///|
/// Compute the AIC change-point characteristic.
pub fn aic_characteristic(
  data : Array[Double],
  minimum_window : Int,
) -> Array[Double] {
  let result = Array::make(data.length(), 0.0)
  let minimum = if minimum_window < 2 { 2 } else { minimum_window }
  if data.length() < 2 * minimum {
    return result
  }
  for split = minimum; split < data.length() - minimum; split = split + 1 {
    let left = slice_samples(data, 0, split)
    let right = slice_samples(data, split, data.length())
    let left_variance = variance_samples(left).max(1.0e-20)
    let right_variance = variance_samples(right).max(1.0e-20)
    result[split] = split.to_double() * @math.ln(left_variance) +
      (data.length() - split).to_double() * @math.ln(right_variance)
  }
  result
}

///|
/// Pick an arrival from a selected characteristic function.
pub fn pick_characteristic(
  characteristic : Array[Double],
  trace : Trace,
  minimum_value : Double,
  strategy : PickMethod,
) -> ArrivalPick? {
  if characteristic.is_empty() || trace.sampling_rate <= 0.0 {
    return None
  }
  let mut best = 0
  let mut value = characteristic[0]
  for index = 1; index < characteristic.length(); index = index + 1 {
    let candidate = if strategy is AkaikeInformationCriterion {
      -characteristic[index]
    } else {
      characteristic[index]
    }
    let current = if strategy is AkaikeInformationCriterion {
      -value
    } else {
      value
    }
    if candidate > current {
      best = index
      value = characteristic[index]
    }
  }
  let measured = if strategy is AkaikeInformationCriterion {
    -value
  } else {
    value
  }
  if measured < minimum_value {
    None
  } else {
    Some({
      index: best,
      time: sample_to_time(trace, best),
      strategy,
      confidence: if minimum_value == 0.0 {
        1.0
      } else {
        (measured / minimum_value).min(1.0)
      },
      characteristic_value: measured,
    })
  }
}

///|
/// Pick the first amplitude threshold crossing after a pre-event window.
pub fn pick_amplitude_arrival(
  trace : Trace,
  threshold : Double,
  quiet_samples : Int,
) -> ArrivalPick? {
  match first_arrival_index(trace.data, threshold, quiet_samples) {
    None => None
    Some(index) =>
      Some({
        index,
        time: sample_to_time(trace, index),
        strategy: AmplitudeThreshold,
        confidence: 1.0,
        characteristic_value: trace.data[index].abs(),
      })
  }
}

///|
/// Pick an arrival with the selected characteristic function.
pub fn pick_arrival(
  trace : Trace,
  strategy : PickMethod,
  threshold : Double,
  window_size : Int,
) -> ArrivalPick? {
  match strategy {
    AmplitudeThreshold => pick_amplitude_arrival(trace, threshold, window_size)
    EnergyRatio =>
      pick_characteristic(
        energy_ratio(trace.data, window_size, window_size * 4),
        trace,
        threshold,
        strategy,
      )
    AkaikeInformationCriterion =>
      pick_characteristic(
        aic_characteristic(trace.data, window_size),
        trace,
        threshold,
        strategy,
      )
    KurtosisChange =>
      pick_characteristic(
        running_kurtosis(trace.data, window_size),
        trace,
        threshold,
        strategy,
      )
  }
}

///|
/// Pick all methods and return the available consensus candidates.
pub fn consensus_picks(
  trace : Trace,
  threshold : Double,
  window_size : Int,
) -> Array[ArrivalPick] {
  let result = Array::new()
  for
    strategy in [
      AmplitudeThreshold,
      EnergyRatio,
      AkaikeInformationCriterion,
      KurtosisChange,
    ] {
    match pick_arrival(trace, strategy, threshold, window_size) {
      Some(pick) => result.push(pick)
      None => ()
    }
  }
  result
}