///|
/// `cp` as at least 4 upper-case hex digits.
fn hex_code(cp : Int) -> String {
  let digits = cp.to_string(radix=16).to_upper()
  if digits.length() < 4 {
    "0".repeat(4 - digits.length()) + digits
  } else {
    digits
  }
}

///|
/// How a string or char literal is quoted (elm-format `StringStyle`).
priv enum Quote {
  CharQuote
  StringQuote
  TripleQuote
}

///|
/// Whether a literal writes the character with code point `cp` as it is
/// (see `literal_plain_ranges`).
fn is_plain(cp : Int) -> Bool {
  let ranges = literal_plain_ranges
  let mut lo = 0
  let mut hi = ranges.length() / 2 - 1
  while lo <= hi {
    let mid = (lo + hi) / 2
    if cp < ranges[mid * 2] {
      hi = mid - 1
    } else if cp > ranges[mid * 2 + 1] {
      lo = mid + 1
    } else {
      return true
    }
  }
  false
}

///|
/// `c` inside a literal quoted with `quote`, escaped as elm-format does
/// (Box.hs `formatString`, `fix`): a line feed stays in a triple-quoted
/// string; `\n`, `\t`, `\\` and the quote are escaped; a character that
/// is not printable or is a space other than U+0020 is `\u{XXXX}`.
fn escaped(c : Char, quote : Quote) -> String {
  match c {
    '\n' if quote is TripleQuote => "\n"
    '\n' => "\\n"
    '\t' => "\\t"
    '\\' => "\\\\"
    '"' if quote is StringQuote => "\\\""
    '\'' if quote is CharQuote => "\\'"
    _ if is_plain(c.to_int()) => c.to_string()
    _ => "\\u{" + hex_code(c.to_int()) + "}"
  }
}

///|
fn string_literal(s : String) -> String {
  let out = StringBuilder()
  out.write_char('"')
  for c in s {
    out.write_string(escaped(c, StringQuote))
  }
  out.write_char('"')
  out.to_string()
}

///|
/// `s` as a triple-quoted string. After the escapes, elm-format
/// (`escapeMultiQuote`) escapes each `"` of a run of three or more, and of
/// a run at the end: `"""a\""""`.
fn triple_literal(s : String) -> String {
  let body = StringBuilder()
  for c in s {
    body.write_string(escaped(c, TripleQuote))
  }
  let out = StringBuilder()
  out.write_string("\"\"\"")
  let mut quotes = 0
  let flush = (escape : Bool) => {
    out.write_string((if escape { "\\\"" } else { "\"" }).repeat(quotes))
    quotes = 0
  }
  for c in body.to_string() {
    if c == '"' {
      quotes += 1
    } else {
      flush(quotes >= 3)
      out.write_char(c)
    }
  }
  flush(true)
  out.write_string("\"\"\"")
  out.to_string()
}

///|
fn char_literal(c : Char) -> String {
  "'" + escaped(c, CharQuote) + "'"
}

///|
/// Whether `x` is below zero or is negative zero.
fn is_negative(x : Double) -> Bool {
  x < 0.0 || (x == 0.0 && 1.0 / x < 0.0)
}

///|
/// The shortest text that reads back as `x` (finite and not negative), with
/// a `.` in the mantissa: `1.0`, `0.1`, `1.0e21`, `5.0e-324`.
fn float_text(x : Double) -> String {
  let parts = x.to_string().split("e").map(p => p.to_owned()).collect()
  let mantissa = if parts[0].contains(".") { parts[0] } else { parts[0] + ".0" }
  if parts.length() == 1 {
    return mantissa
  }
  let exponent = StringBuilder()
  for c in parts[1] {
    if c != '+' {
      exponent.write_char(c)
    }
  }
  mantissa + "e" + exponent.to_string()
}

///|
/// `0x` and the upper-case hex `digits` (no leading zeros), padded with
/// zeros to 2, 4, 8 or 16 digits (elm-format `formatLiteral`).
fn hex_padded(digits : String) -> String {
  let len = digits.length()
  let width = if len <= 2 {
    2
  } else if len <= 4 {
    4
  } else if len <= 8 {
    8
  } else {
    16
  }
  "0x" + "0".repeat(if width > len { width - len } else { 0 }) + digits
}

///|
/// `0x` and the upper-case digits of `n` (not negative), padded with zeros to
/// 2, 4, 8 or 16 digits.
fn hex_text(n : Int64) -> String {
  hex_padded(n.to_string(radix=16).to_upper())
}

///|
/// The shortest decimal digits of `x` (finite, not negative) and their
/// exponent `e`: `x` is `0.digits` times `10^e` (Haskell `floatToDigits`).
/// Zero gives `("0", 0)`.
fn float_digits(x : Double) -> (String, Int) {
  let parts = x.to_string().split("e").map(p => p.to_owned()).collect()
  let mut exponent = 0
  if parts.length() > 1 {
    let mut sign = 1
    for c in parts[1] {
      match c {
        '-' => sign = -1
        '+' => ()
        _ => exponent = exponent * 10 + (c.to_int() - '0'.to_int())
      }
    }
    exponent = sign * exponent
  }
  let digits : Array[Char] = []
  let mut point : Int? = None
  for c in parts[0] {
    if c == '.' {
      point = Some(digits.length())
    } else {
      digits.push(c)
    }
  }
  exponent += point.unwrap_or(digits.length())
  let mut first = 0
  while first < digits.length() && digits[first] == '0' {
    first += 1
    exponent -= 1
  }
  let mut end = digits.length()
  while end > first && digits[end - 1] == '0' {
    end -= 1
  }
  guard first < end else { return ("0", 0) }
  (String::from_array(digits[first:end]), exponent)
}

