// uri.mbt — SIP and SIPS URI parsing, RFC 3261 §19.1.
//
// Two deliberate decisions, both driven by RFC 4475 rather than by taste:
//
// 1. `%` is accepted literally when it is not followed by two hex digits.
//    RFC 4475 §3.1.1.5 ("esc02") declares `branch=z9hG4bK209%fzsnel234` to be
//    well formed, so a parser that rejects a stray `%` is wrong. Strictness
//    about escapes belongs to the diagnostics layer, not the grammar layer.
//
// 2. Unescaping is never recursive (RFC 4475 §3.1.1.3). `value%41` decodes to
//    `valueA` exactly once and is compared as such; `value%2541` decodes to
//    `value%41`, not to `valueA`.

///|
/// Which scheme a request URI carries. Anything that is a syntactically valid
/// absolute URI but not `sip:`/`sips:` is kept as `Other`, because RFC 3261
/// §25.1 admits `absoluteURI` in Request-URI and RFC 4475 §3.3.2/§3.3.3 expect
/// such a message to parse and be rejected at the application layer instead.
pub enum UriScheme {
  Sip
  Sips
  Other(Bytes)
}

///|
pub extend UriScheme with Show::{to_string, output}

///|
pub impl Show for UriScheme with fn to_string(self : UriScheme) -> String {
  match self {
    Sip => "sip"
    Sips => "sips"
    Other(raw) => ascii_of(raw.exact_view())
  }
}

///|
/// Host as written. The IPv6 form keeps its brackets, since dropping them makes
/// the rendered URI ambiguous with a bare IPv6 address.
pub enum Host {
  Name(Bytes)
  Ipv4(Bytes)
  Ipv6Reference(Bytes)
}

///|
pub extend Host with Show::{to_string, output}

///|
pub impl Show for Host with fn to_string(self : Host) -> String {
  match self {
    Name(b) => ascii_of(b.exact_view())
    Ipv4(b) => ascii_of(b.exact_view())
    Ipv6Reference(b) => ascii_of(b.exact_view())
  }
}

///|
/// One `;name[=value]` URI parameter. `value` is `None` for valueless parameters
/// such as the ubiquitous `;lr`.
pub struct UriParam {
  name : Bytes
  value : Bytes?
  raw : Bytes
}

///|
/// The parameter name.
pub fn UriParam::name(self : UriParam) -> Bytes {
  self.name
}

///|
/// The parameter value, or `None` for a flag such as `;lr`.
pub fn UriParam::value(self : UriParam) -> Bytes? {
  self.value
}

///|
/// One `?name=value` URI header. RFC 3261 calls the `?`-suffixed section "URI
/// headers", which is a *different* namespace from message headers.
pub struct UriHeader {
  name : Bytes
  value : Bytes
}

///|
/// The URI header name, the part before `=`.
pub fn UriHeader::name(self : UriHeader) -> Bytes {
  self.name
}

///|
/// The URI header value.
pub fn UriHeader::value(self : UriHeader) -> Bytes {
  self.value
}

///|
/// A parsed URI. `raw` is the exact octets consumed, so a caller can always
/// recover what was on the wire even when a field is interpreted.
pub struct SipUri {
  raw : Bytes
  scheme : UriScheme
  user : Bytes?
  password : Bytes?
  host : Host
  port : Int32?
  params : Array[UriParam]
  headers : Array[UriHeader]
}

///|
/// The exact octets the URI was parsed from.
pub fn SipUri::raw(self : SipUri) -> Bytes {
  self.raw
}

///|
/// `sip`, `sips`, or another scheme kept verbatim.
pub fn SipUri::scheme(self : SipUri) -> UriScheme {
  self.scheme
}

///|
/// The user part, or `None` when absent.
pub fn SipUri::user(self : SipUri) -> Bytes? {
  self.user
}

///|
/// The password, or `None` when absent.
pub fn SipUri::password(self : SipUri) -> Bytes? {
  self.password
}

///|
/// The host in the form it was written, IPv6 brackets included.
pub fn SipUri::host(self : SipUri) -> Host {
  self.host
}

///|
/// The port, or `None` when absent.
pub fn SipUri::port(self : SipUri) -> Int32? {
  self.port
}

///|
/// URI parameters in wire order.
pub fn SipUri::params(self : SipUri) -> Array[UriParam] {
  self.params
}

