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

//go:build !js
// +build !js

package webrtc

import (
	
	
	
	
	

	
	
	
	
)

// trackStreams maintains a mapping of RTP/RTCP streams to a specific track
// a RTPReceiver may contain multiple streams if we are dealing with Simulcast.
type trackStreams struct {
	track *TrackRemote

	streamInfo, repairStreamInfo *interceptor.StreamInfo

	rtpReadStream  *srtp.ReadStreamSRTP
	rtpInterceptor interceptor.RTPReader

	rtcpReadStream  *srtp.ReadStreamSRTCP
	rtcpInterceptor interceptor.RTCPReader

	repairReadStream    *srtp.ReadStreamSRTP
	repairInterceptor   interceptor.RTPReader
	repairStreamChannel chan rtxPacketWithAttributes

	repairRtcpReadStream  *srtp.ReadStreamSRTCP
	repairRtcpInterceptor interceptor.RTCPReader
}

type rtxPacketWithAttributes struct {
	pkt        []byte
	attributes interceptor.Attributes
	pool       *sync.Pool
}

func ( *rtxPacketWithAttributes) () {
	if .pkt != nil {
		 := .pkt[:cap(.pkt)]
		.pool.Put() // nolint:staticcheck
		.pkt = nil
	}
}

// RTPReceiver allows an application to inspect the receipt of a TrackRemote.
type RTPReceiver struct {
	kind      RTPCodecType
	transport *DTLSTransport

	tracks []trackStreams

	closed, received chan interface{}
	mu               sync.RWMutex

	tr *RTPTransceiver

	// A reference to the associated api object
	api *API

	rtxPool sync.Pool
}

// NewRTPReceiver constructs a new RTPReceiver.
func ( *API) ( RTPCodecType,  *DTLSTransport) (*RTPReceiver, error) {
	if  == nil {
		return nil, errRTPReceiverDTLSTransportNil
	}

	 := &RTPReceiver{
		kind:      ,
		transport: ,
		api:       ,
		closed:    make(chan interface{}),
		received:  make(chan interface{}),
		tracks:    []trackStreams{},
		rtxPool: sync.Pool{New: func() interface{} {
			return make([]byte, .settingEngine.getReceiveMTU())
		}},
	}

	return , nil
}

func ( *RTPReceiver) ( *RTPTransceiver) {
	.mu.Lock()
	defer .mu.Unlock()
	.tr = 
}

// Transport returns the currently-configured *DTLSTransport or nil
// if one has not yet been configured.
func ( *RTPReceiver) () *DTLSTransport {
	.mu.RLock()
	defer .mu.RUnlock()

	return .transport
}

func ( *RTPReceiver) () RTPParameters {
	 := .api.mediaEngine.getRTPParametersByKind(
		.kind,
		[]RTPTransceiverDirection{RTPTransceiverDirectionRecvonly},
	)
	if .tr != nil {
		.Codecs = .tr.getCodecs()
	}

	return 
}

// GetParameters describes the current configuration for the encoding and
// transmission of media on the receiver's track.
func ( *RTPReceiver) () RTPParameters {
	.mu.RLock()
	defer .mu.RUnlock()

	return .getParameters()
}

// Track returns the RtpTransceiver TrackRemote.
func ( *RTPReceiver) () *TrackRemote {
	.mu.RLock()
	defer .mu.RUnlock()

	if len(.tracks) != 1 {
		return nil
	}

	return .tracks[0].track
}

// Tracks returns the RtpTransceiver tracks
// A RTPReceiver to support Simulcast may now have multiple tracks.
func ( *RTPReceiver) () []*TrackRemote {
	.mu.RLock()
	defer .mu.RUnlock()

	var  []*TrackRemote
	for  := range .tracks {
		 = append(, .tracks[].track)
	}

	return 
}

// RTPTransceiver returns the RTPTransceiver this
// RTPReceiver belongs too, or nil if none.
func ( *RTPReceiver) () *RTPTransceiver {
	.mu.Lock()
	defer .mu.Unlock()

	return .tr
}

