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

use std::borrow::Cow;
use std::rc::Rc;

use crate::codec::av1::helpers;
use crate::codec::av1::reader::Reader;

pub const TOTAL_REFS_PER_FRAME: usize = 8;
pub const NUM_REF_FRAMES: usize = 8;
pub const REFS_PER_FRAME: usize = 7;
pub const MAX_SEGMENTS: usize = 8;
pub const SEG_LVL_ALT_Q: usize = 0;
pub const SEG_LVL_ALT_LF_Y_V: usize = 1;
pub const SEG_LVL_REF_FRAME: usize = 5;
pub const SEG_LVL_SKIP: usize = 6;
pub const SEG_LVL_GLOBAL_MV: usize = 7;
pub const SEG_LVL_MAX: usize = 8;
pub const MAX_TILE_COLS: usize = 64;
pub const MAX_TILE_ROWS: usize = 64;
pub const CDEF_MAX: usize = 1 << 3;
pub const MAX_NUM_PLANES: usize = 3;
pub const MAX_NUM_Y_POINTS: usize = 16;
pub const MAX_NUM_CB_POINTS: usize = 16;
pub const MAX_NUM_CR_POINTS: usize = 16;
pub const MAX_NUM_POS_LUMA: usize = 25;
pub const MAX_NUM_SPATIAL_LAYERS: usize = 4;
pub const MAX_NUM_TEMPORAL_LAYERS: usize = 8;
pub const MAX_NUM_OPERATING_POINTS: usize = MAX_NUM_SPATIAL_LAYERS * MAX_NUM_TEMPORAL_LAYERS;
pub const SELECT_SCREEN_CONTENT_TOOLS: usize = 2;
pub const SELECT_INTEGER_MV: usize = 2;
pub const PRIMARY_REF_NONE: u32 = 7;
pub const SUPERRES_DENOM_BITS: usize = 3;
pub const SUPERRES_DENOM_MIN: usize = 9;
pub const SUPERRES_NUM: usize = 8;
pub const MAX_TILE_WIDTH: u32 = 4096;
pub const MAX_TILE_HEIGHT: u32 = 2304;
pub const MAX_TILE_AREA: u32 = MAX_TILE_WIDTH * MAX_TILE_HEIGHT;
pub const RESTORATION_TILESIZE_MAX: u16 = 256;
pub const WARPEDMODEL_PREC_BITS: u32 = 16;
pub const WARP_PARAM_REDUCE_BITS: u32 = 6;
pub const GM_ABS_ALPHA_BITS: u32 = 12;
pub const GM_ALPHA_PREC_BITS: u32 = 15;
pub const GM_ABS_TRANS_ONLY_BITS: u32 = 9;
pub const GM_TRANS_ONLY_PREC_BITS: u32 = 3;
pub const GM_ABS_TRANS_BITS: u32 = 12;
pub const GM_TRANS_PREC_BITS: u32 = 6;

// Same as Segmentation_Feature_Bits in the specification. See 5.9.14
pub const FEATURE_BITS: [u8; SEG_LVL_MAX] = [8, 6, 6, 6, 6, 3, 0, 0];
// Same as Segmentation_Feature_Signed in the specification. See 5.9.14
pub const FEATURE_SIGNED: [bool; SEG_LVL_MAX] = [true, true, true, true, true, false, false, false];
// Same as Segmentation_Feature_Max in the specification. See 5.9.14
pub const FEATURE_MAX: [i32; SEG_LVL_MAX] = [255, 63, 63, 63, 63, 7, 0, 0];

