// Ultimate Oscillator (Larry Williams): a weighted blend of buying-pressure

///|
fn fmin(a : Double, b : Double) -> Double {
  if a < b {
    a
  } else {
    b
  }
}

///|
fn fmax(a : Double, b : Double) -> Double {
  if a > b {
    a
  } else {
    b
  }
}
// ratios over three periods.

///|
/// Number of leading NaN values produced by `ultosc`: the longest period
/// `p3` (each output needs `p3` buying-pressure / true-range values).
pub fn ultosc_lookback(p3 : Int32) -> Int32 {
  p3
}

///|
/// Ultimate Oscillator.
///
/// Buying pressure is `close - min(low, prev_close)` and true range is
/// `max(high, prev_close) - min(low, prev_close)`. For each of the three
/// periods the ratio `sum(BP) / sum(TR)` is taken, then blended as
/// `100 * (4 * avg1 + 2 * avg2 + avg3) / 7` with `avg1 <= avg2 <= avg3` the
/// short / mid / long ratios. A window whose true-range sum is zero, or that
/// contains NaN, yields NaN. The periods must satisfy `p1 <= p2 <= p3`.
pub fn ultosc(
  candles : @base.Candles,
  p1~ : Int32,
  p2~ : Int32,
  p3~ : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_candles(candles)
  if p1 <= 0 || p2 <= 0 || p3 <= 0 {
    raise @base.InvalidPeriod("all periods must be positive")
  }
  if p1 > p2 || p2 > p3 {
    raise @base.InvalidPeriod("periods must satisfy p1 <= p2 <= p3")
  }
  let n = candles.close.length()
  // Buying pressure and true range per bar; index 0 has no previous close
  // and is never read (outputs start at `p3`).
  let bp : Array[Double] = []
  let tr : Array[Double] = []
  bp.push(@base.nan())
  tr.push(@base.nan())
  for i in 1.. (Double, Double, Bool) {
    let mut sum_bp = 0.0
    let mut sum_tr = 0.0
    for j in (i - p + 1)..<(i + 1) {
      let b = bp[j]
      let t = tr[j]
      if @base.is_nan(b) || @base.is_nan(t) {
        return (0.0, 0.0, false)
      }
      sum_bp += b
      sum_tr += t
    }
    (sum_bp, sum_tr, true)
  }
  let out : Array[Double] = []
  for i in 0..