// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package codecs

import (
	
	
)

// Use global random generator to properly seed by crypto grade random.
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals

// VP9Payloader payloads VP9 packets.
type VP9Payloader struct {
	// whether to use flexible mode or non-flexible mode.
	FlexibleMode bool

	// InitialPictureIDFn is a function that returns random initial picture ID.
	InitialPictureIDFn func() uint16

	pictureID   uint16
	initialized bool
}

const (
	maxSpatialLayers = 5
	maxVP9RefPics    = 3
)

// Payload fragments an VP9 packet across one or more byte arrays.
func ( *VP9Payloader) ( uint16,  []byte) [][]byte {
	if !.initialized {
		if .InitialPictureIDFn == nil {
			.InitialPictureIDFn = func() uint16 {
				return uint16(globalMathRandomGenerator.Intn(0x7FFF)) // nolint: gosec
			}
		}
		.pictureID = .InitialPictureIDFn() & 0x7FFF
		.initialized = true
	}

	var  [][]byte
	if .FlexibleMode {
		 = .payloadFlexible(, )
	} else {
		 = .payloadNonFlexible(, )
	}

	.pictureID++
	if .pictureID >= 0x8000 {
		.pictureID = 0
	}

	return 
}

func ( *VP9Payloader) ( uint16,  []byte) [][]byte {
	/*
	 * Flexible mode (F=1)
	 *        0 1 2 3 4 5 6 7
	 *       +-+-+-+-+-+-+-+-+
	 *       |I|P|L|F|B|E|V|Z| (REQUIRED)
	 *       +-+-+-+-+-+-+-+-+
	 *  I:   |M| PICTURE ID  | (REQUIRED)
	 *       +-+-+-+-+-+-+-+-+
	 *  M:   | EXTENDED PID  | (RECOMMENDED)
	 *       +-+-+-+-+-+-+-+-+
	 *  L:   | TID |U| SID |D| (CONDITIONALLY RECOMMENDED)
	 *       +-+-+-+-+-+-+-+-+                             -\
	 *  P,F: | P_DIFF      |N| (CONDITIONALLY REQUIRED)    - up to 3 times
	 *       +-+-+-+-+-+-+-+-+                             -/
	 *  V:   | SS            |
	 *       | ..            |
	 *       +-+-+-+-+-+-+-+-+
	 */

	 := 3
	 := int() - 
	 := len()
	 := 0
	var  [][]byte

	if minInt(, ) <= 0 {
		return [][]byte{}
	}

	for  > 0 {
		 := minInt(, )
		 := make([]byte, +)

		[0] = 0x90 // F=1, I=1
		if  == 0 {
			[0] |= 0x08 // B=1
		}
		if  ==  {
			[0] |= 0x04 // E=1
		}

		[1] = byte(.pictureID>>8) | 0x80
		[2] = byte(.pictureID)

		copy([:], [:+])
		 = append(, )

		 -= 
		 += 
	}

	return 
}

func ( *VP9Payloader) ( uint16,  []byte) [][]byte { //nolint:cyclop
	/*
	 * Non-flexible mode (F=0)
	 *        0 1 2 3 4 5 6 7
	 *       +-+-+-+-+-+-+-+-+
	 *       |I|P|L|F|B|E|V|Z| (REQUIRED)
	 *       +-+-+-+-+-+-+-+-+
	 *  I:   |M| PICTURE ID  | (RECOMMENDED)
	 *       +-+-+-+-+-+-+-+-+
	 *  M:   | EXTENDED PID  | (RECOMMENDED)
	 *       +-+-+-+-+-+-+-+-+
	 *  L:   | TID |U| SID |D| (CONDITIONALLY RECOMMENDED)
	 *       +-+-+-+-+-+-+-+-+
	 *       |   TL0PICIDX   | (CONDITIONALLY REQUIRED)
	 *       +-+-+-+-+-+-+-+-+
	 *  V:   | SS            |
	 *       | ..            |
	 *       +-+-+-+-+-+-+-+-+
	 */

	var  vp9.Header
	 := .Unmarshal()
	if  != nil {
		return [][]byte{}
	}

	 := len()
	 := 0
	var  [][]byte

	for  > 0 {
		var  int
		if !.NonKeyFrame &&  == 0 {
			 = 3 + 8
		} else {
			 = 3
		}

		 := int() - 
		 := minInt(, )
		if  <= 0 {
			return [][]byte{}
		}

		 := make([]byte, +)

		[0] = 0x80 | 0x01 // I=1, Z=1

		if .NonKeyFrame {
			[0] |= 0x40 // P=1
		}
		if  == 0 {
			[0] |= 0x08 // B=1
		}
		if  ==  {
			[0] |= 0x04 // E=1
		}

		[1] = byte(.pictureID>>8) | 0x80
		[2] = byte(.pictureID)
		 := 3

		if !.NonKeyFrame &&  == 0 {
			[0] |= 0x02         // V=1
			[] = 0x10 | 0x08 // N_S=0, Y=1, G=1
			++

			 := .Width()
			[] = byte( >> 8)
			++
			[] = byte( & 0xFF)
			++

			 := .Height()
			[] = byte( >> 8)
			++
			[] = byte( & 0xFF)
			++

			[] = 0x01 // N_G=1
			++

			[] = 1<<4 | 1<<2 // TID=0, U=1, R=1
			++

			[] = 0x01 // P_DIFF=1
		}

		copy([:], [:+])
		 = append(, )

		 -= 
		 += 
	}

	return 
}

