///|
fn decimal_power(n : Int) -> @bigint.BigInt {
  @bigint.BigInt::from_int(10).pow(@bigint.BigInt::from_int(n))
}

///|
fn ratio(n : Double) -> (@bigint.BigInt, @bigint.BigInt) {
  let bits = n.reinterpret_as_uint64()
  let power = ((bits >> 52) & 2047UL).to_int()
  let mantissa = (bits & 4503599627370495UL) +
    (if power == 0 { 0UL } else { 4503599627370496UL })
  let shift = if power == 0 { -1074 } else { power - 1075 }
  let numerator = @bigint.BigInt::from_uint64(mantissa)
  if shift >= 0 {
    (numerator << shift, @bigint.BigInt::from_int(1))
  } else {
    (numerator, @bigint.BigInt::from_int(1) << -shift)
  }
}

///|
fn decimal_round(
  numerator : @bigint.BigInt,
  denominator : @bigint.BigInt,
  places : Int,
) -> String {
  let n = if places >= 0 {
    numerator * decimal_power(places)
  } else {
    numerator
  }
  let d = if places < 0 {
    denominator * decimal_power(-places)
  } else {
    denominator
  }
  let whole = n / d
  let remainder = n % d * @bigint.BigInt::from_int(2)
  (if remainder > d ||
    (
      remainder == d &&
      (whole % @bigint.BigInt::from_int(2)).compare_int(1) == 0
    ) {
    whole + @bigint.BigInt::from_int(1)
  } else {
    whole
  }).to_string()
}

///|
fn decimal_exponent(n : @bigint.BigInt, d : @bigint.BigInt) -> Int {
  if n.is_zero() {
    return 0
  }
  let guess = n.to_string().length() - d.to_string().length()
  if (if guess >= 0 {
      n < d * decimal_power(guess)
    } else {
      n * decimal_power(-guess) < d
    }) {
    guess - 1
  } else {
    guess
  }
}

///|
fn zeros(n : Int) -> String {
  String::make(n.max(0), '0')
}

///|
fn fixed_digits(digits : String, precision : Int, point : Bool) -> String {
  let digits = if digits.length() <= precision {
    zeros(precision + 1 - digits.length()) + digits
  } else {
    digits
  }
  if precision == 0 {
    digits + (if point { "." } else { "" })
  } else {
    let at = digits.length() - precision
    digits[:at].to_owned() + "." + digits[at:].to_owned()
  }
}

///|
fn scientific_digits(
  digits : String,
  precision : Int,
  exponent : Int,
  point : Bool,
  upper : Bool,
) -> String {
  let exp = exponent.abs().to_string()
  digits[:1].to_owned() +
  (if precision > 0 {
    "." + digits[1:].to_owned()
  } else if point {
    "."
  } else {
    ""
  }) +
  (if upper { "E" } else { "e" }) +
  (if exponent < 0 { "-" } else { "+" }) +
  zeros(2 - exp.length()) +
  exp
}

///|
fn trim_fraction(text : String) -> String {
  if !text.contains(".") {
    return text
  }
  let cs = text.to_array()
  let mut end = cs.length()
  while end > 0 && cs[end - 1] == '0' {
    end -= 1
  }
  if end > 0 && cs[end - 1] == '.' {
    end -= 1
  }
  String::from_array(cs[:end])
}

