package h264dec

import (
	"bytes"
	"fmt"

	"bitbucket.org/ausocean/av/codec/h264/h264dec/bits"
	"github.com/pkg/errors"
)

var (
	DefaultScalingMatrix4x4 = [][]int{
		{6, 13, 20, 28, 13, 20, 28, 32, 20, 28, 32, 37, 28, 32, 37, 42},
		{10, 14, 20, 24, 14, 20, 24, 27, 20, 24, 27, 30, 24, 27, 30, 34},
	}

	DefaultScalingMatrix8x8 = [][]int{
		{6, 10, 13, 16, 18, 23, 25, 27,
			10, 11, 16, 18, 23, 25, 27, 29,
			13, 16, 18, 23, 25, 27, 29, 31,
			16, 18, 23, 25, 27, 29, 31, 33,
			18, 23, 25, 27, 29, 31, 33, 36,
			23, 25, 27, 29, 31, 33, 36, 38,
			25, 27, 29, 31, 33, 36, 38, 40,
			27, 29, 31, 33, 36, 38, 40, 42},
		{9, 13, 15, 17, 19, 21, 22, 24,
			13, 13, 17, 19, 21, 22, 24, 25,
			15, 17, 19, 21, 22, 24, 25, 27,
			17, 19, 21, 22, 24, 25, 27, 28,
			19, 21, 22, 24, 25, 27, 28, 30,
			21, 22, 24, 25, 27, 28, 30, 32,
			22, 24, 25, 27, 28, 30, 32, 33,
			24, 25, 27, 28, 30, 32, 33, 35},
	}
	Default4x4IntraList = []int{6, 13, 13, 20, 20, 20, 38, 38, 38, 38, 32, 32, 32, 37, 37, 42}
	Default4x4InterList = []int{10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34}
	Default8x8IntraList = []int{
		6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23,
		23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27,
		27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31,
		31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42}
	Default8x8InterList = []int{
		9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21,
		21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24,
		24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27,
		27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35}
	ScalingList4x4 = map[int][]int{
		0:  Default4x4IntraList,
		1:  Default4x4IntraList,
		2:  Default4x4IntraList,
		3:  Default4x4InterList,
		4:  Default4x4InterList,
		5:  Default4x4InterList,
		6:  Default8x8IntraList,
		7:  Default8x8InterList,
		8:  Default8x8IntraList,
		9:  Default8x8InterList,
		10: Default8x8IntraList,
		11: Default8x8InterList,
	}
	ScalingList8x8 = ScalingList4x4
)

