// Ranges over numbers and timestamps, as `valid_values` takes them.
//
// A range is `range::[lower, upper]`. Each bound is a value, a value annotated
// `exclusive::`, or `min` (for the lower bound) or `max` (for the upper) when
// there is no bound. The bounds are both numbers (`int`, `decimal`, or
// `float`) or both timestamps. Numbers compare by their exact value, whatever
// their Ion type, and timestamps by the instant they name.

///|
/// A bound of a range.
pub(all) enum Bound {
  /// `min` or `max`: there is no bound.
  Unbounded
  /// The bound value is in the range.
  Inclusive(@ion.IonValue)
  /// The bound value is not in the range: `exclusive::` in ISL.
  Exclusive(@ion.IonValue)
} derive(Debug)

///|
/// A range of numbers or of timestamps.
pub struct ValueRange {
  lower : Bound
  upper : Bound
} derive(Debug)

///|
/// A range from `lower` to `upper`.
pub fn ValueRange::new(lower : Bound, upper : Bound) -> ValueRange {
  ValueRange::{ lower, upper, }
}

///|
/// The lower bound.
pub fn ValueRange::lower(self : ValueRange) -> Bound {
  self.lower
}

///|
/// The upper bound.
pub fn ValueRange::upper(self : ValueRange) -> Bound {
  self.upper
}

///|
/// Whether `value` is in the range. A null, a `nan`, and a value that is not
/// the same kind as the bounds (a number or a timestamp) are not in any range.
/// Annotations do not count.
pub fn ValueRange::contains(self : ValueRange, value : @ion.IonValue) -> Bool {
  match kind_of(value) {
    Some(kind) if Some(kind) == range_kind(self) => ()
    _ => return false
  }
  // Values of the same kind always compare.
  let order = (bound : @ion.IonValue) => {
    compare_values(value, bound).unwrap_or(0)
  }
  let above = match self.lower {
    Unbounded => true
    Inclusive(bound) => order(bound) >= 0
    Exclusive(bound) => order(bound) > 0
  }
  let below = match self.upper {
    Unbounded => true
    Inclusive(bound) => order(bound) <= 0
    Exclusive(bound) => order(bound) < 0
  }
  above && below
}

///|
/// What a range compares: numbers or timestamps.
priv enum RangeKind {
  Number
  Instant
} derive(Eq)

///|
/// Whether a value can be compared in a range, and as what. A null or a `nan`
/// cannot.
fn kind_of(value : @ion.IonValue) -> RangeKind? {
  match value.kind() {
    Int(_) | Decimal(_) => Some(Number)
    Float(f) => if f.is_nan() { None } else { Some(Number) }
    Timestamp(_) => Some(Instant)
    _ => None
  }
}

///|
/// What the bounds of a range are. A range has at least one bound.
fn range_kind(range : ValueRange) -> RangeKind? {
  match (range.lower, range.upper) {
    (Inclusive(bound) | Exclusive(bound), _) => kind_of(bound)
    (_, Inclusive(bound) | Exclusive(bound)) => kind_of(bound)
    _ => None
  }
}

///|
/// An exact number: a float's infinities, or `coefficient * 10^exponent`.
priv enum Exact {
  NegativeInfinity
  Finite(BigInt, Int32)
  PositiveInfinity
}

///|
/// The exact value of an `int`, `decimal`, or `float`; `None` for anything
/// else, and for `nan`.
fn exact_number(value : @ion.IonValue) -> Exact? {
  match value.kind() {
    Int(n) => Some(Finite(n, 0))
    Decimal(d) => Some(Finite(d.coefficient(), d.exponent()))
    Float(f) =>
      if f.is_nan() {
        None
      } else if f.is_pos_inf() {
        Some(PositiveInfinity)
      } else if f.is_neg_inf() {
        Some(NegativeInfinity)
      } else {
        Some(exact_double(f))
      }
    _ => None
  }
}

///|
/// The exact value of a finite double, `mantissa * 2^exponent`, as a decimal.
/// `2^-k` is `5^k * 10^-k`, so a negative power of two stays exact.
fn exact_double(f : Double) -> Exact {
  let bits = f.reinterpret_as_uint64()
  let biased = ((bits >> 52) & 0x7FFUL).to_int()
  let fraction = bits & 0xF_FFFF_FFFF_FFFFUL
  let (mantissa, exponent) = if biased == 0 {
    (fraction, -1074)
  } else {
    (fraction | (1UL << 52), biased - 1075)
  }
  let mut coefficient = @bigint.BigInt::from_uint64(mantissa)
  if bits >> 63 == 1UL {
    coefficient = -coefficient
  }
  if exponent >= 0 {
    Finite(coefficient * power(2, exponent), 0)
  } else {
    Finite(coefficient * power(5, -exponent), exponent)
  }
}

///|
/// `base` to the power `exponent`, which is not negative.
fn power(base : Int32, exponent : Int32) -> BigInt {
  @bigint.BigInt::from_int(base).pow(@bigint.BigInt::from_int(exponent))
}

///|
/// The sign of a number: -1, 0, or 1. (`compare` gives only a sign, which is
/// not always -1 or 1: on wasm-gc it can be any number.)
fn sign(n : BigInt) -> Int32 {
  if n > 0N {
    1
  } else if n < 0N {
    -1
  } else {
    0
  }
}

