///|
/// An RTSP endpoint without embedded credentials. Paths retain their escaping.
pub struct Endpoint {
  host : String
  port : Int32
  path : String
}

///|
pub fn Endpoint::parse(input : String) -> Endpoint raise RtspError {
  require(input.length() <= 8192 && clean(input), "URL length or characters")
  require(input.has_prefix("rtsp://"), "only rtsp scheme supported")
  let tail = input[7:].to_owned()
  require(
    !tail.contains("@") && !tail.contains("#") && !tail.contains(" "),
    "URL credentials, fragment or spaces",
  )
  let parts = tail.split("/").to_array()
  let authority = parts[0].to_owned()
  require(authority.length() > 0 && !authority.contains("?"), "URL authority")
  let mut host = ""
  let mut port = 554
  if authority.has_prefix("[") {
    let a = authority.split("]").to_array()
    require(a.length() == 2, "IPv6 URL")
    host = a[0].to_owned() + "]"
    let rest = a[1].to_owned()
    port = if rest == "" {
      554
    } else {
      require(rest.has_prefix(":"), "URL port separator")
      number(rest[1:].to_owned(), 65535)
    }
  } else {
    let a = authority.split(":").to_array()
    require(a.length() <= 2, "bracket IPv6 literal")
    host = a[0].to_owned()
    port = if a.length() == 1 { 554 } else { number(a[1].to_owned(), 65535) }
  }
  require(host.length() > 0 && port > 0, "URL host or port")
  let path = if parts.length() == 1 {
    "/"
  } else {
    "/" + parts[1:].map(x => x.to_owned()).join("/")
  }
  { host, port, path, }
}

///|
pub fn Endpoint::url(self : Endpoint) -> String {
  "rtsp://" +
  self.host +
  (if self.port == 554 { "" } else { ":" + self.port.to_string() }) +
  self.path
}

///|
pub fn Endpoint::hostname(self : Endpoint) -> String {
  if self.host.has_prefix("[") {
    self.host[1:self.host.length() - 1].to_owned()
  } else {
    self.host
  }
}

///|
pub fn resolve_control(
  base : String,
  control : String,
) -> String raise RtspError {
  let endpoint = Endpoint::parse(base)
  require(control.length() > 0 && clean(control), "control URL")
  if control == "*" {
    return endpoint.url()
  }
  if control.has_prefix("rtsp://") {
    return Endpoint::parse(control).url()
  }
  require(
    !control.contains("://") && !control.contains("#") && !control.contains(" "),
    "control scheme or characters",
  )
  if control.has_prefix("/") {
    return Endpoint::{
      host: endpoint.host,
      port: endpoint.port,
      path: control,
    }.url()
  }
  let path = endpoint.path.split("?").to_array()[0].to_owned()
  let parts = path.split("/").to_array().map(x => x.to_owned())
  ignore(parts.pop())
  for segment in control.split("/") {
    match segment {
      ".." => if parts.length() > 1 { ignore(parts.pop()) }
      "." => ()
      _ => parts.push(segment.to_owned())
    }
  }
  Endpoint::{ host: endpoint.host, port: endpoint.port, path: parts.join("/"), }.url()
}