// configureReceive initialize the track.
func ( *RTPReceiver) ( RTPReceiveParameters) {
	.mu.Lock()
	defer .mu.Unlock()

	for  := range .Encodings {
		 := trackStreams{
			track: newTrackRemote(
				.kind,
				.Encodings[].SSRC,
				.Encodings[].RTX.SSRC,
				.Encodings[].RID,
				,
			),
		}

		.tracks = append(.tracks, )
	}
}

// startReceive starts all the transports.
func ( *RTPReceiver) ( RTPReceiveParameters) error { //nolint:cyclop
	.mu.Lock()
	defer .mu.Unlock()
	select {
	case <-.received:
		return errRTPReceiverReceiveAlreadyCalled
	default:
	}

	 := .getParameters()
	 := RTPCodecCapability{}
	if len(.Codecs) != 0 {
		 = .Codecs[0].RTPCodecCapability
	}

	for  := range .Encodings {
		if .Encodings[].RID != "" {
			// RID based tracks will be set up in receiveForRid
			continue
		}

		var  *trackStreams
		for ,  := range .tracks {
			if .track != nil && .track.SSRC() == .Encodings[].SSRC {
				 = &.tracks[]

				break
			}
		}
		if  == nil {
			return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, .Encodings[].SSRC)
		}

		.streamInfo = createStreamInfo(
			"",
			.Encodings[].SSRC,
			0, 0, 0, 0, 0,
			,
			.HeaderExtensions,
		)
		var  error

		//nolint:lll // # TODO refactor
		if .rtpReadStream, .rtpInterceptor, .rtcpReadStream, .rtcpInterceptor,  = .transport.streamsForSSRC(.Encodings[].SSRC, *.streamInfo);  != nil {
			return 
		}

		if  := .Encodings[].RTX.SSRC;  != 0 {
			 := createStreamInfo("", , 0, 0, 0, 0, 0, , .HeaderExtensions)
			, , , ,  := .transport.streamsForSSRC(
				,
				*,
			)
			if  != nil {
				return 
			}

			if  = .receiveForRtx(
				,
				"",
				,
				,
				,
				,
				,
			);  != nil {
				return 
			}
		}
	}

	close(.received)

	return nil
}

// Receive initialize the track and starts all the transports.
func ( *RTPReceiver) ( RTPReceiveParameters) error {
	.configureReceive()

	return .startReceive()
}

// Read reads incoming RTCP for this RTPReceiver.
func ( *RTPReceiver) ( []byte) ( int,  interceptor.Attributes,  error) {
	select {
	case <-.received:
		return .tracks[0].rtcpInterceptor.Read(, )
	case <-.closed:
		return 0, nil, io.ErrClosedPipe
	}
}

// ReadSimulcast reads incoming RTCP for this RTPReceiver for given rid.
func ( *RTPReceiver) ( []byte,  string) ( int,  interceptor.Attributes,  error) {
	select {
	case <-.received:
		var  interceptor.RTCPReader

		.mu.Lock()
		for ,  := range .tracks {
			if .track != nil && .track.rid ==  {
				 = .rtcpInterceptor
			}
		}
		.mu.Unlock()

		if  == nil {
			return 0, nil, fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, )
		}

		return .Read(, )

	case <-.closed:
		return 0, nil, io.ErrClosedPipe
	}
}

// ReadRTCP is a convenience method that wraps Read and unmarshal for you.
// It also runs any configured interceptors.
func ( *RTPReceiver) () ([]rtcp.Packet, interceptor.Attributes, error) {
	 := make([]byte, .api.settingEngine.getReceiveMTU())
	, ,  := .Read()
	if  != nil {
		return nil, nil, 
	}

	,  := rtcp.Unmarshal([:])
	if  != nil {
		return nil, nil, 
	}

	return , , nil
}

// ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you.
func ( *RTPReceiver) ( string) ([]rtcp.Packet, interceptor.Attributes, error) {
	 := make([]byte, .api.settingEngine.getReceiveMTU())
	, ,  := .ReadSimulcast(, )
	if  != nil {
		return nil, nil, 
	}

	,  := rtcp.Unmarshal([:])

	return , , 
}

func ( *RTPReceiver) () bool {
	select {
	case <-.received:
		return true
	default:
		return false
	}
}

// Stop irreversibly stops the RTPReceiver.
func ( *RTPReceiver) () error { //nolint:cyclop
	.mu.Lock()
	defer .mu.Unlock()
	var  error

	select {
	case <-.closed:
		return 
	default:
	}

	select {
	case <-.received:
		for  := range .tracks {
			 := []error{}

			if .tracks[].rtcpReadStream != nil {
				 = append(, .tracks[].rtcpReadStream.Close())
			}

			if .tracks[].rtpReadStream != nil {
				 = append(, .tracks[].rtpReadStream.Close())
			}

			if .tracks[].repairReadStream != nil {
				 = append(, .tracks[].repairReadStream.Close())
			}

			if .tracks[].repairRtcpReadStream != nil {
				 = append(, .tracks[].repairRtcpReadStream.Close())
			}

			if .tracks[].streamInfo != nil {
				.api.interceptor.UnbindRemoteStream(.tracks[].streamInfo)
			}

			if .tracks[].repairStreamInfo != nil {
				.api.interceptor.UnbindRemoteStream(.tracks[].repairStreamInfo)
			}

			 = util.FlattenErrs()
		}
	default:
	}

	close(.closed)

	return 
}

func ( *RTPReceiver) ( *TrackRemote) *trackStreams {
	for  := range .tracks {
		if .tracks[].track ==  {
			return &.tracks[]
		}
	}

	return nil
}

// readRTP should only be called by a track, this only exists so we can keep state in one place.
func ( *RTPReceiver) ( []byte,  *TrackRemote) ( int,  interceptor.Attributes,  error) {
	select {
	case <-.received:
	case <-.closed:
		return 0, nil, io.EOF
	}

	if  := .streamsForTrack();  != nil {
		return .rtpInterceptor.Read(, )
	}

	return 0, nil, fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, .SSRC())
}

// receiveForRid is the sibling of Receive expect for RIDs instead of SSRCs
// It populates all the internal state for the given RID.
func ( *RTPReceiver) (
	 string,
	 RTPParameters,
	 *interceptor.StreamInfo,
	 *srtp.ReadStreamSRTP,
	 interceptor.RTPReader,
	 *srtp.ReadStreamSRTCP,
	 interceptor.RTCPReader,
) (*TrackRemote, error) {
	.mu.Lock()
	defer .mu.Unlock()

	for  := range .tracks {
		if .tracks[].track.RID() ==  {
			.tracks[].track.mu.Lock()
			.tracks[].track.kind = .kind
			.tracks[].track.codec = .Codecs[0]
			.tracks[].track.params = 
			.tracks[].track.ssrc = SSRC(.SSRC)
			.tracks[].track.mu.Unlock()

			.tracks[].streamInfo = 
			.tracks[].rtpReadStream = 
			.tracks[].rtpInterceptor = 
			.tracks[].rtcpReadStream = 
			.tracks[].rtcpInterceptor = 

			return .tracks[].track, nil
		}
	}

	return nil, fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, )
}