// SPS describes a sequence parameter set as defined by section 7.3.2.1.1 in
// the Specifications.
// For semantics see section 7.4.2.1. Comments for fields are excerpts from
// section 7.4.2.1.
type SPS struct {
	// pofile_idx and level_idc indicate the profile and level to which the
	// coded video sequence conforms.
	Profile, LevelIDC uint8

	// The constraint_setx_flag flags specify the constraints defined in A.2 for
	// which this stream conforms.
	Constraint0 bool
	Constraint1 bool
	Constraint2 bool
	Constraint3 bool
	Constraint4 bool
	Constraint5 bool

	// seq_parameter_set_id identifies this sequence parameter set, and can then
	// be reference by the picture parameter set. The seq_parameter_set_id is
	// in the range of 0 to 30 inclusive.
	SPSID uint64

	// chroma_format_idc specifies the chroma sampling relative to the luma
	// sampling as specified in caluse 6.2. Range of chroma_format_idc is in
	// from 0 to 3 inclusive.
	ChromaFormatIDC uint64

	// separate_color_plane_flag if true specifies that the three components of
	// the 4:4:4 chroma formta are coded separately.
	SeparateColorPlaneFlag bool

	// bit_depth_luma_minus8 specifies the luma array sample bit depth and the
	// luma quantisation parameter range offset QpBdOffset_y (eq 7-3 and 7-4).
	BitDepthLumaMinus8 uint64

	// bit_depth_luma_minus8 specifies the chroma array sample bit depth and the
	// chroma quantisation parameter range offset QpBdOffset_c (eq 7-3 and 7-4).
	BitDepthChromaMinus8 uint64

	// qpprime_y_zero_transform_bypass_flag equal to 1 specifies that, when QP′ Y
	// is equal to 0, a transform bypass operation for the transform coefficient
	// decoding process and picture construction process prior to deblocking
	// filter process as specified in clause 8.5 shall be applied.
	QPPrimeYZeroTransformBypassFlag bool

	// seq_scaling_matrix_present_flag equal to 1 specifies that
	// seq_scaling_list_present_flag[ i ] are present. When 0 they are not present
	// and the sequence-level scaling lists specified by Flat_4x4_16 and
	// Flat_8x8_16 shall be inferred.
	SeqScalingMatrixPresentFlag bool

	// seq_scaling_lit_present_flag[i] specifics whether the syntax structure for
	// scaling list i is present. If 1 then present, otherwise not, and scaling
	// list for i is inferred as per rule set A in table 7-2.
	SeqScalingListPresentFlag []bool

	// The 4x4 sequence scaling lists for each i.
	ScalingList4x4 [][]uint64

	// Flag to indicate for a 4x4 scaling list, if we use the default.
	UseDefaultScalingMatrix4x4Flag []bool

	// The 8x8 sequence scaling lists for each i.
	ScalingList8x8 [][]uint64

	// Flag to indicate for a 8x8 scaling list, if we use the default.
	UseDefaultScalingMatrix8x8Flag []bool

	// log2_max_frame_num_minus4 allows for derivation of MaxFrameNum using eq 7-10.
	Log2MaxFrameNumMinus4 uint64

	// pic_order_cnt_type specifiess the method to decode picture order count.
	PicOrderCountType uint64

	// log2_max_pic_order_cnt_lsb_minus4 allows for the dreivation of
	// MaxPicOrderCntLsb using eq 7-11.
	Log2MaxPicOrderCntLSBMin4 uint64

	// delta_pic_order_always_zero_flag if true indicates delta_pic_order_cnt[0]
	// and delta_pic_order_cnt[1].
	DeltaPicOrderAlwaysZeroFlag bool

	// offset_for_non_ref_pic is used to calculate the picture order count of a
	// non-reference picture as specified in clause 8.2.1.
	OffsetForNonRefPic int64

	// offset_for_top_to_bottom_field is used to calculate the picture order count
	// of a bottom field as specified in clause 8.2.1.
	OffsetForTopToBottomField int64

	// num_ref_frames_in_pic_order_cnt_cycle is used in the decoding process for
	// picture order count as specified in clause 8.2.1.
	NumRefFramesInPicOrderCntCycle uint64

	// offset_for_ref_frame[ i ] is an element of a list of
	// num_ref_frames_in_pic_order_cnt_cycle values used in the decoding process
	// for picture order count as specified in clause 8.2.1.
	OffsetForRefFrameList []int

	// max_num_ref_frames specifies the max number of short-term and long-term
	// reference frames, complementary reference field pairs, and non-paired
	// reference fields that may be used by the decoding process for inter prediction.
	MaxNumRefFrames uint64

	// gaps_in_frame_num_value_allowed_flag specifies the allowed values of
	// frame_num as specified in clause 7.4.3 and the decoding process in case of
	// an inferred gap between values of frame_num as specified in clause 8.2.5.2.
	GapsInFrameNumValueAllowed bool

	// pic_width_in_mbs_minus1 plus 1 specifies the width of each decode picutre
	// in units of macroblocks. See eq 7-13.
	PicWidthInMBSMinus1 uint64

	// pic_height_in_map_units_minus1 plus 1 specifies the height in slice group
	// map units of a decoded frame or field. See eq 7-16.
	PicHeightInMapUnitsMinus1 uint64

	// frame_mbs_only_flag if 0 coded pictures of the coded video sequence may be
	// coded fields or coded frames. If 1 every coded picture of the coded video
	// sequence is a coded frame containing only frame macroblocks.
	FrameMBSOnlyFlag bool

	// mb_adaptive_frame_field_flag if 0 specifies no switching between
	// frame and field macroblocks within a picture. If 1 specifies the possible
	// use of switching between frame and field macroblocks within frames.
	MBAdaptiveFrameFieldFlag bool

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

	// frame_cropping_flag if 1 then frame cropping offset parameters are next in
	// the sequence parameter set. If 0 they are not.
	FrameCroppingFlag bool

	// frame_crop_left_offset, frame_crop_right_offset, frame_crop_top_offset,
	// frame_crop_bottom_offset specify the samples of the pictures in the coded
	// video sequence that are output from the decoding process, in terms of a
	// rectangular region specified in frame coordinates for output.
	FrameCropLeftOffset   uint64
	FrameCropRightOffset  uint64
	FrameCropTopOffset    uint64
	FrameCropBottomOffset uint64

	// vui_parameters_present_flag if 1 the vui_parameters() syntax structure is
	// present, otherwise it is not.
	VUIParametersPresentFlag bool

	// The vui_parameters() syntax structure specified in appendix E.
	VUIParameters *VUIParameters
}

