///|
// Config's JSON tokenizer shares its Unicode whitespace rules with HOCON.
// Whitespace inside a quoted string remains part of the value.
fn json_source_space(text : String) -> String {
  if !text
    .iter()
    .any(c => whitespace(c) && c != ' ' && c != '\n' && c != '\r' && c != '\t') {
    return text
  }
  let out = StringBuilder()
  let mut quoted = false
  let mut escaped = false
  for c in text {
    if quoted {
      out.write_char(c)
      if escaped {
        escaped = false
      } else if c == '\\' {
        escaped = true
      } else if c == '"' {
        quoted = false
      }
    } else if c == '"' {
      quoted = true
      out.write_char(c)
    } else {
      out.write_char(if whitespace(c) { ' ' } else { c })
    }
  }
  out.to_string()
}

///|
priv struct JsonNumbers {
  values : Array[String]
  mut offset : Int
}

///|
fn configuration_json(text : String, name : String) -> Value raise ParseError {
  let text = json_source_space(text)
  let value = @json.parse(text) catch {
    _ => raise Invalid("invalid JSON source: " + name)
  }
  // Standard JSON parsing establishes syntax first. Scan only object member
  // spellings so a map conversion cannot silently erase duplicate keys.
  let objects : Array[Map[String, Bool]] = []
  let numbers = []
  let mut i = 0
  while i < text.length() {
    if text[i] == 123 {
      objects.push(Map([]))
    } else if text[i] == 125 {
      ignore(objects.pop())
    } else if text[i] == 45 || (text[i] >= 48 && text[i] <= 57) {
      let start = i
      while i + 1 < text.length() &&
            (
              [45, 43, 46, 101, 69].contains(text[i + 1].to_int()) ||
              (text[i + 1] >= 48 && text[i + 1] <= 57)
            ) {
        i += 1
      }
      let number = text[start:i + 1].to_owned()
      numbers.push(number)
      if number.length() > 18 &&
        integer_spelling(number) &&
        java_integer_value(number) == None {
        raise Invalid("integer token exceeds JSON source range: " + name)
      }
    } else if text[i] == 34 {
      let start = i
      let mut escaped = false
      i += 1
      while i < text.length() && text[i] != 34 {
        if text[i] == 92 {
          escaped = true
          i += 1
        }
        i += 1
      }
      let end = i + 1
      let mut next = end
      while next < text.length() &&
            [32, 9, 10, 13].contains(text[next].to_int()) {
        next += 1
      }
      if next < text.length() && text[next] == 58 && !objects.is_empty() {
        let key = if escaped {
          let decoded = @json.parse(text[start:end].to_owned()) catch {
            _ => raise Invalid("invalid JSON key: " + name)
          }
          match decoded {
            String(key) => key
            _ => raise Invalid("invalid JSON key: " + name)
          }
        } else {
          text[start + 1:i].to_owned()
        }
        let keys = objects[objects.length() - 1]
        if keys.contains(key) {
          raise Invalid("duplicate JSON object key: " + key)
        }
        keys[key] = true
      }
    }
    i += 1
  }
  // Json maps retain source insertion order. Replay numeric spellings through
  // the parsed structure so decimal exponents and exact integers survive.
  let spellings : JsonNumbers = { values: numbers, offset: 0, }
  let result = from_json(value, spellings)
  if spellings.offset != numbers.length() {
    raise Invalid("JSON number spelling mismatch")
  }
  result
}