///|
/// URI parsing errors (RFC 3986)
pub(all) enum UriError {
  Empty
  InvalidPercentEncoding
  InvalidPort
  InvalidCharacter
  InvalidScheme
  InvalidHost
  InvalidUtf8
} derive(Show, Eq)

///|
/// Parsed URI (RFC 3986 Section 3)
///
/// URI structure:
///   foo://example.com:8042/over/there?name=ferret#nose
///   \_/   \______________/\_________/ \_________/ \__/
///    |           |            |            |        |
/// scheme     authority       path        query   fragment
pub(all) struct Uri {
  source : String
  scheme_end : Int?
  authority_start : Int?
  authority_end : Int?
  host_end : Int?
  port : Int?
  path_start : Int
  path_end : Int
  query_start : Int?
  query_end : Int?
  fragment_start : Int?
} derive(Show, Eq)

///|
pub fn Uri::parse(input : String) -> Result[Uri, UriError] {
  let s = uri_trim_string(input)
  if s.length() == 0 {
    return Err(Empty)
  }
  let source = s
  let len = source.length()
  let mut pos = 0

  // Parse scheme (RFC 3986 Section 3.1)
  // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
  let (scheme_end, pos_after_scheme) = uri_find_scheme_end(source)
  match scheme_end {
    Some(end) => {
      // Validate scheme
      if !uri_is_alpha(uri_string_char_at(source, 0)) {
        return Err(InvalidScheme)
      }
      let mut idx = 1
      while idx < end {
        let ch = uri_string_char_at(source, idx)
        if !uri_is_alnum(ch) && ch != 43 && ch != 45 && ch != 46 { // +, -, .
          return Err(InvalidScheme)
        }
        idx = idx + 1
      }
      pos = pos_after_scheme
    }
    None => ()
  }

  // Parse authority (RFC 3986 Section 3.2)
  let (authority_start, authority_end, host_end, port) = if pos + 1 < len &&
    uri_string_char_at(source, pos) == 47 &&
    uri_string_char_at(source, pos + 1) == 47 { // "//"
    pos = pos + 2
    let auth_start = pos

    // Find authority end
    let auth_end = uri_find_char_from_set(source, pos, 47, 63, 35) // /, ?, #
    let authority = uri_string_slice(source, auth_start, auth_end)
    let auth_result = uri_parse_authority(authority)
    match auth_result {
      Ok((h_end, p)) => {
        pos = auth_end
        (Some(auth_start), Some(auth_end), Some(auth_start + h_end), p)
      }
      Err(e) => return Err(e)
    }
  } else {
    (None, None, None, None)
  }

  // Parse path (RFC 3986 Section 3.3)
  let path_start = pos
  let path_end = uri_find_char_from_set(source, pos, 63, 35, 0) // ?, # (dummy 0)
  pos = path_end

  // Parse query (RFC 3986 Section 3.4)
  let (query_start, query_end) = if pos < len &&
    uri_string_char_at(source, pos) == 63 { // ?
    pos = pos + 1
    let start = pos
    let end_pos = uri_find_char_in(source, pos, 35) // #
    let actual_end = match end_pos {
      Some(e) => e
      None => len
    }
    pos = actual_end
    (Some(start), Some(actual_end))
  } else {
    (None, None)
  }

  // Parse fragment (RFC 3986 Section 3.5)
  let fragment_start = if pos < len && uri_string_char_at(source, pos) == 35 { // #
    Some(pos + 1)
  } else {
    None
  }
  Ok({
    source,
    scheme_end,
    authority_start,
    authority_end,
    host_end,
    port,
    path_start,
    path_end,
    query_start,
    query_end,
    fragment_start,
  })
}

///|
pub fn Uri::scheme(self : Uri) -> String? {
  match self.scheme_end {
    Some(end) => Some(uri_string_slice(self.source, 0, end))
    None => None
  }
}

///|
pub fn Uri::authority(self : Uri) -> String? {
  match (self.authority_start, self.authority_end) {
    (Some(start), Some(end)) => Some(uri_string_slice(self.source, start, end))
    _ => None
  }
}

///|
pub fn Uri::host(self : Uri) -> String? {
  match (self.authority_start, self.host_end) {
    (Some(start), Some(end)) => {
      let auth = uri_string_slice(self.source, start, end)
      // Remove userinfo
      let at_pos = uri_find_char_in(auth, 0, 64) // @
      match at_pos {
        Some(pos) => Some(uri_string_slice_from(auth, pos + 1))
        None => Some(auth)
      }
    }
    _ => None
  }
}

///|
pub fn Uri::port(self : Uri) -> Int? {
  self.port
}

///|
pub fn Uri::path(self : Uri) -> String {
  uri_string_slice(self.source, self.path_start, self.path_end)
}

