///|
let standard_json_source =
  #|class Json {
  #|  foreign static parse(text)
  #|  static stringify(value) { return encode_(value, []) }
  #|  static encode_(value, seen) {
  #|    if (value == null) return "null"
  #|    if (value is Bool || value is Num) {
  #|      if (value is Num && (value.isNan || value.isInfinity)) Fiber.abort("JSON numbers must be finite.")
  #|      return value.toString
  #|    }
  #|    if (value is String) return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\b", "\\b").replace("\f", "\\f").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t") + "\""
  #|    if (!(value is List) && !(value is Map)) Fiber.abort("Value is not JSON serializable.")
  #|    for (parent in seen) if (Object.same(parent, value)) Fiber.abort("JSON value contains a cycle.")
  #|    seen.add(value)
  #|    var parts = []
  #|    if (value is List) {
  #|      for (item in value) parts.add(encode_(item, seen))
  #|      seen.removeAt(seen.count - 1)
  #|      return "[" + parts.join(",") + "]"
  #|    }
  #|    for (entry in value) {
  #|      if (!(entry.key is String)) Fiber.abort("JSON object keys must be Strings.")
  #|      parts.add(encode_(entry.key, seen) + ":" + encode_(entry.value, seen))
  #|    }
  #|    seen.removeAt(seen.count - 1)
  #|    return "{" + parts.join(",") + "}"
  #|  }
  #|}

///|
let standard_base64_source =
  #|class Base64 {
  #|  foreign static encode(text)
  #|  foreign static decode(text)
  #|}

///|
let standard_url_source =
  #|class Url {
  #|  foreign static encodeComponent(text)
  #|  foreign static decodeComponent(text)
  #|}

///|
let standard_uuid_source =
  #|class Uuid {
  #|  foreign static fromName(namespace, name)
  #|}

///|
let standard_http_source =
  #|class Http {
  #|  static request(method, url) { return request(method, url, {}, null) }
  #|  static request(method, url, headers) { return request(method, url, headers, null) }
  #|  static request(method, url, headers, body) {
  #|    if (!(method is String) || !(url is String) || !(headers is Map)) Fiber.abort("Http.request expects method, URL, and a header Map.")
  #|    return Fiber.yield({"$moonwren": "http", "method": method, "url": url, "headers": headers, "body": body})
  #|  }
  #|  static get(url) { return request("GET", url) }
  #|}

///|
fn standard_module_source(name : String) -> String? {
  match name {
    "moon/json" => Some(standard_json_source)
    "moon/base64" => Some(standard_base64_source)
    "moon/url" => Some(standard_url_source)
    "moon/uuid" => Some(standard_uuid_source)
    "moon/http" => Some(standard_http_source)
    _ => None
  }
}

///|
priv struct StandardJsonParser {
  characters : Array[Char]
  mut cursor : Int
  mut failure : String?
}

///|
fn StandardJsonParser::new(text : String) -> StandardJsonParser {
  { characters: text.to_array(), cursor: 0, failure: None, }
}

///|
fn StandardJsonParser::skip_space(self : StandardJsonParser) -> Unit {
  while self.cursor < self.characters.length() {
    let code = self.characters[self.cursor].to_int()
    if code == 32 || code == 9 || code == 10 || code == 13 {
      self.cursor = self.cursor + 1
    } else {
      break
    }
  }
}

///|
fn StandardJsonParser::fail(
  self : StandardJsonParser,
  message : String,
) -> Unit {
  if self.failure is None {
    self.failure = Some(message + " at character " + self.cursor.to_string())
  }
}

///|
fn StandardJsonParser::take(self : StandardJsonParser, expected : Int) -> Bool {
  if self.cursor < self.characters.length() &&
    self.characters[self.cursor].to_int() == expected {
    self.cursor = self.cursor + 1
    true
  } else {
    false
  }
}

///|
fn StandardJsonParser::literal(
  self : StandardJsonParser,
  text : String,
) -> Bool {
  let expected = text.to_array()
  if self.cursor + expected.length() > self.characters.length() {
    return false
  }
  for index = 0; index < expected.length(); index = index + 1 {
    if self.characters[self.cursor + index] != expected[index] {
      return false
    }
  }
  self.cursor = self.cursor + expected.length()
  true
}

