// Data model, error type and shared options for moontalib.

///|
/// A single OHLCV bar (open, high, low, close, volume). Constructible from
/// any package (`pub(all)`); struct fields are public by default.
pub(all) struct Candle {
  open : Double
  high : Double
  low : Double
  close : Double
  volume : Double
}

///|
/// Column-oriented OHLCV series. All five arrays must have equal length;
/// indicator functions validate this and return `Err(MismatchedLength)`
/// otherwise. Constructible from any package (`pub(all)`).
pub(all) struct Candles {
  open : Array[Double]
  high : Array[Double]
  low : Array[Double]
  close : Array[Double]
  volume : Array[Double]
}

///|
/// Builds a column-oriented series from a row-oriented bar list.
pub fn Candles::from_bars(bars : Array[Candle]) -> Candles {
  let open : Array[Double] = []
  let high : Array[Double] = []
  let low : Array[Double] = []
  let close : Array[Double] = []
  let volume : Array[Double] = []
  for bar in bars {
    open.push(bar.open)
    high.push(bar.high)
    low.push(bar.low)
    close.push(bar.close)
    volume.push(bar.volume)
  }
  { open, high, low, close, volume, }
}

///|
/// Number of bars in the series (taken from the `close` column).
pub fn Candles::length(self : Candles) -> Int32 {
  self.close.length()
}

///|
/// True range of bar `i`: `high - low` for the first bar, otherwise the
/// greatest of `high - low`, `|high - prev_close|` and `|low - prev_close|`.
/// Any NaN component yields NaN.
pub fn true_range(candles : Candles, i : Int32) -> Double {
  let high = candles.high[i]
  let low = candles.low[i]
  if is_nan(high) || is_nan(low) {
    return nan()
  }
  if i == 0 {
    return high - low
  }
  let prev_close = candles.close[i - 1]
  if is_nan(prev_close) {
    return nan()
  }
  let hl = high - low
  let a = (high - prev_close).abs()
  let b = (low - prev_close).abs()
  let mut m = if a > b { a } else { b }
  if hl > m {
    m = hl
  }
  m
}

///|
/// Errors raised by moontalib functions. `TaError::to_string` gives a
/// human-readable message.
pub(all) suberror TaError {
  EmptyInput
  MismatchedLength(String)
  InvalidPeriod(String)
  NotEnoughData(String)
}

///|
/// Human-readable description of the error.
pub fn TaError::to_string(self : TaError) -> String {
  match self {
    EmptyInput => "input series is empty"
    MismatchedLength(msg) => "mismatched input lengths: \{msg}"
    InvalidPeriod(msg) => "invalid period: \{msg}"
    NotEnoughData(msg) => "not enough data: \{msg}"
  }
}

///|
/// Moving-average flavor used by APO/PPO and the `moving_average` dispatcher.
/// Constructible from any package (`pub(all)`).
pub(all) enum MAType {
  Sma
  Ema
}