// receiveForRtx starts a routine that processes the repair stream.
//
//nolint:cyclop
func ( *RTPReceiver) (
	 SSRC,
	 string,
	 *interceptor.StreamInfo,
	 *srtp.ReadStreamSRTP,
	 interceptor.RTPReader,
	 *srtp.ReadStreamSRTCP,
	 interceptor.RTCPReader,
) error {
	var  *trackStreams
	if  != 0 && len(.tracks) == 1 {
		 = &.tracks[0]
	} else {
		for  := range .tracks {
			if .tracks[].track.RID() ==  {
				 = &.tracks[]
				if .track.RtxSSRC() == 0 {
					.track.setRtxSSRC(SSRC(.SSRC))
				}

				break
			}
		}
	}

	if  == nil {
		return fmt.Errorf("%w: ssrc(%d) rsid(%s)", errRTPReceiverForRIDTrackStreamNotFound, , )
	}

	.repairStreamInfo = 
	.repairReadStream = 
	.repairInterceptor = 
	.repairRtcpReadStream = 
	.repairRtcpInterceptor = 
	.repairStreamChannel = make(chan rtxPacketWithAttributes, 50)

	go func() {
		for {
			 := .rtxPool.Get().([]byte) // nolint:forcetypeassert
			, ,  := .repairInterceptor.Read(, nil)
			if  != nil {
				.rtxPool.Put() // nolint:staticcheck

				return
			}

			// RTX packets have a different payload format. Move the OSN in the payload to the RTP header and rewrite the
			// payload type and SSRC, so that we can return RTX packets to the caller 'transparently' i.e. in the same format
			// as non-RTX RTP packets
			 := [0]&0b10000 > 0
			 := [0]&0b100000 > 0
			 := [0] & 0b1111
			 := uint16(12 + (4 * ))
			 := 0
			if  {
				 += 4 * (1 + binary.BigEndian.Uint16([+2:+4]))
			}
			if  {
				 = int([-1])
			}

			if -int()- < 2 {
				// BWE probe packet, ignore
				.rtxPool.Put() // nolint:staticcheck

				continue
			}

			if  == nil {
				 = make(interceptor.Attributes)
			}
			.Set(AttributeRtxPayloadType, [1]&0x7F)
			.Set(AttributeRtxSequenceNumber, binary.BigEndian.Uint16([2:4]))
			.Set(AttributeRtxSsrc, binary.BigEndian.Uint32([8:12]))

			[1] = ([1] & 0x80) | uint8(.track.PayloadType())
			[2] = []
			[3] = [+1]
			binary.BigEndian.PutUint32([8:12], uint32(.track.SSRC()))
			copy([:-2], [+2:])

			select {
			case <-.closed:
				.rtxPool.Put() // nolint:staticcheck

				return
			case .repairStreamChannel <- rtxPacketWithAttributes{pkt: [:-2], attributes: , pool: &.rtxPool}:
			default:
				// skip the RTX packet if the repair stream channel is full, could be blocked in the application's read loop
			}
		}
	}()

	return nil
}

// SetReadDeadline sets the max amount of time the RTCP stream will block before returning. 0 is forever.
func ( *RTPReceiver) ( time.Time) error {
	.mu.RLock()
	defer .mu.RUnlock()

	return .tracks[0].rtcpReadStream.SetReadDeadline()
}

// SetReadDeadlineSimulcast sets the max amount of time the RTCP stream for a given rid will block before returning.
// 0 is forever.
func ( *RTPReceiver) ( time.Time,  string) error {
	.mu.RLock()
	defer .mu.RUnlock()

	for ,  := range .tracks {
		if .track != nil && .track.rid ==  {
			return .rtcpReadStream.SetReadDeadline()
		}
	}

	return fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, )
}

// setRTPReadDeadline sets the max amount of time the RTP stream will block before returning. 0 is forever.
// This should be fired by calling SetReadDeadline on the TrackRemote.
func ( *RTPReceiver) ( time.Time,  *TrackRemote) error {
	.mu.RLock()
	defer .mu.RUnlock()

	if  := .streamsForTrack();  != nil {
		return .rtpReadStream.SetReadDeadline()
	}

	return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, .SSRC())
}

// readRTX returns an RTX packet if one is available on the RTX track, otherwise returns nil.
func ( *RTPReceiver) ( *TrackRemote) *rtxPacketWithAttributes {
	if !.HasRTX() {
		return nil
	}

	select {
	case <-.received:
	default:
		return nil
	}

	if  := .streamsForTrack();  != nil {
		select {
		case  := <-.repairStreamChannel:
			return &
		default:
		}
	}

	return nil
}