// 路径的 percent 编码 / 解码。
// WebDAV 路径里允许任意 Unicode 与保留字符:发请求前逐段编码(`/` 保留),
// 解析 PROPFIND 返回的 href 时解码还原显示名。
// 逐字节操作时 `Bytes[i]` / `String[i]` 返回 `Byte` / `UInt16`,
// 本文件统一在 Int 域上比较,再按需转换。

///|
/// unreserved = ALPHA / DIGIT / `-` / `.` / `_` / `~`(RFC 3986 §2.3)。
fn is_unreserved(byte : Int32) -> Bool {
  (byte >= 0x41 && byte <= 0x5A) ||
  (byte >= 0x61 && byte <= 0x7A) ||
  (byte >= 0x30 && byte <= 0x39) ||
  byte == 0x2D ||
  byte == 0x2E ||
  byte == 0x5F ||
  byte == 0x7E
}

///|
/// `0 ~ 15` 的十六进制字符;调用方保证值域,故直接取 ASCII 码。
fn hex_char(value : Int32) -> Char {
  if value < 10 {
    (0x30 + value).unsafe_to_char()
  } else {
    (0x37 + value).unsafe_to_char()
  }
}

///|
/// 单个十六进制字节的数值;非十六进制返回 `None`。
fn hex_value(code : Byte) -> Int32? {
  let c = code.to_int()
  if c >= 0x30 && c <= 0x39 {
    Some(c - 0x30)
  } else if c >= 0x41 && c <= 0x46 {
    Some(c - 0x41 + 10)
  } else if c >= 0x61 && c <= 0x66 {
    Some(c - 0x61 + 10)
  } else {
    None
  }
}

///|
/// 解析纯 ASCII 数字文本(如 Content-Length、getcontentlength)。
/// 空串、含非数字字符时返回 `None`。
pub fn parse_ascii_int(text : String) -> Int32? {
  if text == "" {
    None
  } else {
    let mut number = 0
    let mut ok = true
    for ch in text {
      let digit = ch.to_int() - 0x30
      if digit >= 0 && digit <= 9 {
        number = number * 10 + digit
      } else {
        ok = false
        break
      }
    }
    if ok {
      Some(number)
    } else {
      None
    }
  }
}

///|
/// 把路径按 RFC 3986 做 percent 编码:保留 `/` 与 unreserved 字符,
/// 其余(空格、中文、`?`、`#` 等保留 / 分隔字符)逐 UTF-8 字节编码为 `%XX`。
/// 注意空格编码为 `%20` 而非表单的 `+`。
pub fn percent_encode_path(path : String) -> String {
  let buf = StringBuilder()
  let bytes = @utf8.encode(path)
  let len = bytes.length()
  let mut i = 0
  while i < len {
    let byte = bytes[i].to_int()
    if byte == 0x2F || is_unreserved(byte) {
      // 走到这里的一定是 ASCII(unreserved 与 `/`),unsafe_to_char 安全
      buf.write_char(byte.unsafe_to_char())
    } else {
      buf.write_char('%')
      buf.write_char(hex_char(byte / 16))
      buf.write_char(hex_char(byte % 16))
    }
    i = i + 1
  }
  buf.to_string()
}

///|
/// 解码 percent 编码的字符串(如 PROPFIND 返回的 href)。
/// 非法转义序列原样保留;解码结果按 UTF-8 还原,失败时保守返回原文。
pub fn percent_decode(text : String) -> String {
  let bytes = @utf8.encode(text)
  let len = bytes.length()
  let out = @buffer.Buffer::Buffer()
  let mut decoded_any = false
  let mut i = 0
  while i < len {
    let byte = bytes[i].to_int()
    if byte == 0x25 && i + 2 < len {
      match (hex_value(bytes[i + 1]), hex_value(bytes[i + 2])) {
        (Some(hi), Some(lo)) => {
          out.write_byte((hi * 16 + lo).to_byte())
          decoded_any = true
          i = i + 3
          continue
        }
        _ => ()
      }
    }
    out.write_byte(bytes[i])
    i = i + 1
  }
  if decoded_any {
    @utf8.decode(out.to_bytes()) catch {
      _ => text // 解码出非 UTF-8 序列,保守返回原文
    }
  } else {
    text
  }
}

///|
/// 把 207 响应里的 href 归一化成服务器上的绝对路径,供「与服务端路径比对」
/// 与「用作本地路径」两种用途:
///
/// - 剥离 `scheme://authority`(部分服务端与反向代理会回绝对 URL);
/// - 丢弃 `?query` 与 `#fragment`;
/// - percent 解码;
/// - 去掉末尾斜杠(根路径 `/` 保留)。
pub fn href_to_path(href : String) -> String {
  let path = match href.find("://") {
    Some(scheme_end) => {
      let rest = href[scheme_end + 3:].to_owned()
      match rest.find("/") {
        Some(slash) => rest[slash:].to_owned()
        None => "/"
      }
    }
    None => href
  }
  trim_trailing_slash(percent_decode(strip_query_fragment(path)))
}

///|
/// 砍掉 `?query` 与 `#fragment`:两者之后的内容不属于路径。
fn strip_query_fragment(text : String) -> String {
  let buf = StringBuilder()
  for ch in text {
    if ch == '?' || ch == '#' {
      break
    }
    buf.write_char(ch)
  }
  buf.to_string()
}

///|
/// 去掉末尾斜杠;根路径 `/` 与空串保持不变。
fn trim_trailing_slash(text : String) -> String {
  if text.has_suffix("/") && text.length() > 1 {
    text[:text.length() - 1].to_owned()
  } else {
    text
  }
}