///|
fn standard_hex_digit(code : Int) -> Int? {
  if code >= 48 && code <= 57 {
    Some(code - 48)
  } else if code >= 65 && code <= 70 {
    Some(code - 55)
  } else if code >= 97 && code <= 102 {
    Some(code - 87)
  } else {
    None
  }
}

///|
fn StandardJsonParser::unicode_escape(self : StandardJsonParser) -> Int? {
  if self.cursor + 4 > self.characters.length() {
    return None
  }
  let mut value = 0
  for _index = 0; _index < 4; _index = _index + 1 {
    let digit = match
      standard_hex_digit(self.characters[self.cursor].to_int()) {
      Some(value) => value
      None => return None
    }
    value = value * 16 + digit
    self.cursor = self.cursor + 1
  }
  Some(value)
}

///|
fn StandardJsonParser::string(self : StandardJsonParser) -> String? {
  if !self.take(34) {
    return None
  }
  let bytes : Array[Int] = []
  while self.cursor < self.characters.length() {
    let code = self.characters[self.cursor].to_int()
    self.cursor = self.cursor + 1
    if code == 34 {
      return match string_value_from_bytes(bytes) {
        StringValue(text) => Some(text)
        _ => None
      }
    }
    if code < 32 {
      self.fail("unescaped control character in JSON string")
      return None
    }
    if code != 92 {
      append_utf8_bytes(bytes, code)
      continue
    }
    if self.cursor >= self.characters.length() {
      return None
    }
    let escaped = self.characters[self.cursor].to_int()
    self.cursor = self.cursor + 1
    match escaped {
      34 | 47 | 92 => bytes.push(escaped)
      98 => bytes.push(8)
      102 => bytes.push(12)
      110 => bytes.push(10)
      114 => bytes.push(13)
      116 => bytes.push(9)
      117 => {
        let mut point = match self.unicode_escape() {
          Some(value) => value
          None => return None
        }
        if point >= 0xd800 && point <= 0xdbff {
          if !self.take(92) || !self.take(117) {
            return None
          }
          let low = match self.unicode_escape() {
            Some(value) => value
            None => return None
          }
          if low < 0xdc00 || low > 0xdfff {
            return None
          }
          point = 0x10000 + ((point - 0xd800) << 10) + low - 0xdc00
        } else if point >= 0xdc00 && point <= 0xdfff {
          return None
        }
        append_utf8_bytes(bytes, point)
      }
      _ => return None
    }
  }
  None
}

///|
fn StandardJsonParser::number(self : StandardJsonParser) -> ForeignOwnedValue? {
  let start = self.cursor
  ignore(self.take(45))
  if self.take(48) {
    if self.cursor < self.characters.length() {
      let next = self.characters[self.cursor].to_int()
      if next >= 48 && next <= 57 {
        return None
      }
    }
  } else {
    if self.cursor >= self.characters.length() {
      return None
    }
    let first = self.characters[self.cursor].to_int()
    if first < 49 || first > 57 {
      return None
    }
    self.cursor = self.cursor + 1
    while self.cursor < self.characters.length() {
      let code = self.characters[self.cursor].to_int()
      if code < 48 || code > 57 {
        break
      }
      self.cursor = self.cursor + 1
    }
  }
  if self.take(46) {
    let fraction_start = self.cursor
    while self.cursor < self.characters.length() {
      let code = self.characters[self.cursor].to_int()
      if code < 48 || code > 57 {
        break
      }
      self.cursor = self.cursor + 1
    }
    if self.cursor == fraction_start {
      return None
    }
  }
  if self.cursor < self.characters.length() &&
    (
      self.characters[self.cursor].to_int() == 69 ||
      self.characters[self.cursor].to_int() == 101
    ) {
    self.cursor = self.cursor + 1
    if self.cursor < self.characters.length() &&
      (
        self.characters[self.cursor].to_int() == 43 ||
        self.characters[self.cursor].to_int() == 45
      ) {
      self.cursor = self.cursor + 1
    }
    let exponent_start = self.cursor
    while self.cursor < self.characters.length() {
      let code = self.characters[self.cursor].to_int()
      if code < 48 || code > 57 {
        break
      }
      self.cursor = self.cursor + 1
    }
    if self.cursor == exponent_start {
      return None
    }
  }
  let text = String::from_array(self.characters[start:self.cursor].to_owned())
  let number = parse_number_value(text)
  if number.is_inf() || number.is_nan() {
    None
  } else {
    Some(ForeignNumber(number))
  }
}