/// Tells what should be done with the OBU after [`Parser::read_obu`] is called.
pub enum ObuAction<'a> {
    /// We should process the OBU normally.
    Process(Obu<'a>),
    /// We should drop this OBU and advance to the next one. The u32 is how much
    /// we should advance.
    Drop(u32),
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum ObuType {
    #[default]
    Reserved = 0,
    SequenceHeader = 1,
    TemporalDelimiter = 2,
    FrameHeader = 3,
    TileGroup = 4,
    Metadata = 5,
    Frame = 6,
    RedundantFrameHeader = 7,
    TileList = 8,
    Reserved2 = 9,
    Reserved3 = 10,
    Reserved4 = 11,
    Reserved5 = 12,
    Reserved6 = 13,
    Reserved7 = 14,
    Padding = 15,
}

impl TryFrom<u32> for ObuType {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(ObuType::Reserved),
            1 => Ok(ObuType::SequenceHeader),
            2 => Ok(ObuType::TemporalDelimiter),
            3 => Ok(ObuType::FrameHeader),
            4 => Ok(ObuType::TileGroup),
            5 => Ok(ObuType::Metadata),
            6 => Ok(ObuType::Frame),
            7 => Ok(ObuType::RedundantFrameHeader),
            8 => Ok(ObuType::TileList),
            9 => Ok(ObuType::Reserved2),
            10 => Ok(ObuType::Reserved3),
            11 => Ok(ObuType::Reserved4),
            12 => Ok(ObuType::Reserved5),
            13 => Ok(ObuType::Reserved6),
            14 => Ok(ObuType::Reserved7),
            15 => Ok(ObuType::Padding),
            _ => Err(format!("Invalid ObuType {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Profile {
    #[default]
    Profile0 = 0,
    Profile1 = 1,
    Profile2 = 2,
}

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

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(Profile::Profile0),
            1 => Ok(Profile::Profile1),
            2 => Ok(Profile::Profile2),
            _ => Err(format!("Invalid Profile {}", value)),
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ObuHeader {
    pub obu_type: ObuType,
    pub extension_flag: bool,
    pub has_size_field: bool,
    pub temporal_id: u32,
    pub spatial_id: u32,
}

impl ObuHeader {
    /// Length in bytes
    #[allow(clippy::len_without_is_empty)]
    pub fn len(&self) -> usize {
        if self.extension_flag {
            2
        } else {
            1
        }
    }
}

/// Contains the OBU header and a reference to its data. The OBU itself hasn't been parsed yet.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Obu<'a> {
    /// The OBU header.
    pub header: ObuHeader,
    /// Amount of bytes from the input consumed to parse this OBU.
    pub bytes_used: usize,
    /// The slice backing the OBU.
    data: Cow<'a, [u8]>,
}

impl<'a> AsRef<[u8]> for Obu<'a> {
    fn as_ref(&self) -> &[u8] {
        self.data.as_ref()
    }
}

/// A fully parsed OBU, with additional data when relevant.
pub enum ParsedObu<'a> {
    Reserved,
    SequenceHeader(Rc<SequenceHeaderObu>),
    TemporalDelimiter,
    FrameHeader(FrameHeaderObu),
    TileGroup(TileGroupObu<'a>),
    Metadata,
    Frame(FrameObu<'a>),
    RedundantFrameHeader,
    TileList,
    Reserved2,
    Reserved3,
    Reserved4,
    Reserved5,
    Reserved6,
    Reserved7,
    Padding,
}

impl<'a> ParsedObu<'a> {
    pub fn obu_type(&self) -> ObuType {
        match self {
            ParsedObu::Reserved => ObuType::Reserved,
            ParsedObu::SequenceHeader(_) => ObuType::SequenceHeader,
            ParsedObu::TemporalDelimiter => ObuType::TemporalDelimiter,
            ParsedObu::FrameHeader(_) => ObuType::FrameHeader,
            ParsedObu::TileGroup(_) => ObuType::TileGroup,
            ParsedObu::Metadata => ObuType::Metadata,
            ParsedObu::Frame(_) => ObuType::Frame,
            ParsedObu::RedundantFrameHeader => ObuType::RedundantFrameHeader,
            ParsedObu::TileList => ObuType::TileList,
            ParsedObu::Reserved2 => ObuType::Reserved2,
            ParsedObu::Reserved3 => ObuType::Reserved3,
            ParsedObu::Reserved4 => ObuType::Reserved4,
            ParsedObu::Reserved5 => ObuType::Reserved5,
            ParsedObu::Reserved6 => ObuType::Reserved6,
            ParsedObu::Reserved7 => ObuType::Reserved7,
            ParsedObu::Padding => ObuType::Padding,
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Tile {
    /// Same as TileOffset in the specification.
    pub tile_offset: u32,
    /// Same as TileSize in the specification.
    pub tile_size: u32,
    /// Same as TileRow in the specification.
    pub tile_row: u32,
    /// Same as TileCol in the specification.
    pub tile_col: u32,
    // Same as MiRowStart in the specification.
    pub mi_row_start: u32,
    // Same as MiRowEnd in the specification.
    pub mi_row_end: u32,
    // Same as MiColStart in the specification.
    pub mi_col_start: u32,
    // Same as MiColEnd in the specification.
    pub mi_col_end: u32,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TileGroupObu<'a> {
    /// The OBU backing this tile group.
    pub obu: Obu<'a>,
    /// Specifies whether tg_start and tg_end are present. If tg_start and
    /// tg_end are not present, this tile group covers the entire frame.
    pub tile_start_and_end_present_flag: bool,
    /// Specifies the zero-based index of the first tile in the current tile
    /// group.
    pub tg_start: u32,
    /// Specifies the zero-based index of the last tile in the current tile
    /// group.
    pub tg_end: u32,
    /// Contains the tiles in this tile group. Use `tile_offset`to index into
    /// the OBU data.
    ///
    /// The tiles in the Vec span from tg_start to tg_end.
    pub tiles: Vec<Tile>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OperatingPoint {
    /// Specifies the level that the coded video sequence conforms to when
    /// operating point i is selected.
    pub seq_level_idx: u8,
    /// Specifies the tier that the coded video sequence conforms to when
    /// operating point i is selected.
    pub seq_tier: u8,
    /// Specifies the value of operating_point_idc for the selected operating
    /// point.
    pub idc: u16,
    /// If set, indicates that there is a decoder model associated with
    /// operating point i. If not set, indicates that there is not a decoder
    /// model associated with operating point i.
    pub decoder_model_present_for_this_op: bool,
    /// Specifies the time interval between the arrival of the first bit in the
    /// smoothing buffer and the subsequent removal of the data that belongs to
    /// the first coded frame for operating point op, measured in units of
    /// 1/90000 seconds. The length of decoder_buffer_delay is specified by
    /// buffer_delay_length_minus_1 + 1, in bits.
    pub decoder_buffer_delay: u32,
    /// Specifies, in combination with decoder_buffer_delay\[ op \] syntax
    /// element, the first bit arrival time of frames to be decoded to the
    /// smoothing buffer. encoder_buffer_delay is measured in units of 1/90000
    /// seconds.
    pub encoder_buffer_delay: u32,
    /// If set, indicates that the smoothing buffer operates in low-delay mode
    /// for operating point op. In low-delay mode late decode times and buffer
    /// underflow are both permitted. If not set, indicates that the smoothing
    /// buffer operates in strict mode, where buffer underflow is not allowed.
    pub low_delay_mode_flag: bool,
    /// If set, indicates that initial_display_delay_minus_1 is specified for
    /// operating point i. If not set, indicates that
    /// initial_display_delay_minus_1 is not specified for operating point i.
    pub initial_display_delay_present_for_this_op: bool,
    /// Plus 1 specifies, for operating point i, the number of decoded frames
    /// that should be present in the buffer pool before the first presentable
    /// frame is displayed. This will ensure that all presentable frames in the
    /// sequence can be decoded at or before the time that they are scheduled
    /// for display. If not signaled then initial_display_delay_minus_1\[ i \] =
    /// BUFFER_POOL_MAX_SIZE - 1.
    pub initial_display_delay_minus_1: u32,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TimingInfo {
    /// The number of time units of a clock operating at the frequency
    /// time_scale Hz that corresponds to one increment of a clock tick counter.
    /// A display clock tick, in seconds, is equal to num_units_in_display_tick
    /// divided by time_scale:
    pub num_units_in_display_tick: u32,
    /// The number of time units that pass in one second.
    pub time_scale: u32,
    /// If set, indicates that pictures should be displayed according to their
    /// output order with the number of ticks between two consecutive pictures
    /// (without dropping frames) specified by num_ticks_per_picture_minus_1 +
    /// 1. If not set, indicates that the interval between two consecutive
    /// pictures is not specified.
    pub equal_picture_interval: bool,
    /// Plus 1 specifies the number of clock ticks corresponding to output time
    /// between two consecutive pictures in the output order.
    pub num_ticks_per_picture_minus_1: u32,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DecoderModelInfo {
    /// Plus 1 specifies the length of the decoder_buffer_delay and the
    /// encoder_buffer_delay syntax elements, in bits.
    pub buffer_delay_length_minus_1: u8,
    /// The number of time units of a decoding clock operating at the frequency
    /// time_scale Hz that corresponds to one increment of a clock tick counter:
    pub num_units_in_decoding_tick: u32,
    /// Plus 1 specifies the length of the buffer_removal_time syntax element,
    /// in bits.
    pub buffer_removal_time_length_minus_1: u8,
    /// Plus 1 specifies the length of the frame_presentation_time syntax
    /// element, in bits.
    pub frame_presentation_time_length_minus_1: u32,
}

/// Defined by the “Color primaries” section of ISO/IEC 23091-4/ITU-T H.273
/// See 6.4.2
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum ColorPrimaries {
    Bt709 = 1,
    #[default]
    Unspecified = 2,
    Bt470M = 4,
    Bt470bg = 5,
    Bt601 = 6,
    Smpte240 = 7,
    GenericFilm = 8,
    Bt2020 = 9,
    Xyz = 10,
    Smpte431 = 11,
    Smpte432 = 12,
    Ebu3213 = 22,
}

impl TryFrom<u32> for ColorPrimaries {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            1 => Ok(ColorPrimaries::Bt709),
            2 => Ok(ColorPrimaries::Unspecified),
            4 => Ok(ColorPrimaries::Bt470M),
            5 => Ok(ColorPrimaries::Bt470bg),
            6 => Ok(ColorPrimaries::Bt601),
            7 => Ok(ColorPrimaries::Smpte240),
            8 => Ok(ColorPrimaries::GenericFilm),
            9 => Ok(ColorPrimaries::Bt2020),
            10 => Ok(ColorPrimaries::Xyz),
            11 => Ok(ColorPrimaries::Smpte431),
            12 => Ok(ColorPrimaries::Smpte432),
            22 => Ok(ColorPrimaries::Ebu3213),
            _ => Err(format!("Invalid ColorPrimaries {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum TransferCharacteristics {
    Reserved0 = 0,
    Bt709 = 1,
    #[default]
    Unspecified = 2,
    Reserved3 = 3,
    Bt470m = 4,
    Bt470bg = 5,
    Bt601 = 6,
    Smpte240 = 7,
    Linear = 8,
    Log100 = 9,
    Log100Sqrt10 = 10,
    Iec61966 = 11,
    Bt1361 = 12,
    Srgb = 13,
    Bt202010Bit = 14,
    Bt202012Bit = 15,
    Smpte2084 = 16,
    Smpte428 = 17,
    Hlg = 18,
}

impl TryFrom<u32> for TransferCharacteristics {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(TransferCharacteristics::Reserved0),
            1 => Ok(TransferCharacteristics::Bt709),
            2 => Ok(TransferCharacteristics::Unspecified),
            3 => Ok(TransferCharacteristics::Reserved3),
            4 => Ok(TransferCharacteristics::Bt470m),
            5 => Ok(TransferCharacteristics::Bt470bg),
            6 => Ok(TransferCharacteristics::Bt601),
            7 => Ok(TransferCharacteristics::Smpte240),
            8 => Ok(TransferCharacteristics::Linear),
            9 => Ok(TransferCharacteristics::Log100),
            10 => Ok(TransferCharacteristics::Log100Sqrt10),
            11 => Ok(TransferCharacteristics::Iec61966),
            12 => Ok(TransferCharacteristics::Bt1361),
            13 => Ok(TransferCharacteristics::Srgb),
            14 => Ok(TransferCharacteristics::Bt202010Bit),
            15 => Ok(TransferCharacteristics::Bt202012Bit),
            16 => Ok(TransferCharacteristics::Smpte2084),
            17 => Ok(TransferCharacteristics::Smpte428),
            18 => Ok(TransferCharacteristics::Hlg),
            _ => Err(format!("Invalid TransferCharacteristics {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum BitDepth {
    #[default]
    Depth8 = 0,
    Depth10 = 1,
    Depth12 = 2,
}

impl TryFrom<u32> for BitDepth {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(BitDepth::Depth8),
            1 => Ok(BitDepth::Depth10),
            2 => Ok(BitDepth::Depth12),
            _ => Err(format!("Invalid BitDepth {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum MatrixCoefficients {
    Identity = 0,
    Bt709 = 1,
    #[default]
    Unspecified = 2,
    Reserved3 = 3,
    Fcc = 4,
    Bt470bg = 5,
    Bt601 = 6,
    Smpte240 = 7,
    Ycgco = 8,
    Bt2020Ncl = 9,
    Bt2020Cl = 10,
    Smpte2085 = 11,
    ChromaDerivedNcl = 12,
    ChromaDerivedCl = 13,
    Ictcp = 14,
}

impl TryFrom<u32> for MatrixCoefficients {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(MatrixCoefficients::Identity),
            1 => Ok(MatrixCoefficients::Bt709),
            2 => Ok(MatrixCoefficients::Unspecified),
            3 => Ok(MatrixCoefficients::Reserved3),
            4 => Ok(MatrixCoefficients::Fcc),
            5 => Ok(MatrixCoefficients::Bt470bg),
            6 => Ok(MatrixCoefficients::Bt601),
            7 => Ok(MatrixCoefficients::Smpte240),
            8 => Ok(MatrixCoefficients::Ycgco),
            9 => Ok(MatrixCoefficients::Bt2020Ncl),
            10 => Ok(MatrixCoefficients::Bt2020Cl),
            11 => Ok(MatrixCoefficients::Smpte2085),
            12 => Ok(MatrixCoefficients::ChromaDerivedNcl),
            13 => Ok(MatrixCoefficients::ChromaDerivedCl),
            14 => Ok(MatrixCoefficients::Ictcp),
            _ => Err(format!("Invalid MatrixCoefficients {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum ChromaSamplePosition {
    #[default]
    Unknown = 0,
    Vertical = 1,
    Colocated = 2,
    Reserved = 3,
}

impl TryFrom<u32> for ChromaSamplePosition {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(ChromaSamplePosition::Unknown),
            1 => Ok(ChromaSamplePosition::Vertical),
            2 => Ok(ChromaSamplePosition::Colocated),
            3 => Ok(ChromaSamplePosition::Reserved),
            _ => Err(format!("Invalid ChromaSamplePosition {}", value)),
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ColorConfig {
    /// Syntax elements which, together with seq_profile, determine the bit
    /// depth.
    pub high_bitdepth: bool,
    /// Syntax elements which, together with seq_profile, determine the bit
    /// depth.
    pub twelve_bit: bool,
    /// If set, indicates that the video does not contain U and V color planes.
    /// If not set, indicates that the video contains Y, U, and V color planes.
    pub mono_chrome: bool,
    /// If set, specifies that color_primaries, transfer_characteristics, and
    /// matrix_coefficients are present. If not set, specifies that
    /// color_primaries, transfer_characteristics and matrix_coefficients are
    /// not present.
    pub color_description_present_flag: bool,
    /// Defined by the “Color primaries” section of ISO/IEC 23091-4/ITU-T H.273.
    pub color_primaries: ColorPrimaries,
    /// Defined by the “Transfer characteristics” section of ISO/IEC
    /// 23091-4/ITU-T H.273.
    pub transfer_characteristics: TransferCharacteristics,
    /// Defined by the “Matrix coefficients” section of ISO/IEC 23091-4/ITU-T
    /// H.273.
    pub matrix_coefficients: MatrixCoefficients,
    /// Binary value that is associated with the VideoFullRangeFlag variable
    /// specified in ISO/IEC 23091-4/ITU- T H.273. color range equal to 0 shall
    /// be referred to as the studio swing representation and color range equal
    /// to 1 shall be referred to as the full swing representation for all
    /// intents relating to this specification.
    pub color_range: bool,
    /// Specify the chroma subsampling format
    pub subsampling_x: bool,
    /// Specify the chroma subsampling format
    pub subsampling_y: bool,
    /// Specifies the sample position for subsampled streams
    pub chroma_sample_position: ChromaSamplePosition,
    /// If set, indicates that the U and V planes may have separate delta
    /// quantizer values. If not set, indicates that the U and V planes will
    /// share the same delta quantizer value.
    pub separate_uv_delta_q: bool,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SequenceHeaderObu {
    /// The OBU header from the OBU that generated this sequence.
    pub obu_header: ObuHeader,
    /// Specifies the features that can be used in the coded video sequence.
    pub seq_profile: Profile,
    /// If set, specifies that the coded video sequence contains only one coded
    /// frame. If not set, specifies that the coded video sequence contains one
    /// or more coded frames.
    pub still_picture: bool,
    /// Specifies that the syntax elements not needed by a still picture are
    /// omitted.
    pub reduced_still_picture_header: bool,
    /// Specifies the number of bits minus 1 used for transmitting the frame
    /// width syntax elements.
    pub frame_width_bits_minus_1: u8,
    /// Specifies the number of bits minus 1 used for transmitting the frame
    /// height syntax elements.
    pub frame_height_bits_minus_1: u8,
    /// Specifies the maximum frame width minus 1 for the frames represented by
    /// this sequence header.
    pub max_frame_width_minus_1: u16,
    /// Specifies the maximum frame height minus 1 for the frames represented by
    /// this sequence header.
    pub max_frame_height_minus_1: u16,
    /// Specifies whether frame id numbers are present in the coded video
    /// sequence.
    pub frame_id_numbers_present_flag: bool,
    /// Specifies the number of bits minus 2 used to encode delta_frame_id
    /// syntax elements.
    pub delta_frame_id_length_minus_2: u32,
    /// Used to calculate the number of bits used to encode the frame_id syntax
    /// element.
    pub additional_frame_id_length_minus_1: u32,
    /// When set, indicates that superblocks contain 128x128 luma samples. When
    /// not set, it indicates that superblocks contain 64x64 luma samples. (The
    /// number of contained chroma samples depends on subsampling_x and
    /// subsampling_y.)
    pub use_128x128_superblock: bool,
    /// When set, specifies that the use_filter_intra syntax element may be
    /// present. When not set, specifies that the use_filter_intra syntax
    /// element will not be present.
    pub enable_filter_intra: bool,
    /// Specifies whether the intra edge filtering process should be enabled.
    pub enable_intra_edge_filter: bool,
    /// When set, specifies that the mode info for inter blocks may contain the
    /// syntax element interintra. If not set, specifies that the syntax element
    /// interintra will not be present.
    pub enable_interintra_compound: bool,
    /// When set, specifies that the mode info for inter blocks may contain the
    /// syntax element compound_type. When not set, specifies that the syntax
    /// element compound_type will not be present.
    pub enable_masked_compound: bool,
    /// When set, indicates that the allow_warped_motion syntax element may be
    /// present. When not set, indicates that the allow_warped_motion syntax
    /// element will not be present.
    pub enable_warped_motion: bool,
    /// When set, indicates that tools based on the values of order hints may be
    /// used. When not set, indicates that tools based on order hints are
    /// disabled.
    pub enable_order_hint: bool,
    /// When set, indicates that the inter prediction filter type may be
    /// specified independently in the horizontal and vertical directions. If
    /// the flag is not set, only one filter type may be specified, which is
    /// then used in both directions.
    pub enable_dual_filter: bool,
    /// If set, indicates that the distance weights process may be used for
    /// inter prediction.
    pub enable_jnt_comp: bool,
    /// If set, indicates that the use_ref_frame_mvs syntax element may be
    /// present. If not set, indicates that the use_ref_frame_mvs syntax element
    /// will not be present.
    pub enable_ref_frame_mvs: bool,
    /// If not set, indicates that the seq_force_screen_content_tools syntax
    /// element will be present. If set, indicates that
    /// seq_force_screen_content_tools should be set equal to
    /// SELECT_SCREEN_CONTENT_TOOLS.
    pub seq_choose_screen_content_tools: bool,
    /// Equal to SELECT_SCREEN_CONTENT_TOOLS indicates that the
    /// allow_screen_content_tools syntax element will be present in the frame
    /// header. Otherwise, seq_force_screen_content_tools contains the value for
    /// allow_screen_content_tools.
    pub seq_force_screen_content_tools: u32,
    /// If not set, indicates that the seq_force_integer_mv syntax element will
    /// be present. If set, indicates that seq_force_integer_mv should be set
    /// equal to SELECT_INTEGER_MV.
    pub seq_choose_integer_mv: bool,
    /// Equal to SELECT_INTEGER_MV indicates that the force_integer_mv syntax
    /// element will be present in the frame header (providing
    /// allow_screen_content_tools is equal to 1). Otherwise,
    /// seq_force_integer_mv contains the value for force_integer_mv.
    pub seq_force_integer_mv: u32,
    /// Used to compute OrderHintBits.
    pub order_hint_bits_minus_1: i32,
    /// Specifies the number of bits used for the order_hint syntax element.
    pub order_hint_bits: i32,
    /// If set, specifies that the use_superres syntax element will be present
    /// in the uncompressed header. If not set, specifies that the use_superres
    /// syntax element will not be present (instead use_superres will be set to
    /// 0 in the uncompressed header without being read).
    pub enable_superres: bool,
    /// If set, specifies that cdef filtering may be enabled. If not set,
    /// specifies that cdef filtering is disabled.
    pub enable_cdef: bool,
    /// If set, specifies that loop restoration filtering may be enabled. If
    /// not set, specifies that loop restoration filtering is disabled.
    pub enable_restoration: bool,
    /// Specifies whether film grain parameters are present in the coded video
    /// sequence.
    pub film_grain_params_present: bool,
    /// Indicates the number of operating points minus 1 present in the coded
    /// video sequence. An operating point specifies which spatial and temporal
    /// layers should be decoded.
    pub operating_points_cnt_minus_1: u32,
    /// The set of operating points.
    pub operating_points: [OperatingPoint; MAX_NUM_OPERATING_POINTS],
    /// Specifies whether decoder model information is present in the coded
    /// video sequence.
    pub decoder_model_info_present_flag: bool,
    /// The decoder model info.
    pub decoder_model_info: DecoderModelInfo,
    /// Specifies whether initial display delay information is present in the
    /// coded video sequence.
    pub initial_display_delay_present_flag: bool,
    /// Specifies whether timing info is present in the coded video sequence.
    pub timing_info_present_flag: bool,
    /// The timing info.
    pub timing_info: TimingInfo,
    /// The color config.
    pub color_config: ColorConfig,

    /* CamelCase variables in the specification */
    pub bit_depth: BitDepth,
    pub num_planes: u32,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct StreamInfo {
    pub seq_header: Rc<SequenceHeaderObu>,
    pub render_width: u32,
    pub render_height: u32,
}

/// A TemporalDelimiterOBU
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TemporalDelimiterObu {
    pub obu_header: ObuHeader,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum InterpolationFilter {
    #[default]
    EightTap = 0,
    EightTapSmooth = 1,
    EightTapSharp = 2,
    Bilinear = 3,
    Switchable = 4,
}

impl TryFrom<u32> for InterpolationFilter {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(InterpolationFilter::EightTap),
            1 => Ok(InterpolationFilter::EightTapSmooth),
            2 => Ok(InterpolationFilter::EightTapSharp),
            3 => Ok(InterpolationFilter::Bilinear),
            4 => Ok(InterpolationFilter::Switchable),
            _ => Err(format!("Invalid InterpolationFilter {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum TxModes {
    #[default]
    Only4x4 = 0,
    Largest = 1,
    Select = 2,
}

impl TryFrom<u32> for TxModes {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(TxModes::Only4x4),
            1 => Ok(TxModes::Largest),
            2 => Ok(TxModes::Select),
            _ => Err(format!("Invalid TxModes {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum FrameRestorationType {
    #[default]
    None = 0,
    Wiener = 1,
    Sgrproj = 2,
    Switchable = 3,
}

impl TryFrom<u32> for FrameRestorationType {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(FrameRestorationType::None),
            1 => Ok(FrameRestorationType::Wiener),
            2 => Ok(FrameRestorationType::Sgrproj),
            3 => Ok(FrameRestorationType::Switchable),
            _ => Err(format!("Invalid FrameRestorationType {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum ReferenceFrameType {
    #[default]
    Intra = 0,
    Last = 1,
    Last2 = 2,
    Last3 = 3,
    Golden = 4,
    BwdRef = 5,
    AltRef2 = 6,
    AltRef = 7,
}

impl TryFrom<u32> for ReferenceFrameType {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(ReferenceFrameType::Intra),
            1 => Ok(ReferenceFrameType::Last),
            2 => Ok(ReferenceFrameType::Last2),
            3 => Ok(ReferenceFrameType::Last3),
            4 => Ok(ReferenceFrameType::Golden),
            5 => Ok(ReferenceFrameType::BwdRef),
            6 => Ok(ReferenceFrameType::AltRef2),
            7 => Ok(ReferenceFrameType::AltRef),
            _ => Err(format!("Invalid ReferenceFrameType {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum WarpModelType {
    #[default]
    Identity = 0,
    Translation = 1,
    RotZoom = 2,
    Affine = 3,
}

impl TryFrom<u32> for WarpModelType {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(WarpModelType::Identity),
            1 => Ok(WarpModelType::Translation),
            2 => Ok(WarpModelType::RotZoom),
            3 => Ok(WarpModelType::Affine),
            _ => Err(format!("Invalid WarpModelType {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum FrameType {
    #[default]
    KeyFrame = 0,
    InterFrame = 1,
    IntraOnlyFrame = 2,
    SwitchFrame = 3,
}

impl TryFrom<u32> for FrameType {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(FrameType::KeyFrame),
            1 => Ok(FrameType::InterFrame),
            2 => Ok(FrameType::IntraOnlyFrame),
            3 => Ok(FrameType::SwitchFrame),
            _ => Err(format!("Invalid FrameType {}", value)),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum TxMode {
    #[default]
    Only4x4 = 0,
    Largest = 1,
    Select = 2,
}

impl TryFrom<u32> for TxMode {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(TxMode::Only4x4),
            1 => Ok(TxMode::Largest),
            2 => Ok(TxMode::Select),
            _ => Err(format!("Invalid TxMode {}", value)),
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FrameObu<'a> {
    pub header: FrameHeaderObu,
    pub tile_group: TileGroupObu<'a>,
}

/// A FrameHeaderOBU
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FrameHeaderObu {
    /// The original OBU header. This may be from a FrameOBU or a FrameHeaderOBU
    /// directly.
    pub obu_header: ObuHeader,
    /// If set, indicates the frame indexed by frame_to_show_map_idx is to be
    /// output; If not set, indicates that further processing is required.
    pub show_existing_frame: bool,
    /// Specifies the frame to be output. It is only available if
    /// show_existing_frame is set.
    pub frame_to_show_map_idx: u8,
    /// Specifies the length of the frame_presentation_time syntax element, in
    /// bits.
    pub frame_presentation_time: u32,
    /// Provides the frame id number for the frame to output.
    pub display_frame_id: u32,
    /// Specifies the type of the frame
    pub frame_type: FrameType,
    /// If set, specifies that this frame should be immediately output once
    /// decoded. If not set specifies that this frame should not be
    /// immediately output. (It may be output later if a later uncompressed
    /// header uses show_existing_frame is set).
    pub show_frame: bool,
    /// When set, specifies that the frame may be output using the
    /// show_existing_frame mechanism. When not set, specifies that this frame
    /// will not be output using the show_existing_frame mechanism.
    pub showable_frame: bool,
    /// If set, indicates that error resilient mode is enabled;
    /// error_resilient_mode equal to 0 indicates that error resilient mode is
    /// disabled.
    pub error_resilient_mode: bool,
    /// Specifies whether the CDF update in the symbol decoding process should
    /// be disabled.
    pub disable_cdf_update: bool,
    /// When set, indicates that intra blocks may use palette encoding; When not
    /// set, indicates that palette encoding is never used.
    pub allow_screen_content_tools: u32,
    /// If set, specifies that motion vectors will always be integers. If not
    /// set, specifies that motion vectors can contain fractional bits.
    pub force_integer_mv: u32,
    /// Specifies the frame id number for the current frame. Frame id numbers
    /// are additional information that do not affect the decoding process, but
    /// provide decoders with a way of detecting missing reference frames so
    /// that appropriate action can be taken.
    pub current_frame_id: u32,
    /// If not set, specifies that the frame size is equal to the size in the
    /// sequence header. If set, specifies that the frame size will either be
    /// specified as the size of one of the reference frames, or computed from
    /// the frame_width_minus_1 and frame_height_minus_1 syntax elements.
    pub frame_size_override_flag: bool,
    /// Specifies OrderHintBits least significant bits of the expected output
    /// order for this frame.
    pub order_hint: u32,
    /// Specifies which reference frame contains the CDF values and other state
    /// that should be loaded at the start of the frame.
    pub primary_ref_frame: u32,
    /// If set, specifies that buffer_removal_time is present.  If not set,
    /// specifies that buffer_removal_time is not present.
    pub buffer_removal_time_present_flag: bool,
    /// Specifies the frame removal time in units of DecCT clock ticks counted
    /// from the removal time of the last random access point for operating
    /// point opNum. buffer_removal_time is signaled as a fixed length unsigned
    /// integer with a length in bits given by
    /// buffer_removal_time_length_minus_1 + 1.
    pub buffer_removal_time: Vec<u32>,
    /// Contains a bitmask that specifies which reference frame slots will be
    /// updated with the current frame after it is decoded.
    pub refresh_frame_flags: u32,
    /// Specifies the expected output order hint for each reference frame.
    pub ref_order_hint: [u32; NUM_REF_FRAMES],
    /// If set, indicates that intra block copy may be used in this frame. If
    /// not set indicates that intra block copy is not allowed in this frame.
    pub allow_intrabc: bool,
    /// If set, indicates that only two reference frames are explicitly
    /// signaled. If not set, indicates that all reference frames are explicitly
    /// signaled.
    pub frame_refs_short_signaling: bool,
    /// Specifies the reference frame to use for LAST_FRAME.
    pub last_frame_idx: u8,
    /// Specifies the reference frame to use for GOLDEN_FRAME.
    pub gold_frame_idx: u8,
    /// Specifies which reference frames are used by inter frames
    pub ref_frame_idx: [u8; REFS_PER_FRAME],
    /// If not set, specifies that motion vectors are specified to quarter pel
    /// precision; If set, specifies that motion vectors are specified to eighth
    /// pel precision.
    pub allow_high_precision_mv: bool,
    /// If not set, specifies that only the SIMPLE motion mode will be used.
    pub is_motion_mode_switchable: bool,
    /// If set, specifies that motion vector information from a previous frame
    /// can be used when decoding the current frame. If not set, specifies that
    /// this information will not be used.
    pub use_ref_frame_mvs: bool,
    /// If set, indicates that the end of frame CDF update is disabled; If not
    /// set, indicates that the end of frame CDF update is enabled.
    pub disable_frame_end_update_cdf: bool,
    /// If set, indicates that the syntax element motion_mode may be present.
    /// If not set, indicates that the syntax element motion_mode will not be
    /// present
    pub allow_warped_motion: bool,
    /// If set, specifies that the frame is restricted to a reduced subset of
    /// the full set of transform types.
    pub reduced_tx_set: bool,
    /// If not set, means that the render width and height are inferred from the
    /// frame width and height. If set, means that the render width and height
    /// are explicitly coded.
    pub render_and_frame_size_different: bool,
    /// If not set, indicates that no upscaling is needed. If set, indicates
    /// that upscaling is needed.
    pub use_superres: bool,
    /// If set indicates that the filter selection is signaled at the block
    /// level; If not set, indicates that the filter selection is signaled at
    /// the frame level.
    pub is_filter_switchable: bool,
    /// The interpolation filter parameters.
    pub interpolation_filter: InterpolationFilter,
    /// The loop filter parameters.
    pub loop_filter_params: LoopFilterParams,
    /// The quantization parameters.
    pub quantization_params: QuantizationParams,
    /// The segmentation parameters.
    pub segmentation_params: SegmentationParams,
    /// The tile info.
    pub tile_info: TileInfo,
    /// The CDEF parameters.
    pub cdef_params: CdefParams,
    /// The loop restoration parameters.
    pub loop_restoration_params: LoopRestorationParams,
    /// Used to compute TxMode.
    pub tx_mode_select: u32,
    /// If set specifies that the syntax element skip_mode will be present.  If
    /// not set, specifies that skip_mode will not be used for this frame.
    pub skip_mode_present: bool,
    /// If set, specifies that the mode info for inter blocks contains the
    /// syntax element comp_mode that indicates whether to use single or
    /// compound reference prediction. If not set, specifies that all inter
    /// blocks will use single prediction.
    pub reference_select: bool,
    /// The global motion parameters.
    pub global_motion_params: GlobalMotionParams,
    /// The film grain parameters.
    pub film_grain_params: FilmGrainParams,

    /* CamelCase variables */
    pub superres_denom: u32,
    pub frame_is_intra: bool,
    pub order_hints: [u32; NUM_REF_FRAMES],
    pub ref_frame_sign_bias: [bool; NUM_REF_FRAMES],
    pub coded_lossless: bool,
    pub all_lossless: bool,
    pub lossless_array: [bool; MAX_SEGMENTS],
    pub seg_qm_level: [[u32; MAX_SEGMENTS]; 3],
    pub upscaled_width: u32,
    pub frame_width: u32,
    pub frame_height: u32,
    pub render_width: u32,
    pub render_height: u32,
    pub tx_mode: TxMode,
    pub skip_mode_frame: [u32; 2],
    pub mi_cols: u32,
    pub mi_rows: u32,
    pub header_bytes: usize,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LoopFilterParams {
    /// An array containing loop filter strength values. Different loop filter
    /// strength values from the array are used depending on the image plane
    /// being filtered, and the edge direction (vertical or horizontal) being
    /// filtered.
    pub loop_filter_level: [u8; 4],
    /// Indicates the sharpness level. The loop_filter_level and
    /// loop_filter_sharpness together determine when a block edge is filtered,
    /// and by how much the filtering can change the sample values.
    pub loop_filter_sharpness: u8,
    /// If set, means that the filter level depends on the mode and reference
    /// frame used to predict a block. If not set, means that the filter level
    /// does not depend on the mode and reference frame.
    pub loop_filter_delta_enabled: bool,
    /// If set, means that additional syntax elements are present that specify
    /// which mode and reference frame deltas are to be updated.
    /// loop_filter_delta_update equal to 0 means that these syntax elements are
    /// not present.
    pub loop_filter_delta_update: bool,
    /// Contains the adjustment needed for the filter level based on the chosen
    /// reference frame. If this syntax element is not present, it maintains
    /// its previous value.
    pub loop_filter_ref_deltas: [i8; TOTAL_REFS_PER_FRAME],
    /// Contains the adjustment needed for the filter level based on the chosen
    /// mode. If this syntax element is not present in the, it maintains its
    /// previous value.
    pub loop_filter_mode_deltas: [i8; 2],
    /// Specifies whether loop filter delta values are present.
    pub delta_lf_present: bool,
    /// Specifies the left shift which should be applied to decoded loop filter
    /// delta values.
    pub delta_lf_res: u8,
    /// If set, specifies that separate loop filter deltas are sent for
    /// horizontal luma edges, vertical luma edges, the U edges, and the V
    /// edges. If not set, specifies that the same loop filter delta is used for
    /// all edges.
    pub delta_lf_multi: bool,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct QuantizationParams {
    /// Indicates the base frame qindex. This is used for Y AC coefficients and
    /// as the base value for the other quantizers.
    pub base_q_idx: u32,
    /// Indicates the base frame qindex. This is used for Y AC coefficients and
    /// as the base value for the other quantizers.
    pub diff_uv_delta: bool,
    /// Specifies that the quantizer matrix will be used to compute quantizers.
    pub using_qmatrix: bool,
    /// Specifies the level in the quantizer matrix that should be used for luma
    /// plane decoding.
    pub qm_y: u32,
    /// Specifies the level in the quantizer matrix that should be used for
    /// chroma U plane decoding.
    pub qm_u: u32,
    /// Specifies the level in the quantizer matrix that should be used for
    /// chroma V plane decoding.
    pub qm_v: u32,
    /// Specifies whether quantizer index delta values are present.
    pub delta_q_present: bool,
    /// Specifies the left shift which should be applied to decoded quantizer
    /// index delta values.
    pub delta_q_res: u32,
    /// Same as DeltaQYDc
    pub delta_q_y_dc: i32,
    /// Same as DeltaQUDc
    pub delta_q_u_dc: i32,
    /// Same as DeltaQUAc
    pub delta_q_u_ac: i32,
    /// Same as DeltaQVDc
    pub delta_q_v_dc: i32,
    /// Same as DeltaQVAc
    pub delta_q_v_ac: i32,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SegmentationParams {
    /// If set, indicates that this frame makes use of the segmentation tool; If
    /// not set, indicates that the frame does not use segmentation.
    pub segmentation_enabled: bool,
    /// If set, indicates that the segmentation map are updated during the
    /// decoding of this frame. If not set, means that the segmentation map from
    /// the previous frame is used.
    pub segmentation_update_map: bool,
    /// If set, indicates that the updates to the segmentation map are coded
    /// relative to the existing segmentation map. If not set, indicates that
    /// the new segmentation map is coded without reference to the existing
    /// segmentation map.
    pub segmentation_temporal_update: bool,
    /// If set, indicates that new parameters are about to be specified for each
    /// segment. If not set, indicates that the segmentation parameters should
    /// keep their existing values.
    pub segmentation_update_data: bool,
    /// If not set, indicates that the corresponding feature is unused and has
    /// value equal to 0. If set, indicates that the feature value is coded.
    pub feature_enabled: [[bool; SEG_LVL_MAX]; MAX_SEGMENTS],
    /// Specifies the feature data for a segment feature.
    pub feature_data: [[i16; SEG_LVL_MAX]; MAX_SEGMENTS],
    /// Same as SegIdPreSkip
    pub seg_id_pre_skip: bool,
    /// Same as LastActiveSegId
    pub last_active_seg_id: u8,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TileInfo {
    /// If set, means that the tiles are uniformly spaced across the frame. (In
    /// other words, all tiles are the same size except for the ones at the
    /// right and bottom edge which can be smaller.) If not set, means that the
    /// tile sizes are coded.
    pub uniform_tile_spacing_flag: bool,
    /// Used to compute TileColsLog2.
    pub increment_tile_rows_log2: u32,
    /// Specifies the width of a tile minus 1 in units of superblocks.
    pub width_in_sbs_minus_1: [u32; MAX_TILE_COLS],
    /// Specifies the height of a tile minus 1 in units of superblocks.
    pub height_in_sbs_minus_1: [u32; MAX_TILE_ROWS],
    /// Specifies which tile to use for the CDF update
    pub context_update_tile_id: u32,
    /// An array specifying the start column (in units of 4x4 luma samples) for
    /// each tile across the image.
    pub mi_col_starts: [u32; MAX_TILE_COLS + 1],
    /// An array specifying the start row (in units of 4x4 luma samples) for
    /// each tile down the image.
    pub mi_row_starts: [u32; MAX_TILE_ROWS + 1],
    /// Specifies the base 2 logarithm of the desired number of tiles down the
    /// frame.
    pub tile_cols_log2: u32,
    /// Specifies the number of tiles across the frame.
    pub tile_cols: u32,
    /// Specifies the base 2 logarithm of the desired number of tiles down the
    /// frame.
    pub tile_rows_log2: u32,
    /// Secifies the number of tiles down the frame
    pub tile_rows: u32,
    /// Specifies the number of bytes needed to code each tile size.
    pub tile_size_bytes: u32,
}

impl Default for TileInfo {
    fn default() -> Self {
        Self {
            uniform_tile_spacing_flag: Default::default(),
            increment_tile_rows_log2: Default::default(),
            width_in_sbs_minus_1: [0; MAX_TILE_COLS],
            height_in_sbs_minus_1: [0; MAX_TILE_ROWS],
            context_update_tile_id: Default::default(),
            mi_col_starts: [0; MAX_TILE_COLS + 1],
            mi_row_starts: [0; MAX_TILE_ROWS + 1],
            tile_cols_log2: Default::default(),
            tile_cols: Default::default(),
            tile_rows_log2: Default::default(),
            tile_rows: Default::default(),
            tile_size_bytes: Default::default(),
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CdefParams {
    /// Controls the amount of damping in the deringing filter.
    pub cdef_damping: u32,
    /// Specifies the number of bits needed to specify which CDEF filter to
    /// apply.
    pub cdef_bits: u32,
    /// Specify the strength of the primary filter.
    pub cdef_y_pri_strength: [u32; CDEF_MAX],
    /// Specify the strength of the secondary filter.
    pub cdef_y_sec_strength: [u32; CDEF_MAX],
    /// Specify the strength of the primary filter.
    pub cdef_uv_pri_strength: [u32; CDEF_MAX],
    /// Specify the strength of the secondary filter.
    pub cdef_uv_sec_strength: [u32; CDEF_MAX],
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LoopRestorationParams {
    /// Specifies if the luma restoration size should be halved.
    pub lr_unit_shift: u8,
    /// Only present for 4:2:0 formats and specifies if the chroma size should
    /// be half the luma size.
    pub lr_uv_shift: u8,
    /// Same as FrameRestorationType in the specification.
    pub frame_restoration_type: [FrameRestorationType; MAX_NUM_PLANES],
    /// Same as LoopRestorationSize in the specification.
    pub loop_restoration_size: [u16; MAX_NUM_PLANES],
    /// Same as UsesLr in the specification.
    pub uses_lr: bool,
    /// Same as UsesChromaLr in the specification.
    pub uses_chroma_lr: bool,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct GlobalMotionParams {
    /// Specifies whether global motion parameters are present for a particular
    /// reference frame.
    pub is_global: [bool; NUM_REF_FRAMES],
    /// Specifies whether a particular reference frame uses rotation and zoom
    /// global motion.
    pub is_rot_zoom: [bool; NUM_REF_FRAMES],
    /// Specifies whether a particular reference frame uses translation global
    /// motion.
    pub is_translation: [bool; NUM_REF_FRAMES],
    /// gm_params\[ ref \]\[ j \] is set equal to SavedGmParams\[
    /// frame_to_show_map_idx \]\[ ref \]\[ j \] for ref = LAST_FRAME..ALTREF_FRAME,
    /// for j = 0..5.
    pub gm_params: [[i32; 6]; NUM_REF_FRAMES],
    /// Whether the parameters are valid (see warpValid and section 7.11.3.6)
    pub warp_valid: [bool; NUM_REF_FRAMES],
    /// Same as GmType.
    pub gm_type: [WarpModelType; NUM_REF_FRAMES],
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FilmGrainParams {
    /// If set, specifies that film grain should be added to this frame. If not
    /// set, specifies that film grain should not be added.
    pub apply_grain: bool,
    /// Specifies the starting value for the pseudo-random numbers used during
    /// film grain synthesis.
    pub grain_seed: u16,
    /// If set means that a new set of parameters should be sent.  If not set,
    /// means that the previous set of parameters should be used.
    pub update_grain: bool,
    /// Indicates which reference frame contains the film grain parameters to be
    /// used for this frame.
    pub film_grain_params_ref_idx: u8,
    /// Specifies the number of points for the piece-wise linear scaling
    /// function of the luma component.
    pub num_y_points: u8,
    /// Represents the x (luma value) coordinate for the i-th point of the
    /// piecewise linear scaling function for luma component. The values are
    /// signaled on the scale of 0..255. (In case of 10 bit video, these values
    /// correspond to luma values divided by 4. In case of 12 bit video, these
    /// values correspond to luma values divided by 16.)
    pub point_y_value: [u8; MAX_NUM_Y_POINTS],
    /// Pepresents the scaling (output) value for the i-th point of the
    /// piecewise linear scaling function for luma component.
    pub point_y_scaling: [u8; MAX_NUM_Y_POINTS],
    /// Specifies that the chroma scaling is inferred from the luma scaling.
    pub chroma_scaling_from_luma: bool,
    /// Specifies the number of points for the piece-wise linear scaling
    /// function of the cb component.
    pub num_cb_points: u8,
    /// Represents the x coordinate for the i-th point of the piece-wise linear
    /// scaling function for cb component. The values are signaled on the scale
    /// of 0..255.
    pub point_cb_value: [u8; MAX_NUM_CB_POINTS],
    /// Represents the scaling (output) value for the i-th point of the
    /// piecewise linear scaling function for cb component.
    pub point_cb_scaling: [u8; MAX_NUM_CB_POINTS],
    /// Specifies represents the number of points for the piece-wise linear
    /// scaling function of the cr component.
    pub num_cr_points: u8,
    /// Represents the x coordinate for the i-th point of the piece-wise linear
    /// scaling function for cr component. The values are signaled on the scale
    /// of 0..255.
    pub point_cr_value: [u8; MAX_NUM_CR_POINTS],
    /// Represents the scaling (output) value for the i-th point of the
    /// piecewise linear scaling function for cr component.
    pub point_cr_scaling: [u8; MAX_NUM_CR_POINTS],
    /// Represents the shift – 8 applied to the values of the chroma component.
    /// The grain_scaling_minus_8 can take values of 0..3 and determines the
    /// range and quantization step of the standard deviation of film grain.
    pub grain_scaling_minus_8: u8,
    /// Specifies the number of auto-regressive coefficients for luma and chroma.
    pub ar_coeff_lag: u32,
    /// Specifies auto-regressive coefficients used for the Y plane.
    pub ar_coeffs_y_plus_128: [u8; MAX_NUM_POS_LUMA],
    /// Specifies auto-regressive coefficients used for the U plane.
    pub ar_coeffs_cb_plus_128: [u8; MAX_NUM_POS_LUMA],
    /// Specifies auto-regressive coefficients used for the V plane.
    pub ar_coeffs_cr_plus_128: [u8; MAX_NUM_POS_LUMA],
    /// Specifies the range of the auto-regressive coefficients. Values of 0, 1,
    /// 2, and 3 correspond to the ranges for auto-regressive coefficients of
    /// [-2, 2), [-1, 1), [-0.5, 0.5) and [-0.25, 0.25) respectively.
    pub ar_coeff_shift_minus_6: u8,
    /// Specifies how much the Gaussian random numbers should be scaled down
    /// during the grain synthesis process.
    pub grain_scale_shift: u8,
    /// Represents a multiplier for the cb component used in derivation of the
    /// input index to the cb component scaling function.
    pub cb_mult: u8,
    /// Represents a multiplier for the average luma component used in
    /// derivation of the input index to the cb component scaling function.
    pub cb_luma_mult: u8,
    /// Represents an offset used in derivation of the input index to the cb
    /// component scaling function.
    pub cb_offset: u16,
    /// Represents a multiplier for the cr component used in derivation of the
    /// input index to the cr component scaling function.
    pub cr_mult: u8,
    /// Represents a multiplier for the average luma component used in
    /// derivation of the input index to the cr component scaling function.
    pub cr_luma_mult: u8,
    /// Represents an offset used in derivation of the input index to the cr
    /// component scaling function.
    pub cr_offset: u16,
    /// If set, indicates that the overlap between film grain blocks shall be
    /// applied. If not set, indicates that the overlap between film grain
    /// blocks shall not be applied.
    pub overlap_flag: bool,
    /// If set, indicates that clipping to the restricted (studio) range shall
    /// be applied to the sample values after adding the film grain (see the
    /// semantics for color_range for an explanation of studio swing).  If not
    /// set, indicates that clipping to the full range shall be applied to the
    /// sample values after adding the film grain.
    pub clip_to_restricted_range: bool,
}

/// Keeps track of the state of the reference frames in the parser. All
/// variables are CamelCase.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct ReferenceFrameInfo {
    /// An array which is indexed by a reference picture slot number. A value of
    /// true in the array signifies that the corresponding reference picture
    /// slot is valid for use as a reference picture, while a value of false
    /// signifies that the corresponding reference picture slot is not valid for
    /// use as a reference picture.
    ref_valid: bool,
    /// Specifies the frame id for each reference frame.
    ref_frame_id: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_upscaled_width: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_frame_width: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_frame_height: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_render_width: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_render_height: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_mi_cols: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_mi_rows: u32,
    /// See 7.20 Reference Frame Update Process.
    ref_frame_type: FrameType,
    /// See 7.20 Reference Frame Update Process.
    ref_subsampling_x: bool,
    /// See 7.20 Reference Frame Update Process.
    ref_subsampling_y: bool,
    /// See 7.20 Reference Frame Update Process.
    ref_bit_depth: BitDepth,
    /// See 7.20 Reference Frame Update Process.
    ref_order_hint: u32,
    /// The saved segmentation parameters.
    segmentation_params: SegmentationParams,
    /// The saved global motion parameters.
    global_motion_params: GlobalMotionParams,
    /// The saved loop filter parameters.
    loop_filter_params: LoopFilterParams,
    /// The saved film grain parameters.
    film_grain_params: FilmGrainParams,
    /// The saved tile info parameters.
    tile_info: TileInfo,
    display_frame_id: u32,
    showable_frame: bool,
}

#[derive(Clone, Debug, Default)]
pub struct AnnexBState {
    pub temporal_unit_size: u32,
    pub frame_unit_size: u32,
    pub temporal_unit_consumed: u32,
    pub frame_unit_consumed: u32,
}

#[derive(Clone, Debug)]
enum StreamFormat {
    LowOverhead,
    AnnexB(AnnexBState),
}

#[derive(Debug)]
pub struct Parser {
    stream_format: StreamFormat,
    operating_point: u32,
    /// Same as SeenFrameHeader in the specification
    seen_frame_header: bool,
    operating_point_idc: u16,
    should_probe_for_annexb: bool,
    is_first_frame: bool,
    ref_info: [ReferenceFrameInfo; NUM_REF_FRAMES],

    /* CamelCase variables */
    mi_cols: u32,
    mi_rows: u32,
    prev_frame_id: u32,
    current_frame_id: u32,
    mi_col_starts: [u32; MAX_TILE_COLS + 1],
    mi_row_starts: [u32; MAX_TILE_ROWS + 1],
    tile_cols_log2: u32,
    tile_cols: u32,
    tile_rows_log2: u32,
    tile_rows: u32,
    tile_size_bytes: u32,

    /// We keep this to implement frame_header_copy() in the specification, and to fill in
    /// StreamInfo render_width and render_height.
    pub last_frame_header: Option<FrameHeaderObu>,
    /// The last SequenceHeaderObu parsed.
    pub sequence_header: Option<Rc<SequenceHeaderObu>>,
}

impl Parser {
    /// Probes the input data for the Annex B format. Anything other than
    /// Ok(true) refers to data in "low-overhead" format instead, as we are trying to parse
    fn annexb_probe(data: &[u8]) -> Result<bool, String> {
        let mut r = Reader::new(data);
        let mut seen_sequence = false;
        let mut seen_frame = false;

        // Try reading the first TU and frame unit size
        let temporal_unit_size = r.read_leb128()?;
        if temporal_unit_size == 0 {
            return Ok(false);
        }

        let frame_unit_size = r.read_leb128()?;
        if frame_unit_size == 0 || frame_unit_size > temporal_unit_size {
            return Ok(false);
        }

        let obu_length = r.read_leb128()?;
        if obu_length == 0 || obu_length > frame_unit_size {
            return Ok(false);
        }

        // The first OBU in the first frame_unit of each temporal_unit must
        // be a temporal delimiter OBU (and this is the only place temporal
        // delimiter OBUs can appear)
        let header = Self::parse_obu_header(&mut r.clone())?;
        if !matches!(header.obu_type, ObuType::TemporalDelimiter) {
            return Ok(false);
        }

        // Try identifying a sequence and a frame.
        r.0.skip_bits(obu_length as usize * 8)?;
        let mut num_bytes_read = 0;

        loop {
            let obu_length = r.read_leb128()?;
            let mut obu_reader = r.clone();

            r.0.skip_bits(obu_length as usize * 8)?;
            num_bytes_read += obu_length;

            if !seen_sequence {
                let header = Self::parse_obu_header(&mut obu_reader)?;
                seen_sequence = matches!(header.obu_type, ObuType::SequenceHeader);
            }

            if !seen_frame {
                let header = Self::parse_obu_header(&mut obu_reader)?;
                seen_frame = matches!(header.obu_type, ObuType::Frame | ObuType::FrameHeader);
            }

            if seen_sequence && seen_frame {
                // OK, enough evidence of Annex B format.
                return Ok(true);
            }

            if num_bytes_read >= frame_unit_size {
                // We read what we've identified as the first frame and yet no
                // sequence and no actual frames were found.
                return Ok(false);
            }
        }
    }

    fn compute_image_size(&mut self, fh: &mut FrameHeaderObu) {
        fh.mi_cols = 2 * ((fh.frame_width + 7) >> 3);
        fh.mi_rows = 2 * ((fh.frame_height + 7) >> 3);
        self.mi_cols = fh.mi_cols;
        self.mi_rows = fh.mi_rows;
    }

    // 5.9.8
    fn parse_superres_params(
        fh: &mut FrameHeaderObu,
        r: &mut Reader,
        seq: &SequenceHeaderObu,
    ) -> Result<(), String> {
        if seq.enable_superres {
            fh.use_superres = r.0.read_bit()?;
        } else {
            fh.use_superres = false;
        }

        if fh.use_superres {
            fh.superres_denom =
                r.0.read_bits::<u32>(SUPERRES_DENOM_BITS)? + SUPERRES_DENOM_MIN as u32;
        } else {
            fh.superres_denom = SUPERRES_NUM as u32;
        }

        fh.upscaled_width = fh.frame_width;
        fh.frame_width =
            (fh.upscaled_width * SUPERRES_NUM as u32 + (fh.superres_denom / 2)) / fh.superres_denom;

        Ok(())
    }

    // 7.8 verbatim.
    fn set_frame_refs(
        &self,
        fh: &mut FrameHeaderObu,
        ref_order_hint: &[u32; NUM_REF_FRAMES],
    ) -> Result<(), String> {
        let seq = self.sequence()?;
        let mut ref_frame_idx = [-1i32; REFS_PER_FRAME];

        ref_frame_idx[0] = fh.last_frame_idx.into();
        ref_frame_idx[ReferenceFrameType::Golden as usize - ReferenceFrameType::Last as usize] =
            fh.gold_frame_idx.into();

        let mut used_frame = [false; NUM_REF_FRAMES];
        used_frame[fh.last_frame_idx as usize] = true;
        used_frame[fh.gold_frame_idx as usize] = true;

        let cur_frame_hint = 1 << (seq.order_hint_bits - 1);
        let mut shifted_order_hints = [0; NUM_REF_FRAMES];
        for i in 0..NUM_REF_FRAMES {
            shifted_order_hints[i] = cur_frame_hint
                + helpers::get_relative_dist(
                    seq.enable_order_hint,
                    seq.order_hint_bits,
                    ref_order_hint[i].try_into().unwrap(),
                    fh.order_hint.try_into().unwrap(),
                );
        }

        let mut latest_order_hint = shifted_order_hints[fh.last_frame_idx as usize];
        if latest_order_hint >= cur_frame_hint {
            return Err("It is a requirement of bitstream conformance that last_order_hint < cur_frame_hint".into());
        }

        let mut earliest_order_hint = shifted_order_hints[fh.gold_frame_idx as usize];
        if earliest_order_hint >= cur_frame_hint {
            return Err("It is a requirement of bitstream conformance that gold_order_hint < cur_frame_hint".into());
        }

        let ref_ = helpers::find_latest_backward(
            &shifted_order_hints,
            &used_frame,
            cur_frame_hint,
            &mut latest_order_hint,
        );

        if ref_ >= 0 {
            ref_frame_idx
                [ReferenceFrameType::AltRef as usize - ReferenceFrameType::Last as usize] = ref_;
            used_frame[ref_ as usize] = true;
        }

        let ref_ = helpers::find_earliest_backward(
            &shifted_order_hints,
            &used_frame,
            cur_frame_hint,
            &mut earliest_order_hint,
        );

        if ref_ >= 0 {
            ref_frame_idx
                [ReferenceFrameType::BwdRef as usize - ReferenceFrameType::Last as usize] = ref_;
            used_frame[ref_ as usize] = true;
        }

        let ref_ = helpers::find_earliest_backward(
            &shifted_order_hints,
            &used_frame,
            cur_frame_hint,
            &mut earliest_order_hint,
        );

        if ref_ >= 0 {
            ref_frame_idx
                [ReferenceFrameType::AltRef2 as usize - ReferenceFrameType::Last as usize] = ref_;
            used_frame[ref_ as usize] = true;
        }

        const REF_FRAME_LIST: [usize; 5] = [
            ReferenceFrameType::Last2 as usize - ReferenceFrameType::Last as usize,
            ReferenceFrameType::Last3 as usize - ReferenceFrameType::Last as usize,
            ReferenceFrameType::BwdRef as usize - ReferenceFrameType::Last as usize,
            ReferenceFrameType::AltRef2 as usize - ReferenceFrameType::Last as usize,
            ReferenceFrameType::AltRef as usize - ReferenceFrameType::Last as usize,
        ];

        #[allow(clippy::needless_range_loop)]
        for i in 0..REFS_PER_FRAME - 2 {
            let ref_frame = REF_FRAME_LIST[i];

            if ref_frame_idx[ref_frame] < 0 {
                let ref_ = helpers::find_latest_forward(
                    &shifted_order_hints,
                    &used_frame,
                    cur_frame_hint,
                    &mut latest_order_hint,
                );

                if ref_ >= 0 {
                    ref_frame_idx[ref_frame] = ref_;
                    used_frame[ref_ as usize] = true;
                }
            }
        }

        let mut ref_ = 0;
        earliest_order_hint = shifted_order_hints[0];
        #[allow(clippy::needless_range_loop)]
        for i in 1..NUM_REF_FRAMES {
            let hint = shifted_order_hints[i];
            if hint < earliest_order_hint {
                ref_ = i as u8;
                earliest_order_hint = hint;
            }
        }

        fh.ref_frame_idx
            .iter_mut()
            .zip(ref_frame_idx.iter().copied())
            .for_each(|(dest, src)| *dest = if src < 0 { ref_ } else { src as u8 });

        Ok(())
    }

    // 5.9.5.
    fn parse_frame_size(&mut self, fh: &mut FrameHeaderObu, r: &mut Reader) -> Result<(), String> {
        let seq = self.sequence()?;
        if fh.frame_size_override_flag {
            let n = seq.frame_width_bits_minus_1 + 1;
            fh.frame_width = r.0.read_bits::<u32>(n as usize)? + 1;

            let n = seq.frame_height_bits_minus_1 + 1;
            fh.frame_height = r.0.read_bits::<u32>(n as usize)? + 1;
        } else {
            fh.frame_width = seq.max_frame_width_minus_1 as u32 + 1;
            fh.frame_height = seq.max_frame_height_minus_1 as u32 + 1;
        }

        Self::parse_superres_params(fh, r, seq)?;
        self.compute_image_size(fh);

        Ok(())
    }

    fn parse_render_size(fh: &mut FrameHeaderObu, r: &mut Reader) -> Result<(), String> {
        fh.render_and_frame_size_different = r.0.read_bit()?;
        if fh.render_and_frame_size_different {
            fh.render_width = r.0.read_bits::<u32>(16)? + 1;
            fh.render_height = r.0.read_bits::<u32>(16)? + 1;
        } else {
            fh.render_width = fh.upscaled_width;
            fh.render_height = fh.frame_height;
        }
        Ok(())
    }

    fn frame_size_with_refs(
        &mut self,
        fh: &mut FrameHeaderObu,
        r: &mut Reader,
    ) -> Result<(), String> {
        let mut found_ref = false;
        let seq = self.sequence()?;

        for i in 0..REFS_PER_FRAME {
            found_ref = r.0.read_bit()?;

            if found_ref {
                let rf = &self.ref_info[fh.ref_frame_idx[i] as usize];
                fh.upscaled_width = rf.ref_upscaled_width;
                fh.frame_width = fh.upscaled_width;
                fh.frame_height = rf.ref_frame_height;
                fh.render_width = rf.ref_render_width;
                fh.render_height = rf.ref_render_height;
                break;
            }
        }

        if !found_ref {
            self.parse_frame_size(fh, r)?;
            Self::parse_render_size(fh, r)?;
        } else {
            Self::parse_superres_params(fh, r, seq)?;
            self.compute_image_size(fh);
        }

        Ok(())
    }

    /// Skip the padding bits, ensuring that they actually make sense.
    fn skip_and_check_trailing_bits(r: &mut Reader, obu: &Obu) -> Result<(), String> {
        // We can't have that in parse_obu as per the spec, because the reader
        // is not initialized on our design at that point, so move the check to
        // inside this function.
        if obu.data.len() == 0
            || matches!(
                obu.header.obu_type,
                ObuType::TileList | ObuType::TileGroup | ObuType::Frame
            )
        {
            return Ok(());
        }
        let num_trailing = obu.as_ref().len() as u64 * 8 - r.0.position();
        r.read_trailing_bits(num_trailing)?;
        Ok(())
    }

    fn parse_obu_header(r: &mut Reader) -> Result<ObuHeader, String> {
        let _obu_forbidden_bit = r.0.read_bit()?;

        let mut header = ObuHeader {
            obu_type: ObuType::try_from(r.0.read_bits::<u32>(4)?)?,
            extension_flag: r.0.read_bit()?,
            has_size_field: r.0.read_bit()?,
            temporal_id: Default::default(),
            spatial_id: Default::default(),
        };

        let obu_reserved_1bit = r.0.read_bit()?;
        assert!(!obu_reserved_1bit); // Must be set to zero as per spec.

        if header.extension_flag {
            header.temporal_id = r.0.read_bits::<u32>(3)?;
            header.spatial_id = r.0.read_bits::<u32>(2)?;
            let _ = r.0.read_bits::<u32>(3)?;
        }

        Ok(header)
    }

    /// Parses one OBU from `data`, which can be in Annex B or low-overhead
    /// format.
    ///
    /// `None` may eventually be returned if the OBU is to be dropped.
    pub fn read_obu<'a>(&mut self, data: &'a [u8]) -> Result<ObuAction<'a>, String> {
        if data.is_empty() {
            return Err("Empty data".into());
        }

        let mut reader = Reader::new(data);

        if self.should_probe_for_annexb {
            // Try probing for Annex B data.
            self.stream_format = if matches!(Self::annexb_probe(data), Ok(true)) {
                log::debug!("Parsing an Annex B stream");
                StreamFormat::AnnexB(AnnexBState::default())
            } else {
                log::debug!("Parsing a low-overhead stream");
                StreamFormat::LowOverhead
            };

            self.should_probe_for_annexb = false;
        }

        let obu_length: usize = if let StreamFormat::AnnexB(annexb_state) = &mut self.stream_format
        {
            // Read the length to skip to the start of the open_bitstream_unit()
            // syntax element.
            let obu_length = reader.current_annexb_obu_length(annexb_state)?;
            match obu_length {
                Some(length) => length,
                None => return Ok(ObuAction::Drop(reader.consumed(0))),
            }
        } else {
            0
        };

        let start_pos = reader.consumed(0);

        // Both "low-overhead" and Annex B are now at the same point, i.e.: a
        // open_bitstream_unit() follows.
        let header = Self::parse_obu_header(&mut reader)?;
        if matches!(self.stream_format, StreamFormat::LowOverhead) {
            assert!(header.has_size_field);
        }

        let obu_size: usize = if header.has_size_field {
            reader.read_leb128()? as usize
        } else {
            /* trap any bugs when computing the final length */
            obu_length
                .checked_sub(1)
                .ok_or::<String>("obu_length must be greater than 0".into())?
                .checked_sub(usize::from(header.extension_flag))
                .ok_or::<String>("obu_length too short".into())?
        };

        let consumed = reader.consumed(start_pos);

        if let StreamFormat::AnnexB(annexb_state) = &mut self.stream_format {
            annexb_state.temporal_unit_consumed += consumed;
            annexb_state.frame_unit_consumed += consumed;

            annexb_state.temporal_unit_consumed += u32::try_from(obu_size).unwrap();
            annexb_state.frame_unit_consumed += u32::try_from(obu_size).unwrap();
        }

        assert!(reader.0.position() % 8 == 0);
        let start_offset: usize = (reader.0.position() / 8).try_into().unwrap();

        log::debug!(
            "Identified OBU type {:?}, data size: {}, obu_size: {}",
            header.obu_type,
            start_offset + obu_size,
            obu_size
        );

        if header.obu_type != ObuType::SequenceHeader
            && header.obu_type != ObuType::TemporalDelimiter
            && self.operating_point_idc != 0
            && header.extension_flag
        {
            let in_temporal_layer = ((self.operating_point_idc >> header.temporal_id) & 1) != 0;
            let in_spatial_layer = ((self.operating_point_idc >> (header.spatial_id + 8)) & 1) != 0;
            if !in_temporal_layer || !in_spatial_layer {
                log::debug!("Dropping obu as per drop_obu() in the specification",);
                return Ok(ObuAction::Drop(reader.consumed(0)));
            }
        }

        Ok(ObuAction::Process(Obu {
            header,
            data: Cow::from(&data[start_offset..start_offset + obu_size]),
            bytes_used: start_offset + obu_size,
        }))
    }

    fn parse_color_config(s: &mut SequenceHeaderObu, r: &mut Reader) -> Result<(), String> {
        let cc = &mut s.color_config;

        cc.high_bitdepth = r.0.read_bit()?;
        if s.seq_profile as u32 == 2 && cc.high_bitdepth {
            cc.twelve_bit = r.0.read_bit()?;
            if cc.twelve_bit {
                s.bit_depth = BitDepth::Depth12;
            } else {
                s.bit_depth = BitDepth::Depth10;
            }
        } else if s.seq_profile as u32 <= 2 {
            s.bit_depth = if cc.high_bitdepth {
                BitDepth::Depth10
            } else {
                BitDepth::Depth8
            };
        }

        if s.seq_profile as u32 == 1 {
            cc.mono_chrome = false;
        } else {
            cc.mono_chrome = r.0.read_bit()?;
        }

        if cc.mono_chrome {
            s.num_planes = 1;
        } else {
            s.num_planes = 3;
        }

        cc.color_description_present_flag = r.0.read_bit()?;
        if cc.color_description_present_flag {
            cc.color_primaries = ColorPrimaries::try_from(r.0.read_bits::<u32>(8)?)?;
            cc.transfer_characteristics =
                TransferCharacteristics::try_from(r.0.read_bits::<u32>(8)?)?;
            cc.matrix_coefficients = MatrixCoefficients::try_from(r.0.read_bits::<u32>(8)?)?;
        } else {
            cc.color_primaries = ColorPrimaries::Unspecified;
            cc.transfer_characteristics = TransferCharacteristics::Unspecified;
            cc.matrix_coefficients = MatrixCoefficients::Unspecified;
        }

        if cc.mono_chrome {
            cc.color_range = r.0.read_bit()?;
            cc.subsampling_x = true;
            cc.subsampling_y = true;
            cc.chroma_sample_position = ChromaSamplePosition::Unknown;
            cc.separate_uv_delta_q = false;
            return Ok(());
        } else if matches!(cc.color_primaries, ColorPrimaries::Bt709)
            && matches!(cc.transfer_characteristics, TransferCharacteristics::Srgb)
            && matches!(cc.matrix_coefficients, MatrixCoefficients::Identity)
        {
            cc.color_range = true;
            cc.subsampling_x = false;
            cc.subsampling_y = false;
        } else {
            cc.color_range = r.0.read_bit()?;
            if s.seq_profile as u32 == 0 {
                cc.subsampling_x = true;
                cc.subsampling_y = true;
            } else if s.seq_profile as u32 == 1 {
                cc.subsampling_x = false;
                cc.subsampling_y = false;
            } else if matches!(s.bit_depth, BitDepth::Depth12) {
                cc.subsampling_x = r.0.read_bit()?;
                if cc.subsampling_x {
                    cc.subsampling_y = r.0.read_bit()?;
                } else {
                    cc.subsampling_y = false;
                }
            } else {
                cc.subsampling_x = true;
                cc.subsampling_y = false;
            }

            if cc.subsampling_x && cc.subsampling_y {
                cc.chroma_sample_position =
                    ChromaSamplePosition::try_from(r.0.read_bits::<u32>(2)?)?;
            }
        }

        cc.separate_uv_delta_q = r.0.read_bit()?;

        Ok(())
    }

    fn parse_operating_parameters_info(
        opi: &mut OperatingPoint,
        r: &mut Reader,
        buffer_delay_length_minus_1: u8,
    ) -> Result<(), String> {
        let n = buffer_delay_length_minus_1 + 1;
        opi.decoder_buffer_delay = r.0.read_bits::<u32>(n as usize)?;
        opi.encoder_buffer_delay = r.0.read_bits::<u32>(n as usize)?;
        opi.low_delay_mode_flag = r.0.read_bit()?;
        Ok(())
    }

    fn parse_decoder_model_info(dmi: &mut DecoderModelInfo, r: &mut Reader) -> Result<(), String> {
        dmi.buffer_delay_length_minus_1 = r.0.read_bits::<u32>(5)? as u8;
        dmi.num_units_in_decoding_tick = r.0.read_bits::<u32>(32)?;
        dmi.buffer_removal_time_length_minus_1 = r.0.read_bits::<u32>(5)? as u8;
        dmi.frame_presentation_time_length_minus_1 = r.0.read_bits::<u32>(5)?;
        Ok(())
    }

    fn parse_timing_info(ti: &mut TimingInfo, r: &mut Reader) -> Result<(), String> {
        ti.num_units_in_display_tick = r.0.read_bits::<u32>(32)?;
        ti.time_scale = r.0.read_bits::<u32>(32)?;
        ti.equal_picture_interval = r.0.read_bit()?;
        if ti.equal_picture_interval {
            ti.num_ticks_per_picture_minus_1 = r.read_uvlc()?;
        }
        Ok(())
    }

    /// Selects an operating point. Only call this after the Sequence OBU for
    /// which the operating point should apply has been parsed.
    pub fn choose_operating_point(&mut self, operating_point: u32) -> Result<(), String> {
        if operating_point > self.sequence()?.operating_points_cnt_minus_1 {
            return Err(format!(
                "Invalid operating point {} (max {})",
                operating_point,
                self.sequence()?.operating_points_cnt_minus_1
            ));
        }
        self.operating_point = operating_point;
        self.operating_point_idc = self.sequence()?.operating_points[operating_point as usize].idc;
        Ok(())
    }

    fn parse_temporal_delimiter_obu(&mut self) -> Result<(), String> {
        self.seen_frame_header = false;
        Ok(())
    }

    fn parse_sequence_header_obu(&mut self, obu: &Obu) -> Result<Rc<SequenceHeaderObu>, String> {
        let mut s = SequenceHeaderObu {
            obu_header: obu.header.clone(),
            ..Default::default()
        };

        let mut r = Reader::new(obu.as_ref());
        let profile = r.0.read_bits::<u32>(3)?;

        s.seq_profile = Profile::try_from(profile)?;
        s.still_picture = r.0.read_bit()?;
        s.reduced_still_picture_header = r.0.read_bit()?;

        if s.reduced_still_picture_header {
            /* Default::default() already ensures a lot of this, but lets go verbatim */
            s.timing_info_present_flag = false;
            s.decoder_model_info_present_flag = false;
            s.initial_display_delay_present_flag = false;
            s.operating_points_cnt_minus_1 = 0;
            s.operating_points[0].idc = 0;
            s.operating_points[0].seq_level_idx = r.0.read_bits::<u32>(5)? as u8;
            s.operating_points[0].seq_tier = 0;
            s.operating_points[0].decoder_model_present_for_this_op = false;
            s.operating_points[0].initial_display_delay_present_for_this_op = false;
        } else {
            s.timing_info_present_flag = r.0.read_bit()?;
            if s.timing_info_present_flag {
                Self::parse_timing_info(&mut s.timing_info, &mut r)?;
                s.decoder_model_info_present_flag = r.0.read_bit()?;
                if s.decoder_model_info_present_flag {
                    Self::parse_decoder_model_info(&mut s.decoder_model_info, &mut r)?;
                }
            } else {
                s.decoder_model_info_present_flag = false;
            }

            s.initial_display_delay_present_flag = r.0.read_bit()?;
            s.operating_points_cnt_minus_1 = r.0.read_bits::<u32>(5)?;
            if s.operating_points_cnt_minus_1 > MAX_NUM_OPERATING_POINTS as u32 {
                return Err(format!(
                    "Invalid operating_points_cnt_minus_1 {}",
                    s.operating_points_cnt_minus_1
                ));
            }

            for i in 0..=s.operating_points_cnt_minus_1 as usize {
                s.operating_points[i].idc = r.0.read_bits::<u32>(12)? as u16;
                s.operating_points[i].seq_level_idx = r.0.read_bits::<u32>(5)? as u8;
                if s.operating_points[i].seq_level_idx > 7 {
                    s.operating_points[i].seq_tier = r.0.read_bit()? as u8;
                } else {
                    s.operating_points[i].seq_tier = 0;
                }
                if s.decoder_model_info_present_flag {
                    s.operating_points[i].decoder_model_present_for_this_op = r.0.read_bit()?;
                    if s.operating_points[i].decoder_model_present_for_this_op {
                        let buffer_delay_length_minus_1 =
                            s.decoder_model_info.buffer_delay_length_minus_1;
                        Self::parse_operating_parameters_info(
                            &mut s.operating_points[i],
                            &mut r,
                            buffer_delay_length_minus_1,
                        )?;
                    }
                } else {
                    s.operating_points[i].decoder_model_present_for_this_op = false;
                }

                if s.initial_display_delay_present_flag {
                    s.operating_points[i].initial_display_delay_present_for_this_op =
                        r.0.read_bit()?;
                    if s.operating_points[i].initial_display_delay_present_for_this_op {
                        s.operating_points[i].initial_display_delay_minus_1 =
                            r.0.read_bits::<u32>(4)?;
                    }
                }
            }
        }

        s.frame_width_bits_minus_1 = r.0.read_bits::<u32>(4)? as u8;
        s.frame_height_bits_minus_1 = r.0.read_bits::<u32>(4)? as u8;
        // frame_width_bits_minus_1 has been read from 4 bits, meaning we can read 16 bits at most.
        s.max_frame_width_minus_1 =
            r.0.read_bits::<u32>(s.frame_width_bits_minus_1 as usize + 1)? as u16;
        // frame_height_bits_minus_1 has been read from 4 bits, meaning we can read 16 bits at most.
        s.max_frame_height_minus_1 =
            r.0.read_bits::<u32>(s.frame_height_bits_minus_1 as usize + 1)? as u16;
        if s.reduced_still_picture_header {
            s.frame_id_numbers_present_flag = false;
        } else {
            s.frame_id_numbers_present_flag = r.0.read_bit()?;
        }
        if s.frame_id_numbers_present_flag {
            s.delta_frame_id_length_minus_2 = r.0.read_bits::<u32>(4)?;
            s.additional_frame_id_length_minus_1 = r.0.read_bits::<u32>(3)?;
            let frame_id_length =
                s.additional_frame_id_length_minus_1 + s.delta_frame_id_length_minus_2 + 3;
            if frame_id_length > 16 {
                return Err(format!("Invalid frame_id_length {}", frame_id_length));
            }
        }

        s.use_128x128_superblock = r.0.read_bit()?;
        s.enable_filter_intra = r.0.read_bit()?;
        s.enable_intra_edge_filter = r.0.read_bit()?;
        if s.reduced_still_picture_header {
            s.enable_interintra_compound = false;
            s.enable_masked_compound = false;
            s.enable_warped_motion = false;
            s.enable_dual_filter = false;
            s.enable_order_hint = false;
            s.enable_jnt_comp = false;
            s.enable_ref_frame_mvs = false;
            s.seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS as _;
            s.seq_force_integer_mv = SELECT_INTEGER_MV as _;
            s.order_hint_bits = 0;
            s.order_hint_bits_minus_1 = -1;
        } else {
            s.enable_interintra_compound = r.0.read_bit()?;
            s.enable_masked_compound = r.0.read_bit()?;
            s.enable_warped_motion = r.0.read_bit()?;
            s.enable_dual_filter = r.0.read_bit()?;
            s.enable_order_hint = r.0.read_bit()?;
            if s.enable_order_hint {
                s.enable_jnt_comp = r.0.read_bit()?;
                s.enable_ref_frame_mvs = r.0.read_bit()?;
            } else {
                s.enable_jnt_comp = false;
                s.enable_ref_frame_mvs = false;
            }
            s.seq_choose_screen_content_tools = r.0.read_bit()?;
            if s.seq_choose_screen_content_tools {
                s.seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS as _;
            } else {
                s.seq_force_screen_content_tools = r.0.read_bit()? as _;
            }
            if s.seq_force_screen_content_tools > 0 {
                s.seq_choose_integer_mv = r.0.read_bit()?;
                if s.seq_choose_integer_mv {
                    s.seq_force_integer_mv = SELECT_INTEGER_MV as _;
                } else {
                    s.seq_force_integer_mv = r.0.read_bit()? as _;
                }
            } else {
                s.seq_force_integer_mv = SELECT_INTEGER_MV as _;
            }

            if s.enable_order_hint {
                s.order_hint_bits_minus_1 = r.0.read_bits::<u32>(3)?.try_into().unwrap();
                s.order_hint_bits = s.order_hint_bits_minus_1 + 1;
            } else {
                s.order_hint_bits_minus_1 = -1;
                s.order_hint_bits = 0;
            }
        }

        s.enable_superres = r.0.read_bit()?;
        s.enable_cdef = r.0.read_bit()?;
        s.enable_restoration = r.0.read_bit()?;

        Self::parse_color_config(&mut s, &mut r)?;

        s.film_grain_params_present = r.0.read_bit()?;

        Self::skip_and_check_trailing_bits(&mut r, obu)?;
        let rc = Rc::new(s);
        self.sequence_header = Some(rc.clone());

        /* Client is supposed to set the operating point through external means,
         * here we just set 0 as default. */
        self.choose_operating_point(0)?;

        Ok(rc)
    }

    /// Implements 7.21. Note that 7.20 will use the information from the
    /// header, so we must save them now, as they will not be parsed from the
    /// bitstream. We also save some internal parser state which will be useful
    /// later.
    fn load_reference_frame(&self, fh: &mut FrameHeaderObu) -> Result<(), String> {
        let rf = &self.ref_info[fh.frame_to_show_map_idx as usize];

        // Section 6.8.1: It is a requirement of bitstream conformance that a
        // sequence header OBU has been received before a frame header OBU.
        let seq = self.sequence()?;

        /* at least save the sizes and for both kf and non-kf */
        fh.frame_type = rf.ref_frame_type;
        fh.upscaled_width = rf.ref_upscaled_width;
        fh.frame_width = rf.ref_frame_width;
        fh.frame_height = rf.ref_frame_height;
        fh.render_width = rf.ref_render_width;
        fh.render_height = rf.ref_render_height;

        /* Save into the frame header */
        if fh.frame_type == FrameType::KeyFrame {
            fh.current_frame_id = rf.ref_frame_id;
            /* We don't keep track of sequence information at the frame level */
            fh.mi_cols = rf.ref_mi_cols;
            fh.mi_rows = rf.ref_mi_rows;
            /* The accelerator is keeping track of CDF values, so that is skipped too */
            fh.global_motion_params = rf.global_motion_params.clone();

            if seq.film_grain_params_present {
                fh.film_grain_params = rf.film_grain_params.clone();
            }
            fh.loop_filter_params = rf.loop_filter_params.clone();
            fh.segmentation_params = rf.segmentation_params.clone();
        }

        Ok(())
    }

    fn setup_past_independence(fh: &mut FrameHeaderObu) {
        fh.segmentation_params.feature_enabled = Default::default();
        fh.segmentation_params.feature_data = Default::default();

        for i in ReferenceFrameType::Last as usize..ReferenceFrameType::AltRef as usize {
            fh.global_motion_params.gm_type[i] = WarpModelType::Identity;
        }

        fh.loop_filter_params.loop_filter_delta_enabled = true;
        fh.loop_filter_params.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];
        fh.loop_filter_params.loop_filter_mode_deltas = Default::default();
    }

    fn parse_tile_info(&mut self, r: &mut Reader, ti: &mut TileInfo) -> Result<(), String> {
        let seq = self.sequence()?;

        let sb_cols = if seq.use_128x128_superblock {
            (self.mi_cols + 31) >> 5
        } else {
            (self.mi_cols + 15) >> 4
        };

        let sb_rows = if seq.use_128x128_superblock {
            (self.mi_rows + 31) >> 5
        } else {
            (self.mi_rows + 15) >> 4
        };

        let sb_shift = if seq.use_128x128_superblock { 5 } else { 4 };
        let sb_size = sb_shift + 2;

        let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size;
        let mut max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size);

        let min_log2_tile_cols = helpers::tile_log2(max_tile_width_sb, sb_cols);

        let max_log2_tile_cols =
            helpers::tile_log2(1, std::cmp::min(sb_cols, MAX_TILE_COLS as u32));

        let max_log2_tile_rows =
            helpers::tile_log2(1, std::cmp::min(sb_rows, MAX_TILE_ROWS as u32));

        let min_log2_tiles = std::cmp::max(
            min_log2_tile_cols,
            helpers::tile_log2(max_tile_area_sb, sb_rows * sb_cols),
        );

        ti.uniform_tile_spacing_flag = r.0.read_bit()?;

        if ti.uniform_tile_spacing_flag {
            self.tile_cols_log2 = min_log2_tile_cols;
            while self.tile_cols_log2 < max_log2_tile_cols {
                let increment_tile_cols_log_2 = r.0.read_bit()?;
                if increment_tile_cols_log_2 {
                    self.tile_cols_log2 += 1;
                } else {
                    break;
                }
            }

            let tile_width_sb = (sb_cols + (1 << self.tile_cols_log2) - 1) >> self.tile_cols_log2;

            let mut i = 0;
            let mut start_sb = 0;

            while start_sb < sb_cols {
                self.mi_col_starts[i] = start_sb << sb_shift;
                i += 1;
                start_sb += tile_width_sb;
            }

            self.mi_col_starts[i] = self.mi_cols;
            self.tile_cols = i as _;

            if self.tile_cols > MAX_TILE_COLS as u32 {
                return Err(format!("Invalid tile_cols {}", self.tile_cols));
            }

            /* compute this anyways */
            while i >= 1 {
                ti.width_in_sbs_minus_1[i - 1] =
                    ((self.mi_col_starts[i] - self.mi_col_starts[i - 1] + ((1 << sb_shift) - 1))
                        >> sb_shift)
                        - 1;
                i -= 1;
            }

            let min_log2_tile_rows =
                std::cmp::max(min_log2_tiles.saturating_sub(self.tile_cols_log2), 0);
            self.tile_rows_log2 = min_log2_tile_rows;

            while self.tile_rows_log2 < max_log2_tile_rows {
                let increment_tile_rows_log_2 = r.0.read_bit()?;

                if increment_tile_rows_log_2 {
                    self.tile_rows_log2 += 1;
                } else {
                    break;
                }
            }

            let tile_height_sb = (sb_rows + (1 << self.tile_rows_log2) - 1) >> self.tile_rows_log2;

            let mut i = 0;
            let mut start_sb = 0;

            while start_sb < sb_rows {
                self.mi_row_starts[i] = start_sb << sb_shift;
                i += 1;
                start_sb += tile_height_sb;
            }

            self.mi_row_starts[i] = self.mi_rows;
            self.tile_rows = i as _;

            if self.tile_rows > MAX_TILE_ROWS as u32 {
                return Err(format!("Invalid tile_rows {}", self.tile_cols));
            }

            /* compute this anyways */
            while i >= 1 {
                ti.height_in_sbs_minus_1[i - 1] =
                    ((self.mi_row_starts[i] - self.mi_row_starts[i - 1] + ((1 << sb_shift) - 1))
                        >> sb_shift)
                        - 1;
                i -= 1;
            }
        } else {
            let mut widest_tile_sb = 0;
            let mut start_sb = 0;
            let mut i = 0;

            while start_sb < sb_cols {
                self.mi_col_starts[i] = start_sb << sb_shift;

                let max_width = std::cmp::min(sb_cols - start_sb, max_tile_width_sb);
                ti.width_in_sbs_minus_1[i] = r.read_ns(max_width.try_into().unwrap())?;

                let size_sb = ti.width_in_sbs_minus_1[i] + 1;
                widest_tile_sb = std::cmp::max(size_sb, widest_tile_sb);

                start_sb += size_sb;
                i += 1;
            }

            self.mi_col_starts[i] = self.mi_cols;
            self.tile_cols = i as _;
            self.tile_cols_log2 = helpers::tile_log2(1, self.tile_cols);

            if min_log2_tiles > 0 {
                max_tile_area_sb = (sb_rows * sb_cols) >> (min_log2_tiles + 1);
            } else {
                max_tile_area_sb = sb_rows * sb_cols;
            }

            let max_tile_height_sb = std::cmp::max(max_tile_area_sb / widest_tile_sb, 1);
            let mut start_sb = 0;
            let mut i = 0;
            while start_sb < sb_rows {
                self.mi_row_starts[i] = start_sb << sb_shift;
                let max_height = std::cmp::min(sb_rows - start_sb, max_tile_height_sb);
                ti.height_in_sbs_minus_1[i] = r.read_ns(max_height.try_into().unwrap())?;

                let size_sb = ti.height_in_sbs_minus_1[i] + 1;
                start_sb += size_sb;
                i += 1;
            }

            self.mi_row_starts[i] = self.mi_rows;
            self.tile_rows = i as _;
            self.tile_rows_log2 = helpers::tile_log2(1, self.tile_rows);
        }

        if self.tile_cols_log2 > 0 || self.tile_rows_log2 > 0 {
            let num_bits: usize = (self.tile_rows_log2 + self.tile_cols_log2)
                .try_into()
                .unwrap();
            ti.context_update_tile_id = r.0.read_bits::<u32>(num_bits)?;

            if ti.context_update_tile_id >= self.tile_rows * self.tile_cols {
                return Err(format!(
                    "Invalid context_update_tile_id {}",
                    ti.context_update_tile_id
                ));
            }
            self.tile_size_bytes = r.0.read_bits::<u32>(2)? + 1;
        } else {
            ti.context_update_tile_id = 0;
        }

        ti.mi_col_starts = self.mi_col_starts;
        ti.mi_row_starts = self.mi_row_starts;
        ti.tile_cols_log2 = self.tile_cols_log2;
        ti.tile_cols = self.tile_cols;
        ti.tile_rows_log2 = self.tile_rows_log2;
        ti.tile_rows = self.tile_rows;
        ti.tile_size_bytes = self.tile_size_bytes;

        Ok(())
    }

    fn parse_quantization_params(
        r: &mut Reader,
        q: &mut QuantizationParams,
        num_planes: u32,
        separate_uv_delta_q: bool,
    ) -> Result<(), String> {
        q.base_q_idx = r.0.read_bits::<u32>(8)?;
        q.delta_q_y_dc = r.read_delta_q()?;
        if num_planes > 1 {
            if separate_uv_delta_q {
                q.diff_uv_delta = r.0.read_bit()?;
            } else {
                q.diff_uv_delta = false;
            }

            q.delta_q_u_dc = r.read_delta_q()?;
            q.delta_q_u_ac = r.read_delta_q()?;
            if q.diff_uv_delta {
                q.delta_q_v_dc = r.read_delta_q()?;
                q.delta_q_v_ac = r.read_delta_q()?;
            } else {
                q.delta_q_v_dc = q.delta_q_u_dc;
                q.delta_q_v_ac = q.delta_q_u_ac;
            }
        } else {
            q.delta_q_u_dc = 0;
            q.delta_q_u_ac = 0;
            q.delta_q_v_dc = 0;
            q.delta_q_v_ac = 0;
        }

        q.using_qmatrix = r.0.read_bit()?;
        if q.using_qmatrix {
            q.qm_y = r.0.read_bits::<u32>(4)?;
            q.qm_u = r.0.read_bits::<u32>(4)?;
            if !separate_uv_delta_q {
                q.qm_v = q.qm_u;
            } else {
                q.qm_v = r.0.read_bits::<u32>(4)?;
            }
        }
        Ok(())
    }

    fn parse_delta_q_params(r: &mut Reader, q: &mut QuantizationParams) -> Result<(), String> {
        q.delta_q_res = 0;
        q.delta_q_present = false;
        if q.base_q_idx > 0 {
            q.delta_q_present = r.0.read_bit()?;
        }
        if q.delta_q_present {
            q.delta_q_res = r.0.read_bits::<u32>(2)?;
        }

        Ok(())
    }

    fn parse_delta_lf_params(
        r: &mut Reader,
        lf: &mut LoopFilterParams,
        delta_q_present: bool,
        allow_intrabc: bool,
    ) -> Result<(), String> {
        lf.delta_lf_present = false;
        lf.delta_lf_res = 0;
        lf.delta_lf_multi = false;
        if delta_q_present {
            if !allow_intrabc {
                lf.delta_lf_present = r.0.read_bit()?;
            }
            if lf.delta_lf_present {
                lf.delta_lf_res = r.0.read_bits::<u32>(2)? as u8;
                lf.delta_lf_multi = r.0.read_bit()?;
            }
        }
        Ok(())
    }

    fn parse_segmentation_params(
        &self,
        r: &mut Reader,
        fh: &mut FrameHeaderObu,
    ) -> Result<(), String> {
        let s = &mut fh.segmentation_params;
        s.segmentation_enabled = r.0.read_bit()?;
        if s.segmentation_enabled {
            if fh.primary_ref_frame == PRIMARY_REF_NONE {
                s.segmentation_update_map = true;
                s.segmentation_temporal_update = false;
                s.segmentation_update_data = true;
            } else {
                s.segmentation_update_map = r.0.read_bit()?;
                if s.segmentation_update_map {
                    s.segmentation_temporal_update = r.0.read_bit()?;
                }
                s.segmentation_update_data = r.0.read_bit()?;
            }
            if s.segmentation_update_data {
                for i in 0..MAX_SEGMENTS {
                    for j in 0..SEG_LVL_MAX {
                        let feature_enabled = r.0.read_bit()?;
                        s.feature_enabled[i][j] = feature_enabled;
                        if feature_enabled {
                            let bits_to_read = FEATURE_BITS[j];
                            let limit = FEATURE_MAX[j];
                            let signed = FEATURE_SIGNED[j];

                            if signed {
                                let feature_value = r.read_su(1 + bits_to_read as usize)?;
                                let clipped_value = helpers::clip3(-limit, limit, feature_value);
                                s.feature_data[i][j] = clipped_value as _;
                            } else {
                                let feature_value = r.0.read_bits::<u32>(bits_to_read as usize)?;
                                let clipped_value = helpers::clip3(
                                    0,
                                    limit,
                                    feature_value
                                        .try_into()
                                        .map_err(|_| "Invalid feature_value")?,
                                );
                                s.feature_data[i][j] = clipped_value as _;
                            }
                        }
                    }
                }
            } else {
                /* copy from prev_frame */
                let prev_frame =
                    &self.ref_info[fh.ref_frame_idx[fh.primary_ref_frame as usize] as usize];

                if !prev_frame.ref_valid {
                    return Err("Reference is invalid".into());
                }

                s.feature_enabled = prev_frame.segmentation_params.feature_enabled;
                s.feature_data = prev_frame.segmentation_params.feature_data;
            }
        } else {
            for i in 0..MAX_SEGMENTS {
                for j in 0..SEG_LVL_MAX {
                    s.feature_enabled[i][j] = false;
                    s.feature_data[i][j] = 0;
                }
            }
        }

        s.seg_id_pre_skip = false;
        s.last_active_seg_id = 0;
        for i in 0..MAX_SEGMENTS {
            for j in 0..SEG_LVL_MAX {
                if s.feature_enabled[i][j] {
                    s.last_active_seg_id = i as u8;
                    if j >= SEG_LVL_REF_FRAME {
                        s.seg_id_pre_skip = true;
                    }
                }
            }
        }

        Ok(())
    }

    fn parse_loop_filter_parameters(
        r: &mut Reader,
        fh: &mut FrameHeaderObu,
        num_planes: u32,
    ) -> Result<(), String> {
        let lf = &mut fh.loop_filter_params;
        if fh.coded_lossless || fh.allow_intrabc {
            lf.loop_filter_level[0] = 0;
            lf.loop_filter_level[1] = 0;
            lf.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];

            lf.loop_filter_mode_deltas = Default::default();

            return Ok(());
        }

        lf.loop_filter_level[0] = r.0.read_bits::<u32>(6)? as u8;
        lf.loop_filter_level[1] = r.0.read_bits::<u32>(6)? as u8;
        if num_planes > 1 && (lf.loop_filter_level[0] > 0 || lf.loop_filter_level[1] > 0) {
            lf.loop_filter_level[2] = r.0.read_bits::<u32>(6)? as u8;
            lf.loop_filter_level[3] = r.0.read_bits::<u32>(6)? as u8;
        }

        lf.loop_filter_sharpness = r.0.read_bits::<u32>(3)? as u8;
        lf.loop_filter_delta_enabled = r.0.read_bit()?;
        if lf.loop_filter_delta_enabled {
            lf.loop_filter_delta_update = r.0.read_bit()?;
            if lf.loop_filter_delta_update {
                for i in 0..TOTAL_REFS_PER_FRAME {
                    let update_ref_delta = r.0.read_bit()?;
                    if update_ref_delta {
                        lf.loop_filter_ref_deltas[i] = r.read_su(7)? as i8;
                    }
                }

                for i in 0..2 {
                    let update_mode_delta = r.0.read_bit()?;
                    if update_mode_delta {
                        lf.loop_filter_mode_deltas[i] = r.read_su(7)? as i8;
                    }
                }
            }
        }

        Ok(())
    }

    fn parse_cdef_params(
        r: &mut Reader,
        fh: &mut FrameHeaderObu,
        enable_cdef: bool,
        num_planes: u32,
    ) -> Result<(), String> {
        let cdef = &mut fh.cdef_params;

        if fh.coded_lossless || fh.allow_intrabc || !enable_cdef {
            cdef.cdef_bits = 0;
            cdef.cdef_y_pri_strength[0] = 0;
            cdef.cdef_y_sec_strength[0] = 0;
            cdef.cdef_uv_pri_strength[0] = 0;
            cdef.cdef_uv_sec_strength[0] = 0;
            cdef.cdef_damping = 3;
            return Ok(());
        }

        cdef.cdef_damping = r.0.read_bits::<u32>(2)? + 3;
        cdef.cdef_bits = r.0.read_bits::<u32>(2)?;
        for i in 0..(1 << cdef.cdef_bits) as usize {
            cdef.cdef_y_pri_strength[i] = r.0.read_bits::<u32>(4)?;
            cdef.cdef_y_sec_strength[i] = r.0.read_bits::<u32>(2)?;
            if cdef.cdef_y_sec_strength[i] == 3 {
                cdef.cdef_y_sec_strength[i] += 1;
            }
            if num_planes > 1 {
                cdef.cdef_uv_pri_strength[i] = r.0.read_bits::<u32>(4)?;
                cdef.cdef_uv_sec_strength[i] = r.0.read_bits::<u32>(2)?;
                if cdef.cdef_uv_sec_strength[i] == 3 {
                    cdef.cdef_uv_sec_strength[i] += 1;
                }
            }
        }

        Ok(())
    }

    fn parse_loop_restoration_params(
        r: &mut Reader,
        fh: &mut FrameHeaderObu,
        enable_restoration: bool,
        num_planes: u32,
        use_128x128_superblock: bool,
        subsampling_x: bool,
        subsampling_y: bool,
    ) -> Result<(), String> {
        let lr = &mut fh.loop_restoration_params;

        if fh.all_lossless || fh.allow_intrabc || !enable_restoration {
            lr.frame_restoration_type[0] = FrameRestorationType::None;
            lr.frame_restoration_type[1] = FrameRestorationType::None;
            lr.frame_restoration_type[2] = FrameRestorationType::None;
            lr.uses_lr = false;
            return Ok(());
        }

        lr.uses_lr = false;
        lr.uses_chroma_lr = false;

        const REMAP_LR_TYPE: [FrameRestorationType; 4] = [
            FrameRestorationType::None,
            FrameRestorationType::Switchable,
            FrameRestorationType::Wiener,
            FrameRestorationType::Sgrproj,
        ];

        for i in 0..num_planes as usize {
            let lr_type = r.0.read_bits::<u32>(2)?;
            lr.frame_restoration_type[i] = REMAP_LR_TYPE[lr_type as usize];
            if lr.frame_restoration_type[i] != FrameRestorationType::None {
                lr.uses_lr = true;
                if i > 0 {
                    lr.uses_chroma_lr = true;
                }
            }
        }

        if lr.uses_lr {
            if use_128x128_superblock {
                lr.lr_unit_shift = r.0.read_bits::<u32>(1)? as u8 + 1;
            } else {
                lr.lr_unit_shift = r.0.read_bits::<u32>(1)? as u8;
                if lr.lr_unit_shift > 0 {
                    lr.lr_unit_shift += r.0.read_bits::<u32>(1)? as u8;
                }
            }

            lr.loop_restoration_size[0] = RESTORATION_TILESIZE_MAX >> (2 - lr.lr_unit_shift);
            if subsampling_x && subsampling_y && lr.uses_chroma_lr {
                lr.lr_uv_shift = r.0.read_bits::<u32>(1)? as u8;
            } else {
                lr.lr_uv_shift = 0;
            }

            lr.loop_restoration_size[1] = lr.loop_restoration_size[0] >> lr.lr_uv_shift;
            lr.loop_restoration_size[2] = lr.loop_restoration_size[0] >> lr.lr_uv_shift;
        }

        Ok(())
    }

    fn read_tx_mode(r: &mut Reader, fh: &mut FrameHeaderObu) -> Result<(), String> {
        if fh.coded_lossless {
            fh.tx_mode = TxMode::Only4x4;
        } else {
            let tx_mode_select = r.0.read_bit()?;

            if tx_mode_select {
                fh.tx_mode = TxMode::Select;
            } else {
                fh.tx_mode = TxMode::Largest;
            }
        }

        Ok(())
    }

    fn parse_skip_mode_params(
        &self,
        r: &mut Reader,
        fh: &mut FrameHeaderObu,
        enable_order_hint: bool,
        order_hint_bits: i32,
    ) -> Result<(), String> {
        let skip_mode_allowed;

        if fh.frame_is_intra || !fh.reference_select || !enable_order_hint {
            skip_mode_allowed = false;
        } else {
            let mut forward_idx = -1;
            let mut backward_idx = -1;
            let mut forward_hint = 0;
            let mut backward_hint = 0;
            for i in 0..REFS_PER_FRAME {
                let ref_hint = self.ref_info[fh.ref_frame_idx[i] as usize].ref_order_hint;
                if helpers::get_relative_dist(
                    enable_order_hint,
                    order_hint_bits,
                    ref_hint.try_into().unwrap(),
                    fh.order_hint.try_into().unwrap(),
                ) < 0
                    && (forward_idx < 0
                        || helpers::get_relative_dist(
                            enable_order_hint,
                            order_hint_bits,
                            ref_hint.try_into().unwrap(),
                            forward_hint,
                        ) > 0)
                {
                    forward_idx = i32::try_from(i).unwrap();
                    forward_hint = ref_hint.try_into().unwrap();
                } else if helpers::get_relative_dist(
                    enable_order_hint,
                    order_hint_bits,
                    ref_hint.try_into().unwrap(),
                    fh.order_hint.try_into().unwrap(),
                ) > 0
                    && (backward_idx < 0 || {
                        helpers::get_relative_dist(
                            enable_order_hint,
                            order_hint_bits,
                            ref_hint.try_into().unwrap(),
                            backward_hint,
                        ) < 0
                    })
                {
                    backward_idx = i32::try_from(i).unwrap();
                    backward_hint = ref_hint.try_into().unwrap();
                }
            }

            if forward_idx < 0 {
                skip_mode_allowed = false;
            } else if backward_idx >= 0 {
                skip_mode_allowed = true;
                fh.skip_mode_frame[0] = ReferenceFrameType::Last as u32
                    + u32::try_from(std::cmp::min(forward_idx, backward_idx)).unwrap();
                fh.skip_mode_frame[1] = ReferenceFrameType::Last as u32
                    + u32::try_from(std::cmp::max(forward_idx, backward_idx)).unwrap();
            } else {
                let mut second_forward_idx = -1;
                let mut second_forward_hint = 0;
                for i in 0..REFS_PER_FRAME {
                    let ref_hint = self.ref_info[fh.ref_frame_idx[i] as usize].ref_order_hint;
                    if helpers::get_relative_dist(
                        enable_order_hint,
                        order_hint_bits,
                        ref_hint.try_into().unwrap(),
                        forward_hint,
                    ) < 0
                        && (second_forward_idx < 0
                            || helpers::get_relative_dist(
                                enable_order_hint,
                                order_hint_bits,
                                ref_hint.try_into().unwrap(),
                                second_forward_hint,
                            ) > 0)
                    {
                        second_forward_idx = i32::try_from(i).unwrap();
                        second_forward_hint = ref_hint.try_into().unwrap();
                    }
                }

                if second_forward_idx < 0 {
                    skip_mode_allowed = false;
                } else {
                    skip_mode_allowed = true;
                    fh.skip_mode_frame[0] = ReferenceFrameType::Last as u32
                        + u32::try_from(std::cmp::min(forward_idx, second_forward_idx)).unwrap();
                    fh.skip_mode_frame[1] = ReferenceFrameType::Last as u32
                        + u32::try_from(std::cmp::max(forward_idx, second_forward_idx)).unwrap();
                }
            }
        }

        if skip_mode_allowed {
            fh.skip_mode_present = r.0.read_bit()?;
        } else {
            fh.skip_mode_present = false;
        }

        Ok(())
    }

    fn parse_frame_reference_mode(r: &mut Reader, fh: &mut FrameHeaderObu) -> Result<(), String> {
        if fh.frame_is_intra {
            fh.reference_select = false;
        } else {
            fh.reference_select = r.0.read_bit()?;
        }
        Ok(())
    }

    fn seg_feature_active_idx(seg: &SegmentationParams, idx: u32, feature: u32) -> bool {
        seg.segmentation_enabled && seg.feature_enabled[idx as usize][feature as usize]
    }

    fn get_qindex(fh: &FrameHeaderObu, ignore_deltaq: bool, segment_id: u32) -> i32 {
        let base_q_idx = i32::try_from(fh.quantization_params.base_q_idx).unwrap();
        if Self::seg_feature_active_idx(&fh.segmentation_params, segment_id, SEG_LVL_ALT_Q as u32) {
            let data = fh.segmentation_params.feature_data[segment_id as usize][SEG_LVL_ALT_Q];
            let mut qindex = base_q_idx + i32::from(data);
            if !ignore_deltaq && fh.quantization_params.delta_q_present {
                qindex += i32::try_from(fh.quantization_params.delta_q_res).unwrap();
            }
            helpers::clip3(0, 255, qindex)
        } else {
            base_q_idx
        }
    }

    fn setup_shear(warp_params: &[i32; 6]) -> Result<bool, String> {
        let mut default = true;
        for (i, param) in warp_params.iter().enumerate() {
            let default_value = if i % 3 == 2 {
                1 << WARPEDMODEL_PREC_BITS
            } else {
                0
            };
            if *param != default_value {
                default = false;
                break;
            }
        }

        /* assume the default params to be valid */
        if default {
            return Ok(true);
        }

        let alpha0 = helpers::clip3(-32768, 32767, warp_params[2] - (1 << WARPEDMODEL_PREC_BITS));
        let beta0 = helpers::clip3(-32768, 32767, warp_params[3]);

        let (div_shift, div_factor) = helpers::resolve_divisor(warp_params[2])?;

        let v = i64::from(warp_params[4] << WARPEDMODEL_PREC_BITS);
        let v = (v * i64::from(div_factor)) as i32;
        let gamma0 = helpers::clip3(-32678, 32767, helpers::round2signed(v, div_shift)?);

        let w = warp_params[3] * warp_params[4];

        let delta0 = helpers::clip3(
            -32768,
            32767,
            warp_params[5]
                - helpers::round2signed(w * div_factor, div_shift)?
                - (1 << WARPEDMODEL_PREC_BITS),
        );

        let alpha =
            helpers::round2signed(alpha0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;
        let beta = helpers::round2signed(beta0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;
        let gamma =
            helpers::round2signed(gamma0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;
        let delta =
            helpers::round2signed(delta0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;

        #[allow(clippy::needless_bool)]
        let warp_valid = if 4 * alpha.abs() + 7 * beta.abs() >= (1 << WARPEDMODEL_PREC_BITS)
            || 4 * gamma.abs() + 4 * delta.abs() >= (1 << WARPEDMODEL_PREC_BITS)
        {
            false
        } else {
            true
        };

        Ok(warp_valid)
    }

    fn read_global_param(
        reader: &mut Reader,
        type_: WarpModelType,
        ref_frame: usize,
        idx: usize,
        allow_high_precision_mv: bool,
        prev_gm_params: &[[i32; 6]; NUM_REF_FRAMES],
        gm_params: &mut [[i32; 6]; NUM_REF_FRAMES],
    ) -> Result<(), String> {
        let mut abs_bits = GM_ABS_ALPHA_BITS;
        let mut prec_bits = GM_ALPHA_PREC_BITS;
        if idx < 2 {
            if type_ == WarpModelType::Translation {
                abs_bits = GM_ABS_TRANS_ONLY_BITS - !allow_high_precision_mv as u32;
                prec_bits = GM_TRANS_ONLY_PREC_BITS - !allow_high_precision_mv as u32;
            } else {
                abs_bits = GM_ABS_TRANS_BITS;
                prec_bits = GM_TRANS_PREC_BITS;
            }
        }

        let prec_diff = WARPEDMODEL_PREC_BITS - prec_bits;

        let (round, sub) = if (idx % 3) == 2 {
            (1 << WARPEDMODEL_PREC_BITS, 1 << prec_bits)
        } else {
            (0, 0)
        };

        let mx = 1 << abs_bits;
        let r = (prev_gm_params[ref_frame][idx] >> prec_diff) - sub;
        gm_params[ref_frame][idx] =
            (reader.decode_signed_subexp_with_ref(-mx, mx + 1, r)? << prec_diff) + round;

        Ok(())
    }

    fn parse_global_motion_params(
        &mut self,
        r: &mut Reader,
        fh: &mut FrameHeaderObu,
    ) -> Result<(), String> {
        let gm = &mut fh.global_motion_params;
        let mut type_;
        let mut prev_gm_params: [[i32; 6]; NUM_REF_FRAMES] = Default::default();

        for ref_frame in ReferenceFrameType::Last as usize..=ReferenceFrameType::AltRef as usize {
            gm.gm_type[ref_frame] = WarpModelType::Identity;
            for i in 0..6 {
                gm.gm_params[ref_frame][i] = if i % 3 == 2 {
                    1 << WARPEDMODEL_PREC_BITS
                } else {
                    0
                }
            }
            gm.warp_valid[ref_frame] = true;
        }

        if fh.frame_is_intra {
            return Ok(());
        }

        // Following libgav1: implement part of setup_past_independence() and
        // load_previous(), i.e.: the parts that refer to the global motion
        // parameters.
        if fh.primary_ref_frame == PRIMARY_REF_NONE {
            // setup_past_independence()
            #[allow(clippy::needless_range_loop)]
            for ref_frame in ReferenceFrameType::Last as usize..ReferenceFrameType::AltRef as usize
            {
                for i in 0..5 {
                    prev_gm_params[ref_frame][i] = if i % 3 == 2 {
                        1 << WARPEDMODEL_PREC_BITS
                    } else {
                        0
                    }
                }
            }
        } else {
            // load_previous():
            // 1. The variable prevFrame is set equal to ref_frame_idx[ primary_ref_frame ].
            // 2. PrevGmParams is set equal to SavedGmParams[ prevFrame ].
            let prev_frame = fh.ref_frame_idx[fh.primary_ref_frame as usize];
            prev_gm_params = self.ref_info[prev_frame as usize]
                .global_motion_params
                .gm_params;
        }

        for ref_frame in ReferenceFrameType::Last as usize..=ReferenceFrameType::AltRef as usize {
            gm.is_global[ref_frame] = r.0.read_bit()?;
            if gm.is_global[ref_frame] {
                gm.is_rot_zoom[ref_frame] = r.0.read_bit()?;
                if gm.is_rot_zoom[ref_frame] {
                    type_ = WarpModelType::RotZoom;
                } else {
                    gm.is_translation[ref_frame] = r.0.read_bit()?;
                    if gm.is_translation[ref_frame] {
                        type_ = WarpModelType::Translation;
                    } else {
                        type_ = WarpModelType::Affine;
                    }
                }
            } else {
                type_ = WarpModelType::Identity;
            }

            gm.gm_type[ref_frame] = type_;
            if gm.gm_type[ref_frame] as u32 >= WarpModelType::RotZoom as u32 {
                Self::read_global_param(
                    r,
                    type_,
                    ref_frame,
                    2,
                    fh.allow_high_precision_mv,
                    &prev_gm_params,
                    &mut gm.gm_params,
                )?;

                Self::read_global_param(
                    r,
                    type_,
                    ref_frame,
                    3,
                    fh.allow_high_precision_mv,
                    &prev_gm_params,
                    &mut gm.gm_params,
                )?;

                if type_ == WarpModelType::Affine {
                    Self::read_global_param(
                        r,
                        type_,
                        ref_frame,
                        4,
                        fh.allow_high_precision_mv,
                        &prev_gm_params,
                        &mut gm.gm_params,
                    )?;

                    Self::read_global_param(
                        r,
                        type_,
                        ref_frame,
                        5,
                        fh.allow_high_precision_mv,
                        &prev_gm_params,
                        &mut gm.gm_params,
                    )?;
                } else {
                    gm.gm_params[ref_frame][4] = -gm.gm_params[ref_frame][3];
                    gm.gm_params[ref_frame][5] = gm.gm_params[ref_frame][2];
                }
            }

            if gm.gm_type[ref_frame] as u32 >= WarpModelType::Translation as u32 {
                Self::read_global_param(
                    r,
                    type_,
                    ref_frame,
                    0,
                    fh.allow_high_precision_mv,
                    &prev_gm_params,
                    &mut gm.gm_params,
                )?;

                Self::read_global_param(
                    r,
                    type_,
                    ref_frame,
                    1,
                    fh.allow_high_precision_mv,
                    &prev_gm_params,
                    &mut gm.gm_params,
                )?;
            }

            gm.warp_valid[ref_frame] = Self::setup_shear(&gm.gm_params[ref_frame])?;
        }

        Ok(())
    }

    fn parse_film_grain_parameters(
        &self,
        r: &mut Reader,
        fh: &mut FrameHeaderObu,
        film_grain_params_present: bool,
        mono_chrome: bool,
        subsampling_x: bool,
        subsampling_y: bool,
    ) -> Result<(), String> {
        let fg = &mut fh.film_grain_params;

        if !film_grain_params_present || (!fh.show_frame && !fh.showable_frame) {
            *fg = Default::default();
            return Ok(());
        }

        fg.apply_grain = r.0.read_bit()?;
        if !fg.apply_grain {
            *fg = Default::default();
            return Ok(());
        }

        fg.grain_seed = r.0.read_bits::<u32>(16)? as u16;
        if fh.frame_type == FrameType::InterFrame {
            fg.update_grain = r.0.read_bit()?;
        } else {
            fg.update_grain = true;
        }

        if !fg.update_grain {
            fg.film_grain_params_ref_idx = r.0.read_bits::<u32>(3)? as u8;
            let temp_grain_seed = fg.grain_seed;

            if !fh
                .ref_frame_idx
                .iter()
                .any(|&ref_frame_idx| ref_frame_idx == fg.film_grain_params_ref_idx)
            {
                return Err("Invalid film_grain_params_ref_idx".into());
            }

            // load_grain_params()
            *fg = self.ref_info[fg.film_grain_params_ref_idx as usize]
                .film_grain_params
                .clone();

            fg.grain_seed = temp_grain_seed;

            return Ok(());
        }

        fg.num_y_points = r.0.read_bits::<u32>(4)? as u8;
        fg.point_y_value
            .iter_mut()
            .zip(fg.point_y_scaling.iter_mut())
            .take(fg.num_y_points as usize)
            .try_for_each(|(point_y_value, point_y_scaling)| {
                *point_y_value = r.0.read_bits::<u32>(8)? as u8;
                *point_y_scaling = r.0.read_bits::<u32>(8)? as u8;
                Ok::<_, String>(())
            })?;

        if mono_chrome {
            fg.chroma_scaling_from_luma = false;
        } else {
            fg.chroma_scaling_from_luma = r.0.read_bit()?;
        }

        if mono_chrome
            || fg.chroma_scaling_from_luma
            || (subsampling_x && subsampling_y && fg.num_y_points == 0)
        {
            fg.num_cb_points = 0;
            fg.num_cr_points = 0;
        } else {
            fg.num_cb_points = r.0.read_bits::<u32>(4)? as u8;
            if fg.num_cb_points > 10 {
                return Err(format!("Invalid num_cb_points {}", fg.num_cb_points));
            }

            for i in 0..fg.num_cb_points as usize {
                fg.point_cb_value[i] = r.0.read_bits::<u32>(8)? as u8;
                if i > 0 && fg.point_cb_value[i - 1] >= fg.point_cb_value[i] {
                    return Err(format!(
                        "Invalid point_cb_value[{}] {}",
                        i, fg.point_cb_value[i]
                    ));
                }
                fg.point_cb_scaling[i] = r.0.read_bits::<u32>(8)? as u8;
            }

            fg.num_cr_points = r.0.read_bits::<u32>(4)? as u8;
            for i in 0..fg.num_cr_points as usize {
                fg.point_cr_value[i] = r.0.read_bits::<u32>(8)? as u8;
                if i > 0 && fg.point_cr_value[i - 1] >= fg.point_cr_value[i] {
                    return Err(format!(
                        "Invalid point_cr_value[{}] {}",
                        i, fg.point_cr_value[i]
                    ));
                }
                fg.point_cr_scaling[i] = r.0.read_bits::<u32>(8)? as u8;
            }
        }

        fg.grain_scaling_minus_8 = r.0.read_bits::<u32>(2)? as u8;
        fg.ar_coeff_lag = r.0.read_bits::<u32>(2)?;

        let num_pos_luma = 2 * fg.ar_coeff_lag * (fg.ar_coeff_lag + 1);
        let num_pos_chroma = if fg.num_y_points > 0 {
            for i in 0..num_pos_luma as usize {
                fg.ar_coeffs_y_plus_128[i] = r.0.read_bits::<u32>(8)? as u8;
            }
            num_pos_luma + 1
        } else {
            num_pos_luma
        };

        if fg.chroma_scaling_from_luma || fg.num_cb_points > 0 {
            for i in 0..num_pos_chroma as usize {
                fg.ar_coeffs_cb_plus_128[i] = r.0.read_bits::<u32>(8)? as u8;
            }
        }

        if fg.chroma_scaling_from_luma || fg.num_cr_points > 0 {
            for i in 0..num_pos_chroma as usize {
                fg.ar_coeffs_cr_plus_128[i] = r.0.read_bits::<u32>(8)? as u8;
            }
        }

        fg.ar_coeff_shift_minus_6 = r.0.read_bits::<u32>(2)? as u8;
        fg.grain_scale_shift = r.0.read_bits::<u32>(2)? as u8;

        if fg.num_cb_points > 0 {
            fg.cb_mult = r.0.read_bits::<u32>(8)? as u8;
            fg.cb_luma_mult = r.0.read_bits::<u32>(8)? as u8;
            fg.cb_offset = r.0.read_bits::<u32>(9)? as u16;
        }

        if fg.num_cr_points > 0 {
            fg.cr_mult = r.0.read_bits::<u32>(8)? as u8;
            fg.cr_luma_mult = r.0.read_bits::<u32>(8)? as u8;
            fg.cr_offset = r.0.read_bits::<u32>(9)? as u16;
        }

        fg.overlap_flag = r.0.read_bit()?;
        fg.clip_to_restricted_range = r.0.read_bit()?;

        Ok(())
    }

    fn sequence(&self) -> Result<&SequenceHeaderObu, String> {
        let Some(seq) = self.sequence_header.as_ref() else {
            return Err("No sequence header parsed yet".into());
        };

        Ok(seq)
    }

    fn parse_uncompressed_frame_header(&mut self, obu: &Obu) -> Result<FrameHeaderObu, String> {
        let mut r = Reader::new(obu.as_ref());

        let mut fh = FrameHeaderObu {
            obu_header: obu.header.clone(),
            ..Default::default()
        };

        // Section 6.8.1: It is a requirement of bitstream conformance that a
        // sequence header OBU has been received before a frame header OBU.
        let &SequenceHeaderObu {
            operating_points_cnt_minus_1,
            seq_force_integer_mv,
            additional_frame_id_length_minus_1,
            delta_frame_id_length_minus_2,
            decoder_model_info_present_flag,
            reduced_still_picture_header,
            frame_id_numbers_present_flag,
            use_128x128_superblock,
            enable_order_hint,
            seq_force_screen_content_tools,
            order_hint_bits,
            enable_cdef,
            enable_restoration,
            enable_warped_motion,
            color_config:
                ColorConfig {
                    subsampling_x,
                    subsampling_y,
                    separate_uv_delta_q,
                    mono_chrome,
                    ..
                },
            timing_info:
                TimingInfo {
                    equal_picture_interval,
                    ..
                },
            decoder_model_info:
                DecoderModelInfo {
                    frame_presentation_time_length_minus_1,
                    buffer_removal_time_length_minus_1,
                    ..
                },
            num_planes,
            film_grain_params_present,
            ..
        } = self.sequence()?;

        let mut id_len = 0;

        if frame_id_numbers_present_flag {
            id_len = additional_frame_id_length_minus_1 + delta_frame_id_length_minus_2 + 3;
        }

        const ALL_FRAMES: u32 = (1 << NUM_REF_FRAMES) - 1;

        if reduced_still_picture_header {
            fh.show_existing_frame = false;
            fh.frame_type = FrameType::KeyFrame;
            fh.frame_is_intra = true;
            fh.show_frame = true;
            fh.showable_frame = false;
        } else {
            fh.show_existing_frame = r.0.read_bit()?;
            if matches!(obu.header.obu_type, ObuType::Frame) && fh.show_existing_frame {
                return Err("If obu_type is equal to OBU_FRAME, it is a requirement of bitstream conformance that show_existing_frame is equal to 0.".into());
            }
            if fh.show_existing_frame {
                fh.frame_to_show_map_idx = r.0.read_bits::<u32>(3)? as u8;

                if decoder_model_info_present_flag && !equal_picture_interval {
                    fh.frame_presentation_time =
                        r.0.read_bits::<u32>(frame_presentation_time_length_minus_1 as usize + 1)?;
                }

                let ref_frame = &self.ref_info[fh.frame_to_show_map_idx as usize];

                fh.refresh_frame_flags = 0;
                if frame_id_numbers_present_flag {
                    if id_len == 0 {
                        return Err(format!("Invalid id_len {}", id_len));
                    }
                    fh.display_frame_id = r.0.read_bits::<u32>(id_len.try_into().unwrap())?;
                    if ref_frame.display_frame_id != fh.display_frame_id || !ref_frame.ref_valid {
                        return Err("Invalid display_frame_id".into());
                    }
                }

                if !ref_frame.showable_frame {
                    return Err("Invalid bitstream: can't show this past frame".into());
                }

                // In decode_frame_wrapup():
                //
                // Otherwise (show_existing_frame is equal to 1), if frame_type
                // is equal to KEY_FRAME, the reference frame loading process as
                // specified in section 7.21 is invoked (this process loads
                // frame state from the reference frames into the current frame
                // state variables)
                //
                // The following ordered steps now apply:
                //
                // 1. The reference frame update process as specified in section
                // 7.20 is invoked (this process saves the current frame state
                // into the reference frames).
                //
                // 2. If show_frame is equal to 1 or show_existing_frame is
                // equal to 1, the output process as specified in section 7.18
                // is invoked (this will output the current frame or a saved
                // frame).
                //
                // We implement 1. here while 2. is left to the actual decoder
                self.load_reference_frame(&mut fh)?;
                if fh.frame_type == FrameType::KeyFrame {
                    fh.refresh_frame_flags = ALL_FRAMES;
                }

                if film_grain_params_present {
                    // load_grain_params()
                    fh.film_grain_params = self.ref_info[fh.frame_to_show_map_idx as usize]
                        .film_grain_params
                        .clone();
                }

                // See 5.10.
                if matches!(obu.header.obu_type, ObuType::Frame) {
                    r.byte_alignment()?;
                }

                fh.header_bytes = usize::try_from(r.0.position() / 8).unwrap();
                return Ok(fh);
            }

            fh.frame_type = FrameType::try_from(r.0.read_bits::<u32>(2)?)?;
            fh.frame_is_intra = matches!(
                fh.frame_type,
                FrameType::IntraOnlyFrame | FrameType::KeyFrame
            );

            fh.show_frame = r.0.read_bit()?;

            if fh.show_frame && decoder_model_info_present_flag && equal_picture_interval {
                fh.frame_presentation_time =
                    r.0.read_bits::<u32>(frame_presentation_time_length_minus_1 as usize + 1)?;
            }

            if fh.show_frame {
                fh.showable_frame = !matches!(fh.frame_type, FrameType::KeyFrame);
            } else {
                fh.showable_frame = r.0.read_bit()?;
            }

            if fh.frame_type == FrameType::SwitchFrame
                || (fh.frame_type == FrameType::KeyFrame && fh.show_frame)
            {
                fh.error_resilient_mode = true;
            } else {
                fh.error_resilient_mode = r.0.read_bit()?;
            }
        }

        if fh.frame_type == FrameType::KeyFrame && fh.show_frame {
            for i in 0..NUM_REF_FRAMES {
                self.ref_info[i].ref_valid = false;
                self.ref_info[i].ref_order_hint = 0;
            }
            for i in 0..REFS_PER_FRAME {
                fh.order_hints[ReferenceFrameType::Last as usize + i] = 0;
            }
        }

        fh.disable_cdf_update = r.0.read_bit()?;
        if seq_force_screen_content_tools == SELECT_SCREEN_CONTENT_TOOLS as u32 {
            fh.allow_screen_content_tools = r.0.read_bit()? as u32;
        } else {
            fh.allow_screen_content_tools = seq_force_screen_content_tools;
        }

        if fh.allow_screen_content_tools > 0 {
            if seq_force_integer_mv == SELECT_INTEGER_MV as u32 {
                fh.force_integer_mv = r.0.read_bit()? as u32;
            } else {
                fh.force_integer_mv = seq_force_integer_mv;
            }
        } else {
            fh.force_integer_mv = 0;
        }

        if fh.frame_is_intra {
            fh.force_integer_mv = 1;
        }

        if frame_id_numbers_present_flag {
            self.prev_frame_id = self.current_frame_id;
            self.current_frame_id = r.0.read_bits::<u32>(id_len.try_into().unwrap())?;
            fh.current_frame_id = self.current_frame_id;

            /* conformance checking, as per aom */
            let have_prev_frame_id =
                !(self.is_first_frame || fh.frame_type == FrameType::KeyFrame && fh.show_frame);

            if have_prev_frame_id {
                let frame_id_length =
                    additional_frame_id_length_minus_1 + delta_frame_id_length_minus_2 + 3;

                let diff_frame_id = if self.current_frame_id > self.prev_frame_id {
                    self.current_frame_id - self.prev_frame_id
                } else {
                    if frame_id_length > 16 {
                        return Err(format!("Invalid frame_id_length {}", frame_id_length));
                    }
                    (1 << frame_id_length) + self.current_frame_id - self.prev_frame_id
                };

                if self.prev_frame_id == self.current_frame_id
                    || diff_frame_id >= (1 << (frame_id_length - 1))
                {
                    return Err(format!(
                        "Invalid frame_id: prev_frame_id = {}, current_frame_id = {}",
                        self.prev_frame_id, self.current_frame_id
                    ));
                }
            }

            /* mark_ref_frames (idLen) */
            let diff_len = delta_frame_id_length_minus_2 + 2;
            let shifted_diff_len = 1 << diff_len;
            let shifted_id_len = 1 << id_len;

            for i in 0..NUM_REF_FRAMES {
                if self.current_frame_id > shifted_diff_len {
                    if self.ref_info[i].ref_frame_id > self.current_frame_id
                        || self.ref_info[i].ref_frame_id
                            < (self.current_frame_id - shifted_diff_len)
                    {
                        self.ref_info[i].ref_valid = false;
                    }
                } else if self.ref_info[i].ref_frame_id > self.current_frame_id
                    && self.ref_info[i].ref_frame_id
                        < shifted_id_len + self.current_frame_id - shifted_diff_len
                {
                    self.ref_info[i].ref_valid = false;
                }
            }
        } else {
            self.current_frame_id = 0;
            self.prev_frame_id = self.current_frame_id;
            fh.current_frame_id = self.current_frame_id;
        }

        if fh.frame_type == FrameType::SwitchFrame {
            fh.frame_size_override_flag = true;
        } else if reduced_still_picture_header {
            fh.frame_size_override_flag = false;
        } else {
            fh.frame_size_override_flag = r.0.read_bit()?;
        }

        fh.order_hint = r.0.read_bits::<u32>(order_hint_bits.try_into().unwrap())?;

        if fh.frame_is_intra || fh.error_resilient_mode {
            fh.primary_ref_frame = PRIMARY_REF_NONE;
        } else {
            fh.primary_ref_frame = r.0.read_bits::<u32>(3)?;
        }

        let operating_points = &self.sequence()?.operating_points;
        if decoder_model_info_present_flag {
            fh.buffer_removal_time_present_flag = r.0.read_bit()?;
            if fh.buffer_removal_time_present_flag {
                #[allow(clippy::needless_range_loop)]
                for op_num in 0..=operating_points_cnt_minus_1 as usize {
                    if operating_points[op_num].decoder_model_present_for_this_op {
                        let op_pt_idc = operating_points[op_num].idc;
                        let in_temporal_layer = (op_pt_idc >> fh.obu_header.temporal_id) & 1 != 0;
                        let in_spatial_layer =
                            (op_pt_idc >> (fh.obu_header.spatial_id + 8)) & 1 != 0;

                        if op_pt_idc == 0 || (in_temporal_layer && in_spatial_layer) {
                            let n = buffer_removal_time_length_minus_1 + 1;
                            fh.buffer_removal_time[op_num] = r.0.read_bits::<u32>(n as usize)?;
                        }
                    }
                }
            }
        }

        fh.allow_high_precision_mv = false;
        fh.use_ref_frame_mvs = false;
        fh.allow_intrabc = false;
        if fh.frame_type == FrameType::SwitchFrame
            || (fh.frame_type == FrameType::KeyFrame && fh.show_frame)
        {
            fh.refresh_frame_flags = ALL_FRAMES;
        } else {
            fh.refresh_frame_flags = r.0.read_bits::<u32>(8)?;
        }

        /* equivalent boolean expression */
        if (!fh.frame_is_intra || fh.refresh_frame_flags != ALL_FRAMES)
            && fh.error_resilient_mode
            && enable_order_hint
        {
            for i in 0..NUM_REF_FRAMES {
                fh.ref_order_hint[i] = r.0.read_bits::<u32>(order_hint_bits.try_into().unwrap())?;
                if fh.ref_order_hint[i] != self.ref_info[i].ref_order_hint {
                    self.ref_info[i].ref_valid = false;
                }
            }
        }

        if fh.frame_is_intra {
            self.parse_frame_size(&mut fh, &mut r)?;
            Self::parse_render_size(&mut fh, &mut r)?;
            if fh.allow_screen_content_tools > 0 && fh.upscaled_width == fh.frame_width {
                fh.allow_intrabc = r.0.read_bit()?;
            }
        } else {
            if !enable_order_hint {
                fh.frame_refs_short_signaling = false;
            } else {
                fh.frame_refs_short_signaling = r.0.read_bit()?;
                if fh.frame_refs_short_signaling {
                    fh.last_frame_idx = r.0.read_bits::<u32>(3)? as u8;
                    fh.gold_frame_idx = r.0.read_bits::<u32>(3)? as u8;
                    let ref_order_hints = self
                        .ref_info
                        .iter()
                        .map(|i| i.ref_order_hint)
                        .collect::<Vec<_>>()
                        .try_into()
                        .unwrap();
                    self.set_frame_refs(&mut fh, &ref_order_hints)?;
                }
            }

            let mut expected_frame_id = [0; REFS_PER_FRAME];
            #[allow(clippy::needless_range_loop)]
            for i in 0..REFS_PER_FRAME {
                if !fh.frame_refs_short_signaling {
                    fh.ref_frame_idx[i] = r.0.read_bits::<u32>(3)?.try_into().unwrap();
                }

                if frame_id_numbers_present_flag {
                    /* DeltaFrameId */
                    let delta_frame_id =
                        r.0.read_bits::<u32>(delta_frame_id_length_minus_2 as usize + 2)? + 1;

                    if id_len == 0 {
                        return Err(format!("Invalid id_len {}", id_len));
                    }

                    let shifted_id_len = 1 << id_len;

                    expected_frame_id[i] =
                        (self.current_frame_id + shifted_id_len - delta_frame_id) % shifted_id_len;

                    let actual_frame_id = self.ref_info[fh.ref_frame_idx[i] as usize].ref_frame_id;

                    if expected_frame_id[i] != actual_frame_id {
                        return Err(format!(
                            "Invalid frame id, expected {} got {}",
                            expected_frame_id[i], actual_frame_id
                        ));
                    }
                }
            }

            if fh.frame_size_override_flag && !fh.error_resilient_mode {
                self.frame_size_with_refs(&mut fh, &mut r)?;
            } else {
                self.parse_frame_size(&mut fh, &mut r)?;
                Self::parse_render_size(&mut fh, &mut r)?;
            }

            if fh.force_integer_mv > 0 {
                fh.allow_high_precision_mv = false;
            } else {
                fh.allow_high_precision_mv = r.0.read_bit()?;
            }

            /* read_interpolation_filter */
            fh.is_filter_switchable = r.0.read_bit()?;
            if fh.is_filter_switchable {
                fh.interpolation_filter = InterpolationFilter::Switchable;
            } else {
                fh.interpolation_filter = InterpolationFilter::try_from(r.0.read_bits::<u32>(2)?)?;
            }

            fh.is_motion_mode_switchable = r.0.read_bit()?;

            if fh.error_resilient_mode || !self.sequence()?.enable_ref_frame_mvs {
                fh.use_ref_frame_mvs = false;
            } else {
                fh.use_ref_frame_mvs = r.0.read_bit()?;
            }

            for i in 0..REFS_PER_FRAME {
                let ref_frame = ReferenceFrameType::Last as usize + i;
                let hint = self.ref_info[fh.ref_frame_idx[i] as usize].ref_order_hint;
                fh.order_hints[ref_frame] = hint;

                if !enable_order_hint {
                    fh.ref_frame_sign_bias[i] = false;
                } else {
                    fh.ref_frame_sign_bias[i] = helpers::get_relative_dist(
                        enable_order_hint,
                        order_hint_bits,
                        hint.try_into().unwrap(),
                        fh.order_hint.try_into().unwrap(),
                    ) > 0;
                }
            }
        }

        if reduced_still_picture_header || fh.disable_cdf_update {
            fh.disable_frame_end_update_cdf = true;
        } else {
            fh.disable_frame_end_update_cdf = r.0.read_bit()?;
        }

        if fh.primary_ref_frame == PRIMARY_REF_NONE {
            Self::setup_past_independence(&mut fh);
        } else {
            /* load from the past reference */
            let prev_frame =
                &self.ref_info[fh.ref_frame_idx[fh.primary_ref_frame as usize] as usize];

            if !prev_frame.ref_valid {
                return Err("Reference is invalid".into());
            }

            /* load_loop_filter_params: load ref_deltas and mode_deltas */
            fh.loop_filter_params.loop_filter_ref_deltas =
                prev_frame.loop_filter_params.loop_filter_ref_deltas;
            fh.loop_filter_params.loop_filter_mode_deltas =
                prev_frame.loop_filter_params.loop_filter_mode_deltas;

            /* load_segmentation_params: load feature_enabled and feature_data */
            fh.segmentation_params.feature_enabled = prev_frame.segmentation_params.feature_enabled;
            fh.segmentation_params.feature_data = prev_frame.segmentation_params.feature_data;
        }

        // TODO: we can live without this for now.
        // if fh.use_ref_frame_mvs {
        //     // motion_field_estimators()
        // }

        self.parse_tile_info(&mut r, &mut fh.tile_info)?;
        Self::parse_quantization_params(
            &mut r,
            &mut fh.quantization_params,
            num_planes,
            separate_uv_delta_q,
        )?;
        self.parse_segmentation_params(&mut r, &mut fh)?;
        Self::parse_delta_q_params(&mut r, &mut fh.quantization_params)?;
        Self::parse_delta_lf_params(
            &mut r,
            &mut fh.loop_filter_params,
            fh.quantization_params.delta_q_present,
            fh.allow_intrabc,
        )?;

        fh.coded_lossless = true;
        for segment_id in 0..MAX_SEGMENTS {
            let q_index = Self::get_qindex(&fh, true, segment_id as _);
            let q = &fh.quantization_params;
            fh.lossless_array[segment_id] = q_index == 0
                && q.delta_q_y_dc == 0
                && q.delta_q_u_ac == 0
                && q.delta_q_u_dc == 0
                && q.delta_q_v_ac == 0
                && q.delta_q_v_dc == 0;
            if !fh.lossless_array[segment_id] {
                fh.coded_lossless = false;
            }
            if q.using_qmatrix {
                if fh.lossless_array[segment_id] {
                    fh.seg_qm_level[0][segment_id] = 15;
                    fh.seg_qm_level[1][segment_id] = 15;
                    fh.seg_qm_level[2][segment_id] = 15;
                } else {
                    fh.seg_qm_level[0][segment_id] = q.qm_y;
                    fh.seg_qm_level[1][segment_id] = q.qm_u;
                    fh.seg_qm_level[2][segment_id] = q.qm_v;
                }
            }
        }

        fh.all_lossless = fh.coded_lossless && fh.frame_width == fh.upscaled_width;
        Self::parse_loop_filter_parameters(&mut r, &mut fh, num_planes)?;
        Self::parse_cdef_params(&mut r, &mut fh, enable_cdef, num_planes)?;
        Self::parse_loop_restoration_params(
            &mut r,
            &mut fh,
            enable_restoration,
            num_planes,
            use_128x128_superblock,
            subsampling_x,
            subsampling_y,
        )?;
        Self::read_tx_mode(&mut r, &mut fh)?;
        Self::parse_frame_reference_mode(&mut r, &mut fh)?;
        self.parse_skip_mode_params(&mut r, &mut fh, enable_order_hint, order_hint_bits)?;

        if fh.frame_is_intra || fh.error_resilient_mode || !enable_warped_motion {
            fh.allow_warped_motion = false;
        } else {
            fh.allow_warped_motion = r.0.read_bit()?;
        }

        fh.reduced_tx_set = r.0.read_bit()?;
        self.parse_global_motion_params(&mut r, &mut fh)?;
        self.parse_film_grain_parameters(
            &mut r,
            &mut fh,
            film_grain_params_present,
            mono_chrome,
            subsampling_x,
            subsampling_y,
        )?;

        Self::skip_and_check_trailing_bits(&mut r, obu)?;

        // See 5.10
        if matches!(obu.header.obu_type, ObuType::Frame) {
            r.byte_alignment()?;
        }

        fh.header_bytes = usize::try_from(r.0.position() / 8).unwrap();
        Ok(fh)
    }

    fn parse_tile_group_obu<'a>(&mut self, obu: Obu<'a>) -> Result<TileGroupObu<'a>, String> {
        let mut tg = TileGroupObu {
            obu,
            ..Default::default()
        };

        let mut r = Reader::new(tg.obu.as_ref());

        if r.0.num_bits_left() % 8 != 0 {
            return Err("Bitstream is not byte aligned".into());
        }

        let mut sz: u64 = r.0.num_bits_left() as u64 / 8;

        let num_tiles = self.tile_rows * self.tile_cols;
        let start_bit_pos = r.0.position();

        if num_tiles > 1 {
            tg.tile_start_and_end_present_flag = r.0.read_bit()?;
        }

        if num_tiles == 1 || !tg.tile_start_and_end_present_flag {
            tg.tg_start = 0;
            tg.tg_end = num_tiles - 1;
        } else {
            let tile_bits = (self.tile_cols_log2 + self.tile_rows_log2) as usize;
            tg.tg_start = r.0.read_bits::<u32>(tile_bits)?;
            tg.tg_end = r.0.read_bits::<u32>(tile_bits)?;
        }

        r.byte_alignment()?;

        let end_bit_pos = r.0.position();
        let header_bytes = (end_bit_pos - start_bit_pos) / 8;
        sz -= header_bytes;

        let mut tile_num = tg.tg_start;
        while tile_num <= tg.tg_end {
            let tile_row = tile_num / self.tile_cols;
            let tile_col = tile_num % self.tile_cols;
            let last_tile = tile_num == tg.tg_end;
            let tile_size;

            if last_tile {
                tile_size = u32::try_from(sz).unwrap();
            } else {
                tile_size =
                    r.0.read_le::<u32>(self.tile_size_bytes.try_into().unwrap())? + 1;
                sz -= u64::from(tile_size + self.tile_size_bytes);
            }

            let tile = Tile {
                tile_offset: u32::try_from(r.0.position()).unwrap() / 8,
                tile_size,
                tile_row,
                tile_col,
                mi_row_start: self.mi_row_starts[tile_row as usize],
                mi_row_end: self.mi_row_starts[tile_row as usize + 1],
                mi_col_start: self.mi_row_starts[tile_col as usize],
                mi_col_end: self.mi_row_starts[tile_col as usize + 1],
            };

            tg.tiles.push(tile);

            // init_symbol, decode_tile() and exit_symbol() left to the accelerator.

            // Skip the actual tile data
            if tile_num < tg.tg_end {
                r.0.skip_bits(tile_size as usize * 8)?;
            }

            tile_num += 1;
        }

        if tg.tg_end == num_tiles - 1 {
            // left to the accelerator:
            // if ( !disable_frame_end_update_cdf ) {
            //  frame_end_update_cdf( )
            // }
            // decode_frame_wrapup( )
            self.seen_frame_header = false;
        }

        Ok(tg)
    }

    fn parse_frame_obu<'a>(&mut self, obu: Obu<'a>) -> Result<FrameObu<'a>, String> {
        if !matches!(obu.header.obu_type, ObuType::Frame) {
            return Err(format!(
                "Expected a FrameOBU, got {:?}",
                obu.header.obu_type
            ));
        }

        let frame_header_obu = self.parse_frame_header_obu(&obu)?;
        let obu = Obu {
            header: obu.header,
            data: match obu.data {
                Cow::Borrowed(d) => Cow::Borrowed(&d[frame_header_obu.header_bytes..]),
                Cow::Owned(d) => Cow::Owned(d[frame_header_obu.header_bytes..].to_owned()),
            },
            bytes_used: obu.bytes_used,
        };
        let tile_group_obu = self.parse_tile_group_obu(obu)?;

        Ok(FrameObu {
            header: frame_header_obu,
            tile_group: tile_group_obu,
        })
    }

    pub fn parse_frame_header_obu(&mut self, obu: &Obu) -> Result<FrameHeaderObu, String> {
        if !matches!(obu.header.obu_type, ObuType::FrameHeader | ObuType::Frame) {
            return Err(format!(
                "Expected a FrameHeaderOBU, got {:?}",
                obu.header.obu_type
            ));
        }

        if self.seen_frame_header {
            Ok(self
                .last_frame_header
                .clone()
                .take()
                .ok_or::<String>("Broken stream: no previous frame header to copy".into())?)
        } else {
            self.seen_frame_header = true;
            let header = self.parse_uncompressed_frame_header(obu)?;
            if header.show_existing_frame {
                self.last_frame_header = None;
                self.seen_frame_header = false;
            } else {
                /* TileNum = 0 */
                self.seen_frame_header = true;
                self.last_frame_header = Some(header.clone());
            }

            Ok(header)
        }
    }

    /// Implements 7.20. This function should be called right after decoding a
    /// frame.
    pub fn ref_frame_update(&mut self, fh: &FrameHeaderObu) -> Result<(), String> {
        // This was found as a bug otherwise by Nicolas Dufresne in GStreamer's
        // av1parse.
        if fh.show_existing_frame && !matches!(fh.frame_type, FrameType::KeyFrame) {
            return Ok(());
        }

        if matches!(fh.frame_type, FrameType::IntraOnlyFrame) && fh.refresh_frame_flags == 0xff {
            return Err("Intra-only frames cannot refresh all of the DPB as per the spec.".into());
        }

        let &SequenceHeaderObu {
            color_config:
                ColorConfig {
                    subsampling_x,
                    subsampling_y,
                    ..
                },
            film_grain_params_present,
            bit_depth,
            ..
        } = self.sequence()?;

        for (i, ref_info) in self.ref_info.iter_mut().enumerate() {
            if ((fh.refresh_frame_flags >> i) & 1) != 0 {
                ref_info.ref_valid = true;

                ref_info.ref_frame_id = fh.current_frame_id;
                ref_info.ref_frame_type = fh.frame_type;
                ref_info.ref_upscaled_width = fh.upscaled_width;
                ref_info.ref_frame_width = fh.frame_width;
                ref_info.ref_frame_height = fh.frame_height;
                ref_info.ref_render_width = fh.render_width;
                ref_info.ref_render_height = fh.render_height;
                ref_info.ref_order_hint = fh.order_hint;
                ref_info.ref_mi_cols = self.mi_cols;
                ref_info.ref_mi_rows = self.mi_rows;
                ref_info.ref_subsampling_x = subsampling_x;
                ref_info.ref_subsampling_y = subsampling_y;
                ref_info.ref_bit_depth = bit_depth;
                ref_info.segmentation_params = fh.segmentation_params.clone();
                ref_info.global_motion_params = fh.global_motion_params.clone();
                ref_info.loop_filter_params = fh.loop_filter_params.clone();
                ref_info.tile_info = fh.tile_info.clone();
                ref_info.display_frame_id = fh.display_frame_id;
                ref_info.showable_frame = fh.showable_frame;

                if film_grain_params_present {
                    ref_info.film_grain_params = fh.film_grain_params.clone();
                }
            }
        }

        Ok(())
    }

    pub fn highest_operating_point(&self) -> Option<u32> {
        if self.operating_point_idc == 0 {
            /* No scalability information, all OBUs must be decoded */
            None
        } else {
            Some(helpers::floor_log2((self.operating_point_idc >> 8) as u32))
        }
    }

    /// Fully parse an OBU.
    pub fn parse_obu<'a>(&mut self, obu: Obu<'a>) -> Result<ParsedObu<'a>, String> {
        match obu.header.obu_type {
            ObuType::Reserved => Ok(ParsedObu::Reserved),
            ObuType::SequenceHeader => self
                .parse_sequence_header_obu(&obu)
                .map(ParsedObu::SequenceHeader),
            ObuType::TemporalDelimiter => self
                .parse_temporal_delimiter_obu()
                .map(|_| ParsedObu::TemporalDelimiter),
            ObuType::FrameHeader => self
                .parse_frame_header_obu(&obu)
                .map(ParsedObu::FrameHeader),
            ObuType::TileGroup => self.parse_tile_group_obu(obu).map(ParsedObu::TileGroup),
            ObuType::Metadata => Ok(ParsedObu::Metadata),
            ObuType::Frame => self.parse_frame_obu(obu).map(ParsedObu::Frame),
            ObuType::RedundantFrameHeader => Ok(ParsedObu::RedundantFrameHeader),
            ObuType::TileList => Ok(ParsedObu::TileList),
            ObuType::Reserved2 => Ok(ParsedObu::Reserved2),
            ObuType::Reserved3 => Ok(ParsedObu::Reserved3),
            ObuType::Reserved4 => Ok(ParsedObu::Reserved4),
            ObuType::Reserved5 => Ok(ParsedObu::Reserved5),
            ObuType::Reserved6 => Ok(ParsedObu::Reserved6),
            ObuType::Reserved7 => Ok(ParsedObu::Reserved7),
            ObuType::Padding => Ok(ParsedObu::Padding),
        }
    }
}

impl Default for Parser {
    fn default() -> Self {
        Self {
            stream_format: StreamFormat::LowOverhead,
            operating_point: Default::default(),
            seen_frame_header: Default::default(),
            last_frame_header: Default::default(),
            operating_point_idc: Default::default(),
            should_probe_for_annexb: true,
            is_first_frame: Default::default(),
            mi_cols: Default::default(),
            mi_rows: Default::default(),
            prev_frame_id: Default::default(),
            current_frame_id: Default::default(),
            ref_info: Default::default(),
            mi_col_starts: [0; MAX_TILE_COLS + 1],
            mi_row_starts: [0; MAX_TILE_ROWS + 1],
            tile_cols_log2: Default::default(),
            tile_cols: Default::default(),
            tile_rows_log2: Default::default(),
            tile_rows: Default::default(),
            tile_size_bytes: Default::default(),
            sequence_header: Default::default(),
        }
    }
}

impl Clone for Parser {
    fn clone(&self) -> Self {
        let sequence_header = self
            .sequence_header
            .as_ref()
            .map(|s| Rc::new((**s).clone()));

        Self {
            stream_format: self.stream_format.clone(),
            operating_point: self.operating_point,
            seen_frame_header: self.seen_frame_header,
            last_frame_header: self.last_frame_header.clone(),
            operating_point_idc: self.operating_point_idc,
            should_probe_for_annexb: self.should_probe_for_annexb,
            is_first_frame: self.is_first_frame,
            ref_info: self.ref_info.clone(),
            mi_cols: self.mi_cols,
            mi_rows: self.mi_rows,
            prev_frame_id: self.prev_frame_id,
            current_frame_id: self.current_frame_id,
            mi_col_starts: self.mi_col_starts,
            mi_row_starts: self.mi_row_starts,
            tile_cols_log2: self.tile_cols_log2,
            tile_cols: self.tile_cols,
            tile_rows_log2: self.tile_rows_log2,
            tile_rows: self.tile_rows,
            tile_size_bytes: self.tile_size_bytes,
            sequence_header,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::bitstream_utils::IvfIterator;
    use crate::codec::av1::parser::{ObuAction, Parser, StreamFormat};

    use super::ObuType;

    /// Same as test-25fps.av1.ivf from Chromium
    const STREAM_TEST_25_FPS: &[u8] = include_bytes!("test_data/test-25fps.ivf.av1");

    /// Encoded with
    ///
    /// gst-launch-1.0 videotestsrc num-buffers=1 !
    /// video/x-raw,format=I420,width=64,height=64 ! filesink
    /// location=aom_input.yuv
    ///
    /// And:
    ///
    /// aomenc -p 1 --ivf -w 64 -h 64 -o av1-annexb.ivf.av1 aom_input.yuv --annexb=1
    const STREAM_ANNEXB: &[u8] = include_bytes!("test_data/av1-annexb.ivf.av1");

    #[test]
    fn parse_test25fps() {
        let mut parser = Parser::default();
        let ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);
        let mut num_obus = 0;

        for packet in ivf_iter {
            let mut consumed = 0;

            while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
                let obu = match obu {
                    ObuAction::Process(obu) => obu,
                    // This OBU should be dropped.
                    ObuAction::Drop(length) => {
                        consumed += usize::try_from(length).unwrap();
                        continue;
                    }
                };
                consumed += obu.bytes_used;
                num_obus += 1;
            }
        }

        // Manually checked with GStreamer under GDB by using a hitcount on
        // "gst_av1_parse_identify_one_obu" *after* the stream format has been
        // detected.
        assert_eq!(num_obus, 525);
    }

    #[test]
    /// Test that we can correctly identify streams in both "low-overhead" and
    /// Annex B formats.
    fn parse_annexb() {
        let mut parser = Parser::default();
        let mut ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);
        let packet = ivf_iter.next().unwrap();

        parser.read_obu(packet).unwrap();
        assert!(matches!(parser.stream_format, StreamFormat::LowOverhead));

        let mut parser = Parser::default();
        let mut ivf_iter = IvfIterator::new(STREAM_ANNEXB);
        let packet = ivf_iter.next().unwrap();

        parser.read_obu(packet).unwrap();
        assert!(matches!(parser.stream_format, StreamFormat::AnnexB { .. }));
    }

    #[test]
    /// Test that we can correctly identify streams in both "low-overhead" and
    /// Annex B formats and identify all the OBUs in the stream until the end.
    fn parse_annexb_full() {
        let mut parser = Parser::default();
        let ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);

        for packet in ivf_iter {
            let mut consumed = 0;

            while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
                let obu = match obu {
                    ObuAction::Process(obu) => obu,
                    // This OBU should be dropped.
                    ObuAction::Drop(length) => {
                        consumed += usize::try_from(length).unwrap();
                        continue;
                    }
                };
                assert!(matches!(parser.stream_format, StreamFormat::LowOverhead));
                consumed += obu.bytes_used;
            }
        }

        let mut parser = Parser::default();
        let ivf_iter = IvfIterator::new(STREAM_ANNEXB);
        let mut num_obus = 0;

        for packet in ivf_iter {
            let mut consumed = 0;

            while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
                let obu = match obu {
                    ObuAction::Process(obu) => obu,
                    // This OBU should be dropped.
                    ObuAction::Drop(length) => {
                        consumed += usize::try_from(length).unwrap();
                        continue;
                    }
                };
                assert!(matches!(parser.stream_format, StreamFormat::AnnexB { .. }));
                consumed += obu.bytes_used;
                num_obus += 1;
            }
        }

        assert_eq!(num_obus, 3);
        let annexb_state = match parser.stream_format {
            StreamFormat::AnnexB(annexb_state) => annexb_state,
            _ => panic!("Wrong StreamFormat, expected AnnexB"),
        };
        assert_eq!(
            annexb_state.temporal_unit_consumed,
            annexb_state.temporal_unit_size
        );
        assert_eq!(
            annexb_state.frame_unit_consumed,
            annexb_state.frame_unit_size
        );
    }

    #[test]
    fn parse_test25fps_obus() {
        let mut parser = Parser::default();
        let ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);

        for packet in ivf_iter {
            let mut consumed = 0;

            while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
                let obu = match obu {
                    ObuAction::Process(obu) => obu,
                    // This OBU should be dropped.
                    ObuAction::Drop(length) => {
                        consumed += usize::try_from(length).unwrap();
                        continue;
                    }
                };

                let data_len = obu.bytes_used;

                match obu.header.obu_type {
                    ObuType::SequenceHeader => {
                        parser.parse_sequence_header_obu(&obu).unwrap();
                    }
                    ObuType::FrameHeader | ObuType::RedundantFrameHeader => {
                        let fh = parser.parse_frame_header_obu(&obu).unwrap();
                        parser.ref_frame_update(&fh).unwrap();
                    }
                    ObuType::TileGroup => {
                        parser.parse_tile_group_obu(obu).unwrap();
                    }
                    ObuType::Frame => {
                        let frame = parser.parse_frame_obu(obu).unwrap();
                        parser.ref_frame_update(&frame.header).unwrap();
                    }
                    _ => {}
                };

                consumed += data_len;
            }
        }
    }
}
