///|
pub fn Pointer::to_uri_fragment(self : Pointer) -> String {
  let pointer_text = self.to_string()
  let out = StringBuilder::new()
  out.write_char('#')
  for ch in pointer_text {
    write_uri_char(out, ch)
  }
  out.to_string()
}

///|
pub fn Pointer::parse_uri_fragment(
  input : String,
) -> Result[Pointer, PointerError] {
  if !input.has_prefix("#") {
    return Err(
      pointer_error(InvalidSyntax, "", "URI fragment must start with '#'"),
    )
  }
  let fragment = input[1:].to_owned()
  match percent_decode_to_string(fragment) {
    Some(pointer_text) => Pointer::parse(pointer_text)
    None =>
      Err(
        pointer_error(
          InvalidSyntax,
          input,
          "URI fragment contains invalid percent encoding",
        ),
      )
  }
}

///|
fn write_uri_char(out : StringBuilder, ch : Char) -> Unit {
  if is_uri_unreserved(ch) || ch == '/' || ch == '~' {
    out.write_char(ch)
  } else if ch == ' ' {
    out.write_string("%20")
  } else if ch == '%' {
    out.write_string("%25")
  } else {
    out.write_char(ch)
  }
}

///|
fn is_uri_unreserved(ch : Char) -> Bool {
  (ch >= 'a' && ch <= 'z') ||
  (ch >= 'A' && ch <= 'Z') ||
  (ch >= '0' && ch <= '9') ||
  ch == '-' ||
  ch == '.' ||
  ch == '_' ||
  ch == '~'
}

///|
fn percent_decode_to_string(input : String) -> String? {
  let bytes : Array[Int] = []
  let chars = input.to_array()
  let mut i = 0
  while i < chars.length() {
    if chars[i] == '%' {
      if i + 2 >= chars.length() {
        return None
      }
      match (hex_value(chars[i + 1]), hex_value(chars[i + 2])) {
        (Some(high), Some(low)) => {
          bytes.push(high * 16 + low)
          i += 3
        }
        _ => return None
      }
    } else {
      bytes.push(chars[i].to_int())
      i += 1
    }
  }
  utf8_bytes_to_string(bytes)
}

///|
fn hex_value(ch : Char) -> Int? {
  if ch >= '0' && ch <= '9' {
    Some(ch.to_int() - '0'.to_int())
  } else if ch >= 'a' && ch <= 'f' {
    Some(10 + ch.to_int() - 'a'.to_int())
  } else if ch >= 'A' && ch <= 'F' {
    Some(10 + ch.to_int() - 'A'.to_int())
  } else {
    None
  }
}

///|
fn utf8_bytes_to_string(bytes : Array[Int]) -> String? {
  let out = StringBuilder::new()
  let mut i = 0
  while i < bytes.length() {
    let first = bytes[i]
    if first < 0x80 {
      out.write_char(Int::unsafe_to_char(first))
      i += 1
    } else if first >= 0xC0 && first < 0xE0 {
      if i + 1 >= bytes.length() || !is_continuation_byte(bytes[i + 1]) {
        return None
      }
      let code = ((first & 0x1F) << 6) | (bytes[i + 1] & 0x3F)
      out.write_char(Int::unsafe_to_char(code))
      i += 2
    } else if first >= 0xE0 && first < 0xF0 {
      if i + 2 >= bytes.length() ||
        !is_continuation_byte(bytes[i + 1]) ||
        !is_continuation_byte(bytes[i + 2]) {
        return None
      }
      let code = ((first & 0x0F) << 12) |
        ((bytes[i + 1] & 0x3F) << 6) |
        (bytes[i + 2] & 0x3F)
      out.write_char(Int::unsafe_to_char(code))
      i += 3
    } else if first >= 0xF0 && first < 0xF8 {
      if i + 3 >= bytes.length() ||
        !is_continuation_byte(bytes[i + 1]) ||
        !is_continuation_byte(bytes[i + 2]) ||
        !is_continuation_byte(bytes[i + 3]) {
        return None
      }
      let code = ((first & 0x07) << 18) |
        ((bytes[i + 1] & 0x3F) << 12) |
        ((bytes[i + 2] & 0x3F) << 6) |
        (bytes[i + 3] & 0x3F)
      out.write_char(Int::unsafe_to_char(code))
      i += 4
    } else {
      return None
    }
  }
  Some(out.to_string())
}

///|
fn is_continuation_byte(value : Int) -> Bool {
  value >= 0x80 && value < 0xC0
}