///|
fn StandardJsonParser::array(self : StandardJsonParser) -> ForeignOwnedValue? {
  if !self.take(91) {
    return None
  }
  self.skip_space()
  let values : Array[ForeignOwnedValue] = []
  if self.take(93) {
    return Some(ForeignList(values))
  }
  while true {
    let value = match self.value() {
      Some(value) => value
      None => return None
    }
    values.push(value)
    self.skip_space()
    if self.take(93) {
      return Some(ForeignList(values))
    }
    if !self.take(44) {
      return None
    }
    self.skip_space()
  } nobreak {
    None
  }
}

///|
fn StandardJsonParser::object(self : StandardJsonParser) -> ForeignOwnedValue? {
  if !self.take(123) {
    return None
  }
  self.skip_space()
  let entries : Array[ForeignOwnedMapEntry] = []
  if self.take(125) {
    return Some(ForeignMap(entries))
  }
  while true {
    let key = match self.string() {
      Some(value) => value
      None => return None
    }
    self.skip_space()
    if !self.take(58) {
      return None
    }
    self.skip_space()
    let value = match self.value() {
      Some(value) => value
      None => return None
    }
    entries.push(ForeignOwnedMapEntry::new(ForeignString(key), value))
    self.skip_space()
    if self.take(125) {
      return Some(ForeignMap(entries))
    }
    if !self.take(44) {
      return None
    }
    self.skip_space()
  } nobreak {
    None
  }
}

///|
fn StandardJsonParser::value(self : StandardJsonParser) -> ForeignOwnedValue? {
  self.skip_space()
  if self.cursor >= self.characters.length() {
    return None
  }
  match self.characters[self.cursor].to_int() {
    34 =>
      match self.string() {
        Some(text) => Some(ForeignString(text))
        None => None
      }
    91 => self.array()
    123 => self.object()
    116 => if self.literal("true") { Some(ForeignBool(true)) } else { None }
    102 => if self.literal("false") { Some(ForeignBool(false)) } else { None }
    110 => if self.literal("null") { Some(ForeignNull) } else { None }
    _ => self.number()
  }
}

///|
fn parse_standard_json(text : String) -> Result[ForeignOwnedValue, String] {
  let parser = StandardJsonParser::new(text)
  let value = match parser.value() {
    Some(value) => value
    None =>
      return Err(
        match parser.failure {
          Some(message) => message
          None => "invalid JSON at character " + parser.cursor.to_string()
        },
      )
  }
  parser.skip_space()
  if parser.cursor != parser.characters.length() {
    Err("unexpected JSON data at character " + parser.cursor.to_string())
  } else {
    Ok(value)
  }
}

///|
fn standard_string_argument(
  call : ForeignCallContext,
  index : Int,
  label : String,
) -> Result[String, String] {
  match call.argument(index) {
    Some(value) =>
      match value.as_string() {
        ValueConverted(text) => Ok(text)
        ValueConversionFailed(_, received) =>
          Err("\{label} must be a String, received \{received}")
      }
    None => Err("\{label} is missing")
  }
}

///|
fn url_unreserved(byte : Int) -> Bool {
  (byte >= 65 && byte <= 90) ||
  (byte >= 97 && byte <= 122) ||
  (byte >= 48 && byte <= 57) ||
  byte == 45 ||
  byte == 46 ||
  byte == 95 ||
  byte == 126
}

///|
fn encode_url_component(text : String) -> String {
  let hex = "0123456789ABCDEF"
  let result : Array[Char] = []
  for byte in @utf8.encode(text) {
    let value = byte.to_int()
    if url_unreserved(value) {
      result.push(byte.to_char())
    } else {
      result.push('%')
      result.push(hex.unsafe_get(value >> 4).to_byte().to_char())
      result.push(hex.unsafe_get(value & 15).to_byte().to_char())
    }
  }
  String::from_array(result)
}