// NewSPS parses a sequence parameter set raw byte sequence from br following
// the syntax structure specified in section 7.3.2.1.1, and returns as a new
// SPS.
func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) {
	logger.Printf("debug: SPS RBSP %d bytes %d bits\n", len(rbsp), len(rbsp)*8)
	logger.Printf("debug: \t%#v\n", rbsp[0:8])
	sps := SPS{}
	br := bits.NewBitReader(bytes.NewReader(rbsp))
	r := newFieldReader(br)

	sps.Profile = uint8(r.readBits(8))
	sps.Constraint0 = r.readBits(1) == 1
	sps.Constraint1 = r.readBits(1) == 1
	sps.Constraint2 = r.readBits(1) == 1
	sps.Constraint3 = r.readBits(1) == 1
	sps.Constraint4 = r.readBits(1) == 1
	sps.Constraint5 = r.readBits(1) == 1
	r.readBits(2) // 2 reserved bits.
	sps.LevelIDC = uint8(r.readBits(8))
	sps.SPSID = r.readUe()
	sps.ChromaFormatIDC = r.readUe()

	// This should be done only for certain ProfileIDC:
	isProfileIDC := []int{100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135}
	// SpecialProfileCase1
	if isInList(isProfileIDC, int(sps.Profile)) {
		if sps.ChromaFormatIDC == chroma444 {
			// TODO: should probably deal with error here.
			sps.SeparateColorPlaneFlag = r.readBits(1) == 1
		}

		sps.BitDepthLumaMinus8 = r.readUe()
		sps.BitDepthChromaMinus8 = r.readUe()
		sps.QPPrimeYZeroTransformBypassFlag = r.readBits(1) == 1
		sps.SeqScalingMatrixPresentFlag = r.readBits(1) == 1

		if sps.SeqScalingMatrixPresentFlag {
			max := 12
			if sps.ChromaFormatIDC != chroma444 {
				max = 8
			}
			logger.Printf("debug: \tbuilding Scaling matrix for %d elements\n", max)
			for i := 0; i < max; i++ {
				sps.SeqScalingListPresentFlag = append(sps.SeqScalingListPresentFlag, r.readBits(1) == 1)

				if sps.SeqScalingListPresentFlag[i] {
					if i < 6 {
						scalingList(
							br,
							ScalingList4x4[i],
							16,
							DefaultScalingMatrix4x4[i])
						// 4x4: Page 75 bottom
					} else {
						// 8x8 Page 76 top
						scalingList(
							br,
							ScalingList8x8[i],
							64,
							DefaultScalingMatrix8x8[i-6])
					}
				}
			}
		}
	} // End SpecialProfileCase1

	// showSPS()
	// return sps
	// Possibly wrong due to no scaling list being built
	sps.Log2MaxFrameNumMinus4 = r.readUe()
	sps.PicOrderCountType = r.readUe()

	if sps.PicOrderCountType == 0 {
		sps.Log2MaxPicOrderCntLSBMin4 = r.readUe()
	} else if sps.PicOrderCountType == 1 {
		sps.DeltaPicOrderAlwaysZeroFlag = r.readBits(1) == 1
		sps.OffsetForNonRefPic = int64(r.readSe())
		sps.OffsetForTopToBottomField = int64(r.readSe())
		sps.NumRefFramesInPicOrderCntCycle = r.readUe()

		for i := 0; i < int(sps.NumRefFramesInPicOrderCntCycle); i++ {
			sps.OffsetForRefFrameList = append(sps.OffsetForRefFrameList, r.readSe())
		}

	}

	sps.MaxNumRefFrames = r.readUe()
	sps.GapsInFrameNumValueAllowed = r.readBits(1) == 1
	sps.PicWidthInMBSMinus1 = r.readUe()
	sps.PicHeightInMapUnitsMinus1 = r.readUe()
	sps.FrameMBSOnlyFlag = r.readBits(1) == 1

	if !sps.FrameMBSOnlyFlag {
		sps.MBAdaptiveFrameFieldFlag = r.readBits(1) == 1
	}

	sps.Direct8x8InferenceFlag = r.readBits(1) == 1
	sps.FrameCroppingFlag = r.readBits(1) == 1

	if sps.FrameCroppingFlag {
		sps.FrameCropLeftOffset = r.readUe()
		sps.FrameCropRightOffset = r.readUe()
		sps.FrameCropTopOffset = r.readUe()
		sps.FrameCropBottomOffset = r.readUe()
	}

	sps.VUIParametersPresentFlag = r.readBits(1) == 1

	if sps.VUIParametersPresentFlag {

	} // End VuiParameters Annex E.1.1

	return &sps, nil
}