///|
fn float_digits(
  n : Double,
  verb : Char,
  precision : Int,
  alternate : Bool,
) -> String {
  if n.is_nan() {
    return if verb == 'E' || verb == 'G' || verb == 'F' { "NAN" } else { "nan" }
  }
  if n.is_inf() {
    return if verb == 'E' || verb == 'G' || verb == 'F' { "INF" } else { "inf" }
  }
  let (numerator, denominator) = ratio(n)
  if verb == 'f' || verb == 'F' {
    return fixed_digits(
      decimal_round(numerator, denominator, precision),
      precision,
      alternate,
    )
  }
  let p = if verb == 'g' || verb == 'G' {
    precision.max(1)
  } else {
    precision + 1
  }
  let mut exponent = decimal_exponent(numerator, denominator)
  let mut digits = decimal_round(numerator, denominator, p - 1 - exponent)
  if digits.length() > p {
    digits = digits[:p].to_owned()
    exponent += 1
  }
  if digits.length() < p {
    digits = zeros(p - digits.length()) + digits
  }
  if verb == 'e' || verb == 'E' {
    return scientific_digits(digits, p - 1, exponent, alternate, verb == 'E')
  }
  if exponent < -4 || exponent >= p {
    let mantissa = if alternate {
      digits
    } else {
      let mut end = digits.length()
      while end > 1 && digits[end - 1:end] == "0" {
        end -= 1
      }
      digits[:end].to_owned()
    }
    scientific_digits(
      mantissa,
      mantissa.length() - 1,
      exponent,
      alternate,
      verb == 'G',
    )
  } else {
    let places = (p - 1 - exponent).max(0)
    let text = fixed_digits(digits + zeros(exponent + 1 - p), places, alternate)
    if alternate {
      text
    } else {
      trim_fraction(text)
    }
  }
}

///|
fn format_integer(n : Double) -> Int64 {
  if n.is_nan() || n >= 9223372036854775808.0 || n < -9223372036854775808.0 {
    -9223372036854775808L
  } else {
    n.to_int64()
  }
}

///|
fn State::text(
  self : State,
  value : Value,
  printing? : Bool = false,
) -> String raise ParseError {
  match value {
    Number(n) => {
      if !n.is_inf() &&
        !n.is_nan() &&
        n == n.trunc() &&
        n >= -9223372036854775808.0 &&
        n < 9223372036854775808.0 {
        return n.to_int64().to_string()
      }
      let key = if printing { "OFMT" } else { "CONVFMT" }
      let format = self.vars.get(key).unwrap_or(Text("%.6g")).text()
      format_values(format, [value], self, conversion=true)
    }
    _ => value.text()
  }
}

