///|
pub struct JsonPointer {
  priv parts : Array[String]
} derive(Debug)

///|
pub extend JsonPointer with @debug.Debug::{to_repr}

///|
/// Parse the plain RFC 6901 representation, without URI percent decoding.
pub fn JsonPointer::parse(
  text : String,
  limits~ : @json.Limits = @json.Limits::new(),
) -> Result[JsonPointer, @json.Diagnostic] {
  if text.length() > limits.max_source() || limits.max_nodes() < 1 {
    return Err(
      @json.Diagnostic::new(
        @json.LimitExceeded,
        "JSON Pointer source/value limit exceeded",
      ),
    )
  }
  if text == "" {
    return Ok({ parts: [], })
  }
  if !text.has_prefix("/") {
    return Err(
      @json.Diagnostic::new(
        @json.InvalidReference,
        "JSON Pointer must start with a slash",
      ),
    )
  }
  let parts : Array[String] = []
  for raw in text[1:].split("/") {
    if parts.length() >= limits.max_depth() ||
      parts.length() >= limits.max_nodes() - 1 {
      return Err(
        @json.Diagnostic::new(
          @json.LimitExceeded,
          "JSON Pointer token limit exceeded",
        ),
      )
    }
    let token = raw.to_owned()
    let output = StringBuilder()
    let mut i = 0
    let mut start = 0
    while i < token.length() {
      let c = token.code_unit_at(i)
      if c == 126 {
        output.write_string(token[start:i].to_owned())
        if i + 1 >= token.length() {
          return Err(
            @json.Diagnostic::new(
              @json.InvalidReference,
              "Incomplete JSON Pointer escape",
            ),
          )
        }
        match token.code_unit_at(i + 1) {
          48 => output.write_string("~")
          49 => output.write_string("/")
          _ =>
            return Err(
              @json.Diagnostic::new(
                @json.InvalidReference,
                "Invalid JSON Pointer escape",
              ),
            )
        }
        i += 2
        start = i
      } else {
        i += 1
      }
    }
    output.write_string(token[start:].to_owned())
    parts.push(output.to_string())
  }
  Ok({ parts, })
}

///|
fn pointer_limits(
  parts : Array[String],
  limits : @json.Limits,
) -> Result[Unit, @json.Diagnostic] {
  if parts.length() > limits.max_depth() ||
    limits.max_nodes() < 1 ||
    parts.length() > limits.max_nodes() - 1 {
    return Err(
      @json.Diagnostic::new(
        @json.LimitExceeded,
        "JSON Pointer token limit exceeded",
      ),
    )
  }
  let mut remaining = limits.max_source()
  for token in parts {
    if remaining < 1 || token.length() > remaining - 1 {
      return Err(
        @json.Diagnostic::new(
          @json.LimitExceeded,
          "JSON Pointer text limit exceeded",
        ),
      )
    }
    remaining -= 1
    for c in token.code_units() {
      let count = if c == 126 || c == 47 { 2 } else { 1 }
      if count > remaining {
        return Err(
          @json.Diagnostic::new(
            @json.LimitExceeded,
            "JSON Pointer text limit exceeded",
          ),
        )
      }
      remaining -= count
    }
  }
  Ok(())
}

///|
pub fn JsonPointer::from_tokens(
  parts : Array[String],
  limits~ : @json.Limits = @json.Limits::new(),
) -> Result[JsonPointer, @json.Diagnostic] {
  match pointer_limits(parts, limits) {
    Ok(_) => Ok({ parts: parts.copy(), })
    Err(d) => Err(d)
  }
}

///|
pub fn JsonPointer::tokens(self : JsonPointer) -> Array[String] {
  self.parts.copy()
}

///|
pub fn JsonPointer::to_string(self : JsonPointer) -> String {
  let result = StringBuilder()
  for token in self.parts {
    result.write_string("/")
    result.write_string(
      token.replace(old="~", new="~0").replace(old="/", new="~1"),
    )
  }
  result.to_string()
}

///|
pub fn JsonPointer::resolve(
  self : JsonPointer,
  root : @json.Value,
  limits~ : @json.Limits = @json.Limits::new(),
) -> Result[@json.Value, @json.Diagnostic] {
  if pointer_limits(self.parts, limits) is Err(d) {
    return Err(d)
  }
  let mut current = root
  let traversed : Array[String] = []
  for token in self.parts {
    traversed.push(token)
    let failure = fn() {
      @json.Diagnostic::new(
        @json.UnresolvedReference,
        "JSON Pointer target does not exist",
        path=traversed,
      )
    }
    if current.array_length() is Some(size) {
      if token.is_empty() || (token.length() > 1 && token.has_prefix("0")) {
        return Err(failure())
      }
      let mut index = 0
      for digit in token.code_units() {
        if digit < 48 || digit > 57 {
          return Err(failure())
        }
        let n = digit.to_int() - 48
        if index > (size - n) / 10 {
          return Err(failure())
        }
        index = index * 10 + n
        if index >= size {
          return Err(failure())
        }
      }
      current = current.array_item(index).unwrap()
    } else {
      match current.get(token) {
        Some(value) => current = value
        None => return Err(failure())
      }
    }
  }
  Ok(current)
}

