///|
struct JsonDocumentParser {
  bytes : Bytes
  depth : Int
  mut pos : Int
  mut nodes : Int
}

///|
fn JsonDocumentParser::space(self : JsonDocumentParser) -> Unit {
  while self.pos < self.bytes.length() &&
        [b' ', b'\n', b'\r', b'\t'].contains(self.bytes[self.pos]) {
    self.pos += 1
  }
}

///|
fn JsonDocumentParser::consume(self : JsonDocumentParser, byte : Byte) -> Bool {
  self.space()
  if self.pos < self.bytes.length() && self.bytes[self.pos] == byte {
    self.pos += 1
    true
  } else {
    false
  }
}

///|
fn JsonDocumentParser::string(
  self : JsonDocumentParser,
) -> String raise McapError {
  self.space()
  let start = self.pos
  if !self.consume(b'"') {
    raise Invalid("JSON string expected")
  }
  while self.pos < self.bytes.length() {
    let byte = self.bytes[self.pos]
    self.pos += 1
    if byte == b'\\' {
      self.pos += 1
      continue
    }
    if byte == b'"' {
      let text = @utf8.decode(self.bytes[start:self.pos]) catch {
        _ => raise Invalid("invalid JSON UTF-8")
      }
      let value = @json.parse(text[:]) catch {
        _ => raise Invalid("invalid JSON string")
      }
      return match value {
        String(value) => value
        _ => raise Invalid("JSON string expected")
      }
    }
  }
  raise Invalid("unterminated JSON string")
}

///|
struct ExactDecimal {
  negative : Bool
  digits : String
  exponent : Int
}

///|
fn decimal_parts(text : String) -> ExactDecimal raise McapError {
  if text.length() == 0 || text.length() > 256 {
    raise Invalid("JSON number digit budget exceeded")
  }
  let bytes = @utf8.encode(text[:])
  let mut i = 0
  let negative = bytes[0] == b'-'
  if negative {
    i += 1
  }
  let mantissa = i
  if i >= bytes.length() {
    raise Invalid("invalid JSON number")
  }
  if bytes[i] == b'0' {
    i += 1
  } else {
    if bytes[i] < b'1' || bytes[i] > b'9' {
      raise Invalid("invalid JSON number")
    }
    while i < bytes.length() && bytes[i] >= b'0' && bytes[i] <= b'9' {
      i += 1
    }
  }
  let digits = StringBuilder::StringBuilder()
  digits.write_string(text[mantissa:i].to_string())
  let mut fraction = 0
  if i < bytes.length() && bytes[i] == b'.' {
    i += 1
    let start = i
    while i < bytes.length() && bytes[i] >= b'0' && bytes[i] <= b'9' {
      i += 1
    }
    fraction = i - start
    if fraction == 0 {
      raise Invalid("invalid JSON fraction")
    }
    digits.write_string(text[start:i].to_string())
  }
  let mut exponent = 0
  if i < bytes.length() && [b'e', b'E'].contains(bytes[i]) {
    i += 1
    let backwards = i < bytes.length() && bytes[i] == b'-'
    if i < bytes.length() && [b'-', b'+'].contains(bytes[i]) {
      i += 1
    }
    let start = i
    while i < bytes.length() && bytes[i] >= b'0' && bytes[i] <= b'9' {
      if exponent > 100000 {
        raise Invalid("JSON exponent budget exceeded")
      }
      exponent = exponent * 10 + bytes[i].to_int() - 48
      i += 1
    }
    if i == start || exponent > 1000000 {
      raise Invalid("invalid JSON exponent")
    }
    if backwards {
      exponent = -exponent
    }
  }
  if i != bytes.length() {
    raise Invalid("invalid JSON number tail")
  }
  let raw = digits.to_string()
  let mut start = 0
  let mut end = raw.length()
  while start < end && raw[start].to_int() == 48 {
    start += 1
  }
  if start == end {
    return { negative: false, digits: "0", exponent: 0, }
  }
  while end > start && raw[end - 1].to_int() == 48 {
    end -= 1
  }
  {
    negative,
    digits: raw[start:end].to_string(),
    exponent: exponent - fraction + raw.length() - end,
  }
}

///|
fn compare_decimal(a : String, b : String) -> Int raise McapError {
  let left = decimal_parts(a)
  let right = decimal_parts(b)
  if left.negative != right.negative {
    return if left.negative { -1 } else { 1 }
  }
  let sign = if left.negative { -1 } else { 1 }
  if left.digits == "0" && right.digits == "0" {
    return 0
  }
  if left.digits == "0" {
    return -sign
  }
  if right.digits == "0" {
    return sign
  }
  let order = (left.digits.length() + left.exponent).compare(
    right.digits.length() + right.exponent,
  )
  if order != 0 {
    return order * sign
  }
  for i = 0; i < left.digits.length().max(right.digits.length()); i = i + 1 {
    let a = if i < left.digits.length() { left.digits[i].to_int() } else { 48 }
    let b = if i < right.digits.length() {
      right.digits[i].to_int()
    } else {
      48
    }
    if a != b {
      return a.compare(b) * sign
    }
  }
  0
}

///|
fn JsonDocumentParser::value(
  self : JsonDocumentParser,
  depth : Int,
) -> Json raise McapError {
  self.space()
  self.nodes += 1
  if depth > self.depth ||
    self.nodes > 100000 ||
    self.pos >= self.bytes.length() {
    raise Invalid("JSON depth/node limit or truncated value")
  }
  if self.bytes[self.pos] == b'"' {
    return Json::string(self.string())
  }
  if self.consume(b'{') {
    let fields : Map[String, Json] = Map([])
    if self.consume(b'}') {
      return Json::object(fields)
    }
    while true {
      let key = self.string()
      if fields.contains(key) || !self.consume(b':') {
        raise Invalid("duplicate JSON key or missing colon")
      }
      fields[key] = self.value(depth + 1)
      if self.consume(b'}') {
        break
      }
      if !self.consume(b',') {
        raise Invalid("missing JSON object separator")
      }
    }
    return Json::object(fields)
  }
  if self.consume(b'[') {
    let values = []
    if self.consume(b']') {
      return Json::array(values)
    }
    while true {
      values.push(self.value(depth + 1))
      if self.consume(b']') {
        break
      }
      if !self.consume(b',') {
        raise Invalid("missing JSON array separator")
      }
    }
    return Json::array(values)
  }
  let start = self.pos
  while self.pos < self.bytes.length() &&
        ![b' ', b'\n', b'\r', b'\t', b',', b']', b'}'].contains(
          self.bytes[self.pos],
        ) {
    self.pos += 1
  }
  let token = @utf8.decode(self.bytes[start:self.pos]) catch {
    _ => raise Invalid("invalid JSON UTF-8")
  }
  match token {
    "null" => Json::null()
    "true" => Json::boolean(true)
    "false" => Json::boolean(false)
    _ => {
      ignore(decimal_parts(token))
      let value = @json.parse(token[:]) catch {
        _ => raise Invalid("invalid JSON number")
      }
      match value {
        Number(number, ..) => Json::number(number, repr=token)
        _ => raise Invalid("invalid JSON literal")
      }
    }
  }
}

///|
fn parse_json_document(
  bytes : Bytes,
  maximum : Int,
  depth : Int,
) -> Json raise McapError {
  if bytes.length() > maximum {
    raise Invalid("JSON payload byte budget exceeded")
  }
  let parser : JsonDocumentParser = { bytes, depth, pos: 0, nodes: 0, }
  let value = parser.value(0)
  parser.space()
  if parser.pos != bytes.length() {
    raise Invalid("JSON trailing data")
  }
  value
}