///|
/// An interval that does not fit the representation.
pub(all) suberror IntervalError {
  Overflow(String)
} derive(Debug, Eq)

///|
let int64_min_big : @bigint.BigInt = @bigint.BigInt::from_int64(
  -9223372036854775808L,
)

///|
let int64_max_big : @bigint.BigInt = @bigint.BigInt::from_int64(
  9223372036854775807L,
)

///|
/// Int64 min is excluded, so that every value has a magnitude.
fn fit_int64(n : @bigint.BigInt, what : String) -> Int64 raise IntervalError {
  if n.compare(int64_min_big) <= 0 || n.compare(int64_max_big) > 0 {
    raise Overflow("\{what} does not fit in Int64")
  }
  n.to_int64()
}

///|
fn big(n : Int64) -> @bigint.BigInt {
  @bigint.BigInt::from_int64(n)
}

///|
/// A year-month interval, as a signed number of months.
pub struct IntervalYM {
  months : Int64
} derive(Debug)

///|
pub fn IntervalYM::new(months : Int64) -> IntervalYM raise IntervalError {
  { months: fit_int64(big(months), "months"), }
}

///|
pub fn IntervalYM::from_parts(
  negative~ : Bool,
  years~ : Int64,
  months~ : Int64,
) -> IntervalYM raise IntervalError {
  let total = big(years) * 12N + big(months)
  { months: fit_int64(if negative { -total } else { total }, "months"), }
}

///|
pub fn IntervalYM::is_negative(self : IntervalYM) -> Bool {
  self.months < 0L
}

///|
fn IntervalYM::magnitude(self : IntervalYM) -> Int64 {
  if self.months < 0L {
    -self.months
  } else {
    self.months
  }
}

///|
pub fn IntervalYM::years(self : IntervalYM) -> Int64 {
  self.magnitude() / 12L
}

///|
pub fn IntervalYM::months_part(self : IntervalYM) -> Int64 {
  self.magnitude() % 12L
}

///|
/// A day-time interval: `seconds` + `nanos` / 10^9, with `nanos` in
/// 0..999,999,999 (floor form).
pub struct IntervalDT {
  seconds : Int64
  nanos : Int
} derive(Debug)

///|
/// Floor form of seconds + nanos / 10^9, computed in BigInt arithmetic.
fn normalize_dt(
  seconds : @bigint.BigInt,
  nanos : @bigint.BigInt,
) -> IntervalDT raise IntervalError {
  let billion = 1000000000N
  let total = seconds * billion + nanos
  let mut s = total / billion
  let mut n = total % billion
  if n.compare(0N) < 0 {
    s = s - 1N
    n = n + billion
  }
  { seconds: fit_int64(s, "seconds"), nanos: n.to_int(), }
}

///|
pub fn IntervalDT::new(
  seconds : Int64,
  nanos : Int64,
) -> IntervalDT raise IntervalError {
  normalize_dt(big(seconds), big(nanos))
}

///|
pub fn IntervalDT::from_parts(
  negative~ : Bool,
  days~ : Int64,
  hours~ : Int64,
  minutes~ : Int64,
  seconds~ : Int64,
  nanos~ : Int64,
) -> IntervalDT raise IntervalError {
  let total = big(days) * 86400N +
    big(hours) * 3600N +
    big(minutes) * 60N +
    big(seconds)
  if negative {
    normalize_dt(-total, -big(nanos))
  } else {
    normalize_dt(total, big(nanos))
  }
}

///|
pub fn IntervalDT::is_negative(self : IntervalDT) -> Bool {
  self.seconds < 0L
}

///|
/// The magnitude as (seconds, nanos) with nanos in 0..999,999,999.
fn IntervalDT::magnitude(self : IntervalDT) -> (Int64, Int64) {
  if self.seconds >= 0L {
    (self.seconds, self.nanos.to_int64())
  } else if self.nanos == 0 {
    (-self.seconds, 0L)
  } else {
    (-self.seconds - 1L, 1000000000L - self.nanos.to_int64())
  }
}

///|
pub fn IntervalDT::days(self : IntervalDT) -> Int64 {
  self.magnitude().0 / 86400L
}

///|
pub fn IntervalDT::hours(self : IntervalDT) -> Int64 {
  self.magnitude().0 % 86400L / 3600L
}

///|
pub fn IntervalDT::minutes(self : IntervalDT) -> Int64 {
  self.magnitude().0 % 3600L / 60L
}

///|
pub fn IntervalDT::seconds_part(self : IntervalDT) -> Int64 {
  self.magnitude().0 % 60L
}

///|
pub fn IntervalDT::nanos_part(self : IntervalDT) -> Int64 {
  self.magnitude().1
}