///|
/// URI headers in wire order.
pub fn SipUri::headers(self : SipUri) -> Array[UriHeader] {
  self.headers
}

///|
/// Looks up a URI parameter by name, case-insensitively. Returns the last
/// occurrence, matching how flags such as `;lr` are conventionally read.
pub fn SipUri::param(self : SipUri, name : String) -> UriParam? {
  let want = ascii_bytes_of(name).exact_view()
  let mut found : UriParam? = None
  for p in self.params {
    if bytes_eq_ignore_ascii_case(p.name.exact_view(), want) {
      found = Some(p)
    }
  }
  found
}

///|
/// True when `;user=phone` is present. RFC 3261 §19.1.4 makes this change how the
/// user part is compared, so callers need it as a first-class query.
pub fn SipUri::is_phone_user(self : SipUri) -> Bool {
  match self.param("user") {
    Some(p) =>
      match p.value {
        Some(v) =>
          bytes_eq_ignore_ascii_case(v.exact_view(), b"phone".exact_view())
        None => false
      }
    None => false
  }
}

///|
/// `unreserved = alphanum / mark`; `mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")"`
pub fn is_unreserved(b : Byte) -> Bool {
  if is_alnum(b) {
    return true
  }
  match b {
    b'-' | b'_' | b'.' | b'!' | b'~' | b'*' | b'\'' | b'(' | b')' => true
    _ => false
  }
}

///|
/// Component octets for the user part: unreserved, `%`, and
/// `user-unreserved = & = + $ , ; ? /`. `@` is deliberately excluded: it is the
/// one octet that terminates a userinfo section.
fn is_user_octet(b : Byte) -> Bool {
  if is_unreserved(b) || b == PERCENT {
    return true
  }
  match b {
    b'&' | b'=' | b'+' | b'$' | b',' | b';' | b'?' | b'/' => true
    _ => false
  }
}

///|
/// Password admits unreserved, `%`, and `& = + $ ,` — but not `; ? /`.
fn is_password_octet(b : Byte) -> Bool {
  if is_unreserved(b) || b == PERCENT {
    return true
  }
  match b {
    b'&' | b'=' | b'+' | b'$' | b',' => true
    _ => false
  }
}

///|
/// Parameter octets: unreserved, `%`, and `param-unreserved = [ ] / : & + $`.
/// `=` and `;` terminate name and value respectively.
fn is_param_octet(b : Byte) -> Bool {
  if is_unreserved(b) || b == PERCENT {
    return true
  }
  match b {
    b'[' | b']' | b'/' | b':' | b'&' | b'+' | b'$' => true
    _ => false
  }
}

///|
/// URI-header octets: unreserved, `%`, and `hnv-unreserved = [ ] / ? : + $`.
fn is_hnv_octet(b : Byte) -> Bool {
  if is_unreserved(b) || b == PERCENT {
    return true
  }
  match b {
    b'[' | b']' | b'/' | b'?' | b':' | b'+' | b'$' => true
    _ => false
  }
}

///|
/// Decodes `% HEX HEX` exactly once. Non-escapes are copied through, which keeps
/// `%41` -> `A` and `%25` -> `%` without recursing into the result.
pub fn unescape_once(src : BytesView) -> Bytes {
  let out : Array[Byte] = []
  let n = src.length()
  let mut i = 0
  while i < n {
    let b = src[i]
    if b == PERCENT && i + 2 < n {
      let hi = hex_value(src[i + 1])
      let lo = hex_value(src[i + 2])
      if hi >= 0 && lo >= 0 {
        out.push((hi * 16 + lo).to_byte())
        i = i + 3
        continue
      }
    }
    out.push(b)
    i = i + 1
  }
  Bytes::from_array(out)
}

///|
/// Parses the scheme prefix. Scheme tokens are case-insensitive (RFC 3261
/// §19.1.1); the URI as a whole is never normalised.
fn parse_scheme(c : Cursor) -> UriScheme raise SipError {
  let start = c.position()
  if c.eof() {
    raise Syntax(UnexpectedEnd, start)
  }
  if !is_alpha(c.src[start]) {
    raise Syntax(IllegalOctet, start)
  }
  c.pos = c.pos + 1
  while c.pos < c.length() {
    let b = c.src[c.pos]
    if is_alnum(b) || b == b'+' || b == b'-' || b == b'.' {
      c.pos = c.pos + 1
    } else {
      break
    }
  }
  let raw = copy_range(c.src, start, c.position())
  if bytes_eq_ignore_ascii_case(raw.exact_view(), b"sip".exact_view()) {
    UriScheme::Sip
  } else if bytes_eq_ignore_ascii_case(raw.exact_view(), b"sips".exact_view()) {
    UriScheme::Sips
  } else {
    UriScheme::Other(raw)
  }
}

