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

package codecs

import (
	
	
	
)

// H264Payloader payloads H264 packets.
type H264Payloader struct {
	spsNalu, ppsNalu []byte
	DisableStapA     bool
}

const (
	stapaNALUType  = 24
	fuaNALUType    = 28
	fubNALUType    = 29
	spsNALUType    = 7
	ppsNALUType    = 8
	audNALUType    = 9
	fillerNALUType = 12

	fuaHeaderSize       = 2
	stapaHeaderSize     = 1
	stapaNALULengthSize = 2

	naluTypeBitmask   = 0x1F
	naluRefIdcBitmask = 0x60
	fuStartBitmask    = 0x80
	fuEndBitmask      = 0x40

	outputStapAHeader = 0x78
)

// nolint:gochecknoglobals
var (
	naluStartCode       = []byte{0x00, 0x00, 0x01}
	annexbNALUStartCode = []byte{0x00, 0x00, 0x00, 0x01}
)

func emitNalus( []byte,  func([]byte)) {
	// look for 3-byte NALU start code
	 := bytes.Index(, naluStartCode)
	 := 3

	if  == -1 {
		// no start code, emit the whole buffer
		()

		return
	}

	 := len()

	for  <  {
		// look for the next NALU start (end of this NALU)
		 := bytes.Index([+:], naluStartCode)
		if  == -1 {
			// no more NALUs, emit the rest of the buffer
			([+:])

			break
		}

		// next NALU start
		 :=  +  + 

		// check if the next NALU is actually a 4-byte start code
		 := [-1] == 0
		if  {
			--
		}

		([+ : ])

		 = 

		if  {
			 = 4
		} else {
			 = 3
		}
	}
}

// Payload fragments a H264 packet across one or more byte arrays.
func ( *H264Payloader) ( uint16,  []byte) [][]byte { //nolint:cyclop
	var  [][]byte
	if len() == 0 {
		return 
	}

	emitNalus(, func( []byte) {
		if len() == 0 {
			return
		}

		 := [0] & naluTypeBitmask
		 := [0] & naluRefIdcBitmask

		switch {
		case  == audNALUType ||  == fillerNALUType:
			return
		case  == spsNALUType:
			if !.DisableStapA {
				.spsNalu = 

				return
			}
		case  == ppsNALUType:
			if !.DisableStapA {
				.ppsNalu = 

				return
			}
		case !.DisableStapA && .spsNalu != nil && .ppsNalu != nil:
			// Pack current NALU with SPS and PPS as STAP-A
			 := make([]byte, 2)
			binary.BigEndian.PutUint16(, uint16(len(.spsNalu))) // nolint: gosec // G115

			 := make([]byte, 2)
			binary.BigEndian.PutUint16(, uint16(len(.ppsNalu))) // nolint: gosec // G115

			 := []byte{outputStapAHeader}
			 = append(, ...)
			 = append(, .spsNalu...)
			 = append(, ...)
			 = append(, .ppsNalu...)
			if len() <= int() {
				 := make([]byte, len())
				copy(, )
				 = append(, )
			}

			.spsNalu = nil
			.ppsNalu = nil
		}

		// Single NALU
		if len() <= int() {
			 := make([]byte, len())
			copy(, )
			 = append(, )

			return
		}

		// FU-A
		 := int() - fuaHeaderSize

		// The FU payload consists of fragments of the payload of the fragmented
		// NAL unit so that if the fragmentation unit payloads of consecutive
		// FUs are sequentially concatenated, the payload of the fragmented NAL
		// unit can be reconstructed.  The NAL unit type octet of the fragmented
		// NAL unit is not included as such in the fragmentation unit payload,
		// 	but rather the information of the NAL unit type octet of the
		// fragmented NAL unit is conveyed in the F and NRI fields of the FU
		// indicator octet of the fragmentation unit and in the type field of
		// the FU header.  An FU payload MAY have any number of octets and MAY
		// be empty.

		// According to the RFC, the first octet is skipped due to redundant information
		 := 1
		 := len() - 
		 := 

		if minInt(, ) <= 0 {
			return
		}

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

			// +---------------+
			// |0|1|2|3|4|5|6|7|
			// +-+-+-+-+-+-+-+-+
			// |F|NRI|  Type   |
			// +---------------+
			[0] = fuaNALUType
			[0] |= 

			// +---------------+
			// |0|1|2|3|4|5|6|7|
			// +-+-+-+-+-+-+-+-+
			// |S|E|R|  Type   |
			// +---------------+

			[1] = 
			if  ==  {
				// Set start bit
				[1] |= 1 << 7
			} else if - == 0 {
				// Set end bit
				[1] |= 1 << 6
			}

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

			 -= 
			 += 
		}
	})

	return 
}