///|
fn decode_url_component(text : String) -> String? {
  let characters = text.to_array()
  let bytes : Array[Byte] = []
  let mut index = 0
  while index < characters.length() {
    if characters[index] == '%' {
      if index + 2 >= characters.length() {
        return None
      }
      let high = match standard_hex_digit(characters[index + 1].to_int()) {
        Some(value) => value
        None => return None
      }
      let low = match standard_hex_digit(characters[index + 2].to_int()) {
        Some(value) => value
        None => return None
      }
      bytes.push((high * 16 + low).to_byte())
      index = index + 3
    } else {
      let encoded = @utf8.encode(String::from_array([characters[index]]))
      for byte in encoded {
        bytes.push(byte)
      }
      index = index + 1
    }
  }
  Some(@utf8.decode(Bytes::from_array(bytes))) catch {
    _ => None
  }
}

///|
fn name_uuid(scope_name : String, name : String) -> String {
  let digest = sha256_hex(scope_name + "\u0000" + name).to_array()
  digest[12] = '8'
  digest[16] = '8'
  String::from_array(digest[0:8].to_owned()) +
  "-" +
  String::from_array(digest[8:12].to_owned()) +
  "-" +
  String::from_array(digest[12:16].to_owned()) +
  "-" +
  String::from_array(digest[16:20].to_owned()) +
  "-" +
  String::from_array(digest[20:32].to_owned())
}

///|
fn append_standard_module_bindings(
  methods : Array[ForeignMethodBinding],
) -> Unit {
  methods.push(
    ForeignMethodBinding::new_context("moon/json", "Json", true, "parse(_)", fn(
      call,
    ) {
      let text = match standard_string_argument(call, 0, "JSON text") {
        Ok(value) => value
        Err(message) => return ForeignContextAborted(message)
      }
      match parse_standard_json(text) {
        Ok(value) => ForeignContextReturned(value)
        Err(message) => ForeignContextAborted(message)
      }
    }),
  )
  methods.push(
    ForeignMethodBinding::new_context(
      "moon/base64",
      "Base64",
      true,
      "encode(_)",
      fn(call) {
        let text = match standard_string_argument(call, 0, "text") {
          Ok(value) => value
          Err(message) => return ForeignContextAborted(message)
        }
        ForeignContextReturned(
          ForeignString(@base64.encode(@utf8.encode(text), padding=true)),
        )
      },
    ),
  )
  methods.push(
    ForeignMethodBinding::new_context(
      "moon/base64",
      "Base64",
      true,
      "decode(_)",
      fn(call) {
        let text = match standard_string_argument(call, 0, "Base64 text") {
          Ok(value) => value
          Err(message) => return ForeignContextAborted(message)
        }
        let bytes = @base64.decode(text) catch {
          _ => return ForeignContextAborted("invalid Base64 text")
        }
        let raw : Array[Int] = []
        for byte in bytes {
          raw.push(byte.to_int())
        }
        ForeignContextReturned(ForeignBytes(raw))
      },
    ),
  )
  for signature in ["encodeComponent(_)", "decodeComponent(_)"] {
    methods.push(
      ForeignMethodBinding::new_context("moon/url", "Url", true, signature, fn(
        call,
      ) {
        let text = match standard_string_argument(call, 0, "URL text") {
          Ok(value) => value
          Err(message) => return ForeignContextAborted(message)
        }
        if signature == "encodeComponent(_)" {
          ForeignContextReturned(ForeignString(encode_url_component(text)))
        } else {
          match decode_url_component(text) {
            Some(value) => ForeignContextReturned(ForeignString(value))
            None => ForeignContextAborted("invalid URL component encoding")
          }
        }
      }),
    )
  }
  methods.push(
    ForeignMethodBinding::new_context(
      "moon/uuid",
      "Uuid",
      true,
      "fromName(_,_)",
      fn(call) {
        let scope_name = match standard_string_argument(call, 0, "namespace") {
          Ok(value) => value
          Err(message) => return ForeignContextAborted(message)
        }
        let name = match standard_string_argument(call, 1, "name") {
          Ok(value) => value
          Err(message) => return ForeignContextAborted(message)
        }
        ForeignContextReturned(ForeignString(name_uuid(scope_name, name)))
      },
    ),
  )
}