///|
/// True when the URI starting at the cursor begins with `sip:` or `sips:`.
pub fn looks_like_sip_uri(c : Cursor) -> Bool {
  c.starts_with_ignore_case("sip:") || c.starts_with_ignore_case("sips:")
}

///|
/// Parses the userinfo section that ends at `at_pos`, leaving the cursor on the
/// `@`. Returns `(user, password)`.
fn parse_userinfo(
  c : Cursor,
  at_pos : Int32,
) -> (Bytes?, Bytes?) raise SipError {
  let ustart = c.position()
  let mut user : Bytes? = None
  let mut password : Bytes? = None
  let mut seen_colon = false
  let mut pstart = -1
  while c.pos < at_pos {
    let b = c.src[c.pos]
    if b == COLON && !seen_colon {
      seen_colon = true
      user = Some(copy_range(c.src, ustart, c.pos))
      c.pos = c.pos + 1
      pstart = c.pos
      continue
    }
    if seen_colon {
      if !is_password_octet(b) {
        raise Syntax(IllegalOctet, c.pos)
      }
    } else if !is_user_octet(b) {
      raise Syntax(IllegalOctet, c.pos)
    }
    c.pos = c.pos + 1
  }
  if seen_colon {
    password = Some(copy_range(c.src, pstart, at_pos))
  } else {
    user = Some(copy_range(c.src, ustart, at_pos))
  }
  (user, password)
}

///|
fn parse_host(c : Cursor) -> Host raise SipError {
  let hstart = c.position()
  if c.eof() {
    raise Syntax(UnexpectedEnd, hstart)
  }
  if c.src[hstart] == LBRACKET {
    c.pos = c.pos + 1
    while c.pos < c.length() {
      let x = c.src[c.pos]
      if x == RBRACKET {
        c.pos = c.pos + 1
        return Host::Ipv6Reference(copy_range(c.src, hstart, c.position()))
      }
      if !(is_hexdig(x) || x == COLON || x == DOT) {
        raise Syntax(IllegalOctet, c.pos)
      }
      c.pos = c.pos + 1
    }
    raise Syntax(UnexpectedEnd, hstart)
  }
  while c.pos < c.length() {
    let x = c.src[c.pos]
    if is_alnum(x) || x == MINUS || x == DOT || x == b'_' {
      c.pos = c.pos + 1
    } else {
      break
    }
  }
  let hn = copy_range(c.src, hstart, c.position())
  if hn.length() == 0 {
    raise Syntax(IllegalOctet, hstart)
  }
  if is_dotted_quad(hn.exact_view()) {
    Host::Ipv4(hn)
  } else {
    Host::Name(hn)
  }
}

///|
fn parse_port(c : Cursor) -> Int32? raise SipError {
  if c.eof() || c.src[c.pos] != COLON {
    return None
  }
  c.pos = c.pos + 1
  let pstart = c.position()
  let mut v = 0
  while c.pos < c.length() {
    let x = c.src[c.pos]
    if is_digit(x) {
      v = v * 10 + (x - b'0').to_int()
      c.pos = c.pos + 1
    } else {
      break
    }
  }
  if c.position() == pstart {
    raise Syntax(IllegalOctet, pstart)
  }
  Some(v)
}

///|
fn parse_uri_params(c : Cursor) -> Array[UriParam] {
  let params : Array[UriParam] = []
  while c.pos < c.length() {
    if c.src[c.pos] != SEMI {
      break
    }
    let pstart = c.position()
    c.pos = c.pos + 1
    let nstart = c.position()
    while c.pos < c.length() {
      let x = c.src[c.pos]
      if is_param_octet(x) {
        c.pos = c.pos + 1
      } else {
        break
      }
    }
    let pname = copy_range(c.src, nstart, c.position())
    let mut pval : Bytes? = None
    if c.pos < c.length() && c.src[c.pos] == EQUALS {
      c.pos = c.pos + 1
      let vstart = c.position()
      while c.pos < c.length() {
        let x = c.src[c.pos]
        if is_param_octet(x) {
          c.pos = c.pos + 1
        } else {
          break
        }
      }
      pval = Some(copy_range(c.src, vstart, c.position()))
    }
    params.push({
      name: pname,
      value: pval,
      raw: copy_range(c.src, pstart, c.position()),
    })
  }
  params
}

