///|
/// Rounds a value to a multiple of an increment.
///
/// The value is carried as an exact `dividend / divisor` pair so that every
/// comparison against the midpoint stays in integer space; Temporal's
/// nanosecond quantities are far too large to round through a `Double` without
/// losing the tie cases.
///
/// Spec: 
priv struct IncrementRounder {
  /// Whether the value being rounded is non-negative.
  is_positive : Bool
  dividend : @int128.Int128
  divisor : @int128.Int128
}

///|
/// Prepares `number` to be rounded to a multiple of `increment`.
fn IncrementRounder::from_signed_num(
  number : @int128.Int128,
  increment : @int128.Int128,
) -> IncrementRounder raise TemporalError {
  if increment.signum() <= 0 {
    raise AssertError("rounding increment must be positive")
  }
  { is_positive: !number.is_negative(), dividend: number, divisor: increment }
}

///|
/// `RoundNumberToIncrement`: rounds the value, honouring its sign.
fn IncrementRounder::round(
  self : IncrementRounder,
  mode : RoundingMode,
) -> @int128.Int128 {
  let unsigned_mode = mode.to_unsigned(self.is_positive)
  // Work on the magnitude, then restore the sign, so that the "half away from
  // zero" family behaves symmetrically about zero.
  let dividend = if self.is_positive {
    self.dividend
  } else {
    self.dividend.neg()
  }
  let rounded = apply_unsigned_rounding_mode(
    dividend,
    self.divisor,
    unsigned_mode,
  )
  let rounded = if self.is_positive { rounded } else { rounded.neg() }
  rounded.mul(self.divisor)
}

///|
/// `RoundNumberToIncrementAsIfPositive`: rounds as though the value were
/// positive, which is what epoch-nanosecond rounding needs so that instants
/// before the epoch round the same direction as those after it.
fn IncrementRounder::round_as_if_positive(
  self : IncrementRounder,
  mode : RoundingMode,
) -> @int128.Int128 {
  let unsigned_mode = mode.to_unsigned(true)
  let rounded = apply_unsigned_rounding_mode(
    self.dividend,
    self.divisor,
    unsigned_mode,
  )
  rounded.mul(self.divisor)
}

///|
/// `ApplyUnsignedRoundingMode`, returning the rounded quotient.
///
/// Spec: 
fn apply_unsigned_rounding_mode(
  dividend : @int128.Int128,
  divisor : @int128.Int128,
  mode : UnsignedRoundingMode,
) -> @int128.Int128 {
  let (floor, remainder) = dividend.div_rem_euclid(divisor)
  // The quotient is exact, so every mode agrees on it.
  if remainder.is_zero() {
    return floor
  }
  let ceil = floor.add(@int128.one)
  match mode {
    Zero => floor
    Infinity => ceil
    _ => {
      let two = @int128.of_int(2)
      let midway = divisor.div_euclid(two)
      // With an odd divisor the true midpoint falls between two integers, so
      // the remainder can never actually tie.
      let cmp = if remainder == midway && !divisor.rem_euclid(two).is_zero() {
        -1
      } else {
        remainder.compare(midway)
      }
      if cmp < 0 {
        floor
      } else if cmp > 0 {
        ceil
      } else {
        match mode {
          HalfZero => floor
          HalfInfinity => ceil
          // half-even: keep whichever of the two neighbours is even.
          _ => if floor.rem_euclid(two).is_zero() { floor } else { ceil }
        }
      }
    }
  }
}

///|
/// `ApplyUnsignedRoundingMode` for a value expressed as `dividend / divisor`
/// between two already-known neighbours `r1` and `r2`.
///
/// The nudge-rounding code knows the surrounding calendar-unit values but not
/// the quotient itself, so it needs this variant rather than the one above.
fn UnsignedRoundingMode::apply(
  self : UnsignedRoundingMode,
  dividend : @int128.Int128,
  divisor : @int128.Int128,
  r1 : @int128.Int128,
  r2 : @int128.Int128,
) -> @int128.Int128 {
  // Multiply through by `divisor` to keep every comparison in integer space.
  if dividend == r1.mul(divisor) {
    return r1
  }
  if self is Zero {
    return r1
  }
  if self is Infinity {
    return r2
  }
  let d1 = dividend.sub(r1.mul(divisor))
  let d2 = r2.mul(divisor).sub(dividend)
  let cmp = d1.compare(d2)
  if cmp < 0 {
    r1
  } else if cmp > 0 {
    r2
  } else {
    match self {
      HalfZero => r1
      HalfInfinity => r2
      _ => {
        // half-even, over neighbours that are `r2 - r1` apart.
        let two = @int128.of_int(2)
        let diff = r2.sub(r1)
        if r1.div_euclid(diff).rem_euclid(two).is_zero() {
          r1
        } else {
          r2
        }
      }
    }
  }
}