// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Can't reasonably expect client code to consume everything that has been parsed.
#![allow(dead_code)]

use std::collections::BTreeMap;
use std::io::Cursor;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::rc::Rc;

use crate::bitstream_utils::BitReader;
use crate::codec::h264::nalu;
use crate::codec::h264::nalu::Header;
use crate::codec::h264::picture::Field;

pub type Nalu<'a> = nalu::Nalu<'a, NaluHeader>;

pub(super) const DEFAULT_4X4_INTRA: [u8; 16] = [
    6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42,
];

pub(super) const DEFAULT_4X4_INTER: [u8; 16] = [
    10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34,
];

pub(super) const DEFAULT_8X8_INTRA: [u8; 64] = [
    6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, 23, 23, 25, 25, 25,
    25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31,
    31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42,
];

pub(super) const DEFAULT_8X8_INTER: [u8; 64] = [
    9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 22, 22, 22,
    22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27,
    27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35,
];

const MAX_PPS_COUNT: u16 = 256;
const MAX_SPS_COUNT: u8 = 32;

/// The maximum number of pictures in the DPB, as per A.3.1, clause h)
const DPB_MAX_SIZE: usize = 16;

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Point<T> {
    pub x: T,
    pub y: T,
}

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Rect<T> {
    pub min: Point<T>,
    pub max: Point<T>,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum NaluType {
    Unknown = 0,
    Slice = 1,
    SliceDpa = 2,
    SliceDpb = 3,
    SliceDpc = 4,
    SliceIdr = 5,
    Sei = 6,
    Sps = 7,
    Pps = 8,
    AuDelimiter = 9,
    SeqEnd = 10,
    StreamEnd = 11,
    FillerData = 12,
    SpsExt = 13,
    PrefixUnit = 14,
    SubsetSps = 15,
    DepthSps = 16,
    SliceAux = 19,
    SliceExt = 20,
    SliceDepth = 21,
}

impl TryFrom<u8> for NaluType {
    type Error = String;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(NaluType::Unknown),
            1 => Ok(NaluType::Slice),
            2 => Ok(NaluType::SliceDpa),
            3 => Ok(NaluType::SliceDpb),
            4 => Ok(NaluType::SliceDpc),
            5 => Ok(NaluType::SliceIdr),
            6 => Ok(NaluType::Sei),
            7 => Ok(NaluType::Sps),
            8 => Ok(NaluType::Pps),
            9 => Ok(NaluType::AuDelimiter),
            10 => Ok(NaluType::SeqEnd),
            11 => Ok(NaluType::StreamEnd),
            12 => Ok(NaluType::FillerData),
            13 => Ok(NaluType::SpsExt),
            14 => Ok(NaluType::PrefixUnit),
            15 => Ok(NaluType::SubsetSps),
            16 => Ok(NaluType::DepthSps),
            19 => Ok(NaluType::SliceAux),
            20 => Ok(NaluType::SliceExt),
            21 => Ok(NaluType::SliceDepth),
            _ => Err(format!("Invalid NaluType {}", value)),
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RefPicListModification {
    pub modification_of_pic_nums_idc: u8,
    /* if modification_of_pic_nums_idc == 0 || 1 */
    pub abs_diff_pic_num_minus1: u32,
    /* if modification_of_pic_nums_idc == 2 */
    pub long_term_pic_num: u32,
    /* if modification_of_pic_nums_idc == 4 || 5 */
    pub abs_diff_view_idx_minus1: u32,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PredWeightTable {
    pub luma_log2_weight_denom: u8,
    pub chroma_log2_weight_denom: u8,

    pub luma_weight_l0: [i16; 32],
    pub luma_offset_l0: [i8; 32],

    /* if seq->ChromaArrayType != 0 */
    pub chroma_weight_l0: [[i16; 2]; 32],
    pub chroma_offset_l0: [[i8; 2]; 32],

    /* if slice->slice_type % 5 == 1 */
    pub luma_weight_l1: [i16; 32],
    pub luma_offset_l1: [i16; 32],

    /* and if seq->ChromaArrayType != 0 */
    pub chroma_weight_l1: [[i16; 2]; 32],
    pub chroma_offset_l1: [[i8; 2]; 32],
}

/// Representation of `MaxLongTermFrameIdx`.
///
/// `MaxLongTermFrameIdx` is derived from `max_long_term_frame_idx_plus1`, an unsigned integer with
/// a special value indicating "no long-term frame indices". This type allows easy conversion
/// between the actual and "plus1" representation, while ensuring that the special value is always
/// handled by the code.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum MaxLongTermFrameIdx {
    #[default]
    NoLongTermFrameIndices,
    Idx(u32),
}

impl MaxLongTermFrameIdx {
    /// Create a value from `max_long_term_frame_idx_plus1`.
    pub fn from_value_plus1(max_long_term_frame_idx_plus1: u32) -> Self {
        match max_long_term_frame_idx_plus1 {
            0 => Self::NoLongTermFrameIndices,
            i @ 1.. => Self::Idx(i - 1),
        }
    }

    /// Convert this value to the representation used by `max_long_term_frame_idx_plus1`.
    pub fn to_value_plus1(self) -> u32 {
        match self {
            Self::NoLongTermFrameIndices => 0,
            Self::Idx(i) => i + 1,
        }
    }
}

impl PartialEq<u32> for MaxLongTermFrameIdx {
    fn eq(&self, other: &u32) -> bool {
        match self {
            MaxLongTermFrameIdx::NoLongTermFrameIndices => false,
            MaxLongTermFrameIdx::Idx(idx) => idx.eq(other),
        }
    }
}

impl PartialOrd<u32> for MaxLongTermFrameIdx {
    fn partial_cmp(&self, other: &u32) -> Option<std::cmp::Ordering> {
        match self {
            MaxLongTermFrameIdx::NoLongTermFrameIndices => Some(std::cmp::Ordering::Less),
            MaxLongTermFrameIdx::Idx(idx) => Some(idx.cmp(other)),
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RefPicMarkingInner {
    /// Specifies a control operation to be applied to affect the reference
    /// picture marking. The `memory_management_control_operation` syntax element
    /// is followed by data necessary for the operation specified by the value
    /// of `memory_management_control_operation`. The values and control
    /// operations associated with `memory_management_control_operation` are
    /// specified in Table 7-9
    pub memory_management_control_operation: u8,

    /// Used (with memory_management_control_operation equal to 3 or 1) to
    /// assign a long-term frame index to a short-term reference picture or to
    /// mark a short-term reference picture as "unused for reference".
    pub difference_of_pic_nums_minus1: u32,

    /// Used (with memory_management_control_operation equal to 2) to mark a
    /// long-term reference picture as "unused for reference".
    pub long_term_pic_num: u32,

    /// Used (with memory_management_control_operation equal to 3 or 6) to
    /// assign a long-term frame index to a picture.
    pub long_term_frame_idx: u32,

    /// Specifies the maximum value of long-term frame index allowed for
    /// long-term reference pictures (until receipt of another value of
    /// `max_long_term_frame_idx_plus1`).
    pub max_long_term_frame_idx: MaxLongTermFrameIdx,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RefPicMarking {
    /// Specifies how the previously-decoded pictures in the decoded picture
    /// buffer are treated after decoding of an IDR picture. See Annex C.
    pub no_output_of_prior_pics_flag: bool,

    /// If unset, specifies that the MaxLongTermFrameIdx variable is set equal
    /// to "no long-term frame indices" and that the IDR picture is marked as
    /// "used for short-term reference". If set, specifies that the
    /// MaxLongTermFrameIdx variable is set equal to 0 and that the current IDR
    /// picture is marked "used for long-term reference" and is assigned
    /// LongTermFrameIdx equal to 0.
    pub long_term_reference_flag: bool,

    /// Selects the reference picture marking mode of the currently decoded
    /// picture as specified in Table 7-8.
    pub adaptive_ref_pic_marking_mode_flag: bool,

    /// An Vec with additional data used in the marking process.
    pub inner: Vec<RefPicMarkingInner>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SliceHeader {
    /// Specifies the address of the first macroblock in the slice.
    pub first_mb_in_slice: u32,

    /// Specifies the coding type of the slice according to Table 7-6.
    pub slice_type: SliceType,

    // Specifies the picture parameter set in use
    pub pic_parameter_set_id: u8,

    /// Specifies the colour plane associated with the current slice RBSP when
    /// `separate_colour_plane_flag` is set.
    pub colour_plane_id: u8,

    /// Used as an identifier for pictures and shall be represented by
    /// `log2_max_frame_num_minus4 + 4` bits in the bitstream.
    pub frame_num: u16,

    /// If set, specifies that the slice is a slice of a coded field. If not
    /// set, specifies that the slice is a slice of a coded frame.
    pub field_pic_flag: bool,

    /// If set, specifies that the slice is part of a coded bottom field. If not
    /// set, specifies that the picture is a coded top field.
    pub bottom_field_flag: bool,

    /// Identifies an IDR picture. The values of `idr_pic_id` in all the slices
    /// of an IDR picture shall remain unchanged. When two consecutive access
    /// units in decoding order are both IDR access units, the value of
    /// `idr_pic_id` in the slices of the first such IDR access unit shall
    /// differ from the `idr_pic_id` in the second such IDR access unit
    pub idr_pic_id: u16,

    /// Specifies the picture order count modulo `MaxPicOrderCntLsb` for the top
    /// field of a coded frame or for a coded field. The length of the
    /// `pic_order_cnt_lsb` syntax element is
    /// `log2_max_pic_order_cnt_lsb_minus4` + 4 bits.
    pub pic_order_cnt_lsb: u16,

    ///  Specifies the picture order count difference between the bottom field
    ///  and the top field of a coded frame as follows
    pub delta_pic_order_cnt_bottom: i32,

    /// The first entry specifies the picture order count difference from the
    /// expected picture order count for the top field of a coded frame or for a
    /// coded field as specified in clause 8.2.1 The second entry  specifies the
    /// picture order count difference from the expected picture order count for
    /// the bottom field of a coded frame specified in clause 8.2.1.
    pub delta_pic_order_cnt: [i32; 2],

    /// This value is required by V4L2 stateless decode params so it is calculated
    /// by parser while processing slice header.
    pub pic_order_cnt_bit_size: usize,

    /// Shall be equal to 0 for slices and slice data partitions belonging to
    /// the primary coded picture. The value of `redundant_pic_cnt shall` be
    /// greater than 0 for coded slices or coded slice data partitions of a
    /// redundant coded picture
    pub redundant_pic_cnt: u8,

    /// Specifies the method used in the decoding process to derive motion
    /// vectors and reference indices for inter prediction >
    pub direct_spatial_mv_pred_flag: bool,

    /// If set, specifies that the syntax element `num_ref_idx_l0_active_minus1`
    /// is present for P, SP, and B slices and that the syntax element
    /// `num_ref_idx_l1_active_minus1` is present for B slices. If not set,
    /// specifies that the syntax elements `num_ref_idx_l0_active_minus1` and
    /// `num_ref_idx_l1_active_minus1` are not present.
    pub num_ref_idx_active_override_flag: bool,

    /// Specifies the maximum reference index for reference picture list 0 that
    /// shall be used to decode the slice.
    pub num_ref_idx_l0_active_minus1: u8,

    /// Specifies the maximum reference index for reference picture list 1 that
    /// shall be used to decode the slice.
    pub num_ref_idx_l1_active_minus1: u8,

    /// If set, specifies that the syntax element `modification_of_pic_nums_idc`
    /// is present for specifying reference picture list 0. If not set,
    /// specifies that this syntax element is not present.
    pub ref_pic_list_modification_flag_l0: bool,

    /// Reference picture list 0 modification as parsed with the
    /// `ref_pic_list_modification()` process.
    pub ref_pic_list_modification_l0: Vec<RefPicListModification>,

    /// If set, specifies that the syntax element `modification_of_pic_nums_idc`
    /// is present for specifying reference picture list 1. If not set,
    /// specifies that this syntax element is not present.
    pub ref_pic_list_modification_flag_l1: bool,

    /// Reference picture list 1 modification as parsed with the
    /// `ref_pic_list_modification()` process.
    pub ref_pic_list_modification_l1: Vec<RefPicListModification>,

    /// Prediction weight table as parsed using 7.3.3.2
    pub pred_weight_table: PredWeightTable,

    /// Decoded reference picture marking parsed using 7.3.3.3
    pub dec_ref_pic_marking: RefPicMarking,

    /// This value is required by V4L2 stateless decode params so it is calculated
    /// by parser while processing slice header.
    pub dec_ref_pic_marking_bit_size: usize,

    /// Specifies the index for determining the initialization table used in the
    /// initialization process for context variables.
    pub cabac_init_idc: u8,

    /// Specifies the initial value of QP Y to be used for all the macroblocks
    /// in the slice until modified by the value of `mb_qp_delta` in the
    /// macroblock layer. The initial QPY quantization parameter for the slice
    /// is computed using 7-30.
    pub slice_qp_delta: i8,

    /// Specifies the decoding process to be used to decode P macroblocks in an
    /// SP slice.
    pub sp_for_switch_flag: bool,

    /// Specifies the value of QSY for all the macroblocks in SP and SI slices.
    /// The QSY quantization parameter for the slice is computed using 7-31.
    pub slice_qs_delta: i8,

    /// Specifies whether the operation of the deblocking filter shall be
    /// disabled across some block edges of the slice and specifies for which
    /// edges the filtering is disabled.
    pub disable_deblocking_filter_idc: u8,

    /// Specifies the offset used in accessing the α and tC0 deblocking filter
    /// tables for filtering operations controlled by the macroblocks within the
    /// slice. From this value, the offset that shall be applied when addressing
    /// these tables shall be computed using 7-32.
    pub slice_alpha_c0_offset_div2: i8,

    /// Specifies the offset used in accessing the β deblocking filter table for
    /// filtering operations controlled by the macroblocks within the slice.
    /// From this value, the offset that is applied when addressing the β table
    /// of the deblocking filter shall be computed using 7-33.
    pub slice_beta_offset_div2: i8,

    /// Same as `MaxPicNum` in the specification.
    pub max_pic_num: u32,

    /// Size of the slice_header() in bits
    pub header_bit_size: usize,

    /// Number of emulation prevention bytes (EPB) in this slice_header()
    pub n_emulation_prevention_bytes: usize,
}

impl SliceHeader {
    /// Returns the field that is coded by this header.
    pub fn field(&self) -> Field {
        if self.field_pic_flag {
            if self.bottom_field_flag {
                Field::Bottom
            } else {
                Field::Top
            }
        } else {
            Field::Frame
        }
    }
}

pub struct SliceHeaderBuilder(SliceHeader);

impl SliceHeaderBuilder {
    pub fn new(pps: &Pps) -> Self {
        SliceHeaderBuilder(SliceHeader {
            pic_parameter_set_id: pps.pic_parameter_set_id,
            ..Default::default()
        })
    }

    pub fn slice_type(mut self, type_: SliceType) -> Self {
        self.0.slice_type = type_;
        self
    }

    pub fn first_mb_in_slice(mut self, value: u32) -> Self {
        self.0.first_mb_in_slice = value;
        self
    }

    pub fn pic_order_cnt_lsb(mut self, value: u16) -> Self {
        self.0.pic_order_cnt_lsb = value;
        self
    }

    pub fn idr_pic_id(mut self, value: u16) -> Self {
        self.0.idr_pic_id = value;
        self
    }

    pub fn num_ref_idx_active_override_flag(mut self, value: bool) -> Self {
        self.0.num_ref_idx_active_override_flag = value;
        self
    }

    pub fn num_ref_idx_l0_active_minus1(mut self, value: u8) -> Self {
        self = self.num_ref_idx_active_override_flag(true);
        self.0.num_ref_idx_l0_active_minus1 = value;
        self
    }

    pub fn num_ref_idx_l0_active(self, value: u8) -> Self {
        self.num_ref_idx_l0_active_minus1(value - 1)
    }

    pub fn num_ref_idx_l1_active_minus1(mut self, value: u8) -> Self {
        self = self.num_ref_idx_active_override_flag(true);
        self.0.num_ref_idx_l1_active_minus1 = value;
        self
    }

    pub fn num_ref_idx_l1_active(self, value: u8) -> Self {
        self.num_ref_idx_l1_active_minus1(value - 1)
    }

    pub fn build(self) -> SliceHeader {
        self.0
    }
}

/// A H264 slice. An integer number of macroblocks or macroblock pairs ordered
/// consecutively in the raster scan within a particular slice group
pub struct Slice<'a> {
    /// The slice header.
    pub header: SliceHeader,
    /// The NAL unit backing this slice.
    pub nalu: Nalu<'a>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// See table 7-6 in the specification.
pub enum SliceType {
    P = 0,
    B = 1,
    I = 2,
    Sp = 3,
    Si = 4,
}

impl TryFrom<u8> for SliceType {
    type Error = String;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(SliceType::P),
            1 => Ok(SliceType::B),
            2 => Ok(SliceType::I),
            3 => Ok(SliceType::Sp),
            4 => Ok(SliceType::Si),
            _ => Err(format!("Invalid SliceType {}", value)),
        }
    }
}

impl SliceType {
    /// Whether this is a P slice. See table 7-6 in the specification.
    pub fn is_p(&self) -> bool {
        matches!(self, SliceType::P)
    }

    /// Whether this is a B slice. See table 7-6 in the specification.
    pub fn is_b(&self) -> bool {
        matches!(self, SliceType::B)
    }

    /// Whether this is an I slice. See table 7-6 in the specification.
    pub fn is_i(&self) -> bool {
        matches!(self, SliceType::I)
    }

    /// Whether this is a SP slice. See table 7-6 in the specification.
    pub fn is_sp(&self) -> bool {
        matches!(self, SliceType::Sp)
    }

    /// Whether this is a SI slice. See table 7-6 in the specification.
    pub fn is_si(&self) -> bool {
        matches!(self, SliceType::Si)
    }
}

impl Default for SliceType {
    fn default() -> Self {
        Self::P
    }
}

#[derive(Clone, Copy)]
#[repr(u8)]
pub enum Profile {
    Baseline = 66,
    Main = 77,
    Extended = 88,
    High = 100,
    High10 = 110,
    High422P = 122,
}

impl TryFrom<u8> for Profile {
    type Error = String;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            66 => Ok(Profile::Baseline),
            77 => Ok(Profile::Main),
            88 => Ok(Profile::Extended),
            100 => Ok(Profile::High),
            110 => Ok(Profile::High10),
            122 => Ok(Profile::High422P),
            _ => Err(format!("Invalid Profile {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
    #[default]
    L1 = 10,
    L1B = 9,
    L1_1 = 11,
    L1_2 = 12,
    L1_3 = 13,
    L2_0 = 20,
    L2_1 = 21,
    L2_2 = 22,
    L3 = 30,
    L3_1 = 31,
    L3_2 = 32,
    L4 = 40,
    L4_1 = 41,
    L4_2 = 42,
    L5 = 50,
    L5_1 = 51,
    L5_2 = 52,
    L6 = 60,
    L6_1 = 61,
    L6_2 = 62,
}

impl TryFrom<u8> for Level {
    type Error = String;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            10 => Ok(Level::L1),
            9 => Ok(Level::L1B),
            11 => Ok(Level::L1_1),
            12 => Ok(Level::L1_2),
            13 => Ok(Level::L1_3),
            20 => Ok(Level::L2_0),
            21 => Ok(Level::L2_1),
            22 => Ok(Level::L2_2),
            30 => Ok(Level::L3),
            31 => Ok(Level::L3_1),
            32 => Ok(Level::L3_2),
            40 => Ok(Level::L4),
            41 => Ok(Level::L4_1),
            42 => Ok(Level::L4_2),
            50 => Ok(Level::L5),
            51 => Ok(Level::L5_1),
            52 => Ok(Level::L5_2),
            60 => Ok(Level::L6),
            61 => Ok(Level::L6_1),
            62 => Ok(Level::L6_2),
            _ => Err(format!("Invalid Level {}", value)),
        }
    }
}

/// A H264 Sequence Parameter Set. A syntax structure containing syntax elements
/// that apply to zero or more entire coded video sequences as determined by the
/// content of a seq_parameter_set_id syntax element found in the picture
/// parameter set referred to by the pic_parameter_set_id syntax element found
/// in each slice header.
#[derive(Debug, PartialEq, Eq)]
pub struct Sps {
    /// Identifies the sequence parameter set that is referred to by the picture
    /// parameter set
    pub seq_parameter_set_id: u8,

    /// Profile to which the coded video sequence conforms
    pub profile_idc: u8,

    /// Retains the same meaning as in the specification. See 7.4.2.1.1
    pub constraint_set0_flag: bool,
    /// Retains the same meaning as in the specification. See 7.4.2.1.1
    pub constraint_set1_flag: bool,
    /// Retains the same meaning as in the specification. See 7.4.2.1.1
    pub constraint_set2_flag: bool,
    /// Retains the same meaning as in the specification. See 7.4.2.1.1
    pub constraint_set3_flag: bool,
    /// Retains the same meaning as in the specification. See 7.4.2.1.1
    pub constraint_set4_flag: bool,
    /// Retains the same meaning as in the specification. See 7.4.2.1.1
    pub constraint_set5_flag: bool,

    /// Level to which the coded video sequence conforms
    pub level_idc: Level,

    /// Specifies the chroma sampling relative to the luma sampling as specified
    /// in clause 6.2.
    pub chroma_format_idc: u8,

    /// Specifies whether the three colour components of the 4:4:4 chroma format
    /// are coded separately.
    pub separate_colour_plane_flag: bool,

    /// Specifies the bit depth of the samples of the luma array and the value
    /// of the luma quantization parameter range offset QpBdOffsetY. See 7-3 and
    /// 7-4.
    pub bit_depth_luma_minus8: u8,

    /// Specifies the bit depth of the samples of the chroma arrays and the
    /// value of the chroma quantization parameter range offset QpBdOffsetC. See
    /// 7-5 and 7-6.
    pub bit_depth_chroma_minus8: u8,

    /// qpprime_y_zero_transform_bypass_flag equal to 1 specifies that, when
    /// QP′Y is equal to 0, a transform bypass operation for the transform
    /// coefficient decoding process and picture construction process prior to
    /// deblocking filter process as specified in clause 8.5 shall be applied.
    /// qpprime_y_zero_transform_bypass_flag equal to 0 specifies that the
    /// transform coefficient decoding process and picture construction process
    /// prior to deblocking filter process shall not use the transform bypass
    /// operation
    /// QP′Y is defined in 7-38 as QP′Y = QPY + QpBdOffsetY
    pub qpprime_y_zero_transform_bypass_flag: bool,

    /// Whether `seq_scaling_list_present_flag[i]` for i = 0..7 or i = 0..11 is
    /// present or whether the sequence level scaling list shall be specified by
    /// Flat_4x4_16 for i = 0..5 and flat_8x8_16 for i = 6..11
    pub seq_scaling_matrix_present_flag: bool,

    /// 4x4 Scaling list as read with 7.3.2.1.1.1
    pub scaling_lists_4x4: [[u8; 16]; 6],
    /// 8x8 Scaling list as read with 7.3.2.1.1.1
    pub scaling_lists_8x8: [[u8; 64]; 6],

    /// Specifies the value of the variable MaxFrameNum that is used in
    /// frame_num related derivations as follows: MaxFrameNum = 2 ^
    /// (log2_max_frame_num_minus4 + 4 )
    pub log2_max_frame_num_minus4: u8,

    /// Specifies the method to decode picture order count (as specified in
    /// clause 8.2.1)
    pub pic_order_cnt_type: u8,

    /// Specifies the value of the variable MaxPicOrderCntLsb that is used in
    /// the decoding process for picture order count as specified in clause
    /// 8.2.1 as follows: MaxPicOrderCntLsb = 2 ^ (
    /// log2_max_pic_order_cnt_lsb_minus4 + 4 ).
    pub log2_max_pic_order_cnt_lsb_minus4: u8,

    /// If true, specifies that `delta_pic_order_cnt[0]` and
    /// `delta_pic_order_cnt[1]` are not present in the slice headers of the
    /// sequence and shall be inferred to be equal to 0.
    /// If false, specifies that `delta_pic_order_cnt[0]` is present in the
    /// slice headers of the sequence and `delta_pic_order_cnt[1]` may be
    /// present in the slice headers of the sequence.
    pub delta_pic_order_always_zero_flag: bool,

    /// Used to calculate the picture order count of a non-reference picture as
    /// specified in clause 8.2.1.
    pub offset_for_non_ref_pic: i32,

    /// Used to calculate the picture order count of a bottom field as specified
    /// in clause 8.2.1.
    pub offset_for_top_to_bottom_field: i32,

    /// Used in the decoding process for picture order count as specified in
    /// clause 8.2.1
    pub num_ref_frames_in_pic_order_cnt_cycle: u8,

    /// An element of a list of num_ref_frames_in_pic_order_cnt_cycle values
    /// used in the decoding process for picture order count as specified in
    /// clause 8.2.
    pub offset_for_ref_frame: [i32; 255],

    /// Specifies the maximum number of short-term and long-term reference
    /// frames, complementary reference field pairs, and non-paired reference
    /// fields that may be used by the decoding process for inter prediction of
    /// any picture in the coded video sequence. Also
    /// determines the size of the sliding window operation as specified in
    /// clause 8.2.5.3.
    pub max_num_ref_frames: u8,

    /// Specifies the allowed values of frame_num as specified in clause 7.4.3
    /// and the decoding process in case of an inferred gap between values of
    /// frame_num as specified in clause 8.2.5.2
    pub gaps_in_frame_num_value_allowed_flag: bool,

    /// Plus 1 specifies the width of each decoded picture in units of
    /// macroblocks.
    pub pic_width_in_mbs_minus1: u16,
    /// Plus 1 specifies the height in slice group map units of a decoded frame
    /// or field.
    pub pic_height_in_map_units_minus1: u16,

    /// If true,  specifies that every coded picture of the coded video sequence
    /// is a coded frame containing only frame macroblocks, else specifies that
    /// coded pictures of the coded video sequence may either be coded fields or
    /// coded frames.
    pub frame_mbs_only_flag: bool,

    /// If true, specifies the possible use of switching between frame and field
    /// macroblocks within frames, else, specifies no switching between frame
    /// and field macroblocks within a picture.
    pub mb_adaptive_frame_field_flag: bool,

    /// Specifies the method used in the derivation process for luma motion
    /// vectors for B_Skip, B_Direct_16x16 and B_Direct_8x8 as specified in
    /// clause 8.4.1.2.
    pub direct_8x8_inference_flag: bool,

    /// If true, specifies that the frame cropping offset parameters follow next
    /// in the sequence parameter, else specifies that the frame cropping offset
    /// parameters are not present
    pub frame_cropping_flag: bool,

    /// Specify the samples of the pictures in the coded video sequence that are
    /// output from the decoding process, in terms of a rectangular region
    /// specified in frame coordinates for output.
    pub frame_crop_left_offset: u32,
    /// Specify the samples of the pictures in the coded video sequence that are
    /// output from the decoding process, in terms of a rectangular region
    /// specified in frame coordinates for output.
    pub frame_crop_right_offset: u32,
    /// Specify the samples of the pictures in the coded video sequence that are
    /// output from the decoding process, in terms of a rectangular region
    /// specified in frame coordinates for output.
    pub frame_crop_top_offset: u32,
    /// Specify the samples of the pictures in the coded video sequence that are
    /// output from the decoding process, in terms of a rectangular region
    /// specified in frame coordinates for output.
    pub frame_crop_bottom_offset: u32,

    // Calculated
    /// Same as ExpectedDeltaPerPicOrderCntCycle, see 7-12 in the specification.
    pub expected_delta_per_pic_order_cnt_cycle: i32,

    pub vui_parameters_present_flag: bool,
    pub vui_parameters: VuiParams,
}

impl Sps {
    /// Returns the coded width of the stream.
    ///
    /// See 7-13 through 7-17 in the specification.
    pub const fn width(&self) -> u32 {
        (self.pic_width_in_mbs_minus1 as u32 + 1) * 16
    }

    /// Returns the coded height of the stream.
    ///
    /// See 7-13 through 7-17 in the specification.
    pub const fn height(&self) -> u32 {
        (self.pic_height_in_map_units_minus1 as u32 + 1)
            * 16
            * (2 - self.frame_mbs_only_flag as u32)
    }

    /// Returns `ChromaArrayType`, as computed in the specification.
    pub const fn chroma_array_type(&self) -> u8 {
        match self.separate_colour_plane_flag {
            false => self.chroma_format_idc,
            true => 0,
        }
    }

    /// Returns `SubWidthC` and `SubHeightC`.
    ///
    /// See table 6-1 in the specification.
    fn sub_width_height_c(&self) -> (u32, u32) {
        match (self.chroma_format_idc, self.separate_colour_plane_flag) {
            (1, false) => (2, 2),
            (2, false) => (2, 1),
            (3, false) => (1, 1),
            // undefined.
            _ => (1, 1),
        }
    }

    /// Returns `CropUnitX` and `CropUnitY`.
    ///
    /// See 7-19 through 7-22 in the specification.
    fn crop_unit_x_y(&self) -> (u32, u32) {
        match self.chroma_array_type() {
            0 => (1, 2 - u32::from(self.frame_mbs_only_flag)),
            _ => {
                let (sub_width_c, sub_height_c) = self.sub_width_height_c();
                (
                    sub_width_c,
                    sub_height_c * (2 - u32::from(self.frame_mbs_only_flag)),
                )
            }
        }
    }

    /// Same as MaxFrameNum. See 7-10 in the specification.
    pub fn max_frame_num(&self) -> u32 {
        1 << (self.log2_max_frame_num_minus4 + 4)
    }

    pub fn visible_rectangle(&self) -> Rect<u32> {
        if !self.frame_cropping_flag {
            return Rect {
                min: Point { x: 0, y: 0 },
                max: Point {
                    x: self.width(),
                    y: self.height(),
                },
            };
        }

        let (crop_unit_x, crop_unit_y) = self.crop_unit_x_y();

        let crop_left = crop_unit_x * self.frame_crop_left_offset;
        let crop_right = crop_unit_x * self.frame_crop_right_offset;
        let crop_top = crop_unit_y * self.frame_crop_top_offset;
        let crop_bottom = crop_unit_y * self.frame_crop_bottom_offset;

        Rect {
            min: Point {
                x: crop_left,
                y: crop_top,
            },
            max: Point {
                x: self.width() - crop_left - crop_right,
                y: self.height() - crop_top - crop_bottom,
            },
        }
    }

    pub fn max_dpb_frames(&self) -> usize {
        let profile = self.profile_idc;
        let mut level = self.level_idc;

        // A.3.1 and A.3.2: Level 1b for Baseline, Constrained Baseline and Main
        // profile if level_idc == 11 and constraint_set3_flag == 1
        if matches!(level, Level::L1_1)
            && (profile == Profile::Baseline as u8 || profile == Profile::Main as u8)
            && self.constraint_set3_flag
        {
            level = Level::L1B;
        };

        // Table A.1
        let max_dpb_mbs = match level {
            Level::L1 => 396,
            Level::L1B => 396,
            Level::L1_1 => 900,
            Level::L1_2 => 2376,
            Level::L1_3 => 2376,
            Level::L2_0 => 2376,
            Level::L2_1 => 4752,
            Level::L2_2 => 8100,
            Level::L3 => 8100,
            Level::L3_1 => 18000,
            Level::L3_2 => 20480,
            Level::L4 => 32768,
            Level::L4_1 => 32768,
            Level::L4_2 => 34816,
            Level::L5 => 110400,
            Level::L5_1 => 184320,
            Level::L5_2 => 184320,
            Level::L6 => 696320,
            Level::L6_1 => 696320,
            Level::L6_2 => 696320,
        };

        let width_mb = self.width() / 16;
        let height_mb = self.height() / 16;

        let max_dpb_frames =
            std::cmp::min(max_dpb_mbs / (width_mb * height_mb), DPB_MAX_SIZE as u32) as usize;

        let mut max_dpb_frames = std::cmp::max(max_dpb_frames, self.max_num_ref_frames as usize);

        if self.vui_parameters_present_flag && self.vui_parameters.bitstream_restriction_flag {
            max_dpb_frames = std::cmp::max(1, self.vui_parameters.max_dec_frame_buffering as usize);
        }

        max_dpb_frames
    }

    pub fn max_num_order_frames(&self) -> u32 {
        let vui = &self.vui_parameters;
        let present = self.vui_parameters_present_flag && vui.bitstream_restriction_flag;

        if present {
            vui.max_num_reorder_frames
        } else {
            let profile = self.profile_idc;
            if (profile == 44
                || profile == 86
                || profile == 100
                || profile == 110
                || profile == 122
                || profile == 244)
                && self.constraint_set3_flag
            {
                0
            } else {
                self.max_dpb_frames() as u32
            }
        }
    }
}

// TODO: Replace with builder
impl Default for Sps {
    fn default() -> Self {
        Self {
            scaling_lists_4x4: [[0; 16]; 6],
            scaling_lists_8x8: [[0; 64]; 6],
            offset_for_ref_frame: [0; 255],
            seq_parameter_set_id: Default::default(),
            profile_idc: Default::default(),
            constraint_set0_flag: Default::default(),
            constraint_set1_flag: Default::default(),
            constraint_set2_flag: Default::default(),
            constraint_set3_flag: Default::default(),
            constraint_set4_flag: Default::default(),
            constraint_set5_flag: Default::default(),
            level_idc: Default::default(),
            chroma_format_idc: Default::default(),
            separate_colour_plane_flag: Default::default(),
            bit_depth_luma_minus8: Default::default(),
            bit_depth_chroma_minus8: Default::default(),
            qpprime_y_zero_transform_bypass_flag: Default::default(),
            seq_scaling_matrix_present_flag: Default::default(),
            log2_max_frame_num_minus4: Default::default(),
            pic_order_cnt_type: Default::default(),
            log2_max_pic_order_cnt_lsb_minus4: Default::default(),
            delta_pic_order_always_zero_flag: Default::default(),
            offset_for_non_ref_pic: Default::default(),
            offset_for_top_to_bottom_field: Default::default(),
            num_ref_frames_in_pic_order_cnt_cycle: Default::default(),
            max_num_ref_frames: Default::default(),
            gaps_in_frame_num_value_allowed_flag: Default::default(),
            pic_width_in_mbs_minus1: Default::default(),
            pic_height_in_map_units_minus1: Default::default(),
            frame_mbs_only_flag: Default::default(),
            mb_adaptive_frame_field_flag: Default::default(),
            direct_8x8_inference_flag: Default::default(),
            frame_cropping_flag: Default::default(),
            frame_crop_left_offset: Default::default(),
            frame_crop_right_offset: Default::default(),
            frame_crop_top_offset: Default::default(),
            frame_crop_bottom_offset: Default::default(),
            expected_delta_per_pic_order_cnt_cycle: Default::default(),
            vui_parameters_present_flag: Default::default(),
            vui_parameters: Default::default(),
        }
    }
}

#[derive(Default)]
pub struct SpsBuilder(Sps);

impl SpsBuilder {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn seq_parameter_set_id(mut self, value: u8) -> Self {
        self.0.seq_parameter_set_id = value;
        self
    }

    pub fn profile_idc(mut self, value: Profile) -> Self {
        self.0.profile_idc = value as u8;
        self
    }

    pub fn level_idc(mut self, value: Level) -> Self {
        self.0.level_idc = value;
        self
    }

    pub fn frame_crop_offsets(mut self, top: u32, bottom: u32, left: u32, right: u32) -> Self {
        self.0.frame_cropping_flag = true;
        self.0.frame_crop_top_offset = top;
        self.0.frame_crop_bottom_offset = bottom;
        self.0.frame_crop_left_offset = left;
        self.0.frame_crop_right_offset = right;
        self
    }

    pub fn frame_crop(self, top: u32, bottom: u32, left: u32, right: u32) -> Self {
        let sub_width_c = if self.0.chroma_format_idc > 2 { 1 } else { 2 };
        let sub_height_c = if self.0.chroma_format_idc > 1 { 1 } else { 2 };

        let crop_unit_x = sub_width_c;
        let crop_unit_y = sub_height_c * (if self.0.frame_mbs_only_flag { 1 } else { 2 });

        self.frame_crop_offsets(
            top / crop_unit_y,
            bottom / crop_unit_y,
            left / crop_unit_x,
            right / crop_unit_x,
        )
    }

    pub fn resolution(mut self, width: u32, height: u32) -> Self {
        const MB_SIZE: u32 = 16;

        let mb_width = (width + MB_SIZE - 1) / MB_SIZE;
        let mb_height = (height + MB_SIZE - 1) / MB_SIZE;

        self.0.pic_width_in_mbs_minus1 = (mb_width - 1) as u16;
        self.0.pic_height_in_map_units_minus1 = (mb_height - 1) as u16;

        let compressed_width = mb_width * MB_SIZE;
        let compressed_height = mb_height * MB_SIZE;

        if compressed_width != width || compressed_height != height {
            self = self.frame_crop(0, compressed_height - height, 0, compressed_width - width);
        }

        self
    }

    pub fn chroma_format_idc(mut self, value: u8) -> Self {
        self.0.chroma_format_idc = value;
        self
    }

    pub fn max_num_ref_frames(mut self, value: u8) -> Self {
        self.0.max_num_ref_frames = value;
        self
    }

    pub fn frame_mbs_only_flag(mut self, value: bool) -> Self {
        self.0.frame_mbs_only_flag = value;
        self
    }

    pub fn mb_adaptive_frame_field_flag(mut self, value: bool) -> Self {
        self.0.mb_adaptive_frame_field_flag = value;
        self
    }

    pub fn seq_scaling_matrix_present_flag(mut self, value: bool) -> Self {
        self.0.seq_scaling_matrix_present_flag = value;
        self
    }

    pub fn direct_8x8_inference_flag(mut self, value: bool) -> Self {
        self.0.direct_8x8_inference_flag = value;
        self
    }

    pub fn vui_parameters_present(mut self) -> Self {
        if self.0.vui_parameters_present_flag {
            return self;
        }

        self.0.vui_parameters_present_flag = true;
        // Disable all options at default
        self.0.vui_parameters.aspect_ratio_info_present_flag = false;
        self.0.vui_parameters.overscan_info_present_flag = false;
        self.0.vui_parameters.video_signal_type_present_flag = false;
        self.0.vui_parameters.colour_description_present_flag = false;
        self.0.vui_parameters.chroma_loc_info_present_flag = false;
        self.0.vui_parameters.timing_info_present_flag = false;
        self.0.vui_parameters.nal_hrd_parameters_present_flag = false;
        self.0.vui_parameters.vcl_hrd_parameters_present_flag = false;
        self.0.vui_parameters.pic_struct_present_flag = false;
        self.0.vui_parameters.bitstream_restriction_flag = false;
        self
    }

    pub fn aspect_ratio_idc(mut self, value: u8) -> Self {
        self = self.vui_parameters_present();
        self.0.vui_parameters.aspect_ratio_info_present_flag = true;
        self.0.vui_parameters.aspect_ratio_idc = value;
        self
    }

    pub fn sar_resolution(mut self, width: u16, height: u16) -> Self {
        self = self.aspect_ratio_idc(255);
        self.0.vui_parameters.sar_width = width;
        self.0.vui_parameters.sar_height = height;
        self
    }

    pub fn aspect_ratio(self, width_ratio: u16, height_ratio: u16) -> Self {
        // H.264 Table E-1
        match (width_ratio, height_ratio) {
            (1, 1) => self.aspect_ratio_idc(1),
            (12, 11) => self.aspect_ratio_idc(2),
            (10, 11) => self.aspect_ratio_idc(3),
            (16, 11) => self.aspect_ratio_idc(4),
            (40, 33) => self.aspect_ratio_idc(5),
            (24, 11) => self.aspect_ratio_idc(6),
            (20, 11) => self.aspect_ratio_idc(7),
            (32, 11) => self.aspect_ratio_idc(8),
            (80, 33) => self.aspect_ratio_idc(9),
            (18, 11) => self.aspect_ratio_idc(10),
            (15, 11) => self.aspect_ratio_idc(11),
            (64, 33) => self.aspect_ratio_idc(12),
            (160, 99) => self.aspect_ratio_idc(13),
            (4, 3) => self.aspect_ratio_idc(14),
            (3, 2) => self.aspect_ratio_idc(15),
            (2, 1) => self.aspect_ratio_idc(16),

            _ => self.sar_resolution(width_ratio, height_ratio),
        }
    }

    pub fn timing_info(
        mut self,
        num_units_in_tick: u32,
        time_scale: u32,
        fixed_frame_rate_flag: bool,
    ) -> Self {
        self = self.vui_parameters_present();
        self.0.vui_parameters.timing_info_present_flag = true;
        self.0.vui_parameters.num_units_in_tick = num_units_in_tick;
        self.0.vui_parameters.time_scale = time_scale;
        self.0.vui_parameters.fixed_frame_rate_flag = fixed_frame_rate_flag;
        self
    }

    pub fn log2_max_frame_num_minus4(mut self, value: u8) -> Self {
        self.0.log2_max_frame_num_minus4 = value;
        self
    }

    pub fn max_frame_num(self, value: u32) -> Self {
        self.log2_max_frame_num_minus4(value.ilog2() as u8 - 4u8)
    }

    pub fn pic_order_cnt_type(mut self, value: u8) -> Self {
        self.0.pic_order_cnt_type = value;
        self
    }

    pub fn log2_max_pic_order_cnt_lsb_minus4(mut self, value: u8) -> Self {
        self.0.log2_max_pic_order_cnt_lsb_minus4 = value;
        self
    }

    pub fn max_pic_order_cnt_lsb(self, value: u32) -> Self {
        self.log2_max_pic_order_cnt_lsb_minus4(value.ilog2() as u8 - 4u8)
    }

    pub fn delta_pic_order_always_zero_flag(mut self, value: bool) -> Self {
        self.0.delta_pic_order_always_zero_flag = value;
        self
    }

    pub fn bit_depth_chroma_minus8(mut self, value: u8) -> Self {
        self.0.bit_depth_chroma_minus8 = value;
        self
    }

    pub fn bit_depth_chroma(self, value: u8) -> Self {
        self.bit_depth_luma_minus8(value - 8u8)
    }

    pub fn bit_depth_luma_minus8(mut self, value: u8) -> Self {
        self.0.bit_depth_luma_minus8 = value;
        self
    }

    pub fn bit_depth_luma(self, value: u8) -> Self {
        self.bit_depth_luma_minus8(value - 8u8)
    }

    pub fn build(self) -> Rc<Sps> {
        Rc::new(self.0)
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HrdParams {
    /// Plus 1 specifies the number of alternative CPB specifications in the
    /// bitstream. The value of `cpb_cnt_minus1` shall be in the range of 0 to 31,
    /// inclusive
    pub cpb_cnt_minus1: u8,
    /// Together with `bit_rate_value_minus1[ SchedSelIdx ]` specifies the
    /// maximum input bit rate of the `SchedSelIdx`-th CPB.
    pub bit_rate_scale: u8,
    /// Together with `cpb_size_value_minus1[ SchedSelIdx ]` specifies the CPB
    /// size of the SchedSelIdx-th CPB.
    pub cpb_size_scale: u8,

    /// `[ SchedSelIdx ]` (together with bit_rate_scale) specifies the maximum
    /// input bit rate for the SchedSelIdx-th CPB.
    pub bit_rate_value_minus1: [u32; 32],
    /// `[ SchedSelIdx ]` is used together with cpb_size_scale to specify the
    /// SchedSelIdx-th CPB size.
    pub cpb_size_value_minus1: [u32; 32],
    /// `[ SchedSelIdx ]` equal to 0 specifies that to decode this bitstream by
    /// the HRD using the `SchedSelIdx`-th CPB specification, the hypothetical
    /// stream delivery scheduler (HSS) operates in an intermittent bit rate
    /// mode. `cbr_flag[ SchedSelIdx ]` equal to 1 specifies that the HSS operates
    /// in a constant bit rate (CBR) mode
    pub cbr_flag: [bool; 32],

    /// Specifies the length in bits of the `initial_cpb_removal_delay[
    /// SchedSelIdx ]` and `initial_cpb_removal_delay_offset[ SchedSelIdx ]` syntax
    /// elements of the buffering period SEI message.
    pub initial_cpb_removal_delay_length_minus1: u8,
    /// Specifies the length in bits of the `cpb_removal_delay` syntax element.
    pub cpb_removal_delay_length_minus1: u8,
    /// Specifies the length in bits of the `dpb_output_delay` syntax element.
    pub dpb_output_delay_length_minus1: u8,
    /// If greater than 0, specifies the length in bits of the `time_offset`
    /// syntax element. `time_offset_length` equal to 0 specifies that the
    /// `time_offset` syntax element is not present
    pub time_offset_length: u8,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VuiParams {
    /// Specifies whether `aspect_ratio_idc` is present.
    pub aspect_ratio_info_present_flag: bool,
    /// Specifies the value of the sample aspect ratio of the luma samples.
    /// Table E-1 shows the meaning of the code. When aspect_ratio_idc indicates
    /// Extended_SAR, the sample aspect ratio is represented by sar_width :
    /// sar_height. When the aspect_ratio_idc syntax element is not present,
    /// aspect_ratio_idc value shall be inferred to be equal to 0
    pub aspect_ratio_idc: u8,

    /* if aspect_ratio_idc == 255 */
    /// Indicates the horizontal size of the sample aspect ratio (in arbitrary
    /// units)
    pub sar_width: u16,
    /// Indicates the vertical size of the sample aspect ratio (in the same
    /// arbitrary units as sar_width).
    pub sar_height: u16,

    /// If true specifies that the overscan_appropriate_flag is present. Else,
    /// the preferred display method for the video signal is unspecified
    pub overscan_info_present_flag: bool,
    /* if overscan_info_present_flag */
    /// If true, indicates that the cropped decoded pictures output are suitable
    /// for display using overscan. Else, indicates that the cropped decoded
    /// pictures output contain visually important information in the entire
    /// region out to the edges of the cropping rectangle of the picture, such
    /// that the cropped decoded pictures output should not be displayed using
    /// overscan.
    pub overscan_appropriate_flag: bool,

    /// Specifies that video_format, video_full_range_flag and
    /// colour_description_present_flag are present
    pub video_signal_type_present_flag: bool,
    /// Indicates the representation of the pictures as specified in Table E-2,
    /// before being coded in accordance with this Recommendation |
    /// International Standard. When the video_format syntax element is not
    /// present, video_format value shall be inferred to be equal to 5.
    pub video_format: u8,
    /// Indicates the black level and range of the luma and chroma signals as
    /// derived from E′Y, E′PB, and E′PR or E′ R, E′G, and E′B real-valued
    /// component signals.
    pub video_full_range_flag: bool,
    /// Specifies that colour_primaries, transfer_characteristics and
    /// matrix_coefficients are present.
    pub colour_description_present_flag: bool,
    /// Indicates the chromaticity coordinates of the source primaries as
    /// specified in Table E-3 in terms of the CIE 1931 definition of x and y as
    /// specified by ISO 11664-1.
    pub colour_primaries: u8,
    /// Retains same meaning as in the specification.
    pub transfer_characteristics: u8,
    /// Describes the matrix coefficients used in deriving luma and chroma
    /// signals from the green, blue, and red, or Y, Z, and X primaries, as
    /// specified in Table E-5.
    pub matrix_coefficients: u8,

    /// Specifies that chroma_sample_loc_type_top_field and
    /// chroma_sample_loc_type_bottom_field are present
    pub chroma_loc_info_present_flag: bool,
    /// Specify the location of chroma samples. See the spec for more details.
    pub chroma_sample_loc_type_top_field: u8,
    /// Specify the location of chroma samples. See the spec for more details.
    pub chroma_sample_loc_type_bottom_field: u8,

    /// Specifies that num_units_in_tick, time_scale and fixed_frame_rate_flag
    /// are present in the bitstream
    pub timing_info_present_flag: bool,
    /* if timing_info_present_flag */
    /// The number of time units of a clock operating at the frequency
    /// time_scale Hz that corresponds to one increment (called a clock tick) of
    /// a clock tick counter
    pub num_units_in_tick: u32,
    /// The number of time units that pass in one second. For example, a time
    /// coordinate system that measures time using a 27 MHz clock has a
    /// time_scale of 27 000 000. time_scale shall be greater than 0.
    pub time_scale: u32,
    /// Retains the same meaning as the specification.
    pub fixed_frame_rate_flag: bool,

    /// Specifies that NAL HRD parameters (pertaining to Type II bitstream
    /// conformance) are present.
    pub nal_hrd_parameters_present_flag: bool,
    /* if nal_hrd_parameters_present_flag */
    /// The NAL HDR parameters
    pub nal_hrd_parameters: HrdParams,
    /// Specifies that VCL HRD parameters (pertaining to all bitstream
    /// conformance) are present.
    pub vcl_hrd_parameters_present_flag: bool,
    /* if vcl_hrd_parameters_present_flag */
    /// The VCL HRD parameters
    pub vcl_hrd_parameters: HrdParams,

    /// Specifies the HRD operational mode as specified in Annex C.
    pub low_delay_hrd_flag: bool,

    /// Specifies that picture timing SEI messages (clause D.2.3) are present
    /// that include the pic_struct syntax element.
    pub pic_struct_present_flag: bool,

    /// Specifies that the following coded video sequence bitstream restriction
    /// parameters are present
    pub bitstream_restriction_flag: bool,
    /*  if bitstream_restriction_flag */
    /// If false, indicates that no sample outside the picture boundaries and no
    /// sample at a fractional sample position for which the sample value is
    /// derived using one or more samples outside the picture boundaries is used
    /// for inter prediction of any sample. If true, indicates that one or more
    /// samples outside picture boundaries may be used in inter prediction. When
    /// the motion_vectors_over_pic_boundaries_flag syntax element is not
    /// present, motion_vectors_over_pic_boundaries_flag value shall be inferred
    /// to be true.
    pub motion_vectors_over_pic_boundaries_flag: bool,
    /// Indicates a number of bytes not exceeded by the sum of the sizes of the
    /// VCL NAL units associated with any coded picture in the coded video
    /// sequence.
    pub max_bytes_per_pic_denom: u32,
    /// Indicates an upper bound for the number of coded bits of
    /// macroblock_layer( ) data for any macroblock in any picture of the coded
    /// video sequence
    pub max_bits_per_mb_denom: u32,
    /// Retains the same meaning as the specification.
    pub log2_max_mv_length_horizontal: u32,
    /// Retains the same meaning as the specification.
    pub log2_max_mv_length_vertical: u32,
    /// Indicates an upper bound for the number of frames buffers, in the
    /// decoded picture buffer (DPB), that are required for storing frames,
    /// complementary field pairs, and non-paired fields before output. It is a
    /// requirement of bitstream conformance that the maximum number of frames,
    /// complementary field pairs, or non-paired fields that precede any frame,
    /// complementary field pair, or non-paired field in the coded video
    /// sequence in decoding order and follow it in output order shall be less
    /// than or equal to max_num_reorder_frames. The value of
    /// max_num_reorder_frames shall be in the range of 0 to
    /// max_dec_frame_buffering, inclusive.
    ///
    /// When the max_num_reorder_frames syntax element is not present, the value
    /// of max_num_reorder_frames value shall be inferred as follows:
    /// If profile_idc is equal to 44, 86, 100, 110, 122, or 244 and
    /// constraint_set3_flag is equal to 1, the value of max_num_reorder_frames
    /// shall be inferred to be equal to 0.
    ///
    /// Otherwise (profile_idc is not equal to 44, 86, 100, 110, 122, or 244 or
    /// constraint_set3_flag is equal to 0), the value of max_num_reorder_frames
    /// shall be inferred to be equal to MaxDpbFrames.
    pub max_num_reorder_frames: u32,
    /// Specifies the required size of the HRD decoded picture buffer (DPB) in
    /// units of frame buffers. It is a requirement of bitstream conformance
    /// that the coded video sequence shall not require a decoded picture buffer
    /// with size of more than Max( 1, max_dec_frame_buffering ) frame buffers
    /// to enable the output of decoded pictures at the output times specified
    /// by dpb_output_delay of the picture timing SEI messages. The value of
    /// max_dec_frame_buffering shall be greater than or equal to
    /// max_num_ref_frames. An upper bound for the value of
    /// max_dec_frame_buffering is specified by the level limits in clauses
    /// A.3.1, A.3.2, G.10.2.1, and H.10.2.
    ///
    /// When the max_dec_frame_buffering syntax element is not present, the
    /// value of max_dec_frame_buffering shall be inferred as follows:
    ///
    /// If profile_idc is equal to 44, 86, 100, 110, 122, or 244 and
    /// constraint_set3_flag is equal to 1, the value of max_dec_frame_buffering
    /// shall be inferred to be equal to 0.
    ///
    /// Otherwise (profile_idc is not equal to 44, 86, 100, 110, 122, or 244 or
    /// constraint_set3_flag is equal to 0), the value of
    /// max_dec_frame_buffering shall be inferred to be equal to MaxDpbFrames.
    pub max_dec_frame_buffering: u32,
}

impl Default for VuiParams {
    fn default() -> Self {
        Self {
            aspect_ratio_info_present_flag: Default::default(),
            aspect_ratio_idc: Default::default(),
            sar_width: Default::default(),
            sar_height: Default::default(),
            overscan_info_present_flag: Default::default(),
            overscan_appropriate_flag: Default::default(),
            video_signal_type_present_flag: Default::default(),
            video_format: 5,
            video_full_range_flag: Default::default(),
            colour_description_present_flag: Default::default(),
            colour_primaries: 2,
            transfer_characteristics: 2,
            matrix_coefficients: 2,
            chroma_loc_info_present_flag: Default::default(),
            chroma_sample_loc_type_top_field: Default::default(),
            chroma_sample_loc_type_bottom_field: Default::default(),
            timing_info_present_flag: Default::default(),
            num_units_in_tick: Default::default(),
            time_scale: Default::default(),
            fixed_frame_rate_flag: Default::default(),
            nal_hrd_parameters_present_flag: Default::default(),
            nal_hrd_parameters: Default::default(),
            vcl_hrd_parameters_present_flag: Default::default(),
            vcl_hrd_parameters: Default::default(),
            low_delay_hrd_flag: Default::default(),
            pic_struct_present_flag: Default::default(),
            bitstream_restriction_flag: Default::default(),
            motion_vectors_over_pic_boundaries_flag: Default::default(),
            max_bytes_per_pic_denom: Default::default(),
            max_bits_per_mb_denom: Default::default(),
            log2_max_mv_length_horizontal: Default::default(),
            log2_max_mv_length_vertical: Default::default(),
            max_num_reorder_frames: Default::default(),
            max_dec_frame_buffering: Default::default(),
        }
    }
}

/// A H264 Picture Parameter Set. A syntax structure containing syntax elements
/// that apply to zero or more entire coded pictures as determined by the
/// `pic_parameter_set_id` syntax element found in each slice header.
#[derive(Debug, PartialEq, Eq)]
pub struct Pps {
    /// Identifies the picture parameter set that is referred to in the slice header.
    pub pic_parameter_set_id: u8,

    /// Refers to the active sequence parameter set.
    pub seq_parameter_set_id: u8,

    /// Selects the entropy decoding method to be applied for the syntax
    /// elements for which two descriptors appear in the syntax tables as
    /// follows: If `entropy_coding_mode_flag` is false, the method specified by
    /// the left descriptor in the syntax table is applied (Exp-Golomb coded,
    /// see clause 9.1 or CAVLC, see clause 9.2). Otherwise
    /// (`entropy_coding_mode_flag` is true), the method specified by the right
    /// descriptor in the syntax table is applied (CABAC, see clause 9.3).
    pub entropy_coding_mode_flag: bool,

    /// If true, specifies that the syntax elements delta_pic_order_cnt_bottom
    /// (when `pic_order_cnt_type` is equal to 0) or `delta_pic_order_cnt[1]`
    /// (when `pic_order_cnt_type` is equal to 1), which are related to picture
    /// order counts for the bottom field of a coded frame, are present in the
    /// slice headers for coded frames as specified in clause 7.3.3. Otherwise,
    /// specifies that the syntax elements `delta_pic_order_cnt_bottom` and
    /// `delta_pic_order_cnt[1]` are not present in the slice headers.
    pub bottom_field_pic_order_in_frame_present_flag: bool,

    /// Plus 1 specifies the number of slice groups for a picture. When
    /// `num_slice_groups_minus1` is equal to 0, all slices of the picture
    /// belong to the same slice group. The allowed range of
    /// `num_slice_groups_minus1` is specified in Annex A.
    pub num_slice_groups_minus1: u32,

    /// Specifies how `num_ref_idx_l0_active_minus1` is inferred for P, SP, and
    /// B slices with `num_ref_idx_active_override_flag` not set.
    pub num_ref_idx_l0_default_active_minus1: u8,

    /// Specifies how `num_ref_idx_l1_active_minus1` is inferred for B slices
    /// with `num_ref_idx_active_override_flag` not set.
    pub num_ref_idx_l1_default_active_minus1: u8,

    /// If not set, specifies that the default weighted prediction shall be
    /// applied to P and SP slices. If set, specifies that explicit weighted
    /// prediction shall be applied to P and SP slices.
    pub weighted_pred_flag: bool,

    /// `weighted_bipred_idc` equal to 0 specifies that the default weighted
    /// prediction shall be applied to B slices. `weighted_bipred_idc` equal to
    /// 1 specifies that explicit weighted prediction shall be applied to B
    /// slices. `weighted_bipred_idc` equal to 2 specifies that implicit
    /// weighted prediction shall be applied to B slices
    pub weighted_bipred_idc: u8,

    /// Specifies the initial value minus 26 of SliceQPY for each slice. The
    /// initial value is modified at the slice layer when a non-zero value of
    /// `slice_qp_delta` is decoded, and is modified further when a non-zero
    /// value of `mb_qp_delta` is decoded at the macroblock layer.
    pub pic_init_qp_minus26: i8,

    /// Specifies the initial value minus 26 of SliceQSY for all macroblocks in
    /// SP or SI slices. The initial value is modified at the slice layer when a
    /// non-zero value of `slice_qs_delta` is decoded.
    pub pic_init_qs_minus26: i8,

    /// Specifies the offset that shall be added to QP Y and QSY for addressing
    /// the table of QPC values for the Cb chroma component.
    pub chroma_qp_index_offset: i8,

    /// If set, specifies that a set of syntax elements controlling the
    /// characteristics of the deblocking filter is present in the slice header.
    /// If not set, specifies that the set of syntax elements controlling the
    /// characteristics of the deblocking filter is not present in the slice
    /// headers and their inferred values are in effect.
    pub deblocking_filter_control_present_flag: bool,

    /// If not set, specifies that intra prediction allows usage of residual
    /// data and decoded samples of neighbouring macroblocks coded using Inter
    /// macroblock prediction modes for the prediction of macroblocks coded
    /// using Intra macroblock prediction modes. If set, specifies constrained
    /// intra prediction, in which case prediction of macroblocks coded using
    /// Intra macroblock prediction modes only uses residual data and decoded
    /// samples from I or SI macroblock types.
    pub constrained_intra_pred_flag: bool,

    /// If not set, specifies that the `redundant_pic_cnt` syntax element is not
    /// present in slice headers, coded slice data partition B NAL units, and
    /// coded slice data partition C NAL units that refer (either directly or by
    /// association with a corresponding coded slice data partition A NAL unit)
    /// to the picture parameter set. If set, specifies that the
    /// `redundant_pic_cnt` syntax element is present in all slice headers,
    /// coded slice data partition B NAL units, and coded slice data partition C
    /// NAL units that refer (either directly or by association with a
    /// corresponding coded slice data partition A NAL unit) to the picture
    /// parameter set.
    pub redundant_pic_cnt_present_flag: bool,

    /// If set, specifies that the 8x8 transform decoding process may be in use
    /// (see clause 8.5). If not set, specifies that the 8x8 transform decoding
    /// process is not in use.
    pub transform_8x8_mode_flag: bool,

    ///  If set, specifies that parameters are present to modify the scaling
    ///  lists specified in the sequence parameter set. If not set, specifies
    ///  that the scaling lists used for the picture shall be inferred to be
    ///  equal to those specified by the sequence parameter set.
    pub pic_scaling_matrix_present_flag: bool,

    /// 4x4 Scaling list as read with 7.3.2.1.1.1
    pub scaling_lists_4x4: [[u8; 16]; 6],
    /// 8x8 Scaling list as read with 7.3.2.1.1.1
    pub scaling_lists_8x8: [[u8; 64]; 6],

    /// Specifies the offset that shall be added to QPY and QSY for addressing
    /// the table of QPC values for the Cr chroma component. When
    /// `second_chroma_qp_index_offset` is not present, it shall be inferred to be
    /// equal to `chroma_qp_index_offset`.
    pub second_chroma_qp_index_offset: i8,

    /// The SPS referenced by this PPS.
    pub sps: Rc<Sps>,
}

pub struct PpsBuilder(Pps);

impl PpsBuilder {
    pub fn new(sps: Rc<Sps>) -> Self {
        PpsBuilder(Pps {
            pic_parameter_set_id: 0,
            seq_parameter_set_id: sps.seq_parameter_set_id,
            entropy_coding_mode_flag: false,
            bottom_field_pic_order_in_frame_present_flag: false,
            num_slice_groups_minus1: 0,
            num_ref_idx_l0_default_active_minus1: 0,
            num_ref_idx_l1_default_active_minus1: 0,
            weighted_pred_flag: false,
            weighted_bipred_idc: 0,
            pic_init_qp_minus26: 0,
            pic_init_qs_minus26: 0,
            chroma_qp_index_offset: 0,
            deblocking_filter_control_present_flag: false,
            constrained_intra_pred_flag: false,
            redundant_pic_cnt_present_flag: false,
            transform_8x8_mode_flag: false,
            pic_scaling_matrix_present_flag: false,
            scaling_lists_4x4: [[0; 16]; 6],
            scaling_lists_8x8: [[0; 64]; 6],
            second_chroma_qp_index_offset: 0,
            sps,
        })
    }

    pub fn pic_parameter_set_id(mut self, value: u8) -> Self {
        self.0.pic_parameter_set_id = value;
        self
    }

    pub fn pic_init_qp_minus26(mut self, value: i8) -> Self {
        self.0.pic_init_qp_minus26 = value;
        self
    }

    pub fn pic_init_qp(self, value: u8) -> Self {
        self.pic_init_qp_minus26(value as i8 - 26)
    }

    pub fn deblocking_filter_control_present_flag(mut self, value: bool) -> Self {
        self.0.deblocking_filter_control_present_flag = value;
        self
    }

    pub fn num_ref_idx_l0_default_active_minus1(mut self, value: u8) -> Self {
        self.0.num_ref_idx_l0_default_active_minus1 = value;
        self
    }

    pub fn num_ref_idx_l0_default_active(self, value: u8) -> Self {
        self.num_ref_idx_l0_default_active_minus1(value - 1)
    }

    pub fn num_ref_idx_l1_default_active_minus1(mut self, value: u8) -> Self {
        self.0.num_ref_idx_l1_default_active_minus1 = value;
        self
    }

    pub fn num_ref_idx_l1_default_active(self, value: u8) -> Self {
        self.num_ref_idx_l1_default_active_minus1(value - 1)
    }

    pub fn build(self) -> Rc<Pps> {
        Rc::new(self.0)
    }
}

#[derive(Debug, Default)]
pub struct Parser {
    active_spses: BTreeMap<u8, Rc<Sps>>,
    active_ppses: BTreeMap<u8, Rc<Pps>>,
}

impl Parser {
    fn fill_default_scaling_list_4x4(scaling_list4x4: &mut [u8; 16], i: usize) {
        // See table 7.2 in the spec.
        assert!(i < 6);
        if i < 3 {
            *scaling_list4x4 = DEFAULT_4X4_INTRA;
        } else if i < 6 {
            *scaling_list4x4 = DEFAULT_4X4_INTER;
        }
    }

    fn fill_default_scaling_list_8x8(scaling_list8x8: &mut [u8; 64], i: usize) {
        assert!(i < 6);
        if i % 2 == 0 {
            *scaling_list8x8 = DEFAULT_8X8_INTRA;
        } else {
            *scaling_list8x8 = DEFAULT_8X8_INTER;
        }
    }

    fn fill_fallback_scaling_list_4x4(
        scaling_list4x4: &mut [[u8; 16]; 6],
        i: usize,
        default_scaling_list_intra: &[u8; 16],
        default_scaling_list_inter: &[u8; 16],
    ) {
        // See table 7.2 in the spec.
        scaling_list4x4[i] = match i {
            0 => *default_scaling_list_intra,
            1 => scaling_list4x4[0],
            2 => scaling_list4x4[1],
            3 => *default_scaling_list_inter,
            4 => scaling_list4x4[3],
            5 => scaling_list4x4[4],
            _ => panic!("Unexpected value {}", i),
        }
    }

    fn fill_fallback_scaling_list_8x8(
        scaling_list8x8: &mut [[u8; 64]; 6],
        i: usize,
        default_scaling_list_intra: &[u8; 64],
        default_scaling_list_inter: &[u8; 64],
    ) {
        // See table 7.2 in the spec.
        scaling_list8x8[i] = match i {
            0 => *default_scaling_list_intra,
            1 => *default_scaling_list_inter,
            2 => scaling_list8x8[0],
            3 => scaling_list8x8[1],
            4 => scaling_list8x8[2],
            5 => scaling_list8x8[3],
            _ => panic!("Unexpected value {}", i),
        }
    }

    fn fill_scaling_list_flat(
        scaling_list4x4: &mut [[u8; 16]; 6],
        scaling_list8x8: &mut [[u8; 64]; 6],
    ) {
        // (7-8) in the spec.
        for outer in scaling_list4x4 {
            for inner in outer {
                *inner = 16;
            }
        }

        // (7-9) in the spec.
        for outer in scaling_list8x8 {
            for inner in outer {
                *inner = 16;
            }
        }
    }

    fn parse_scaling_list<U: AsMut<[u8]>>(
        r: &mut BitReader,
        scaling_list: &mut U,
        use_default: &mut bool,
    ) -> Result<(), String> {
        // 7.3.2.1.1.1
        let mut last_scale = 8u8;
        let mut next_scale = 8u8;

        for j in 0..scaling_list.as_mut().len() {
            if next_scale != 0 {
                let delta_scale = r.read_se::<i32>()?;
                next_scale = ((last_scale as i32 + delta_scale + 256) % 256) as u8;
                *use_default = j == 0 && next_scale == 0;
                if *use_default {
                    return Ok(());
                }
            }

            scaling_list.as_mut()[j] = if next_scale == 0 {
                last_scale
            } else {
                next_scale
            };

            last_scale = scaling_list.as_mut()[j];
        }

        Ok(())
    }

    fn parse_sps_scaling_lists(r: &mut BitReader, sps: &mut Sps) -> Result<(), String> {
        let scaling_lists4x4 = &mut sps.scaling_lists_4x4;
        let scaling_lisst8x8 = &mut sps.scaling_lists_8x8;

        // Parse scaling_list4x4
        for i in 0..6 {
            let seq_scaling_list_present_flag = r.read_bit()?;
            if seq_scaling_list_present_flag {
                let mut use_default = false;

                Parser::parse_scaling_list(r, &mut scaling_lists4x4[i], &mut use_default)?;

                if use_default {
                    Parser::fill_default_scaling_list_4x4(&mut scaling_lists4x4[i], i);
                }
            } else {
                Parser::fill_fallback_scaling_list_4x4(
                    scaling_lists4x4,
                    i,
                    &DEFAULT_4X4_INTRA,
                    &DEFAULT_4X4_INTER,
                );
            }
        }

        // Parse scaling_list8x8
        let num_8x8 = if sps.chroma_format_idc != 3 { 2 } else { 6 };
        for i in 0..num_8x8 {
            let seq_scaling_list_present_flag = r.read_bit()?;
            if seq_scaling_list_present_flag {
                let mut use_default = false;
                Parser::parse_scaling_list(r, &mut scaling_lisst8x8[i], &mut use_default)?;

                if use_default {
                    Parser::fill_default_scaling_list_8x8(&mut scaling_lisst8x8[i], i);
                }
            } else {
                Parser::fill_fallback_scaling_list_8x8(
                    scaling_lisst8x8,
                    i,
                    &DEFAULT_8X8_INTRA,
                    &DEFAULT_8X8_INTER,
                );
            }
        }
        Ok(())
    }

    fn parse_pps_scaling_lists(r: &mut BitReader, pps: &mut Pps, sps: &Sps) -> Result<(), String> {
        let scaling_lists4x4 = &mut pps.scaling_lists_4x4;
        let scaling_lists8x8 = &mut pps.scaling_lists_8x8;

        for i in 0..6 {
            let pic_scaling_list_present_flag = r.read_bit()?;
            if pic_scaling_list_present_flag {
                let mut use_default = false;

                Parser::parse_scaling_list(r, &mut scaling_lists4x4[i], &mut use_default)?;

                if use_default {
                    Parser::fill_default_scaling_list_4x4(&mut scaling_lists4x4[i], i);
                }
            } else if !sps.seq_scaling_matrix_present_flag {
                // Table 7-2: Fallback rule A
                Parser::fill_fallback_scaling_list_4x4(
                    scaling_lists4x4,
                    i,
                    &DEFAULT_4X4_INTRA,
                    &DEFAULT_4X4_INTER,
                );
            } else {
                // Table 7-2: Fallback rule B
                Parser::fill_fallback_scaling_list_4x4(
                    scaling_lists4x4,
                    i,
                    &sps.scaling_lists_4x4[0],
                    &sps.scaling_lists_4x4[3],
                );
            }
        }

        if pps.transform_8x8_mode_flag {
            let num8x8 = if sps.chroma_format_idc != 3 { 2 } else { 6 };

            for i in 0..num8x8 {
                let pic_scaling_list_present_flag = r.read_bit()?;
                if pic_scaling_list_present_flag {
                    let mut use_default = false;

                    Parser::parse_scaling_list(r, &mut scaling_lists8x8[i], &mut use_default)?;

                    if use_default {
                        Parser::fill_default_scaling_list_8x8(&mut scaling_lists8x8[i], i);
                    }
                } else if !sps.seq_scaling_matrix_present_flag {
                    // Table 7-2: Fallback rule A
                    Parser::fill_fallback_scaling_list_8x8(
                        scaling_lists8x8,
                        i,
                        &DEFAULT_8X8_INTRA,
                        &DEFAULT_8X8_INTER,
                    );
                } else {
                    // Table 7-2: Fallback rule B
                    Parser::fill_fallback_scaling_list_8x8(
                        scaling_lists8x8,
                        i,
                        &sps.scaling_lists_8x8[0],
                        &sps.scaling_lists_8x8[1],
                    );
                }
            }
        }

        Ok(())
    }

    fn parse_hrd(r: &mut BitReader, hrd: &mut HrdParams) -> Result<(), String> {
        hrd.cpb_cnt_minus1 = r.read_ue_max(31)?;
        hrd.bit_rate_scale = r.read_bits(4)?;
        hrd.cpb_size_scale = r.read_bits(4)?;

        for sched_sel_idx in 0..=usize::from(hrd.cpb_cnt_minus1) {
            hrd.bit_rate_value_minus1[sched_sel_idx] = r.read_ue()?;
            hrd.cpb_size_value_minus1[sched_sel_idx] = r.read_ue()?;
            hrd.cbr_flag[sched_sel_idx] = r.read_bit()?;
        }

        hrd.initial_cpb_removal_delay_length_minus1 = r.read_bits(5)?;
        hrd.cpb_removal_delay_length_minus1 = r.read_bits(5)?;
        hrd.dpb_output_delay_length_minus1 = r.read_bits(5)?;
        hrd.time_offset_length = r.read_bits(5)?;
        Ok(())
    }

    fn parse_vui(r: &mut BitReader, sps: &mut Sps) -> Result<(), String> {
        let vui = &mut sps.vui_parameters;

        vui.aspect_ratio_info_present_flag = r.read_bit()?;
        if vui.aspect_ratio_info_present_flag {
            vui.aspect_ratio_idc = r.read_bits(8)?;
            if vui.aspect_ratio_idc == 255 {
                vui.sar_width = r.read_bits(16)?;
                vui.sar_height = r.read_bits(16)?;
            }
        }

        vui.overscan_info_present_flag = r.read_bit()?;
        if vui.overscan_info_present_flag {
            vui.overscan_appropriate_flag = r.read_bit()?;
        }

        vui.video_signal_type_present_flag = r.read_bit()?;
        if vui.video_signal_type_present_flag {
            vui.video_format = r.read_bits(3)?;
            vui.video_full_range_flag = r.read_bit()?;
            vui.colour_description_present_flag = r.read_bit()?;
            if vui.colour_description_present_flag {
                vui.colour_primaries = r.read_bits(8)?;
                vui.transfer_characteristics = r.read_bits(8)?;
                vui.matrix_coefficients = r.read_bits(8)?;
            }
        }

        vui.chroma_loc_info_present_flag = r.read_bit()?;
        if vui.chroma_loc_info_present_flag {
            vui.chroma_sample_loc_type_top_field = r.read_ue_max(5)?;
            vui.chroma_sample_loc_type_bottom_field = r.read_ue_max(5)?;
        }

        vui.timing_info_present_flag = r.read_bit()?;
        if vui.timing_info_present_flag {
            vui.num_units_in_tick = r.read_bits::<u32>(31)? << 1;
            vui.num_units_in_tick |= r.read_bit()? as u32;
            if vui.num_units_in_tick == 0 {
                return Err("num_units_in_tick == 0, which is not allowed by E.2.1".into());
            }

            vui.time_scale = r.read_bits::<u32>(31)? << 1;
            vui.time_scale |= r.read_bit()? as u32;
            if vui.time_scale == 0 {
                return Err("time_scale == 0, which is not allowed by E.2.1".into());
            }

            vui.fixed_frame_rate_flag = r.read_bit()?;
        }

        vui.nal_hrd_parameters_present_flag = r.read_bit()?;
        if vui.nal_hrd_parameters_present_flag {
            Parser::parse_hrd(r, &mut vui.nal_hrd_parameters)?;
        }

        vui.vcl_hrd_parameters_present_flag = r.read_bit()?;
        if vui.vcl_hrd_parameters_present_flag {
            Parser::parse_hrd(r, &mut vui.vcl_hrd_parameters)?;
        }

        if vui.nal_hrd_parameters_present_flag || vui.vcl_hrd_parameters_present_flag {
            vui.low_delay_hrd_flag = r.read_bit()?;
        }

        vui.pic_struct_present_flag = r.read_bit()?;
        vui.bitstream_restriction_flag = r.read_bit()?;

        if vui.bitstream_restriction_flag {
            vui.motion_vectors_over_pic_boundaries_flag = r.read_bit()?;
            vui.max_bytes_per_pic_denom = r.read_ue()?;
            vui.max_bits_per_mb_denom = r.read_ue_max(16)?;
            vui.log2_max_mv_length_horizontal = r.read_ue_max(16)?;
            vui.log2_max_mv_length_vertical = r.read_ue_max(16)?;
            vui.max_num_reorder_frames = r.read_ue()?;
            vui.max_dec_frame_buffering = r.read_ue()?;
        }

        Ok(())
    }

    /// Parse a SPS and add it to the list of active SPSes.
    ///
    /// Returns a reference to the new SPS.
    pub fn parse_sps(&mut self, nalu: &Nalu) -> Result<&Rc<Sps>, String> {
        if !matches!(nalu.header.type_, NaluType::Sps) {
            return Err(format!(
                "Invalid NALU type, expected {:?}, got {:?}",
                NaluType::Sps,
                nalu.header.type_
            ));
        }

        let data = nalu.as_ref();
        // Skip the header
        let mut r = BitReader::new(&data[nalu.header.len()..], true);
        let mut sps = Sps {
            profile_idc: r.read_bits(8)?,
            constraint_set0_flag: r.read_bit()?,
            constraint_set1_flag: r.read_bit()?,
            constraint_set2_flag: r.read_bit()?,
            constraint_set3_flag: r.read_bit()?,
            constraint_set4_flag: r.read_bit()?,
            constraint_set5_flag: r.read_bit()?,
            ..Default::default()
        };

        // skip reserved_zero_2bits
        r.skip_bits(2)?;

        let level: u8 = r.read_bits(8)?;
        sps.level_idc = Level::try_from(level)?;
        sps.seq_parameter_set_id = r.read_ue_max(31)?;

        if sps.profile_idc == 100
            || sps.profile_idc == 110
            || sps.profile_idc == 122
            || sps.profile_idc == 244
            || sps.profile_idc == 44
            || sps.profile_idc == 83
            || sps.profile_idc == 86
            || sps.profile_idc == 118
            || sps.profile_idc == 128
            || sps.profile_idc == 138
            || sps.profile_idc == 139
            || sps.profile_idc == 134
            || sps.profile_idc == 135
        {
            sps.chroma_format_idc = r.read_ue_max(3)?;
            if sps.chroma_format_idc == 3 {
                sps.separate_colour_plane_flag = r.read_bit()?;
            }

            sps.bit_depth_luma_minus8 = r.read_ue_max(6)?;
            sps.bit_depth_chroma_minus8 = r.read_ue_max(6)?;
            sps.qpprime_y_zero_transform_bypass_flag = r.read_bit()?;
            sps.seq_scaling_matrix_present_flag = r.read_bit()?;

            if sps.seq_scaling_matrix_present_flag {
                Parser::parse_sps_scaling_lists(&mut r, &mut sps)?;
            } else {
                Parser::fill_scaling_list_flat(
                    &mut sps.scaling_lists_4x4,
                    &mut sps.scaling_lists_8x8,
                );
            }
        } else {
            sps.chroma_format_idc = 1;
            Parser::fill_scaling_list_flat(&mut sps.scaling_lists_4x4, &mut sps.scaling_lists_8x8);
        }

        sps.log2_max_frame_num_minus4 = r.read_ue_max(12)?;

        sps.pic_order_cnt_type = r.read_ue_max(2)?;

        if sps.pic_order_cnt_type == 0 {
            sps.log2_max_pic_order_cnt_lsb_minus4 = r.read_ue_max(12)?;
            sps.expected_delta_per_pic_order_cnt_cycle = 0;
        } else if sps.pic_order_cnt_type == 1 {
            sps.delta_pic_order_always_zero_flag = r.read_bit()?;
            sps.offset_for_non_ref_pic = r.read_se()?;
            sps.offset_for_top_to_bottom_field = r.read_se()?;
            sps.num_ref_frames_in_pic_order_cnt_cycle = r.read_ue_max(254)?;

            let mut offset_acc = 0;
            for i in 0..usize::from(sps.num_ref_frames_in_pic_order_cnt_cycle) {
                sps.offset_for_ref_frame[i] = r.read_se()?;

                // (7-12) in the spec.
                offset_acc += sps.offset_for_ref_frame[i];
            }

            sps.expected_delta_per_pic_order_cnt_cycle = offset_acc;
        }

        sps.max_num_ref_frames = r.read_ue_max(DPB_MAX_SIZE as u32)?;
        sps.gaps_in_frame_num_value_allowed_flag = r.read_bit()?;
        sps.pic_width_in_mbs_minus1 = r.read_ue()?;
        sps.pic_height_in_map_units_minus1 = r.read_ue()?;
        sps.frame_mbs_only_flag = r.read_bit()?;

        if !sps.frame_mbs_only_flag {
            sps.mb_adaptive_frame_field_flag = r.read_bit()?;
        }

        sps.direct_8x8_inference_flag = r.read_bit()?;
        sps.frame_cropping_flag = r.read_bit()?;

        if sps.frame_cropping_flag {
            sps.frame_crop_left_offset = r.read_ue()?;
            sps.frame_crop_right_offset = r.read_ue()?;
            sps.frame_crop_top_offset = r.read_ue()?;
            sps.frame_crop_bottom_offset = r.read_ue()?;

            // Validate that cropping info is valid.
            let (crop_unit_x, crop_unit_y) = sps.crop_unit_x_y();

            let _ = sps
                .frame_crop_left_offset
                .checked_add(sps.frame_crop_right_offset)
                .and_then(|r| r.checked_mul(crop_unit_x))
                .and_then(|r| sps.width().checked_sub(r))
                .ok_or::<String>("Invalid frame crop width".into())?;

            let _ = sps
                .frame_crop_top_offset
                .checked_add(sps.frame_crop_bottom_offset)
                .and_then(|r| r.checked_mul(crop_unit_y))
                .and_then(|r| sps.height().checked_sub(r))
                .ok_or::<String>("invalid frame crop height".into())?;
        }

        sps.vui_parameters_present_flag = r.read_bit()?;
        if sps.vui_parameters_present_flag {
            Parser::parse_vui(&mut r, &mut sps)?;
        }

        let key = sps.seq_parameter_set_id;

        if self.active_spses.keys().len() >= MAX_SPS_COUNT as usize {
            return Err("Broken data: Number of active SPSs > MAX_SPS_COUNT".into());
        }

        let sps = Rc::new(sps);
        self.active_spses.remove(&key);
        Ok(self.active_spses.entry(key).or_insert(sps))
    }

    pub fn parse_pps(&mut self, nalu: &Nalu) -> Result<&Pps, String> {
        if !matches!(nalu.header.type_, NaluType::Pps) {
            return Err(format!(
                "Invalid NALU type, expected {:?}, got {:?}",
                NaluType::Pps,
                nalu.header.type_
            ));
        }

        let data = nalu.as_ref();
        // Skip the header
        let mut r = BitReader::new(&data[nalu.header.len()..], true);
        let pic_parameter_set_id = r.read_ue_max(MAX_PPS_COUNT as u32 - 1)?;
        let seq_parameter_set_id = r.read_ue_max(MAX_SPS_COUNT as u32 - 1)?;
        let sps = self.get_sps(seq_parameter_set_id).ok_or::<String>(format!(
            "Could not get SPS for seq_parameter_set_id {}",
            seq_parameter_set_id
        ))?;
        let mut pps = Pps {
            pic_parameter_set_id,
            seq_parameter_set_id,
            sps: Rc::clone(sps),
            scaling_lists_4x4: [[0; 16]; 6],
            scaling_lists_8x8: [[0; 64]; 6],
            entropy_coding_mode_flag: Default::default(),
            bottom_field_pic_order_in_frame_present_flag: Default::default(),
            num_slice_groups_minus1: Default::default(),
            num_ref_idx_l0_default_active_minus1: Default::default(),
            num_ref_idx_l1_default_active_minus1: Default::default(),
            weighted_pred_flag: Default::default(),
            weighted_bipred_idc: Default::default(),
            pic_init_qp_minus26: Default::default(),
            pic_init_qs_minus26: Default::default(),
            chroma_qp_index_offset: Default::default(),
            deblocking_filter_control_present_flag: Default::default(),
            constrained_intra_pred_flag: Default::default(),
            redundant_pic_cnt_present_flag: Default::default(),
            transform_8x8_mode_flag: Default::default(),
            second_chroma_qp_index_offset: Default::default(),
            pic_scaling_matrix_present_flag: Default::default(),
        };

        pps.entropy_coding_mode_flag = r.read_bit()?;
        pps.bottom_field_pic_order_in_frame_present_flag = r.read_bit()?;
        pps.num_slice_groups_minus1 = r.read_ue_max(7)?;

        if pps.num_slice_groups_minus1 > 0 {
            return Err("Stream contain unsupported/unimplemented NALs".into());
        }

        pps.num_ref_idx_l0_default_active_minus1 = r.read_ue_max(31)?;
        pps.num_ref_idx_l1_default_active_minus1 = r.read_ue_max(31)?;

        pps.weighted_pred_flag = r.read_bit()?;
        pps.weighted_bipred_idc = r.read_bits(2)?;

        let qp_bd_offset_y = i32::from(6 * (sps.bit_depth_luma_minus8));
        pps.pic_init_qp_minus26 = r.read_se_bounded(-(26 + qp_bd_offset_y), 25)?;
        pps.pic_init_qs_minus26 = r.read_se_bounded(-26, 25)?;

        pps.chroma_qp_index_offset = r.read_se_bounded(-12, 12)?;

        // When second_chroma_qp_index_offset is not present, it shall be
        // inferred to be equal to chroma_qp_index_offset.
        pps.second_chroma_qp_index_offset = pps.chroma_qp_index_offset;

        pps.deblocking_filter_control_present_flag = r.read_bit()?;
        pps.constrained_intra_pred_flag = r.read_bit()?;
        pps.redundant_pic_cnt_present_flag = r.read_bit()?;

        if r.has_more_rsbp_data() {
            pps.transform_8x8_mode_flag = r.read_bit()?;
            pps.pic_scaling_matrix_present_flag = r.read_bit()?;

            if pps.pic_scaling_matrix_present_flag {
                Parser::parse_pps_scaling_lists(&mut r, &mut pps, sps)?;
            }

            pps.second_chroma_qp_index_offset = r.read_se()?;
        }

        if !pps.pic_scaling_matrix_present_flag {
            // If not set, specifies that the scaling lists used for the picture
            // shall be inferred to be equal to those specified by the sequence
            // parameter set. When pic_scaling_matrix_present_flag is not
            // present, it shall be inferred to be not set.
            pps.scaling_lists_4x4 = sps.scaling_lists_4x4;
            pps.scaling_lists_8x8 = sps.scaling_lists_8x8;
        }

        let key = pps.pic_parameter_set_id;

        if self.active_ppses.keys().len() >= MAX_PPS_COUNT as usize {
            return Err("Broken Data: number of active PPSs > MAX_PPS_COUNT".into());
        }

        let pps = Rc::new(pps);
        self.active_ppses.remove(&key);
        Ok(self.active_ppses.entry(key).or_insert(pps))
    }

    fn parse_ref_pic_list_modification(
        r: &mut BitReader,
        num_ref_idx_active_minus1: u8,
        ref_list_mods: &mut Vec<RefPicListModification>,
    ) -> Result<(), String> {
        if num_ref_idx_active_minus1 >= 32 {
            return Err("Broken Data: num_ref_idx_active_minus1 >= 32".into());
        }

        loop {
            let mut pic_num_mod = RefPicListModification {
                modification_of_pic_nums_idc: r.read_ue_max(3)?,
                ..Default::default()
            };

            match pic_num_mod.modification_of_pic_nums_idc {
                0 | 1 => {
                    pic_num_mod.abs_diff_pic_num_minus1 = r.read_ue()?;
                }

                2 => {
                    pic_num_mod.long_term_pic_num = r.read_ue()?;
                }

                3 => {
                    ref_list_mods.push(pic_num_mod);
                    break;
                }

                _ => return Err("Broken Data: modification_of_pic_nums_idc > 3".into()),
            }

            ref_list_mods.push(pic_num_mod);
        }

        Ok(())
    }

    fn parse_ref_pic_list_modifications(
        r: &mut BitReader,
        header: &mut SliceHeader,
    ) -> Result<(), String> {
        if !header.slice_type.is_i() && !header.slice_type.is_si() {
            header.ref_pic_list_modification_flag_l0 = r.read_bit()?;
            if header.ref_pic_list_modification_flag_l0 {
                Parser::parse_ref_pic_list_modification(
                    r,
                    header.num_ref_idx_l0_active_minus1,
                    &mut header.ref_pic_list_modification_l0,
                )?;
            }
        }

        if header.slice_type.is_b() {
            header.ref_pic_list_modification_flag_l1 = r.read_bit()?;
            if header.ref_pic_list_modification_flag_l1 {
                Parser::parse_ref_pic_list_modification(
                    r,
                    header.num_ref_idx_l1_active_minus1,
                    &mut header.ref_pic_list_modification_l1,
                )?;
            }
        }

        Ok(())
    }

    fn parse_pred_weight_table(
        r: &mut BitReader,
        sps: &Sps,
        header: &mut SliceHeader,
    ) -> Result<(), String> {
        let pt = &mut header.pred_weight_table;
        pt.luma_log2_weight_denom = r.read_ue_max(7)?;

        // When luma_weight_l0_flag is equal to 0, luma_weight_l0[i] shall be
        // inferred to be equal to 2 ^ luma_log2_weight_denom for
        // RefPicList0[i].
        let default_luma_weight = 1 << pt.luma_log2_weight_denom;
        for i in 0..=header.num_ref_idx_l0_active_minus1 {
            pt.luma_weight_l0[usize::from(i)] = default_luma_weight;
        }

        // When luma_weight_l1_flag is equal to 1, luma_weight_l1[i] shall be
        // inferred to be equal to 2 ^ luma_log2_weight_denom for
        // RefPicList1[i].
        if header.slice_type.is_b() {
            for i in 0..=header.num_ref_idx_l1_active_minus1 {
                pt.luma_weight_l1[usize::from(i)] = default_luma_weight;
            }
        }

        if sps.chroma_array_type() != 0 {
            pt.chroma_log2_weight_denom = r.read_ue_max(7)?;
            let default_chroma_weight = 1 << pt.chroma_log2_weight_denom;

            // When chroma_weight_l0_flag is equal to 0, chroma_weight_l0[i]
            // shall be inferred to be equal to 2 ^ chroma_log2_weight_denom for
            // RefPicList0[i].
            for i in 0..=header.num_ref_idx_l0_active_minus1 {
                pt.chroma_weight_l0[usize::from(i)][0] = default_chroma_weight;
                pt.chroma_weight_l0[usize::from(i)][1] = default_chroma_weight;
            }

            // When chroma_weight_l1_flag is equal to 0, chroma_weight_l1[i]
            // shall be inferred to be equal to 2 ^ chroma_log2_weight_denom for
            // RefPicList1[i].
            for i in 0..=header.num_ref_idx_l1_active_minus1 {
                pt.chroma_weight_l1[usize::from(i)][0] = default_chroma_weight;
                pt.chroma_weight_l1[usize::from(i)][1] = default_chroma_weight;
            }
        }

        for i in 0..=header.num_ref_idx_l0_active_minus1 {
            let luma_weight_l0_flag = r.read_bit()?;

            if luma_weight_l0_flag {
                pt.luma_weight_l0[usize::from(i)] = r.read_se_bounded(-128, 127)?;
                pt.luma_offset_l0[usize::from(i)] = r.read_se_bounded(-128, 127)?;
            }

            if sps.chroma_array_type() != 0 {
                let chroma_weight_l0_flag = r.read_bit()?;
                if chroma_weight_l0_flag {
                    for j in 0..2 {
                        pt.chroma_weight_l0[usize::from(i)][j] = r.read_se_bounded(-128, 127)?;
                        pt.chroma_offset_l0[usize::from(i)][j] = r.read_se_bounded(-128, 127)?;
                    }
                }
            }
        }

        if header.slice_type.is_b() {
            for i in 0..=header.num_ref_idx_l1_active_minus1 {
                let luma_weight_l1_flag = r.read_bit()?;

                if luma_weight_l1_flag {
                    pt.luma_weight_l1[usize::from(i)] = r.read_se_bounded(-128, 127)?;
                    pt.luma_offset_l1[usize::from(i)] = r.read_se_bounded(-128, 127)?;
                }

                if sps.chroma_array_type() != 0 {
                    let chroma_weight_l1_flag = r.read_bit()?;
                    if chroma_weight_l1_flag {
                        for j in 0..2 {
                            pt.chroma_weight_l1[usize::from(i)][j] =
                                r.read_se_bounded(-128, 127)?;
                            pt.chroma_offset_l1[usize::from(i)][j] =
                                r.read_se_bounded(-128, 127)?;
                        }
                    }
                }
            }
        }

        Ok(())
    }

    fn parse_dec_ref_pic_marking(
        r: &mut BitReader,
        nalu: &Nalu,
        header: &mut SliceHeader,
    ) -> Result<(), String> {
        let rpm = &mut header.dec_ref_pic_marking;

        let num_bits_left = r.num_bits_left();
        if nalu.header.idr_pic_flag {
            rpm.no_output_of_prior_pics_flag = r.read_bit()?;
            rpm.long_term_reference_flag = r.read_bit()?;
        } else {
            rpm.adaptive_ref_pic_marking_mode_flag = r.read_bit()?;

            if rpm.adaptive_ref_pic_marking_mode_flag {
                loop {
                    let mut marking = RefPicMarkingInner::default();

                    let mem_mgmt_ctrl_op = r.read_ue_max::<u8>(6)?;
                    marking.memory_management_control_operation = mem_mgmt_ctrl_op;

                    if mem_mgmt_ctrl_op == 0 {
                        break;
                    }

                    if mem_mgmt_ctrl_op == 1 || mem_mgmt_ctrl_op == 3 {
                        marking.difference_of_pic_nums_minus1 = r.read_ue()?;
                    }

                    if mem_mgmt_ctrl_op == 2 {
                        marking.long_term_pic_num = r.read_ue()?;
                    }

                    if mem_mgmt_ctrl_op == 3 || mem_mgmt_ctrl_op == 6 {
                        marking.long_term_frame_idx = r.read_ue()?;
                    }

                    if mem_mgmt_ctrl_op == 4 {
                        marking.max_long_term_frame_idx =
                            MaxLongTermFrameIdx::from_value_plus1(r.read_ue()?);
                    }

                    rpm.inner.push(marking);
                }
            }
        }
        header.dec_ref_pic_marking_bit_size = num_bits_left - r.num_bits_left();

        Ok(())
    }

    pub fn parse_slice_header<'a>(&self, nalu: Nalu<'a>) -> Result<Slice<'a>, String> {
        if !matches!(
            nalu.header.type_,
            NaluType::Slice
                | NaluType::SliceDpa
                | NaluType::SliceDpb
                | NaluType::SliceDpc
                | NaluType::SliceIdr
                | NaluType::SliceExt
        ) {
            return Err(format!(
                "Invalid NALU type: {:?} is not a slice NALU",
                nalu.header.type_
            ));
        }

        let data = nalu.as_ref();
        // Skip the header
        let mut r = BitReader::new(&data[nalu.header.len()..], true);

        let mut header = SliceHeader {
            first_mb_in_slice: r.read_ue()?,
            ..Default::default()
        };

        let slice_type = r.read_ue_max::<u8>(9)? % 5;
        header.slice_type = SliceType::try_from(slice_type)?;

        header.pic_parameter_set_id = r.read_ue()?;

        let pps = self
            .get_pps(header.pic_parameter_set_id)
            .ok_or::<String>(format!(
                "Could not get PPS for pic_parameter_set_id {}",
                header.pic_parameter_set_id
            ))?;

        let sps = &pps.sps;

        if sps.separate_colour_plane_flag {
            header.colour_plane_id = r.read_bits(2)?;
        }

        header.frame_num = r.read_bits(usize::from(sps.log2_max_frame_num_minus4) + 4)?;

        if !sps.frame_mbs_only_flag {
            header.field_pic_flag = r.read_bit()?;
            if header.field_pic_flag {
                header.bottom_field_flag = r.read_bit()?;
            }
        }

        if header.field_pic_flag {
            header.max_pic_num = 2 * sps.max_frame_num();
        } else {
            header.max_pic_num = sps.max_frame_num();
        }

        if nalu.header.idr_pic_flag {
            header.idr_pic_id = r.read_ue_max(0xffff)?;
        }

        let num_bits_left = r.num_bits_left();
        if sps.pic_order_cnt_type == 0 {
            header.pic_order_cnt_lsb =
                r.read_bits(usize::from(sps.log2_max_pic_order_cnt_lsb_minus4) + 4)?;

            if pps.bottom_field_pic_order_in_frame_present_flag && !header.field_pic_flag {
                header.delta_pic_order_cnt_bottom = r.read_se()?;
            }
        }

        if sps.pic_order_cnt_type == 1 && !sps.delta_pic_order_always_zero_flag {
            header.delta_pic_order_cnt[0] = r.read_se()?;
            if pps.bottom_field_pic_order_in_frame_present_flag && !header.field_pic_flag {
                header.delta_pic_order_cnt[1] = r.read_se()?;
            }
        }
        header.pic_order_cnt_bit_size = num_bits_left - r.num_bits_left();

        if pps.redundant_pic_cnt_present_flag {
            header.redundant_pic_cnt = r.read_ue_max(127)?;
        }

        if header.slice_type.is_b() {
            header.direct_spatial_mv_pred_flag = r.read_bit()?;
        }

        if header.slice_type.is_p() || header.slice_type.is_sp() || header.slice_type.is_b() {
            header.num_ref_idx_active_override_flag = r.read_bit()?;
            if header.num_ref_idx_active_override_flag {
                header.num_ref_idx_l0_active_minus1 = r.read_ue()?;
                if header.slice_type.is_b() {
                    header.num_ref_idx_l1_active_minus1 = r.read_ue()?;
                }
            } else {
                header.num_ref_idx_l0_active_minus1 = pps.num_ref_idx_l0_default_active_minus1;
                if header.slice_type.is_b() {
                    header.num_ref_idx_l1_active_minus1 = pps.num_ref_idx_l1_default_active_minus1;
                }
            }
        }

        if header.field_pic_flag {
            if header.num_ref_idx_l0_active_minus1 > 31 || header.num_ref_idx_l1_active_minus1 > 31
            {
                return Err("Broken Data".into());
            }
        } else if header.num_ref_idx_l0_active_minus1 > 15
            || header.num_ref_idx_l1_active_minus1 > 15
        {
            return Err("Broken Data".into());
        }

        if let NaluType::SliceExt = nalu.header.type_ {
            return Err("Stream contain unsupported/unimplemented NALs".into());
        }

        Parser::parse_ref_pic_list_modifications(&mut r, &mut header)?;

        if (pps.weighted_pred_flag && (header.slice_type.is_p() || header.slice_type.is_sp()))
            || (pps.weighted_bipred_idc == 1 && header.slice_type.is_b())
        {
            Parser::parse_pred_weight_table(&mut r, sps, &mut header)?;
        }

        if nalu.header.ref_idc != 0 {
            Parser::parse_dec_ref_pic_marking(&mut r, &nalu, &mut header)?;
        }

        if pps.entropy_coding_mode_flag && !header.slice_type.is_i() && !header.slice_type.is_si() {
            header.cabac_init_idc = r.read_ue_max(2)?;
        }

        header.slice_qp_delta = r.read_se_bounded(-87, 77)?;

        if header.slice_type.is_sp() || header.slice_type.is_si() {
            if header.slice_type.is_sp() {
                header.sp_for_switch_flag = r.read_bit()?;
            }

            header.slice_qs_delta = r.read_se_bounded(-51, 51)?;
        }

        if pps.deblocking_filter_control_present_flag {
            header.disable_deblocking_filter_idc = r.read_ue_max(2)?;

            if header.disable_deblocking_filter_idc != 1 {
                header.slice_alpha_c0_offset_div2 = r.read_se_bounded(-6, 6)?;
                header.slice_beta_offset_div2 = r.read_se_bounded(-6, 6)?;
            }
        }

        if pps.num_slice_groups_minus1 > 0 {
            return Err("Stream contain unsupported/unimplemented NALs".into());
        }

        let epb = r.num_epb();
        header.header_bit_size = (nalu.size - epb) * 8 - r.num_bits_left();

        header.n_emulation_prevention_bytes = epb;

        Ok(Slice { header, nalu })
    }

    pub fn get_sps(&self, sps_id: u8) -> Option<&Rc<Sps>> {
        self.active_spses.get(&sps_id)
    }

    pub fn get_pps(&self, pps_id: u8) -> Option<&Rc<Pps>> {
        self.active_ppses.get(&pps_id)
    }
}

#[derive(Debug)]
pub struct NaluHeader {
    pub ref_idc: u8,
    pub type_: NaluType,
    pub idr_pic_flag: bool,
}

impl Header for NaluHeader {
    fn parse<T: AsRef<[u8]>>(cursor: &mut Cursor<T>) -> Result<Self, String> {
        let mut byte_buf = [0u8; 1];
        cursor
            .read_exact(&mut byte_buf)
            .map_err(|_| String::from("Broken Data"))?;
        let byte = byte_buf[0];
        let _ = cursor.seek(SeekFrom::Current(-1 * byte_buf.len() as i64));

        let type_ = NaluType::try_from(byte & 0x1f)?;

        if let NaluType::SliceExt = type_ {
            return Err("Stream contain unsupported/unimplemented NALs".into());
        }

        let ref_idc = (byte & 0x60) >> 5;
        let idr_pic_flag = matches!(type_, NaluType::SliceIdr);

        Ok(NaluHeader {
            ref_idc,
            type_,
            idr_pic_flag,
        })
    }

    fn is_end(&self) -> bool {
        matches!(self.type_, NaluType::SeqEnd | NaluType::StreamEnd)
    }

    fn len(&self) -> usize {
        1
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use crate::codec::h264::parser::Level;
    use crate::codec::h264::parser::MaxLongTermFrameIdx;
    use crate::codec::h264::parser::Nalu;
    use crate::codec::h264::parser::NaluType;
    use crate::codec::h264::parser::Parser;

    const STREAM_TEST_25_FPS: &[u8] = include_bytes!("test_data/test-25fps.h264");
    const STREAM_TEST_25_FPS_NUM_NALUS: usize = 759;

    const STREAM_TEST_25_FPS_SLICE_0: &[u8] =
        include_bytes!("test_data/test-25fps-h264-slice-data-0.bin");
    const STREAM_TEST_25_FPS_SLICE_2: &[u8] =
        include_bytes!("test_data/test-25fps-h264-slice-data-2.bin");
    const STREAM_TEST_25_FPS_SLICE_4: &[u8] =
        include_bytes!("test_data/test-25fps-h264-slice-data-4.bin");

    /// This test is adapted from chromium, available at media/video/h264_parser_unittest.cc
    #[test]
    fn parse_nalus_from_stream_file() {
        let mut cursor = Cursor::new(STREAM_TEST_25_FPS);
        let mut num_nalus = 0;
        while Nalu::next(&mut cursor).is_ok() {
            num_nalus += 1;
        }

        assert_eq!(num_nalus, STREAM_TEST_25_FPS_NUM_NALUS)
    }

    /// The results were manually extracted from the GStreamer parser using GDB
    /// (gsth264parser.c) in order to compare both implementations using the
    /// following pipeline:
    /// gst-launch-1.0 filesrc location=test-25fps.h264 ! h264parse ! 'video/x-h264,stream-format=byte-stream' ! vah264dec ! fakevideosink
    #[test]
    fn parse_test25fps() {
        let mut cursor = Cursor::new(STREAM_TEST_25_FPS);
        let mut sps_ids = Vec::new();
        let mut pps_ids = Vec::new();
        let mut slices = Vec::new();

        let mut parser = Parser::default();

        while let Ok(nalu) = Nalu::next(&mut cursor) {
            match nalu.header.type_ {
                NaluType::Slice
                | NaluType::SliceDpa
                | NaluType::SliceDpb
                | NaluType::SliceDpc
                | NaluType::SliceIdr
                | NaluType::SliceExt => {
                    let slice = parser.parse_slice_header(nalu).unwrap();
                    slices.push(slice);
                }
                NaluType::Sps => {
                    let sps = parser.parse_sps(&nalu).unwrap();
                    sps_ids.push(sps.seq_parameter_set_id);
                }
                NaluType::Pps => {
                    let pps = parser.parse_pps(&nalu).unwrap();
                    pps_ids.push(pps.pic_parameter_set_id);
                }
                _ => {
                    continue;
                }
            }
        }

        for sps_id in &sps_ids {
            // four identical SPSes in this stream
            let sps = parser.get_sps(*sps_id).unwrap();

            assert_eq!(sps.seq_parameter_set_id, 0);
            assert_eq!(sps.profile_idc, 77);
            assert!(!sps.constraint_set0_flag);
            assert!(sps.constraint_set1_flag);
            assert!(!sps.constraint_set2_flag);
            assert!(!sps.constraint_set3_flag);
            assert!(!sps.constraint_set4_flag);
            assert!(!sps.constraint_set5_flag);
            assert_eq!(sps.level_idc, Level::L1_3);
            assert_eq!(sps.chroma_format_idc, 1);
            assert!(!sps.separate_colour_plane_flag);
            assert_eq!(sps.bit_depth_luma_minus8, 0);
            assert_eq!(sps.bit_depth_chroma_minus8, 0);
            assert!(!sps.qpprime_y_zero_transform_bypass_flag);
            assert!(!sps.seq_scaling_matrix_present_flag);

            for outer in &sps.scaling_lists_4x4 {
                for inner in outer {
                    assert_eq!(*inner, 16);
                }
            }

            for outer in &sps.scaling_lists_8x8 {
                for inner in outer {
                    assert_eq!(*inner, 16);
                }
            }

            assert_eq!(sps.log2_max_frame_num_minus4, 1);
            assert_eq!(sps.pic_order_cnt_type, 0);
            assert_eq!(sps.log2_max_pic_order_cnt_lsb_minus4, 3);
            assert!(!sps.delta_pic_order_always_zero_flag);
            assert_eq!(sps.offset_for_non_ref_pic, 0);
            assert_eq!(sps.offset_for_top_to_bottom_field, 0);
            assert_eq!(sps.num_ref_frames_in_pic_order_cnt_cycle, 0);

            for offset in sps.offset_for_ref_frame {
                assert_eq!(offset, 0);
            }

            assert_eq!(sps.max_num_ref_frames, 2);
            assert!(!sps.gaps_in_frame_num_value_allowed_flag);
            assert_eq!(sps.pic_width_in_mbs_minus1, 19);
            assert_eq!(sps.pic_height_in_map_units_minus1, 14);
            assert!(sps.frame_mbs_only_flag);
            assert!(!sps.mb_adaptive_frame_field_flag);
            assert!(!sps.direct_8x8_inference_flag);
            assert!(!sps.frame_cropping_flag);
            assert_eq!(sps.frame_crop_left_offset, 0);
            assert_eq!(sps.frame_crop_right_offset, 0);
            assert_eq!(sps.frame_crop_top_offset, 0);
            assert_eq!(sps.frame_crop_bottom_offset, 0);
            assert_eq!(sps.chroma_array_type(), 1);
            assert_eq!(sps.max_frame_num(), 32);
            assert_eq!(sps.width(), 320);
            assert_eq!(sps.height(), 240);
        }

        for pps_id in &pps_ids {
            // four identical SPSes in this stream
            let pps = parser.get_pps(*pps_id).unwrap();
            assert_eq!(pps.pic_parameter_set_id, 0);
            assert_eq!(pps.seq_parameter_set_id, 0);
            assert!(pps.bottom_field_pic_order_in_frame_present_flag);
            assert_eq!(pps.num_slice_groups_minus1, 0);
            assert_eq!(pps.num_ref_idx_l0_default_active_minus1, 0);
            assert_eq!(pps.num_ref_idx_l1_default_active_minus1, 0);
            assert!(!pps.weighted_pred_flag);
            assert_eq!(pps.weighted_bipred_idc, 0);
            assert_eq!(pps.pic_init_qp_minus26, 2);
            assert_eq!(pps.pic_init_qs_minus26, 0);
            assert_eq!(pps.chroma_qp_index_offset, 0);
            assert!(!pps.deblocking_filter_control_present_flag);
            assert!(!pps.constrained_intra_pred_flag);
            assert!(!pps.redundant_pic_cnt_present_flag);
            assert!(!pps.transform_8x8_mode_flag);

            for outer in &pps.scaling_lists_4x4 {
                for inner in outer {
                    assert_eq!(*inner, 16);
                }
            }

            for outer in &pps.scaling_lists_8x8 {
                for inner in outer {
                    assert_eq!(*inner, 16);
                }
            }

            assert_eq!(pps.second_chroma_qp_index_offset, 0);
            assert!(!pps.pic_scaling_matrix_present_flag);
        }

        // test an I slice
        let hdr = &slices[0].header;
        let nalu = &slices[0].nalu;
        assert_eq!(nalu.as_ref(), STREAM_TEST_25_FPS_SLICE_0);

        assert_eq!(hdr.first_mb_in_slice, 0);
        assert!(hdr.slice_type.is_i());
        assert_eq!(hdr.colour_plane_id, 0);
        assert_eq!(hdr.frame_num, 0);
        assert!(!hdr.field_pic_flag);
        assert!(!hdr.bottom_field_flag);
        assert_eq!(hdr.idr_pic_id, 0);
        assert_eq!(hdr.pic_order_cnt_lsb, 0);
        assert_eq!(hdr.delta_pic_order_cnt_bottom, 0);
        assert_eq!(hdr.delta_pic_order_cnt[0], 0);
        assert_eq!(hdr.delta_pic_order_cnt[1], 0);
        assert_eq!(hdr.redundant_pic_cnt, 0);
        assert!(!hdr.direct_spatial_mv_pred_flag);
        assert_eq!(hdr.num_ref_idx_l0_active_minus1, 0);
        assert_eq!(hdr.num_ref_idx_l1_active_minus1, 0);
        assert!(!hdr.ref_pic_list_modification_flag_l0);

        assert_eq!(hdr.ref_pic_list_modification_l0.len(), 0);

        for rplm in &hdr.ref_pic_list_modification_l0 {
            assert_eq!(rplm.modification_of_pic_nums_idc, 0);
            assert_eq!(rplm.abs_diff_pic_num_minus1, 0);
            assert_eq!(rplm.long_term_pic_num, 0);
            assert_eq!(rplm.abs_diff_view_idx_minus1, 0);
        }

        assert!(!hdr.ref_pic_list_modification_flag_l1);
        assert_eq!(hdr.ref_pic_list_modification_l1.len(), 0);

        for rplm in &hdr.ref_pic_list_modification_l1 {
            assert_eq!(rplm.modification_of_pic_nums_idc, 0);
            assert_eq!(rplm.abs_diff_pic_num_minus1, 0);
            assert_eq!(rplm.long_term_pic_num, 0);
            assert_eq!(rplm.abs_diff_view_idx_minus1, 0);
        }

        // Safe because this type does not have any references
        assert_eq!(hdr.pred_weight_table, Default::default());

        assert_eq!(hdr.dec_ref_pic_marking, Default::default());

        assert_eq!(hdr.cabac_init_idc, 0);
        assert_eq!(hdr.slice_qp_delta, 12);
        assert_eq!(hdr.slice_qs_delta, 0);
        assert_eq!(hdr.disable_deblocking_filter_idc, 0);
        assert_eq!(hdr.slice_alpha_c0_offset_div2, 0);
        assert_eq!(hdr.slice_beta_offset_div2, 0);
        assert_eq!(hdr.max_pic_num, 32);
        assert_eq!(hdr.header_bit_size, 38);
        assert!(!hdr.num_ref_idx_active_override_flag);

        // test a P slice
        let hdr = &slices[2].header;
        let nalu = &slices[2].nalu;
        assert_eq!(nalu.as_ref(), STREAM_TEST_25_FPS_SLICE_2);

        assert_eq!(hdr.first_mb_in_slice, 0);
        assert!(hdr.slice_type.is_p());
        assert_eq!(hdr.colour_plane_id, 0);
        assert_eq!(hdr.frame_num, 1);
        assert!(!hdr.field_pic_flag);
        assert!(!hdr.bottom_field_flag);
        assert_eq!(hdr.idr_pic_id, 0);
        assert_eq!(hdr.pic_order_cnt_lsb, 4);
        assert_eq!(hdr.delta_pic_order_cnt_bottom, 0);
        assert_eq!(hdr.delta_pic_order_cnt[0], 0);
        assert_eq!(hdr.delta_pic_order_cnt[1], 0);
        assert_eq!(hdr.redundant_pic_cnt, 0);
        assert!(!hdr.direct_spatial_mv_pred_flag);
        assert_eq!(hdr.num_ref_idx_l0_active_minus1, 0);
        assert_eq!(hdr.num_ref_idx_l1_active_minus1, 0);
        assert!(!hdr.ref_pic_list_modification_flag_l0);

        assert_eq!(hdr.ref_pic_list_modification_l0.len(), 0);

        for rplm in &hdr.ref_pic_list_modification_l0 {
            assert_eq!(rplm.modification_of_pic_nums_idc, 0);
            assert_eq!(rplm.abs_diff_pic_num_minus1, 0);
            assert_eq!(rplm.long_term_pic_num, 0);
            assert_eq!(rplm.abs_diff_view_idx_minus1, 0);
        }

        assert!(!hdr.ref_pic_list_modification_flag_l1);
        assert_eq!(hdr.ref_pic_list_modification_l1.len(), 0);

        for rplm in &hdr.ref_pic_list_modification_l1 {
            assert_eq!(rplm.modification_of_pic_nums_idc, 0);
            assert_eq!(rplm.abs_diff_pic_num_minus1, 0);
            assert_eq!(rplm.long_term_pic_num, 0);
            assert_eq!(rplm.abs_diff_view_idx_minus1, 0);
        }

        // Safe because this type does not have any references
        assert_eq!(hdr.pred_weight_table, Default::default());

        assert_eq!(hdr.dec_ref_pic_marking, Default::default());

        assert_eq!(hdr.cabac_init_idc, 0);
        assert_eq!(hdr.slice_qp_delta, 0);
        assert_eq!(hdr.slice_qs_delta, 0);
        assert_eq!(hdr.disable_deblocking_filter_idc, 0);
        assert_eq!(hdr.slice_alpha_c0_offset_div2, 0);
        assert_eq!(hdr.slice_beta_offset_div2, 0);
        assert_eq!(hdr.max_pic_num, 32);
        assert_eq!(hdr.header_bit_size, 28);
        assert!(!hdr.num_ref_idx_active_override_flag);

        // test a B slice
        let hdr = &slices[4].header;
        let nalu = &slices[4].nalu;
        assert_eq!(nalu.as_ref(), STREAM_TEST_25_FPS_SLICE_4);

        assert_eq!(hdr.first_mb_in_slice, 0);
        assert!(hdr.slice_type.is_b());
        assert_eq!(hdr.colour_plane_id, 0);
        assert_eq!(hdr.frame_num, 2);
        assert!(!hdr.field_pic_flag);
        assert!(!hdr.bottom_field_flag);
        assert_eq!(hdr.idr_pic_id, 0);
        assert_eq!(hdr.pic_order_cnt_lsb, 2);
        assert_eq!(hdr.delta_pic_order_cnt_bottom, 0);
        assert_eq!(hdr.delta_pic_order_cnt[0], 0);
        assert_eq!(hdr.delta_pic_order_cnt[1], 0);
        assert_eq!(hdr.redundant_pic_cnt, 0);
        assert!(hdr.direct_spatial_mv_pred_flag);
        assert_eq!(hdr.num_ref_idx_l0_active_minus1, 0);
        assert_eq!(hdr.num_ref_idx_l1_active_minus1, 0);
        assert!(!hdr.ref_pic_list_modification_flag_l0);

        assert_eq!(hdr.ref_pic_list_modification_l0.len(), 0);

        for rplm in &hdr.ref_pic_list_modification_l0 {
            assert_eq!(rplm.modification_of_pic_nums_idc, 0);
            assert_eq!(rplm.abs_diff_pic_num_minus1, 0);
            assert_eq!(rplm.long_term_pic_num, 0);
            assert_eq!(rplm.abs_diff_view_idx_minus1, 0);
        }

        assert!(!hdr.ref_pic_list_modification_flag_l1);
        assert_eq!(hdr.ref_pic_list_modification_l1.len(), 0);

        for rplm in &hdr.ref_pic_list_modification_l1 {
            assert_eq!(rplm.modification_of_pic_nums_idc, 0);
            assert_eq!(rplm.abs_diff_pic_num_minus1, 0);
            assert_eq!(rplm.long_term_pic_num, 0);
            assert_eq!(rplm.abs_diff_view_idx_minus1, 0);
        }

        // Safe because this type does not have any references
        assert_eq!(hdr.pred_weight_table, Default::default());

        assert_eq!(hdr.dec_ref_pic_marking, Default::default());

        assert_eq!(hdr.cabac_init_idc, 0);
        assert_eq!(hdr.slice_qp_delta, 16);
        assert_eq!(hdr.slice_qs_delta, 0);
        assert_eq!(hdr.disable_deblocking_filter_idc, 0);
        assert_eq!(hdr.slice_alpha_c0_offset_div2, 0);
        assert_eq!(hdr.slice_beta_offset_div2, 0);
        assert_eq!(hdr.max_pic_num, 32);
        assert_eq!(hdr.header_bit_size, 41);
        assert!(!hdr.num_ref_idx_active_override_flag);
    }

    #[test]
    fn invalid_sps_crop_width() {
        // This SPS contains invalid frame_crop_*_offset settings. This led to
        // unconditional panic in the parser in the past. This test make sure a
        // panic is avoided.
        let invalid_sps = vec![
            0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x0a, 0xfb, 0xb0, 0x32, 0xc0, 0xca, 0x80,
        ];

        let mut cursor = Cursor::new(invalid_sps.as_ref());
        let mut parser = Parser::default();

        while let Ok(nalu) = Nalu::next(&mut cursor) {
            assert_eq!(nalu.header.type_, NaluType::Sps);
            parser.parse_sps(&nalu).unwrap_err();
        }
    }

    #[test]
    fn max_long_term_frame_idx() {
        assert_eq!(
            MaxLongTermFrameIdx::from_value_plus1(0),
            MaxLongTermFrameIdx::NoLongTermFrameIndices
        );
        assert_eq!(
            MaxLongTermFrameIdx::NoLongTermFrameIndices.to_value_plus1(),
            0
        );

        assert_eq!(
            MaxLongTermFrameIdx::from_value_plus1(1),
            MaxLongTermFrameIdx::Idx(0)
        );
        assert_eq!(MaxLongTermFrameIdx::Idx(0).to_value_plus1(), 1);

        assert_eq!(
            MaxLongTermFrameIdx::from_value_plus1(25),
            MaxLongTermFrameIdx::Idx(24)
        );
        assert_eq!(MaxLongTermFrameIdx::Idx(24).to_value_plus1(), 25);

        // Check PartialOrd<u32> implementation.
        assert!(MaxLongTermFrameIdx::NoLongTermFrameIndices < 0);
        assert_ne!(MaxLongTermFrameIdx::NoLongTermFrameIndices, 0);
        assert_eq!(MaxLongTermFrameIdx::Idx(0), 0);
        assert!(MaxLongTermFrameIdx::Idx(0) < 1);
        assert_eq!(MaxLongTermFrameIdx::Idx(24), 24);
        assert!(MaxLongTermFrameIdx::Idx(24) < 25);
    }
}