// SPS describes a sequence parameter set as defined by section E.1.1 in the
// Specifications.
// Semantics for fields are define in section E.2.1. Comments on fields are
// excerpts from the this section.
type VUIParameters struct {
	// aspect_ratio_info_present_flag if 1 then aspect_ratio_idc is present,
	// otherwsise is not.
	AspectRatioInfoPresentFlag bool

	// aspect_ratio_idc specifies the value of sample aspect ratio of the luma samples.
	AspectRatioIDC uint8

	// sar_width indicates the horizontal size of the sample aspect ratio (in
	// arbitrary units).
	SARWidth uint32

	// sar_height indicates the vertical size of the sample aspect ratio (in the
	// same arbitrary units as sar_width).
	SARHeight uint32

	// overscan_info_present_flag if 1 then overscan_appropriate_flag is present,
	// otherwise if 0, then the display method for the video signal is unspecified.
	OverscanInfoPresentFlag bool

	// overscan_appropriate_flag if 1 then the cropped decoded pictures output
	// are suitable for display using overscan, othersise if 0, then the cropped
	// decoded pictures output should not be displayed using overscan.
	OverscanAppropriateFlag bool

	// video_signal_type_present_flag equal to 1 specifies that video_format,
	// video_full_range_flag and colour_description_present_flag are present,
	// otherwise if 0, then they are not present.
	VideoSignalTypePresentFlag bool

	// video_format indicates the representation of the pictures as specified in
	// Table E-2, before being coded in accordance with this Recommendation |
	// International Standard.
	VideoFormat uint8

	// video_full_range_flag indicates the black level and range of the luma and
	// chroma signals as derived from E′_Y, E′_PB, and E′_PR or E′_R, E′_G,
	// and E′_B real-valued component signals.
	VideoFullRangeFlag bool

	// colour_description_present_flag if 1 specifies that colour_primaries,
	// transfer_characteristics and matrix_coefficients are present, otherwise if
	// 0 then they are not present.
	ColorDescriptionPresentFlag bool

	// colour_primaries indicates the chromaticity coordinates of the source
	// primaries as specified in Table E-3 in terms of the CIE 1931 definition of
	// x and y as specified by ISO 11664-1.
	ColorPrimaries uint8

	// transfer_characteristics either indicates the reference opto-electronic
	// transfer characteristic function of the source picture, or indicates the
	// inverse of the reference electro-optical transfer characteristic function.
	TransferCharacteristics uint8

	// matrix_coefficients describes the matrix coefficients used in deriving luma
	// and chroma signals from the green, blue, and red, or Y, Z, and X primaries,
	// as specified in Table E-5.
	MatrixCoefficients uint8

	// chroma_loc_info_present_flag if 1 specifies that chroma_sample_loc_type_top_field
	// and chroma_sample_loc_type_bottom_field are present, otherwise if 0,
	// they are not present.
	ChromaLocInfoPresentFlag bool

	// chroma_sample_loc_type_top_field and chroma_sample_loc_type_bottom_field
	// specify the location of chroma samples.
	ChromaSampleLocTypeTopField, ChromaSampleLocTypeBottomField uint64

	// timing_info_present_flag if 1 specifies that num_units_in_tick, time_scale
	// and fixed_frame_rate_flag are present in the bitstream, otherwise if 0,
	// they are not present.
	TimingInfoPresentFlag bool

	// num_units_in_tick is the number of time units of a clock operating at the
	// frequency time_scale Hz that corresponds to one increment (called a clock
	// tick) of a clock tick counter.
	NumUnitsInTick uint32

	// time_scale is the number of time units that pass in one second.
	TimeScale uint32

	// fixed_frame_rate_flag if 1 indicates that the temporal distance
	// between the HRD output times of any two consecutive pictures in output
	// order is constrained as follows. fixed_frame_rate_flag equal to 0 indicates
	// that no such constraints apply to the temporal distance between the HRD
	// output times of any two consecutive pictures in output order.
	FixedFrameRateFlag bool

	// nal_hrd_parameters_present_flag if 1 then NAL HRD parameters (pertaining to
	// Type II bitstream conformance) are present, otherwise if 0, then they
	// are not present.
	NALHRDParametersPresentFlag bool

	// The nal_hrd_parameters() syntax structure as specified in section E.1.2.
	NALHRDParameters *HRDParameters

	// vcl_hrd_parameters_present_flag if 1 specifies that VCL HRD parameters
	// (pertaining to all bitstream conformance) are present, otherwise if 0, then
	// they are not present.
	VCLHRDParametersPresentFlag bool

	// The vcl_nal_hrd_parameters() syntax structure as specified in section E.1.2.
	VCLHRDParameters *HRDParameters

	// low_delay_hrd_flag specifies the HRD operational mode as specified in Annex C.
	LowDelayHRDFlag bool

	// pic_struct_present_flag if 1 then picture timing SEI messages (clause D.2.3)
	// are present that include the pic_struct syntax element, otherwise if 0, then
	// not present.
	PicStructPresentFlag bool

	// bitstream_restriction_flag if 1, then the following coded video sequence
	// bitstream restriction parameters are present, otherwise if 0, then they are
	// not present.
	BitstreamRestrictionFlag bool

	// motion_vectors_over_pic_boundaries_flag if 0 then no sample outside the
	// picture boundaries and no sample at a fractional sample position for which
	// the sample value is derived using one or more samples outside the picture
	// boundaries is used for inter prediction of any sample, otherwise if 1,
	// indicates that one or more samples outside picture boundaries may be used
	// in inter prediction.
	MotionVectorsOverPicBoundariesFlag bool

	// max_bytes_per_pic_denom indicates a number of bytes not exceeded by the sum
	// of the sizes of the VCL NAL units associated with any coded picture in the
	// coded video sequence.
	MaxBytesPerPicDenom uint64

	// max_bits_per_mb_denom indicates an upper bound for the number of coded bits
	// of macroblock_layer() data for any macroblock in any picture of the coded
	// video sequence.
	MaxBitsPerMBDenom uint64

	// log2_max_mv_length_horizontal and log2_max_mv_length_vertical indicate the
	// maximum absolute value of a decoded horizontal and vertical motion vector
	// component, respectively, in 1⁄4 luma sample units, for all pictures in the
	// coded video sequence.
	Log2MaxMVLengthHorizontal, Log2MaxMVLengthVertical uint64

	// max_num_reorder_frames indicates an upper bound for the number of frames
	// buffers, in the decoded picture buffer (DPB), that are required for storing
	// frames, complementary field pairs, and non-paired fields before output.
	MaxNumReorderFrames uint64

	// max_dec_frame_buffering specifies the required size of the HRD decoded
	// picture buffer (DPB) in units of frame buffers.
	MaxDecFrameBuffering uint64
}