///|
fn format_values(
  format : String,
  values : Array[Value],
  st : State,
  conversion? : Bool = false,
) -> String raise ParseError {
  let cs = format.to_array()
  let out = StringBuilder()
  let mut i = 0
  let mut next = 0
  let mut size = 0
  let add = fn(s : String) -> Unit raise ParseError {
    size += s.length()
    if size > 1000000 {
      raise Invalid("formatted output limit")
    }
    out.write_string(s)
  }
  let argument = fn(index : Int) -> Value raise ParseError {
    if index < 0 || index >= values.length() {
      raise Invalid("not enough format arguments")
    }
    values[index]
  }
  while i < cs.length() {
    st.tick()
    if cs[i] != '%' {
      add(cs[i].to_string())
      i += 1
      continue
    }
    i += 1
    if i < cs.length() && cs[i] == '%' {
      add("%")
      i += 1
      continue
    }
    let mut left = false
    let mut plus = false
    let mut space = false
    let mut zero = false
    let mut alternate = false
    while i < cs.length() && ['-', '+', ' ', '0', '#'].contains(cs[i]) {
      match cs[i] {
        '-' => left = true
        '+' => plus = true
        ' ' => space = true
        '0' => zero = true
        _ => alternate = true
      }
      i += 1
    }
    let mut width = 0
    if i < cs.length() && cs[i] == '*' {
      i += 1
      let n = argument(next).number()
      next += 1
      if n.abs() > 1000000.0 {
        raise Invalid("format width limit")
      }
      width = n.to_int()
      if width < 0 {
        left = true
        width = -width
      }
    } else {
      while i < cs.length() && digit(cs[i]) {
        width = width * 10 + cs[i].to_int() - 48
        if width > 1000000 {
          raise Invalid("format width limit")
        }
        i += 1
      }
    }
    let mut precision = -1
    if i < cs.length() && cs[i] == '.' {
      i += 1
      precision = 0
      if i < cs.length() && cs[i] == '*' {
        i += 1
        let n = argument(next).number()
        next += 1
        if n > 1000.0 {
          raise Invalid("format precision limit")
        }
        precision = if n < 0.0 { -1 } else { n.to_int() }
      } else {
        while i < cs.length() && digit(cs[i]) {
          precision = precision * 10 + cs[i].to_int() - 48
          if precision > 1000 {
            raise Invalid("format precision limit")
          }
          i += 1
        }
      }
    }
    while i < cs.length() && ['h', 'l', 'L'].contains(cs[i]) {
      i += 1
    }
    if i >= cs.length() {
      raise Invalid("incomplete format")
    }
    let verb = cs[i]
    i += 1
    let v = argument(
      {
        let index = next
        next += 1
        index
      },
    )
    let mut prefix = ""
    let mut body = if verb == 's' {
      let text = if conversion && v is Number(_) {
        ""
      } else if conversion {
        v.text()
      } else {
        st.text(v)
      }
      let chars = text.to_array()
      if precision >= 0 {
        String::from_array(chars[:precision.min(chars.length())])
      } else {
        text
      }
    } else if verb == 'c' {
      match v {
        Text(s) => s.to_array().get(0).unwrap_or('\u0000').to_string()
        _ => {
          let n = v.number()
          let code = if n >= 0.0 && n <= 1114111.0 { n.to_int() } else { 65533 }
          (if code >= 55296 && code <= 57343 {
            (65533).unsafe_to_char()
          } else {
            code.unsafe_to_char()
          }).to_string()
        }
      }
    } else if ['d', 'i', 'u', 'o', 'x', 'X'].contains(verb) {
      let n = format_integer(v.number())
      let signed = verb == 'd' || verb == 'i'
      let magnitude = if signed && n < 0L {
        -@bigint.BigInt::from_int64(n)
      } else if signed {
        @bigint.BigInt::from_int64(n)
      } else {
        @bigint.BigInt::from_uint64(n.reinterpret_as_uint64())
      }
      if signed {
        prefix = if n < 0L {
          "-"
        } else if plus {
          "+"
        } else if space {
          " "
        } else {
          ""
        }
      }
      let radix = if verb == 'o' {
        8
      } else if verb == 'x' || verb == 'X' {
        16
      } else {
        10
      }
      let mut text = if precision == 0 && magnitude.is_zero() {
        ""
      } else {
        magnitude.to_string(radix~)
      }
      text = zeros(precision - text.length()) + text
      if alternate && verb == 'o' && !text.has_prefix("0") {
        text = "0" + text
      }
      if alternate && (verb == 'x' || verb == 'X') && !magnitude.is_zero() {
        prefix = if verb == 'x' { "0x" } else { "0X" }
      }
      if precision >= 0 {
        zero = false
      }
      if verb == 'X' {
        text.to_upper()
      } else {
        text
      }
    } else if ['e', 'E', 'f', 'F', 'g', 'G'].contains(verb) {
      let n = v.number()
      let negative = !n.is_nan() && n.reinterpret_as_uint64() >> 63 != 0UL
      prefix = if negative {
        "-"
      } else if plus {
        "+"
      } else if space {
        " "
      } else {
        ""
      }
      if n.is_nan() || n.is_inf() {
        zero = false
      }
      float_digits(
        n,
        verb,
        if precision < 0 {
          6
        } else {
          precision
        },
        alternate,
      )
    } else {
      raise Invalid("unsupported format conversion: " + verb.to_string())
    }
    if verb == 's' || verb == 'c' {
      zero = false
    }
    let padding = (width - prefix.to_array().length() - body.to_array().length()).max(
      0,
    )
    if left {
      body = prefix + body + String::make(padding, ' ')
    } else if zero {
      body = prefix + zeros(padding) + body
    } else {
      body = String::make(padding, ' ') + prefix + body
    }
    add(body)
  }
  out.to_string()
}