///|
pub fn Uri::query(self : Uri) -> String? {
  match (self.query_start, self.query_end) {
    (Some(start), Some(end)) => Some(uri_string_slice(self.source, start, end))
    _ => None
  }
}

///|
pub fn Uri::fragment(self : Uri) -> String? {
  match self.fragment_start {
    Some(start) => Some(uri_string_slice_from(self.source, start))
    None => None
  }
}

///|
pub fn Uri::as_str(self : Uri) -> String {
  self.source
}

///|
pub fn Uri::origin_form(self : Uri) -> String {
  let path = self.path()
  let path = if path.length() == 0 { "/" } else { path }
  match self.query() {
    Some(q) => path + "?" + q
    None => path
  }
}

///|
pub fn Uri::is_absolute(self : Uri) -> Bool {
  self.scheme_end != None
}

///|
pub fn Uri::is_relative(self : Uri) -> Bool {
  self.scheme_end == None
}

///|
pub fn Uri::to_string(self : Uri) -> String {
  self.source
}

///|
/// Percent encoding (RFC 3986 Section 2.1)
///

///|
pub fn percent_encode(input : String) -> String {
  let result : Array[Int] = []
  let mut idx = 0
  let len = input.length()
  while idx < len {
    let byte = uri_string_char_at(input, idx)
    if uri_is_unreserved(byte) {
      result.push(byte)
    } else {
      result.push(37) // %
      result.push(uri_to_hex_char(byte >> 4))
      result.push(uri_to_hex_char(byte & 0x0F))
    }
    idx = idx + 1
  }
  uri_bytes_to_string(result)
}

///|
pub fn percent_encode_path(input : String) -> String {
  let result : Array[Int] = []
  let mut idx = 0
  let len = input.length()
  while idx < len {
    let byte = uri_string_char_at(input, idx)
    if uri_is_unreserved(byte) || byte == 47 { // /
      result.push(byte)
    } else {
      result.push(37) // %
      result.push(uri_to_hex_char(byte >> 4))
      result.push(uri_to_hex_char(byte & 0x0F))
    }
    idx = idx + 1
  }
  uri_bytes_to_string(result)
}

///|
pub fn percent_encode_query(input : String) -> String {
  let result : Array[Int] = []
  let mut idx = 0
  let len = input.length()
  while idx < len {
    let byte = uri_string_char_at(input, idx)
    if uri_is_unreserved(byte) || byte == 61 || byte == 38 { // =, &
      result.push(byte)
    } else {
      result.push(37) // %
      result.push(uri_to_hex_char(byte >> 4))
      result.push(uri_to_hex_char(byte & 0x0F))
    }
    idx = idx + 1
  }
  uri_bytes_to_string(result)
}

///|
pub fn percent_decode(input : String) -> Result[String, UriError] {
  match percent_decode_bytes(input) {
    Ok(bytes) => uri_bytes_to_string_result(bytes)
    Err(e) => Err(e)
  }
}

///|
pub fn percent_decode_bytes(input : String) -> Result[Array[Int], UriError] {
  let result : Array[Int] = []
  let mut idx = 0
  let len = input.length()
  while idx < len {
    let byte = uri_string_char_at(input, idx)
    if byte == 37 { // %
      if idx + 2 >= len {
        return Err(InvalidPercentEncoding)
      }
      let high = uri_string_char_at(input, idx + 1)
      let low = uri_string_char_at(input, idx + 2)
      let high_val = uri_from_hex_char(high)
      let low_val = uri_from_hex_char(low)
      match (high_val, low_val) {
        (Some(h), Some(l)) => {
          result.push((h << 4) | l)
          idx = idx + 3
        }
        _ => return Err(InvalidPercentEncoding)
      }
    } else {
      result.push(byte)
      idx = idx + 1
    }
  }
  Ok(result)
}

///|
/// Helper functions
///

///|
fn uri_is_unreserved(byte : Int) -> Bool {
  uri_is_alnum(byte) || byte == 45 || byte == 46 || byte == 95 || byte == 126 // -, ., _, ~
}

///|
fn uri_is_alnum(byte : Int) -> Bool {
  uri_is_alpha(byte) || uri_is_digit(byte)
}

///|
fn uri_is_alpha(byte : Int) -> Bool {
  (byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122) // A-Z, a-z
}

///|
fn uri_is_digit(byte : Int) -> Bool {
  byte >= 48 && byte <= 57 // 0-9
}

///|
fn uri_to_hex_char(nibble : Int) -> Int {
  if nibble >= 0 && nibble <= 9 {
    48 + nibble // '0' + nibble
  } else {
    65 + nibble - 10 // 'A' + nibble - 10
  }
}

