///|
pub struct Frame {
  timestamp : UInt32
  data : Bytes
  packets : Int32
}

///|
/// Consecutive, same-source RTP access units. Any gap invalidates the whole frame.
pub struct FrameAssembler {
  priv codec : @rtp.PayloadCodec
  priv max_bytes : Int32
  priv max_packets : Int32
  priv chunks : Array[Bytes]
  priv mut timestamp : UInt32?
  priv mut source : UInt32?
  priv mut expected : Int32?
  priv mut size : Int32
  priv mut broken : Bool
}

///|
pub fn FrameAssembler::new(
  codec : @rtp.PayloadCodec,
  max_bytes~ : Int32 = 4194304,
  max_packets~ : Int32 = 4096,
) -> FrameAssembler raise RtspError {
  require(
    max_bytes > 0 &&
    max_bytes <= 16777216 &&
    max_packets > 0 &&
    max_packets <= 16384,
    "frame limits",
  )
  {
    codec,
    max_bytes,
    max_packets,
    chunks: [],
    timestamp: None,
    source: None,
    expected: None,
    size: 0,
    broken: false,
  }
}

///|
pub fn FrameAssembler::reset(self : FrameAssembler) -> Unit {
  self.chunks.clear()
  self.timestamp = None
  self.source = None
  self.expected = None
  self.size = 0
  self.broken = false
}

///|
pub fn FrameAssembler::push(
  self : FrameAssembler,
  packet : @rtp.Packet,
) -> Frame? raise RtspError {
  let changed = self.timestamp != Some(packet.timestamp()) ||
    self.source != Some(packet.ssrc())
  if changed {
    self.chunks.clear()
    self.size = 0
    self.broken = false
    self.timestamp = Some(packet.timestamp())
    self.source = Some(packet.ssrc())
    if !self.codec.is_partition_head(packet.payload()) {
      self.broken = true
    }
  }
  if self.expected is Some(sequence) {
    if sequence != packet.sequence_number().to_int() {
      self.broken = true
    }
  }
  self.expected = Some((packet.sequence_number().to_int() + 1) % 65536)
  if packet.payload().length() > self.max_bytes - self.size ||
    self.chunks.length() >= self.max_packets {
    self.chunks.clear()
    self.broken = true
    raise Limit("frame budget")
  }
  if !self.broken {
    self.chunks.push(packet.payload())
    self.size += packet.payload().length()
  }
  if !packet.marker() {
    return None
  }
  if self.broken {
    self.chunks.clear()
    self.size = 0
    return None
  }
  let data = @rtp.depacketize_payload(self.codec, self.chunks) catch {
    _ => {
      self.chunks.clear()
      self.size = 0
      raise Invalid("codec payload")
    }
  }
  let frame = {
    timestamp: packet.timestamp(),
    data,
    packets: self.chunks.length(),
  }
  self.chunks.clear()
  self.size = 0
  Some(frame)
}