// VP9Packet represents the VP9 header that is stored in the payload of an RTP Packet.
type VP9Packet struct {
	// Required header
	I bool // PictureID is present
	P bool // Inter-picture predicted frame
	L bool // Layer indices is present
	F bool // Flexible mode
	B bool // Start of a frame
	E bool // End of a frame
	V bool // Scalability structure (SS) data present
	Z bool // Not a reference frame for upper spatial layers

	// Recommended headers
	PictureID uint16 // 7 or 16 bits, picture ID

	// Conditionally recommended headers
	TID uint8 // Temporal layer ID
	U   bool  // Switching up point
	SID uint8 // Spatial layer ID
	D   bool  // Inter-layer dependency used

	// Conditionally required headers
	PDiff     []uint8 // Reference index (F=1)
	TL0PICIDX uint8   // Temporal layer zero index (F=0)

	// Scalability structure headers
	NS      uint8 // N_S + 1 indicates the number of spatial layers present in the VP9 stream
	Y       bool  // Each spatial layer's frame resolution present
	G       bool  // PG description present flag.
	NG      uint8 // N_G indicates the number of pictures in a Picture Group (PG)
	Width   []uint16
	Height  []uint16
	PGTID   []uint8   // Temporal layer ID of pictures in a Picture Group
	PGU     []bool    // Switching up point of pictures in a Picture Group
	PGPDiff [][]uint8 // Reference indecies of pictures in a Picture Group

	Payload []byte

	videoDepacketizer
}

// Unmarshal parses the passed byte slice and stores the result in the VP9Packet this method is called upon.
func ( *VP9Packet) ( []byte) ([]byte, error) { // nolint:cyclop
	if  == nil {
		return nil, errNilPacket
	}
	if len() < 1 {
		return nil, errShortPacket
	}

	.I = [0]&0x80 != 0
	.P = [0]&0x40 != 0
	.L = [0]&0x20 != 0
	.F = [0]&0x10 != 0
	.B = [0]&0x08 != 0
	.E = [0]&0x04 != 0
	.V = [0]&0x02 != 0
	.Z = [0]&0x01 != 0

	 := 1
	var  error

	if .I {
		,  = .parsePictureID(, )
		if  != nil {
			return nil, 
		}
	}

	if .L {
		,  = .parseLayerInfo(, )
		if  != nil {
			return nil, 
		}
	}

	if .F && .P {
		,  = .parseRefIndices(, )
		if  != nil {
			return nil, 
		}
	}

	if .V {
		,  = .parseSSData(, )
		if  != nil {
			return nil, 
		}
	}

	.Payload = [:]

	return .Payload, nil
}

// Picture ID:
/*
*      +-+-+-+-+-+-+-+-+
* I:   |M| PICTURE ID  |   M:0 => picture id is 7 bits.
*      +-+-+-+-+-+-+-+-+   M:1 => picture id is 15 bits.
* M:   | EXTENDED PID  |
*      +-+-+-+-+-+-+-+-+
**/
// .
func ( *VP9Packet) ( []byte,  int) (int, error) {
	if len() <=  {
		return , errShortPacket
	}

	.PictureID = uint16([] & 0x7F)
	if []&0x80 != 0 {
		++
		if len() <=  {
			return , errShortPacket
		}
		.PictureID = .PictureID<<8 | uint16([])
	}
	++

	return , nil
}

func ( *VP9Packet) ( []byte,  int) (int, error) {
	,  := .parseLayerInfoCommon(, )
	if  != nil {
		return , 
	}

	if .F {
		return , nil
	}

	return .parseLayerInfoNonFlexibleMode(, )
}