// NewVUIParameters parses video usability information parameters from br
// following the syntax structure specified in section E.1.1, and returns as a
// new VUIParameters.
func NewVUIParameters(br *bits.BitReader) (*VUIParameters, error) {
	p := &VUIParameters{}
	r := newFieldReader(br)

	p.AspectRatioInfoPresentFlag = r.readBits(1) == 1

	if p.AspectRatioInfoPresentFlag {
		p.AspectRatioIDC = uint8(r.readBits(8))

		EXTENDED_SAR := 999
		if int(p.AspectRatioIDC) == EXTENDED_SAR {
			p.SARWidth = uint32(r.readBits(16))
			p.SARHeight = uint32(r.readBits(16))
		}
	}

	p.OverscanInfoPresentFlag = r.readBits(1) == 1

	if p.OverscanInfoPresentFlag {
		p.OverscanAppropriateFlag = r.readBits(1) == 1
	}

	p.VideoSignalTypePresentFlag = r.readBits(1) == 1

	if p.VideoSignalTypePresentFlag {
		p.VideoFormat = uint8(r.readBits(3))
	}

	if p.VideoSignalTypePresentFlag {
		p.VideoFullRangeFlag = r.readBits(1) == 1
		p.ColorDescriptionPresentFlag = r.readBits(1) == 1

		if p.ColorDescriptionPresentFlag {
			p.ColorPrimaries = uint8(r.readBits(8))
			p.TransferCharacteristics = uint8(r.readBits(8))
			p.MatrixCoefficients = uint8(r.readBits(8))
		}
	}
	p.ChromaLocInfoPresentFlag = r.readBits(1) == 1

	if p.ChromaLocInfoPresentFlag {
		p.ChromaSampleLocTypeTopField = uint64(r.readUe())
		p.ChromaSampleLocTypeBottomField = uint64(r.readUe())
	}

	p.TimingInfoPresentFlag = r.readBits(1) == 1

	if p.TimingInfoPresentFlag {
		p.NumUnitsInTick = uint32(r.readBits(32))
		p.TimeScale = uint32(r.readBits(32))
		p.FixedFrameRateFlag = r.readBits(1) == 1
	}

	p.NALHRDParametersPresentFlag = r.readBits(1) == 1

	var err error
	if p.NALHRDParametersPresentFlag {
		p.NALHRDParameters, err = NewHRDParameters(br)
		if err != nil {
			return nil, errors.Wrap(err, "could not get hrdParameters")
		}
	}

	p.VCLHRDParametersPresentFlag = r.readBits(1) == 1

	if p.VCLHRDParametersPresentFlag {
		p.VCLHRDParameters, err = NewHRDParameters(br)
		if err != nil {
			return nil, errors.Wrap(err, "could not get hrdParameters")
		}
	}
	if p.NALHRDParametersPresentFlag || p.VCLHRDParametersPresentFlag {
		p.LowDelayHRDFlag = r.readBits(1) == 1
	}

	p.PicStructPresentFlag = r.readBits(1) == 1
	p.BitstreamRestrictionFlag = r.readBits(1) == 1

	if p.BitstreamRestrictionFlag {
		p.MotionVectorsOverPicBoundariesFlag = r.readBits(1) == 1
		p.MaxBytesPerPicDenom = r.readUe()
		p.MaxBitsPerMBDenom = r.readUe()
		p.Log2MaxMVLengthHorizontal = r.readUe()
		p.Log2MaxMVLengthVertical = r.readUe()
		p.MaxNumReorderFrames = r.readUe()
		p.MaxDecFrameBuffering = r.readUe()
	}
	return p, nil
}

