/// OBU envelopes and frame assembly. Payload bytes retain their layer IDs until
/// the selected operating point has accepted them and frame syntax consumes it.

///|
priv struct Av1ObuPacket {
  kind : Int
  extension : Bool
  temporal_id : Int
  spatial_id : Int
  offset : Int
  payload_offset : Int
  payload : Array[Byte]
}

///|
fn av1_obu_packets(
  data : Array[Byte],
  budget? : DecodeBudget? = None,
) -> Array[Av1ObuPacket]? {
  let limit = match budget {
    Some(value) => value.limits.max_input_bytes
    None => 0x7FFFFFFF
  }
  match av1_obu_packets_checked(data, limit~) {
    Ok(packets) => Some(packets)
    Err(error) => {
      decode_record_error(budget, error)
      None
    }
  }
}

///|
/// OBUs without an extension are not filtered by layer, as specified by
/// open_bitstream_unit(). The default chosen operating point is index zero.
fn av1_obu_selected(
  packet : Av1ObuPacket,
  sequence : Av1SequenceInfo,
  operating_point? : Int = 0,
) -> Bool {
  if operating_point < 0 ||
    operating_point >= sequence.operating_point_idc.length() {
    return false
  }
  if packet.kind == 1 || packet.kind == 2 || !packet.extension {
    return true
  }
  let idc = sequence.operating_point_idc[operating_point]
  idc == 0 ||
  (
    ((idc >> packet.temporal_id) & 1) != 0 &&
    ((idc >> (packet.spatial_id + 8)) & 1) != 0
  )
}

///|
fn av1_obu_trailing_bits(data : Array[Byte], bit_pos : Int) -> Bool {
  let bits : Av1FrameBits = { data, bit_pos, }
  if bits.take(1) != Some(1) {
    return false
  }
  while bits.bit_pos < data.length() * 8 {
    if bits.take(1) != Some(0) {
      return false
    }
  }
  true
}

///|
/// Assemble complete, sequential tile groups into the existing tile decoder's
/// raster payload. A group's last tile has no size prefix; add a four-byte
/// prefix when another group follows, so no original prefix-width limit is
/// imposed on that tile. The caller sets its normalized TileSizeBytes to four.
fn av1_assemble_tile_groups(
  groups : Array[Array[Byte]],
  tile_cols : Int,
  tile_rows : Int,
  tile_bits : Int,
  tile_size_bytes : Int,
) -> Array[Byte]? {
  let count = tile_cols * tile_rows
  if count <= 0 ||
    count > 4096 ||
    groups.is_empty() ||
    (
      count > 1 &&
      (
        tile_bits <= 0 ||
        tile_bits > 12 ||
        tile_size_bytes < 1 ||
        tile_size_bytes > 4
      )
    ) {
    return None
  }
  let out : Array[Byte] = []
  let mut next_tile = 0
  for payload in groups {
    let bits : Av1FrameBits = { data: payload, bit_pos: 0, }
    let explicit_range = if count > 1 {
      match bits.take(1) {
        Some(value) => value == 1
        None => return None
      }
    } else {
      false
    }
    let (start, end) = if explicit_range {
      let start = match bits.take(tile_bits) {
        Some(value) => value
        None => return None
      }
      let end = match bits.take(tile_bits) {
        Some(value) => value
        None => return None
      }
      (start, end)
    } else {
      (0, count - 1)
    }
    if start != next_tile || end < start || end >= count {
      return None
    }
    while (bits.bit_pos & 7) != 0 {
      if bits.take(1) != Some(0) {
        return None
      }
    }
    let mut pos = bits.bit_pos / 8
    for tile in start..<=end {
      let size = if tile == end {
        payload.length() - pos
      } else {
        if tile_size_bytes > payload.length() - pos {
          return None
        }
        let mut minus_one = 0
        for byte in 0..= 128 {
            return None
          }
          minus_one = minus_one | (value << (8 * byte))
        }
        pos = pos + tile_size_bytes
        if minus_one >= payload.length() - pos {
          return None
        }
        minus_one + 1
      }
      if size <= 0 || size > payload.length() - pos {
        return None
      }
      if tile != count - 1 {
        for byte in 0..<4 {
          out.push((((size - 1) >> (byte * 8)) & 255).to_byte())
        }
      }
      for index in pos..<(pos + size) {
        out.push(payload[index])
      }
      pos = pos + size
    }
    if pos != payload.length() {
      return None
    }
    next_tile = end + 1
  }
  if next_tile == count {
    Some(out)
  } else {
    None
  }
}