// H264Packet represents the H264 header that is stored in the payload of an RTP Packet.
type H264Packet struct {
	IsAVC     bool
	fuaBuffer []byte

	videoDepacketizer
}

func ( *H264Packet) (,  []byte) []byte {
	if .IsAVC {
		 = binary.BigEndian.AppendUint32(, uint32(len())) // nolint: gosec // G115 false positive
		 = append(, ...)

		return 
	}

	 = append(, annexbNALUStartCode...)
	 = append(, ...)

	return 
}

// IsDetectedFinalPacketInSequence returns true of the packet passed in has the
// marker bit set indicated the end of a packet sequence.
func ( *H264Packet) ( bool) bool {
	return 
}

// Unmarshal parses the passed byte slice and stores the result in the H264Packet this method is called upon.
func ( *H264Packet) ( []byte) ([]byte, error) {
	if .zeroAllocation {
		return , nil
	}

	return .parseBody()
}

func ( *H264Packet) ( []byte) ([]byte, error) { //nolint:cyclop
	if len() == 0 {
		return nil, fmt.Errorf("%w: %d <=0", errShortPacket, len())
	}

	// NALU Types
	// https://tools.ietf.org/html/rfc6184#section-5.4
	 := [0] & naluTypeBitmask
	switch {
	case  > 0 &&  < 24:
		return .doPackaging(nil, ), nil

	case  == stapaNALUType:
		 := int(stapaHeaderSize)
		 := []byte{}
		for  < len() {
			 := [:]
			if len() < stapaNALULengthSize {
				break
			}
			 := int(binary.BigEndian.Uint16())
			 += stapaNALULengthSize

			if len() < + {
				return nil, fmt.Errorf(
					"%w STAP-A declared size(%d) is larger than buffer(%d)",
					errShortPacket,
					,
					len()-,
				)
			}

			 = .doPackaging(, [:+])
			 += 
		}

		return , nil

	case  == fuaNALUType:
		if len() < fuaHeaderSize {
			return nil, errShortPacket
		}

		if .fuaBuffer == nil {
			.fuaBuffer = []byte{}
		}

		.fuaBuffer = append(.fuaBuffer, [fuaHeaderSize:]...)

		if [1]&fuEndBitmask != 0 {
			 := [0] & naluRefIdcBitmask
			 := [1] & naluTypeBitmask

			 := append([]byte{}, |)
			 = append(, .fuaBuffer...)
			.fuaBuffer = nil

			return .doPackaging(nil, ), nil
		}

		return []byte{}, nil
	}

	return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, )
}

// H264PartitionHeadChecker checks H264 partition head.
//
// Deprecated: replaced by H264Packet.IsPartitionHead().
type H264PartitionHeadChecker struct{}

// IsPartitionHead checks if this is the head of a packetized nalu stream.
//
// Deprecated: replaced by H264Packet.IsPartitionHead().
func (*H264PartitionHeadChecker) ( []byte) bool {
	return (&H264Packet{}).IsPartitionHead()
}

// IsPartitionHead checks if this is the head of a packetized nalu stream.
func (*H264Packet) ( []byte) bool {
	if len() < 2 {
		return false
	}

	if [0]&naluTypeBitmask == fuaNALUType ||
		[0]&naluTypeBitmask == fubNALUType {
		return [1]&fuStartBitmask != 0
	}

	return true
}