// HRDParameters describes hypothetical reference decoder parameters as defined
// by section E.1.2 in the specifications.
// Field semantics are defined in section E.2.2. Comments on fields are excerpts
// from section E.2.2.
type HRDParameters struct {
	// cpb_cnt_minus1 plus 1 specifies the number of alternative CPB specifications
	// in the bitstream.
	CPBCntMinus1 uint64

	// bit_rate_scale (together with bit_rate_value_minus1[ SchedSelIdx ])
	// specifies the maximum input bit rate of the SchedSelIdx-th CPB.
	BitRateScale uint8

	// cpb_size_scale (together with cpb_size_value_minus1[ SchedSelIdx ])
	// specifies the CPB size of the SchedSelIdx-th CPB.
	CPBSizeScale uint8

	// bit_rate_value_minus1[ SchedSelIdx ] (together with bit_rate_scale)
	//specifies the maximum input bit rate for the SchedSelIdx-th CPB.
	BitRateValueMinus1 []uint64

	// cpb_size_value_minus1[ SchedSelIdx ] is used together with cpb_size_scale
	// to specify the SchedSelIdx-th CPB size.
	CPBSizeValueMinus1 []uint64

	// cbr_flag[ SchedSelIdx ] equal to 0 specifies that to decode this bitstream
	// by the HRD using the SchedSelIdx-th CPB specification, the hypothetical
	// stream delivery scheduler (HSS) operates in an intermittent bit rate mode,
	// otherwise if 1 specifies that the HSS operates in a constant bit rate mode.
	CBRFlag []bool

	// initial_cpb_removal_delay_length_minus1 specifies the length in bits of the
	// initial_cpb_removal_delay[ SchedSelIdx ] and
	// initial_cpb_removal_delay_offset[ SchedSelIdx ] syntax elements of the
	// buffering period SEI message.
	InitialCPBRemovalDelayLenMinus1 uint8

	// cpb_removal_delay_length_minus1 specifies the length in bits of the
	// cpb_removal_delay syntax element.
	CPBRemovalDelayLenMinus1 uint8

	// dpb_output_delay_length_minus1 specifies the length in bits of the
	// dpb_output_delay syntax element.
	DPBOutputDelayLenMinus1 uint8

	// time_offset_length greater than 0 specifies the length in bits of the
	// time_offset syntax element.
	TimeOffsetLen uint8
}