///|
/// `x` (finite, not negative) with one digit before the point and an
/// exponent: Haskell `showEFloat Nothing` (`1.0e3`, `1.5e-7`, `0.0e0`), as
/// elm-format writes a float with an exponent.
fn exponent_float_text(x : Double) -> String {
  let (digits, e) = float_digits(x)
  if digits == "0" {
    return "0.0e0"
  }
  let rest = digits.view(start_offset=1).to_owned()
  digits.view(end_offset=1).to_owned() +
  "." +
  (if rest.is_empty() { "0" } else { rest }) +
  "e" +
  (e - 1).to_string()
}

///|
/// `x` (finite, not negative) with all its digits and no exponent: Haskell
/// `showFFloat Nothing` (`1000.0`, `0.000001`), as elm-format writes a
/// float without an exponent.
fn decimal_float_text(x : Double) -> String {
  let (digits, e) = float_digits(x)
  if e <= 0 {
    return "0." + "0".repeat(-e) + digits
  }
  let whole = if digits.length() >= e {
    digits.view(end_offset=e).to_owned()
  } else {
    digits + "0".repeat(e - digits.length())
  }
  let fraction = if digits.length() > e {
    digits.view(start_offset=e).to_owned()
  } else {
    "0"
  }
  whole + "." + fraction
}

///|
/// Whether `s` is one or more ASCII digits.
fn all_digits(s : StringView, hex? : Bool = false) -> Bool {
  !s.is_empty() &&
  s
  .iter()
  .all(c => {
    (c >= '0' && c <= '9') ||
    (hex && ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
  })
}

///|
/// `digits` without its leading zeros (`"0"` stays).
fn without_leading_zeros(digits : StringView) -> String {
  let mut first = 0
  while first + 1 < digits.length() && digits.get_char(first) == Some('0') {
    first += 1
  }
  digits.view(start_offset=first).to_owned()
}

///|
/// The lexeme of the literal token that starts at `r.start` in the
/// `ElmFormat` layout with source, else `None`. elm-format reads the form
/// of a literal from its source: a float with or without an exponent, a
/// triple-quoted string.
fn Ctx::literal_lexeme(self : Ctx, r : @ast.Range) -> String? {
  match (self.layout, self.source) {
    (ElmFormat, Some(facts)) => facts.lexeme_at(r.start)
    _ => None
  }
}

///|
/// A decimal integer (not negative). elm-format writes its value; from a
/// lexeme, its digits without leading zeros, so a value that the parser
/// clamps (above the largest `Int64`) keeps its digits.
fn Ctx::integer_text(self : Ctx, r : @ast.Range, n : Int64) -> String {
  match self.literal_lexeme(r) {
    Some(l) if all_digits(l) => without_leading_zeros(l)
    _ => n.to_string()
  }
}

///|
/// A hex integer (not negative): upper-case digits, padded (see
/// `hex_padded`). From a lexeme, its digits, as for `Ctx::integer_text`.
fn Ctx::hex_literal_text(self : Ctx, r : @ast.Range, n : Int64) -> String {
  match self.literal_lexeme(r) {
    Some(l) if (l.has_prefix("0x") || l.has_prefix("0X")) &&
      all_digits(l.view(start_offset=2), hex=true) =>
      hex_padded(without_leading_zeros(l.view(start_offset=2)).to_upper())
    _ => hex_text(n)
  }
}

///|
/// A float (not negative) from its lexeme, in elm-format's form for it
/// (`exponent_float_text` when the lexeme has an exponent, else
/// `decimal_float_text`); `None` without a lexeme. A value too big for a
/// `Double` keeps its lexeme.
fn Ctx::float_lexeme_text(self : Ctx, r : @ast.Range, x : Double) -> String? {
  guard self.literal_lexeme(r) is Some(l) &&
    l.iter().all(c => c is ('0'..='9' | '.' | 'e' | 'E' | '+' | '-')) else {
    return None
  }
  if x.is_nan() || x.is_inf() {
    Some(l)
  } else if l.contains_char('e') || l.contains_char('E') {
    Some(exponent_float_text(x))
  } else {
    Some(decimal_float_text(x))
  }
}

///|
/// A string literal: triple-quoted when its lexeme is (`ElmFormat` layout
/// with source), else quoted with `"`.
fn Ctx::string_doc(self : Ctx, r : @ast.Range, s : String) -> @pretty.Doc {
  match self.literal_lexeme(r) {
    Some(l) if l.has_prefix("\"\"\"") => {
      let text = triple_literal(s)
      // Its line breaks are part of the value: no indentation after them.
      if text.contains_char('\n') {
        @pretty.verbatim(text)
      } else {
        @pretty.text(text)
      }
    }
    _ => @pretty.text(string_literal(s))
  }
}

///|
/// Whether `n` is the smallest `Int64` (the only value whose negation is
/// itself, apart from zero).
fn is_min_int(n : Int64) -> Bool {
  n < 0L && -n < 0L
}