///|
/// RTP and RTCP are decoded by rtc.mbt; this layer binds packets to RTSP tracks.
pub(all) enum MediaEvent {
  Rtp(Int32, @rtp.Packet)
  Rtcp(Int32, Array[@rtcp.Packet])
}

///|
pub struct ChannelMap {
  priv channels : Map[Int32, (Int32, Bool)]
}

///|
pub fn ChannelMap::new() -> ChannelMap {
  { channels: Map([]), }
}

///|
pub fn ChannelMap::bind(
  self : ChannelMap,
  track : Int32,
  transport : Transport,
) -> Unit raise RtspError {
  require(track >= 0 && track < 16, "track index")
  match transport {
    Tcp(a, b) => {
      ignore(transport.encode())
      require(
        !self.channels.contains(a) && !self.channels.contains(b),
        "channel collision",
      )
      self.channels[a] = (track, false)
      self.channels[b] = (track, true)
    }
    Udp(_, _) => raise Invalid("UDP has no interleaved channels")
  }
}

///|
pub fn decode_media(
  track : Int32,
  control : Bool,
  bytes : Bytes,
) -> MediaEvent raise RtspError {
  require(track >= 0 && track < 16 && bytes.length() <= 65535, "media bounds")
  if control {
    let packets = @rtcp.decode_compound(bytes) catch {
      _ => raise Invalid("invalid RTCP")
    }
    Rtcp(track, packets)
  } else {
    let packet = @rtp.Packet::unmarshal(bytes) catch {
      _ => raise Invalid("invalid RTP")
    }
    Rtp(track, packet)
  }
}

///|
pub fn ChannelMap::route(
  self : ChannelMap,
  channel : Int32,
  bytes : Bytes,
) -> MediaEvent raise RtspError {
  match self.channels.get(channel) {
    Some((track, control)) => decode_media(track, control, bytes)
    None => raise Invalid("unnegotiated interleaved channel")
  }
}

///|
pub struct MediaQueue {
  priv events : Array[MediaEvent]
  priv capacity : Int32
  priv mut discarded : Int64
}

///|
pub fn MediaQueue::new(capacity~ : Int32 = 256) -> MediaQueue raise RtspError {
  require(capacity > 0 && capacity <= 4096, "queue capacity")
  { events: [], capacity, discarded: 0L, }
}

///|
/// Drop oldest on overload to bound latency and memory. Loss is explicit.
pub fn MediaQueue::push(self : MediaQueue, event : MediaEvent) -> Unit {
  if self.events.length() == self.capacity {
    ignore(self.events.remove(0))
    self.discarded += 1L
  }
  self.events.push(event)
}

///|
pub fn MediaQueue::pop(self : MediaQueue) -> MediaEvent? {
  if self.events.length() == 0 {
    None
  } else {
    Some(self.events.remove(0))
  }
}

///|
pub fn MediaQueue::length(self : MediaQueue) -> Int32 {
  self.events.length()
}

///|
pub fn MediaQueue::discarded(self : MediaQueue) -> Int64 {
  self.discarded
}