// NewHRDParameters parses hypothetical reference decoder parameter from br
// following the syntax structure specified in section E.1.2, and returns as a
// new HRDParameters.
func NewHRDParameters(br *bits.BitReader) (*HRDParameters, error) {
	h := &HRDParameters{}
	r := newFieldReader(br)

	h.CPBCntMinus1 = r.readUe()
	h.BitRateScale = uint8(r.readBits(4))
	h.CPBSizeScale = uint8(r.readBits(4))

	// SchedSelIdx E1.2
	for sseli := 0; sseli <= int(h.CPBCntMinus1); sseli++ {
		h.BitRateValueMinus1 = append(h.BitRateValueMinus1, r.readUe())
		h.CPBSizeValueMinus1 = append(h.CPBSizeValueMinus1, r.readUe())

		if v, _ := br.ReadBits(1); v == 1 {
			h.CBRFlag = append(h.CBRFlag, true)
		} else {
			h.CBRFlag = append(h.CBRFlag, false)
		}

		h.InitialCPBRemovalDelayLenMinus1 = uint8(r.readBits(5))
		h.CPBRemovalDelayLenMinus1 = uint8(r.readBits(5))
		h.DPBOutputDelayLenMinus1 = uint8(r.readBits(5))
		h.TimeOffsetLen = uint8(r.readBits(5))
	}

	if r.err() != nil {
		return nil, fmt.Errorf("error from fieldReader: %v", r.err())
	}
	return h, nil
}

func isInList(l []int, term int) bool {
	for _, m := range l {
		if m == term {
			return true
		}
	}
	return false
}

func scalingList(br *bits.BitReader, scalingList []int, sizeOfScalingList int, defaultScalingMatrix []int) error {
	lastScale := 8
	nextScale := 8
	for i := 0; i < sizeOfScalingList; i++ {
		if nextScale != 0 {
			deltaScale, err := readSe(br)
			if err != nil {
				return errors.Wrap(err, "could not parse deltaScale")
			}
			nextScale = (lastScale + deltaScale + 256) % 256
			if i == 0 && nextScale == 0 {
				// Scaling list should use the default list for this point in the matrix
				_ = defaultScalingMatrix
			}
		}
		if nextScale == 0 {
			scalingList[i] = lastScale
		} else {
			scalingList[i] = nextScale
		}
		lastScale = scalingList[i]
	}
	return nil
}