/// Typed views over common RFC 8866 and RTP attributes.

///|
/// Parses an `a=rtpmap: /[/]` attribute.
pub fn parse_rtpmap(item : Attribute) -> Result[RtpMap, SdpError] {
  if !item.name().equal_ignore_ascii_case("rtpmap") {
    return Err(
      sdp_error(
        "attribute",
        "wrong-kind",
        item.line(),
        3,
        "rtpmap",
        "attribute is not rtpmap",
      ),
    )
  }
  let value = match item.value() {
    None =>
      return Err(
        sdp_error(
          "attribute",
          "missing-value",
          item.line(),
          3,
          "rtpmap",
          "rtpmap requires a value",
        ),
      )
    Some(value) => value
  }
  match value.split_once(" ") {
    None =>
      Err(
        sdp_error(
          "attribute",
          "invalid-rtpmap",
          item.line(),
          3,
          "rtpmap",
          "rtpmap requires payload and encoding",
        ),
      )
    Some(pair) => {
      let payload = match parse_non_negative_int(pair.0.to_owned()) {
        None =>
          return Err(
            sdp_error(
              "attribute",
              "invalid-payload",
              item.line(),
              3,
              "rtpmap",
              "payload type must be an integer",
            ),
          )
        Some(value) => value
      }
      let encoding = pair.1.split("/").to_array()
      if encoding.length() < 2 ||
        encoding.length() > 3 ||
        encoding[0].length() == 0 {
        return Err(
          sdp_error(
            "attribute",
            "invalid-encoding",
            item.line(),
            3,
            "rtpmap",
            "encoding must be name/clock[/parameters]",
          ),
        )
      }
      let clock = match parse_non_negative_int(encoding[1].to_owned()) {
        None =>
          return Err(
            sdp_error(
              "attribute",
              "invalid-clock",
              item.line(),
              3,
              "rtpmap",
              "clock rate must be an integer",
            ),
          )
        Some(value) => value
      }
      let parameters = if encoding.length() == 3 {
        Some(encoding[2].to_owned())
      } else {
        None
      }
      Ok(rtp_map(payload, encoding[0].to_owned(), clock, parameters))
    }
  }
}

///|
/// Parses an `a=fmtp: ` attribute.
pub fn parse_fmtp(item : Attribute) -> Result[FormatParameters, SdpError] {
  if !item.name().equal_ignore_ascii_case("fmtp") {
    return Err(
      sdp_error(
        "attribute",
        "wrong-kind",
        item.line(),
        3,
        "fmtp",
        "attribute is not fmtp",
      ),
    )
  }
  let value = match item.value() {
    None =>
      return Err(
        sdp_error(
          "attribute",
          "missing-value",
          item.line(),
          3,
          "fmtp",
          "fmtp requires a value",
        ),
      )
    Some(value) => value
  }
  match value.split_once(" ") {
    None =>
      Err(
        sdp_error(
          "attribute",
          "invalid-fmtp",
          item.line(),
          3,
          "fmtp",
          "fmtp requires format and parameters",
        ),
      )
    Some(pair) =>
      if pair.0.length() == 0 || pair.1.length() == 0 {
        Err(
          sdp_error(
            "attribute",
            "invalid-fmtp",
            item.line(),
            3,
            "fmtp",
            "fmtp fields must not be empty",
          ),
        )
      } else {
        Ok(format_parameters(pair.0.to_owned(), pair.1.to_owned()))
      }
  }
}

///|
/// Returns every successfully parsed RTP mapping in media order.
pub fn MediaDescription::rtp_maps(self : MediaDescription) -> Array[RtpMap] {
  let out : Array[RtpMap] = Array::new()
  for item in self.attributes() {
    if item.name().equal_ignore_ascii_case("rtpmap") {
      match parse_rtpmap(item) {
        Ok(mapping) => out.push(mapping)
        Err(_) => ()
      }
    }
  }
  out
}

///|
/// Returns every successfully parsed format-parameter mapping.
pub fn MediaDescription::format_parameters(
  self : MediaDescription,
) -> Array[FormatParameters] {
  let out : Array[FormatParameters] = Array::new()
  for item in self.attributes() {
    if item.name().equal_ignore_ascii_case("fmtp") {
      match parse_fmtp(item) {
        Ok(mapping) => out.push(mapping)
        Err(_) => ()
      }
    }
  }
  out
}

///|
/// Finds an RTP mapping by numeric payload type.
pub fn MediaDescription::rtp_map_for(
  self : MediaDescription,
  payload_type : Int,
) -> RtpMap? {
  for mapping in self.rtp_maps() {
    if mapping.payload_type() == payload_type {
      return Some(mapping)
    }
  }
  None
}

///|
/// Parses an attribute whose value starts with a numeric payload identifier.
pub fn attribute_payload(item : Attribute) -> Int? {
  match item.value() {
    None => None
    Some(value) => {
      let head = match value.split_once(" ") {
        None => value
        Some(pair) => pair.0.to_owned()
      }
      parse_non_negative_int(head)
    }
  }
}

///|
/// Whether a media section advertises a format token on its `m=` line.
pub fn MediaDescription::has_format(
  self : MediaDescription,
  format : String,
) -> Bool {
  for item in self.formats() {
    if item == format {
      return true
    }
  }
  false
}

///|
/// Returns the effective connection, applying the RFC 8866 session default.
pub fn effective_connection(
  document : SessionDescription,
  media : MediaDescription,
) -> Connection? {
  if media.connections().length() > 0 {
    Some(media.connections()[0])
  } else {
    document.connection()
  }
}

///|
/// Returns the effective value of a single-valued attribute.
pub fn effective_attribute(
  document : SessionDescription,
  media : MediaDescription,
  name : String,
) -> String? {
  match media.attribute_value(name) {
    Some(value) => Some(value)
    None => {
      for item in document.attributes() {
        if item.name().equal_ignore_ascii_case(name) {
          return item.value()
        }
      }
      None
    }
  }
}