///|
fn uri_from_hex_char(byte : Int) -> Int? {
  if byte >= 48 && byte <= 57 { // 0-9
    Some(byte - 48)
  } else if byte >= 65 && byte <= 70 { // A-F
    Some(byte - 65 + 10)
  } else if byte >= 97 && byte <= 102 { // a-f
    Some(byte - 97 + 10)
  } else {
    None
  }
}

///|
fn uri_find_scheme_end(s : String) -> (Int?, Int) {
  let mut idx = 0
  let len = s.length()
  while idx < len {
    let ch = uri_string_char_at(s, idx)
    if ch == 58 { // :
      if idx > 0 {
        return (Some(idx), idx + 1)
      } else {
        return (None, 0)
      }
    }
    // Check if char is valid for scheme
    if !uri_is_alnum(ch) && ch != 43 && ch != 45 && ch != 46 { // +, -, .
      return (None, 0)
    }
    idx = idx + 1
  }
  (None, 0)
}

///|
fn uri_parse_authority(authority : String) -> Result[(Int, Int?), UriError] {
  if authority.length() == 0 {
    return Ok((0, None))
  }

  // Remove userinfo
  let at_pos = uri_find_char_in(authority, 0, 64) // @
  let host_part = match at_pos {
    Some(pos) => uri_string_slice_from(authority, pos + 1)
    None => authority
  }

  // IPv6 address
  if uri_string_char_at(host_part, 0) == 91 { // [
    let bracket_end = uri_find_char_in(host_part, 0, 93) // ]
    match bracket_end {
      Some(end) => {
        let after_bracket = uri_string_slice_from(host_part, end + 1)
        if after_bracket.length() == 0 {
          Ok((authority.length(), None))
        } else if uri_string_char_at(after_bracket, 0) == 58 { // :
          let port_str = uri_string_slice_from(after_bracket, 1)
          match uri_parse_port(port_str) {
            Some(p) =>
              Ok((authority.length() - after_bracket.length(), Some(p)))
            None => Err(InvalidPort)
          }
        } else {
          Err(InvalidHost)
        }
      }
      None => Err(InvalidHost)
    }
  } else {
    // Regular host:port
    let colon_pos = uri_find_char_in(host_part, 0, 58) // :
    match colon_pos {
      Some(pos) => {
        let port_str = uri_string_slice_from(host_part, pos + 1)
        if port_str.length() > 0 {
          match uri_parse_port(port_str) {
            Some(p) => {
              let host_end = match at_pos {
                Some(at) => at + 1 + pos
                None => pos
              }
              Ok((host_end, Some(p)))
            }
            None => Err(InvalidPort)
          }
        } else {
          Ok((authority.length(), None))
        }
      }
      None => Ok((authority.length(), None))
    }
  }
}

///|
fn uri_parse_port(port_str : String) -> Int? {
  let mut result = 0
  let mut idx = 0
  let len = port_str.length()
  if len == 0 {
    return None
  }
  while idx < len {
    let ch = uri_string_char_at(port_str, idx)
    if !uri_is_digit(ch) {
      return None
    }
    result = result * 10 + (ch - 48)
    if result > 65535 {
      return None
    }
    idx = idx + 1
  }
  Some(result)
}

///|
fn uri_find_char_from_set(
  s : String,
  start : Int,
  c1 : Int,
  c2 : Int,
  c3 : Int,
) -> Int {
  let mut idx = start
  let len = s.length()
  while idx < len {
    let ch = uri_string_char_at(s, idx)
    if ch == c1 || ch == c2 || ch == c3 {
      return idx
    }
    idx = idx + 1
  }
  len
}

///|
fn uri_find_char_in(s : String, start : Int, target : Int) -> Int? {
  let mut idx = start
  let len = s.length()
  while idx < len {
    if uri_string_char_at(s, idx) == target {
      return Some(idx)
    }
    idx = idx + 1
  }
  None
}

///|
fn uri_string_char_at(s : String, idx : Int) -> Int {
  str_char_at(s, idx)
}

///|
fn uri_string_slice(s : String, start : Int, end : Int) -> String {
  str_string_slice(s, start, end)
}

///|
fn uri_string_slice_from(s : String, start : Int) -> String {
  str_string_slice_from(s, start)
}

///|
fn uri_trim_string(s : String) -> String {
  str_trim_string(s)
}

///|
fn uri_bytes_to_string(bytes : Array[Int]) -> String {
  let mut result = ""
  let mut idx = 0
  while idx < bytes.length() {
    let byte = bytes[idx]
    result = result + Int::unsafe_to_char(byte).to_string()
    idx = idx + 1
  }
  result
}

///|
fn uri_bytes_to_string_result(bytes : Array[Int]) -> Result[String, UriError] {
  Ok(uri_bytes_to_string(bytes))
}