///|
/// A key-frame show-existing operation performs reference_frame_loading and
/// refreshes all eight slots. Saved pixels and syntax are already immutable
/// snapshots; copy the mutable metadata arrays with the same contents.
fn av1_show_existing_refresh(
  map : Av1FrameMap,
  slot : Int,
  frame : Av1RefFrame,
) -> Unit {
  if frame.frame_type != av1_key_frame {
    return
  }
  map.current_frame_id = frame.frame_id
  let segmentation = map.saved_segmentation[slot]
  let segments = map.saved_segment_ids[slot]
  let grain = map.saved_film_grain[slot]
  for target in 0.. Av1FramePlanes? {
  // OBU_FRAME always includes tile syntax; show-existing is a frame-header OBU.
  if packet.kind != 3 {
    return None
  }
  let envelope = match
    av1_parse_show_existing_frame(packet.payload, {
      decoder_model_info_present: sequence.decoder_model_info_present,
      equal_picture_interval: sequence.equal_picture_interval,
      frame_presentation_time_length_minus_1: sequence.frame_presentation_time_length_minus_1,
      frame_id_numbers_present: sequence.frame_id_numbers_present,
      additional_frame_id_len_minus1: sequence.additional_frame_id_length_minus_1,
      delta_frame_id_length_minus2: sequence.delta_frame_id_length_minus_2,
      reference_frame_types: map.frame_types,
    }) {
    Some(value) => value
    None => return None
  }
  if !av1_obu_trailing_bits(packet.payload, envelope.header_bits) {
    return None
  }
  let slot = envelope.frame_to_show_map_idx
  if !map.saved_showable[slot] {
    return None
  }
  let reference = match av1_frame_map_show(map, slot) {
    Some(value) => value
    None => return None
  }
  if !decode_reserve(budget, reference.width, reference.height) {
    return None
  }
  match envelope.display_frame_id {
    Some(id) => if id != reference.frame_id { return None }
    None => ()
  }
  let frame : Av1FramePlanes = {
    width: reference.width,
    height: reference.height,
    coded_width: reference.coded_width,
    bit_depth: reference.bit_depth,
    full_range: sequence.full_range,
    matrix_coefficients: sequence.matrix_coefficients,
    color_primaries: sequence.color_primaries,
    transfer_characteristics: sequence.transfer_characteristics,
    sub_x: reference.sub_x,
    sub_y: reference.sub_y,
    planes: reference.planes,
  }
  let grain = map.saved_film_grain[slot]
  av1_show_existing_refresh(map, slot, reference)
  match grain {
    None => Some(frame)
    Some(params) =>
      av1_frame_apply_grain(
        frame,
        params,
        sequence.monochrome,
        sequence.subsampling_x,
        sequence.subsampling_y,
      )
  }
}

///|
/// Decode frame OBUs in order and present the highest spatial layer actually
/// present in each temporal unit of the selected operating point (AV1 ยง7.18.1).
/// An explicit spatial layer limits presentation; every decoded layer and hidden
/// frame still updates references. Temporal delimiters separate units; the input
/// end closes the final unit, as for an ISOBMFF sample without a TD.
fn av1_decode_obu_planes(
  data : Array[Byte],
  map : Av1FrameMap,
  sequence : Av1SequenceInfo?,
  allow_monochrome : Bool,
  operating_point? : Int = 0,
  spatial_layer? : Int? = None,
  budget? : DecodeBudget? = None,
) -> (Av1SequenceInfo, Array[Av1FramePlanes])? {
  if operating_point < 0 || operating_point > 31 {
    return None
  }
  match spatial_layer {
    Some(layer) => if layer < 0 || layer > 3 { return None }
    None => ()
  }
  match sequence {
    Some(seq) =>
      if operating_point >= seq.operating_point_idc.length() {
        return None
      }
    None => ()
  }
  let packets = match av1_obu_packets(data, budget~) {
    Some(value) => value
    None => return None
  }
  let displayed : Array[Av1FramePlanes] = []
  let mut selected : Av1FramePlanes? = None
  let mut selected_spatial = -1
  let mut active = sequence
  let mut index = 0
  while index < packets.length() {
    let packet = packets[index]
    decode_context(budget, "obu", Some(packet.offset))
    index = index + 1
    if packet.kind == 1 {
      decode_context(budget, "sequence_header", Some(packet.payload_offset))
      active = av1_parse_sequence_payload(packet.payload)
      match active {
        Some(seq) =>
          if operating_point >= seq.operating_point_idc.length() {
            return None
          }
        None => return None
      }
      continue
    }
    if packet.kind == 2 {
      match selected {
        Some(frame) => displayed.push(frame)
        None => ()
      }
      selected = None
      selected_spatial = -1
      continue
    }
    let seq = match active {
      Some(value) => value
      None => {
        decode_fail(
          budget,
          MissingSequence,
          "A sequence header is required before frame decoding",
        )
        return None
      }
    }
    if !av1_obu_selected(packet, seq, operating_point~) {
      continue
    }
    if packet.kind != 3 && packet.kind != 6 {
      if packet.kind == 4 || packet.kind == 7 || packet.kind == 8 {
        decode_fail(
          budget,
          if packet.kind == 8 {
            Unsupported
          } else {
            InvalidData
          },
          "Unexpected tile group, redundant header or tile-list OBU",
        )
        return None
      }
      continue
    }
    if packet.payload.is_empty() || (seq.monochrome && !allow_monochrome) {
      decode_fail(
        budget,
        if packet.payload.is_empty() {
          TruncatedInput
        } else {
          Unsupported
        },
        "Empty frame payload or monochrome was not enabled",
      )
      return None
    }
    decode_context(budget, "frame_header", Some(packet.payload_offset))
    if !seq.reduced_still_picture_header &&
      (packet.payload[0].to_int() & 128) != 0 {
      let slot = (packet.payload[0].to_int() >> 4) & 7
      if !map.valid[slot] || map.slots[slot] is None {
        decode_fail(
          budget,
          MissingReference,
          "show-existing names an unavailable reference slot",
        )
      }
      match av1_show_existing_planes(packet, seq, map, budget~) {
        Some(frame) =>
          if packet.spatial_id >= selected_spatial &&
            (spatial_layer is None || spatial_layer == Some(packet.spatial_id)) {
            selected = Some(frame)
            selected_spatial = packet.spatial_id
          }
        None => return None
      }
      continue
    }
    let groups : Array[Array[Byte]] = []
    if packet.kind == 3 {
      while index < packets.length() {
        let next = packets[index]
        if !av1_obu_selected(next, seq, operating_point~) {
          index = index + 1
          continue
        }
        if next.kind == 1 || next.kind == 2 || next.kind == 3 || next.kind == 6 {
          break
        }
        index = index + 1
        if next.kind == 4 || next.kind == 7 {
          if next.extension != packet.extension ||
            next.temporal_id != packet.temporal_id ||
            next.spatial_id != packet.spatial_id {
            return None
          }
          if next.kind == 4 {
            groups.push(next.payload)
          } else if next.payload != packet.payload {
            return None
          }
        } else if next.kind == 8 {
          return None
        }
      }
    }
    let header = match
      av1_parse_stage1_frame(
        packet.payload,
        seq,
        allow_highbd=true,
        allow_monochrome~,
        map~,
        frame_header_obu=packet.kind == 3,
        tile_groups=groups,
        temporal_id=packet.temporal_id,
        spatial_id=packet.spatial_id,
      ) {
      Some(value) => value
      None => {
        if !seq.reduced_still_picture_header &&
          ((packet.payload[0].to_int() >> 5) & 3) % 2 == 1 &&
          !map.valid.any(value => value) {
          decode_fail(
            budget,
            MissingReference,
            "Inter picture has no decoded references",
          )
        }
        return None
      }
    }
    decode_context(budget, "reconstruction", Some(packet.payload_offset))
    let decoded = match
      av1_decode_parsed_frame_planes(header, seq, map, budget~) {
      Some(value) => value
      None => return None
    }
    if header.show_frame {
      let frame = match header.film_grain {
        None => decoded
        Some(params) => {
          let frame = match
            av1_frame_apply_grain(
              decoded,
              params,
              seq.monochrome,
              seq.subsampling_x,
              seq.subsampling_y,
            ) {
            Some(value) => value
            None => return None
          }
          frame
        }
      }
      if packet.spatial_id >= selected_spatial &&
        (spatial_layer is None || spatial_layer == Some(packet.spatial_id)) {
        selected = Some(frame)
        selected_spatial = packet.spatial_id
      }
    }
  }
  match selected {
    Some(frame) => displayed.push(frame)
    None => ()
  }
  match active {
    Some(seq) => Some((seq, displayed))
    None => None
  }
}