///|
/// Compare two exact numbers: -1, 0, or 1.
fn compare_exact(a : Exact, b : Exact) -> Int32 {
  match (a, b) {
    (NegativeInfinity, NegativeInfinity)
    | (PositiveInfinity, PositiveInfinity) => 0
    (NegativeInfinity, _) | (_, PositiveInfinity) => -1
    (_, NegativeInfinity) | (PositiveInfinity, _) => 1
    (Finite(c1, e1), Finite(c2, e2)) => {
      let (s1, s2) = (sign(c1), sign(c2))
      if s1 != s2 || s1 == 0 {
        return if s1 < s2 { -1 } else if s1 > s2 { 1 } else { 0 }
      }
      // The same sign. Compare the magnitudes first, as the position of the
      // leading digit, so a large exponent never scales a coefficient.
      let m1 = digits(c1) + e1
      let m2 = digits(c2) + e2
      if m1 != m2 {
        return if m1 < m2 { -s1 } else { s1 }
      }
      let e = if e1 < e2 { e1 } else { e2 }
      sign(c1 * power(10, e1 - e) - c2 * power(10, e2 - e))
    }
  }
}

///|
/// The number of decimal digits in a coefficient's magnitude.
fn digits(n : BigInt) -> Int32 {
  let text = n.to_string()
  if text.has_prefix("-") {
    text.length() - 1
  } else {
    text.length()
  }
}

///|
/// Compare two values of the same range kind: numbers by value, timestamps by
/// instant. `None` when they cannot be compared.
fn compare_values(a : @ion.IonValue, b : @ion.IonValue) -> Int32? {
  match (a.kind(), b.kind()) {
    (Timestamp(x), Timestamp(y)) => Some(x.compare_instant(y))
    _ =>
      match (exact_number(a), exact_number(b)) {
        (Some(x), Some(y)) => Some(compare_exact(x, y))
        _ => None
      }
  }
}

///|
/// Load a `range::[lower, upper]` of numbers or timestamps.
fn load_value_range(value : @ion.IonValue) -> ValueRange raise @ion.IonError {
  let (lower_value, upper_value) = range_bounds(value)
  let lower = load_bound(lower_value, "min")
  let upper = load_bound(upper_value, "max")
  let range : ValueRange = { lower, upper, }
  match (lower, upper) {
    (Unbounded, Unbounded) =>
      raise @ion.IonError::DataModel("a range needs at least one bound")
    (Inclusive(a) | Exclusive(a), Inclusive(b) | Exclusive(b)) => {
      if kind_of(a) != kind_of(b) {
        raise @ion.IonError::DataModel(
          "the bounds of a range must both be numbers or both be timestamps",
        )
      }
      let order = compare_values(a, b).unwrap_or(0)
      let exclusive = lower is Exclusive(_) || upper is Exclusive(_)
      if order > 0 || (order == 0 && exclusive) {
        raise @ion.IonError::DataModel("the range contains no value")
      }
    }
    _ => ()
  }
  range
}

///|
/// Whether a bound of the range is a timestamp with the unknown offset.
fn ValueRange::has_unknown_offset(self : ValueRange) -> Bool {
  [self.lower, self.upper]
  .iter()
  .any(bound => {
    match bound {
      Inclusive(value) | Exclusive(value) =>
        value.kind() is @ion.IonValueKind::Timestamp(timestamp) &&
        timestamp.offset().unwrap_or(@ion.TimestampOffset::Unknown) is Unknown
      Unbounded => false
    }
  })
}

///|
/// The two bounds of a `range::[lower, upper]`.
fn range_bounds(
  value : @ion.IonValue,
) -> (@ion.IonValue, @ion.IonValue) raise @ion.IonError {
  let annotations = value.annotations().map(token => token.text())
  match value.kind() {
    List([lower, upper]) if annotations == [Some("range")] => (lower, upper)
    _ =>
      raise @ion.IonError::DataModel(
        "a range must be a list of two bounds annotated with 'range'",
      )
  }
}

///|
/// Load one bound of a range. `open` is the symbol for no bound: `min` for
/// the lower bound, `max` for the upper.
fn load_bound(
  value : @ion.IonValue,
  open : String,
) -> Bound raise @ion.IonError {
  match bound_value(value, open) {
    None => Unbounded
    Some((bare, exclusive)) if kind_of(bare) is Some(_) =>
      if exclusive {
        Exclusive(bare)
      } else {
        Inclusive(bare)
      }
    _ =>
      raise @ion.IonError::DataModel(
        "a range bound must be a number, a timestamp, or '\{open}'",
      )
  }
}

///|
/// One bound of a range, without its annotations, and whether it is
/// `exclusive::`; `None` for `open` (`min` or `max`), which cannot be
/// exclusive.
fn bound_value(
  value : @ion.IonValue,
  open : String,
) -> (@ion.IonValue, Bool)? raise @ion.IonError {
  let annotations = value.annotations().map(token => token.text())
  let exclusive = match annotations {
    [] => false
    [Some("exclusive")] => true
    _ =>
      raise @ion.IonError::DataModel(
        "a range bound may only be annotated with 'exclusive'",
      )
  }
  match value.kind() {
    Symbol(token) if token.text() == Some(open) =>
      if exclusive {
        raise @ion.IonError::DataModel("'\{open}' cannot be exclusive")
      } else {
        None
      }
    _ => Some((value.with_annotations([]), exclusive))
  }
}

///|
pub extend Bound with @debug.Debug::{to_repr}

///|
pub extend ValueRange with @debug.Debug::{to_repr}