// Layer indices (flexible mode):
/*
*      +-+-+-+-+-+-+-+-+
* L:   |  T  |U|  S  |D|
*      +-+-+-+-+-+-+-+-+
**/
// .
func ( *VP9Packet) ( []byte,  int) (int, error) {
	if len() <=  {
		return , errShortPacket
	}

	.TID = [] >> 5
	.U = []&0x10 != 0
	.SID = ([] >> 1) & 0x7
	.D = []&0x01 != 0

	if .SID >= maxSpatialLayers {
		return , errTooManySpatialLayers
	}

	++

	return , nil
}

// Layer indices (non-flexible mode):
/*
*      +-+-+-+-+-+-+-+-+
* L:   |  T  |U|  S  |D|
*      +-+-+-+-+-+-+-+-+
*      |   TL0PICIDX   |
*      +-+-+-+-+-+-+-+-+
**/
// .
func ( *VP9Packet) ( []byte,  int) (int, error) {
	if len() <=  {
		return , errShortPacket
	}

	.TL0PICIDX = []
	++

	return , nil
}

// Reference indices: .
/*
*      +-+-+-+-+-+-+-+-+                P=1,F=1: At least one reference index
* P,F: | P_DIFF      |N|  up to 3 times          has to be specified.
*      +-+-+-+-+-+-+-+-+                    N=1: An additional P_DIFF follows
*                                              current P_DIFF.
*
**/
// .
func ( *VP9Packet) ( []byte,  int) (int, error) {
	for {
		if len() <=  {
			return , errShortPacket
		}
		.PDiff = append(.PDiff, []>>1)
		if []&0x01 == 0 {
			break
		}
		if len(.PDiff) >= maxVP9RefPics {
			return , errTooManyPDiff
		}
		++
	}
	++

	return , nil
}

// Scalability structure (SS):
/*
*      +-+-+-+-+-+-+-+-+
* V:   | N_S |Y|G|-|-|-|
*      +-+-+-+-+-+-+-+-+              -|
* Y:   |     WIDTH     | (OPTIONAL)    .
*      +               .
*      |               | (OPTIONAL)    .
*      +-+-+-+-+-+-+-+-+               . N_S + 1 times
*      |     HEIGHT    | (OPTIONAL)    .
*      +               .
*      |               | (OPTIONAL)    .
*      +-+-+-+-+-+-+-+-+              -|
* G:   |      N_G      | (OPTIONAL)
*      +-+-+-+-+-+-+-+-+                           -|
* N_G: |  T  |U| R |-|-| (OPTIONAL)                 .
*      +-+-+-+-+-+-+-+-+              -|            . N_G times
*      |    P_DIFF     | (OPTIONAL)    . R times    .
*      +-+-+-+-+-+-+-+-+              -|           -|
**/
// .
func ( *VP9Packet) ( []byte,  int) (int, error) { // nolint: cyclop
	if len() <=  {
		return , errShortPacket
	}

	.NS = [] >> 5
	.Y = []&0x10 != 0
	.G = []&0x8 != 0
	++

	 := .NS + 1
	.NG = 0

	if .Y {
		.Width = make([]uint16, )
		.Height = make([]uint16, )
		for  := 0;  < int(); ++ {
			if len() <= ( + 3) {
				return , errShortPacket
			}

			.Width[] = uint16([])<<8 | uint16([+1])
			 += 2
			.Height[] = uint16([])<<8 | uint16([+1])
			 += 2
		}
	}

	if .G {
		if len() <=  {
			return , errShortPacket
		}

		.NG = []
		++
	}

	for  := 0;  < int(.NG); ++ {
		if len() <=  {
			return , errShortPacket
		}

		.PGTID = append(.PGTID, []>>5)
		.PGU = append(.PGU, []&0x10 != 0)
		 := ([] >> 2) & 0x3
		++

		.PGPDiff = append(.PGPDiff, []uint8{})

		if len() <= ( + int() - 1) {
			return , errShortPacket
		}

		for  := 0;  < int(); ++ {
			.PGPDiff[] = append(.PGPDiff[], [])
			++
		}
	}

	return , nil
}

// VP9PartitionHeadChecker checks VP9 partition head.
//
// Deprecated: replaced by VP9Packet.IsPartitionHead().
type VP9PartitionHeadChecker struct{}

// IsPartitionHead checks whether if this is a head of the VP9 partition.
//
// Deprecated: replaced by VP9Packet.IsPartitionHead().
func (*VP9PartitionHeadChecker) ( []byte) bool {
	return (&VP9Packet{}).IsPartitionHead()
}

// IsPartitionHead checks whether if this is a head of the VP9 partition.
func (*VP9Packet) ( []byte) bool {
	if len() < 1 {
		return false
	}

	return ([0] & 0x08) != 0
}