///|
/// Settle an exact total with an explicit decimal scale and rounding rule.
/// Currency is a label, not an ISO currency database; callers choose the scale.
pub fn settle(
  calculation : Calculation,
  scale~ : Int32,
  rounding~ : Rounding,
) -> Settlement raise PricingError {
  settle_total(calculation.currency, calculation.total, scale~, rounding~)
}

///|
fn settle_total(
  currency : String,
  exact : String,
  scale~ : Int32,
  rounding~ : Rounding,
) -> Settlement raise PricingError {
  if scale < 0 || scale > 18 {
    invalid(
      "scale", "settlement.scale", "choose an explicit scale between 0 and 18",
    )
  }
  // Totals can have up to 60 integer digits, unlike individual unit prices.
  let total = amount_atoms(exact, "calculation.total")
  let divisor = BigInt::from_string("1" + "0".repeat(18 - scale))
  let quotient = total / divisor
  let remainder = total % divisor
  let increase = match rounding {
    Down => false
    Up => remainder != 0N
    HalfUp => remainder * 2N >= divisor
    HalfEven =>
      remainder * 2N > divisor ||
      (remainder * 2N == divisor && quotient % 2N != 0N)
  }
  let units = if increase { quotient + 1N } else { quotient }
  let digits = units.to_string()
  let amount = if scale == 0 {
    digits
  } else {
    let padded = "0".repeat(
        if digits.length() <= scale {
          scale + 1 - digits.length()
        } else {
          0
        },
      ) +
      digits
    let split = padded.length() - scale
    padded[:split].to_owned() + "." + padded[split:].to_owned()
  }
  {
    currency,
    scale,
    rounding,
    minor_units: digits,
    amount,
    delta: format_atoms(units * divisor - total),
  }
}

///|
fn amount_atoms(text : String, path : String) -> BigInt raise PricingError {
  // Keep the normal decimal parser's syntax, but allow larger accumulated totals.
  if text.length() > 80 {
    invalid(
      "amount", path, "amount exceeds 60 integer and 18 fractional digits",
    )
  }
  let parts = text.split(".").collect()
  if parts.length() > 2 || parts[0].length() == 0 || parts[0].length() > 60 {
    invalid("amount", path, "expected a nonnegative exact decimal amount")
  }
  let whole = parts[0].to_owned()
  if whole.length() > 1 && whole[0] == '0' {
    invalid("amount", path, "remove leading zeroes")
  }
  for c in whole {
    if c < '0' || c > '9' {
      invalid("amount", path, "expected ASCII decimal digits")
    }
  }
  let fraction = if parts.length() == 2 {
    decimal("0." + parts[1].to_owned(), path)
  } else {
    0N
  }
  BigInt::from_string(whole) * factor() + fraction
}