///|
fn parse_uri_headers(c : Cursor) -> Array[UriHeader] raise SipError {
  let headers : Array[UriHeader] = []
  if c.eof() || c.src[c.pos] != b'?' {
    return headers
  }
  c.pos = c.pos + 1
  let mut first = true
  while c.pos < c.length() {
    if !first {
      if c.src[c.pos] != b'&' {
        break
      }
      c.pos = c.pos + 1
    }
    first = false
    let nstart = c.position()
    while c.pos < c.length() {
      let x = c.src[c.pos]
      if is_hnv_octet(x) {
        c.pos = c.pos + 1
      } else {
        break
      }
    }
    let hname = copy_range(c.src, nstart, c.position())
    if c.eof() || c.src[c.pos] != EQUALS {
      raise Syntax(IllegalOctet, c.position())
    }
    c.pos = c.pos + 1
    let vstart = c.position()
    while c.pos < c.length() {
      let x = c.src[c.pos]
      if is_hnv_octet(x) {
        c.pos = c.pos + 1
      } else {
        break
      }
    }
    headers.push({
      name: hname,
      value: copy_range(c.src, vstart, c.position()),
    })
  }
  headers
}

///|
/// Parses a SIP/SIPS URI.
///
/// The `@` that separates userinfo from host is the first one that appears
/// before any `?`. This is the only rule that keeps `sip:host?to=sip:a@b` from
/// being read as a userinfo section, and it matches what production stacks do.
/// The residual ambiguity — `?` is legal inside a user part — is documented in
/// the README rather than papered over.
pub fn parse_sip_uri(c : Cursor) -> SipUri raise SipError {
  let start = c.position()
  let scheme = parse_scheme(c)
  match scheme {
    Other(_) => raise Syntax(IllegalOctet, start)
    _ => ()
  }
  if c.eof() || c.src[c.pos] != COLON {
    raise Syntax(IllegalOctet, c.position())
  }
  c.pos = c.pos + 1
  let n = c.length()
  let mut scan = c.position()
  let mut at_pos = -1
  // The user part may legally contain `; , ? / & = + $` (RFC 3261 §25.1
  // `user-unreserved`), so the scan for the `@` cannot stop on any of them.
  // It stops only where the URI itself must end: whitespace or angle brackets.
  // RFC 4475 §3.1.1.2 ("intmeth") has a user part containing `,/;;*:` and is
  // the reason this is not a naive "first separator wins" scan.
  while scan < n {
    let b = c.src[scan]
    if b == AT {
      break
    }
    if b == CR || b == LF || b == SP || b == LT || b == GT || b == b'"' {
      break
    }
    scan = scan + 1
  }
  if scan < n && c.src[scan] == AT {
    at_pos = scan
  }
  let has_user = at_pos >= 0
  let mut user : Bytes? = None
  let mut password : Bytes? = None
  if has_user {
    let pair = parse_userinfo(c, at_pos)
    user = pair.0
    password = pair.1
    if c.eof() || c.src[c.pos] != AT {
      raise Syntax(IllegalOctet, c.position())
    }
    c.pos = c.pos + 1
  }
  let host = parse_host(c)
  let port = parse_port(c)
  let params = parse_uri_params(c)
  let headers = parse_uri_headers(c)
  {
    raw: copy_range(c.src, start, c.position()),
    scheme,
    user,
    password,
    host,
    port,
    params,
    headers,
  }
}

///|
/// True for a syntactically valid dotted quad. Used only to label the host for
/// diagnostics; both forms parse identically.
fn is_dotted_quad(b : BytesView) -> Bool {
  let mut parts = 0
  let mut digits = 0
  for i in 0.. 3 {
        return false
      }
      parts = parts + 1
      digits = 0
    } else if is_digit(b[i]) {
      digits = digits + 1
      if digits > 3 {
        return false
      }
    } else {
      return false
    }
  }
  if digits == 0 || digits > 3 {
    return false
  }
  parts = parts + 1
  parts == 4
}