///|
pub struct Reference {
  uri : UriReference
  fragment : ReferenceFragment
} derive(Debug)

///|
pub extend Reference with @debug.Debug::{to_repr}

///|
pub enum ReferenceFragment {
  WholeResource
  PointerFragment(JsonPointer)
  IdentifierFragment(String)
} derive(Debug)

///|
pub extend ReferenceFragment with @debug.Debug::{to_repr}

///|
fn hex(c : UInt16) -> Int32? {
  if c >= 48 && c <= 57 {
    Some(c.to_int() - 48)
  } else if c >= 65 && c <= 70 {
    Some(c.to_int() - 55)
  } else if c >= 97 && c <= 102 {
    Some(c.to_int() - 87)
  } else {
    None
  }
}

///|
fn fragment_decode(text : String) -> Result[String, @json.Diagnostic] {
  let bytes : Array[Byte] = []
  let encoded = @utf8.encode(text[:])
  let mut i = 0
  while i < encoded.length() {
    if encoded[i] == 37 {
      if i + 2 >= encoded.length() {
        return Err(
          @json.Diagnostic::new(
            @json.InvalidReference,
            "Incomplete URI percent escape",
          ),
        )
      }
      match
        (
          hex(encoded[i + 1].to_int().to_uint16()),
          hex(encoded[i + 2].to_int().to_uint16()),
        ) {
        (Some(a), Some(b)) => bytes.push((a * 16 + b).to_byte())
        _ =>
          return Err(
            @json.Diagnostic::new(
              @json.InvalidReference,
              "Invalid URI percent escape",
            ),
          )
      }
      i += 3
    } else {
      bytes.push(encoded[i])
      i += 1
    }
  }
  Ok(@utf8.decode(Bytes::from_array(bytes)[:], ignore_bom=false)) catch {
    _ =>
      Err(
        @json.Diagnostic::new(
          @json.InvalidReference,
          "URI fragment is not UTF-8",
        ),
      )
  }
}

///|
/// Checked JSON-target reference; reading performs no resolution or fetching.
pub fn Reference::parse(
  text : String,
  limits~ : @json.Limits = @json.Limits::new(),
) -> Result[Reference, @json.Diagnostic] {
  match UriReference::parse(text, limits~) {
    Ok(uri) => Reference::from_uri(uri, limits~)
    Err(d) => Err(d)
  }
}

///|
pub fn Reference::from_uri(
  uri : UriReference,
  limits~ : @json.Limits = @json.Limits::new(),
) -> Result[Reference, @json.Diagnostic] {
  if uri.raw.length() > limits.max_source() || limits.max_nodes() < 1 {
    return Err(
      @json.Diagnostic::new(
        @json.LimitExceeded,
        "JSON reference source/value limit exceeded",
      ),
    )
  }
  let fragment = match uri.fragment {
    None => WholeResource
    Some(raw) => {
      let decoded = match fragment_decode(raw) {
        Ok(text) => text
        Err(d) => return Err(d)
      }
      if decoded.is_empty() {
        WholeResource
      } else if decoded.has_prefix("/") {
        match JsonPointer::parse(decoded, limits~) {
          Ok(pointer) => PointerFragment(pointer)
          Err(d) => return Err(d)
        }
      } else {
        IdentifierFragment(decoded)
      }
    }
  }
  Ok({ uri, fragment, })
}

///|
pub fn Reference::text(self : Reference) -> String {
  self.uri.raw
}

///|
pub fn Reference::uri(self : Reference) -> UriReference {
  self.uri
}

///|
pub fn Reference::fragment(self : Reference) -> ReferenceFragment {
  self.fragment
}

///|
pub fn Reference::local_pointer(self : Reference) -> JsonPointer? {
  if self.uri.scheme is Some(_) ||
    self.uri.authority is Some(_) ||
    !self.uri.path.is_empty() ||
    self.uri.query is Some(_) {
    return None
  }
  match self.fragment {
    WholeResource => Some({ parts: [], })
    PointerFragment(pointer) => Some(pointer)
    IdentifierFragment(_) => None
  }
}

///|
pub fn Reference::resolve(
  self : Reference,
  base : UriReference,
  limits~ : @json.Limits = @json.Limits::new(),
) -> Result[Reference, @json.Diagnostic] {
  match self.uri.resolve(base, limits~) {
    Ok(uri) => Reference::from_uri(uri, limits~)
    Err(d) => Err(d)
  }
}