///|
pub(all) enum Transport {
  Tcp(Int32, Int32)
  Udp(Int32, Int32)
}

///|
pub fn Transport::encode(
  self : Transport,
  record~ : Bool = false,
) -> String raise RtspError {
  let base = match self {
    Tcp(a, b) => {
      require(a >= 0 && b == a + 1 && b <= 255, "TCP channel pair")
      "RTP/AVP/TCP;unicast;interleaved=" + a.to_string() + "-" + b.to_string()
    }
    Udp(a, b) => {
      require(a > 0 && b == a + 1 && b <= 65535, "UDP port pair")
      "RTP/AVP;unicast;client_port=" + a.to_string() + "-" + b.to_string()
    }
  }
  base + (if record { ";mode=record" } else { ";mode=play" })
}

///|
pub struct Negotiated {
  transport : Transport
  server_ports : (Int32, Int32)?
  source : String?
  ssrc : String?
}

///|
fn pair(s : String, limit : Int32) -> (Int32, Int32) raise RtspError {
  let parts = s.split("-").to_array()
  require(parts.length() == 2, "transport pair")
  let a = number(parts[0].to_owned(), limit)
  let b = number(parts[1].to_owned(), limit)
  require(b == a + 1, "adjacent RTP RTCP pair")
  (a, b)
}

///|
pub fn negotiate(
  value : String,
  requested : Transport,
) -> Negotiated raise RtspError {
  ignore(requested.encode())
  require(
    value.length() <= 8192 && !value.contains(","),
    "single transport response",
  )
  let segments = value.split(";").to_array()
  let protocol = lower(trim(segments[0].to_owned()))
  let params : Map[String, String] = Map([])
  for i = 1; i < segments.length(); i = i + 1 {
    let xs = trim(segments[i].to_owned()).split("=").to_array()
    let key = lower(xs[0].to_owned())
    require(!params.contains(key), "duplicate transport parameter")
    params[key] = if xs.length() == 1 {
      ""
    } else {
      xs[1:].map(x => x.to_owned()).join("=")
    }
  }
  require(
    params.contains("unicast") && !params.contains("multicast"),
    "unicast transport required",
  )
  match requested {
    Tcp(a, b) => {
      require(protocol == "rtp/avp/tcp", "transport protocol mismatch")
      let selected = match params.get("interleaved") {
        Some(s) => pair(s, 255)
        None => raise Invalid("missing channels")
      }
      require(selected == (a, b), "server changed channels")
    }
    Udp(a, b) => {
      require(
        protocol == "rtp/avp" || protocol == "rtp/avp/udp",
        "UDP protocol mismatch",
      )
      if params.get("client_port") is Some(s) {
        require(pair(s, 65535) == (a, b), "client ports changed")
      }
    }
  }
  let server_ports = match params.get("server_port") {
    Some(s) => Some(pair(s, 65535))
    None => None
  }
  if requested is Udp(_, _) {
    match server_ports {
      Some((a, _)) => require(a > 0, "server UDP port must be positive")
      None => raise Invalid("missing server ports")
    }
  }
  {
    transport: requested,
    server_ports,
    source: params.get("source"),
    ssrc: params.get("ssrc"),
  }
}