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

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

package webrtc

import (
	
	
	
	

	
	
	
	
	
)

type trackEncoding struct {
	track TrackLocal

	srtpStream *srtpWriterFuture

	rtcpInterceptor interceptor.RTCPReader
	streamInfo      interceptor.StreamInfo

	context *baseTrackLocalContext

	ssrc, ssrcRTX, ssrcFEC SSRC
}

// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer.
type RTPSender struct {
	trackEncodings []*trackEncoding

	transport *DTLSTransport

	payloadType PayloadType
	kind        RTPCodecType

	// nolint:godox
	// TODO(sgotti) remove this when in future we'll avoid replacing
	// a transceiver sender since we can just check the
	// transceiver negotiation status
	negotiated bool

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

	rtpTransceiver *RTPTransceiver

	mu                     sync.RWMutex
	sendCalled, stopCalled chan struct{}
}

// NewRTPSender constructs a new RTPSender.
func ( *API) ( TrackLocal,  *DTLSTransport) (*RTPSender, error) {
	if  == nil {
		return nil, errRTPSenderTrackNil
	} else if  == nil {
		return nil, errRTPSenderDTLSTransportNil
	}

	,  := randutil.GenerateCryptoRandomString(32, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	if  != nil {
		return nil, 
	}

	 := &RTPSender{
		transport:  ,
		api:        ,
		sendCalled: make(chan struct{}),
		stopCalled: make(chan struct{}),
		id:         ,
		kind:       .Kind(),
	}

	.addEncoding()

	return , nil
}

func ( *RTPSender) () bool {
	.mu.RLock()
	defer .mu.RUnlock()

	return .negotiated
}

func ( *RTPSender) () {
	.mu.Lock()
	defer .mu.Unlock()
	.negotiated = true
}

func ( *RTPSender) ( *RTPTransceiver) {
	.mu.Lock()
	defer .mu.Unlock()
	.rtpTransceiver = 
}

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

	return .transport
}

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

	var  []RTPEncodingParameters
	for ,  := range .trackEncodings {
		var  string
		if .track != nil {
			 = .track.RID()
		}
		 = append(, RTPEncodingParameters{
			RTPCodingParameters: RTPCodingParameters{
				RID:         ,
				SSRC:        .ssrc,
				RTX:         RTPRtxParameters{SSRC: .ssrcRTX},
				FEC:         RTPFecParameters{SSRC: .ssrcFEC},
				PayloadType: .payloadType,
			},
		})
	}
	 := RTPSendParameters{
		RTPParameters: .api.mediaEngine.getRTPParametersByKind(
			.kind,
			[]RTPTransceiverDirection{RTPTransceiverDirectionSendonly},
		),
		Encodings: ,
	}
	if .rtpTransceiver != nil {
		.Codecs = .rtpTransceiver.getCodecs()
	} else {
		.Codecs = .api.mediaEngine.getCodecsByKind(.kind)
	}

	return 
}

// AddEncoding adds an encoding to RTPSender. Used by simulcast senders.
func ( *RTPSender) ( TrackLocal) error { //nolint:cyclop
	.mu.Lock()
	defer .mu.Unlock()

	if  == nil {
		return errRTPSenderTrackNil
	}

	if .RID() == "" {
		return errRTPSenderRidNil
	}

	if .hasStopped() {
		return errRTPSenderStopped
	}

	if .hasSent() {
		return errRTPSenderSendAlreadyCalled
	}

	var  TrackLocal
	if len(.trackEncodings) != 0 {
		 = .trackEncodings[0].track
	}
	if  == nil || .RID() == "" {
		return errRTPSenderNoBaseEncoding
	}

	if .ID() != .ID() || .StreamID() != .StreamID() || .Kind() != .Kind() {
		return errRTPSenderBaseEncodingMismatch
	}

	for ,  := range .trackEncodings {
		if .track == nil {
			continue
		}

		if .track.RID() == .RID() {
			return errRTPSenderRIDCollision
		}
	}

	.addEncoding()

	return nil
}

func ( *RTPSender) ( TrackLocal) {
	 := &trackEncoding{
		track: ,
		ssrc:  SSRC(util.RandUint32()),
	}

	if .api.mediaEngine.isRTXEnabled(.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
		.ssrcRTX = SSRC(util.RandUint32())
	}

	if .api.mediaEngine.isFECEnabled(.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
		.ssrcFEC = SSRC(util.RandUint32())
	}

	.trackEncodings = append(.trackEncodings, )
}

// Track returns the RTCRtpTransceiver track, or nil.
func ( *RTPSender) () TrackLocal {
	.mu.RLock()
	defer .mu.RUnlock()

	if len(.trackEncodings) == 0 {
		return nil
	}

	return .trackEncodings[0].track
}

// ReplaceTrack replaces the track currently being used as the sender's source with a new TrackLocal.
// The new track must be of the same media kind (audio, video, etc) and switching the track should not
// require negotiation.
func ( *RTPSender) ( TrackLocal) error { //nolint:cyclop
	.mu.Lock()
	defer .mu.Unlock()

	if  != nil && .kind != .Kind() {
		return ErrRTPSenderNewTrackHasIncorrectKind
	}

	// cannot replace simulcast envelope
	if  != nil && len(.trackEncodings) > 1 {
		return ErrRTPSenderNewTrackHasIncorrectEnvelope
	}

	var  TrackLocal
	var  *baseTrackLocalContext
	for ,  := range .trackEncodings {
		 = .track
		 = .context

		if .hasSent() &&  != nil {
			if  := .Unbind();  != nil {
				return 
			}
		}

		if !.hasSent() ||  == nil {
			.track = 
		}
	}

	if !.hasSent() ||  == nil {
		return nil
	}

	 := .api.mediaEngine.getRTPParametersByKind(
		.Kind(),
		[]RTPTransceiverDirection{RTPTransceiverDirectionSendonly},
	)

	// If we reach this point in the routine, there is only 1 track encoding
	,  := .Bind(&baseTrackLocalContext{
		id:              .ID(),
		params:          ,
		ssrc:            .SSRC(),
		ssrcRTX:         .SSRCRetransmission(),
		ssrcFEC:         .SSRCForwardErrorCorrection(),
		writeStream:     .WriteStream(),
		rtcpInterceptor: .RTCPReader(),
	})
	if  != nil {
		// Re-bind the original track
		if ,  := .Bind();  != nil {
			return 
		}

		return 
	}

	// Codec has changed
	if .payloadType != .PayloadType {
		.params.Codecs = []RTPCodecParameters{}
	}

	.trackEncodings[0].track = 

	return nil
}

// Send Attempts to set the parameters controlling the sending of media.
func ( *RTPSender) ( RTPSendParameters) error {
	.mu.Lock()
	defer .mu.Unlock()

	switch {
	case .hasSent():
		return errRTPSenderSendAlreadyCalled
	case .trackEncodings[0].track == nil:
		return errRTPSenderTrackRemoved
	}

	for  := range .trackEncodings {
		 := .trackEncodings[]
		 := &srtpWriterFuture{ssrc: .Encodings[].SSRC, rtpSender: }
		 := &interceptorToTrackLocalWriter{}
		 := .api.mediaEngine.getRTPParametersByKind(
			.track.Kind(),
			[]RTPTransceiverDirection{RTPTransceiverDirectionSendonly},
		)

		.srtpStream = 
		.ssrc = .Encodings[].SSRC
		.ssrcRTX = .Encodings[].RTX.SSRC
		.ssrcFEC = .Encodings[].FEC.SSRC
		.rtcpInterceptor = .api.interceptor.BindRTCPReader(
			interceptor.RTCPReaderFunc(
				func( []byte,  interceptor.Attributes) ( int,  interceptor.Attributes,  error) {
					,  = .srtpStream.Read()

					return , , 
				},
			),
		)
		.context = &baseTrackLocalContext{
			id:              .id,
			params:          ,
			ssrc:            .Encodings[].SSRC,
			ssrcFEC:         .Encodings[].FEC.SSRC,
			ssrcRTX:         .Encodings[].RTX.SSRC,
			writeStream:     ,
			rtcpInterceptor: .rtcpInterceptor,
		}

		,  := .track.Bind(.context)
		if  != nil {
			return 
		}
		.context.params.Codecs = []RTPCodecParameters{}

		.streamInfo = *createStreamInfo(
			.id,
			.Encodings[].SSRC,
			.Encodings[].RTX.SSRC,
			.Encodings[].FEC.SSRC,
			.PayloadType,
			findRTXPayloadType(.PayloadType, .Codecs),
			findFECPayloadType(.Codecs),
			.RTPCodecCapability,
			.HeaderExtensions,
		)

		 := .api.interceptor.BindLocalStream(
			&.streamInfo,
			interceptor.RTPWriterFunc(func( *rtp.Header,  []byte,  interceptor.Attributes) (int, error) {
				return .WriteRTP(, )
			}),
		)

		.interceptor.Store()
	}

	close(.sendCalled)

	return nil
}

// Stop irreversibly stops the RTPSender.
func ( *RTPSender) () error {
	.mu.Lock()

	if  := .hasStopped();  {
		.mu.Unlock()

		return nil
	}

	close(.stopCalled)
	.mu.Unlock()

	if !.hasSent() {
		return nil
	}

	if  := .ReplaceTrack(nil);  != nil {
		return 
	}

	 := []error{}
	for ,  := range .trackEncodings {
		.api.interceptor.UnbindLocalStream(&.streamInfo)
		if .srtpStream != nil {
			 = append(, .srtpStream.Close())
		}
	}

	return util.FlattenErrs()
}

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

// ReadRTCP is a convenience method that wraps Read and unmarshals for you.
func ( *RTPSender) () ([]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
}

// ReadSimulcast reads incoming RTCP for this RTPSender for given rid.
func ( *RTPSender) ( []byte,  string) ( int,  interceptor.Attributes,  error) {
	select {
	case <-.sendCalled:
		.mu.Lock()
		for ,  := range .trackEncodings {
			if .track != nil && .track.RID() ==  {
				 := .rtcpInterceptor
				.mu.Unlock()

				return .Read(, )
			}
		}
		.mu.Unlock()

		return 0, nil, fmt.Errorf("%w: %s", errRTPSenderNoTrackForRID, )
	case <-.stopCalled:
		return 0, nil, io.ErrClosedPipe
	}
}

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

	,  := rtcp.Unmarshal([:])

	return , , 
}

// SetReadDeadline sets the deadline for the Read operation.
// Setting to zero means no deadline.
func ( *RTPSender) ( time.Time) error {
	return .trackEncodings[0].srtpStream.SetReadDeadline()
}

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

	for ,  := range .trackEncodings {
		if .track != nil && .track.RID() ==  {
			return .srtpStream.SetReadDeadline()
		}
	}

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

// hasSent tells if data has been ever sent for this instance.
func ( *RTPSender) () bool {
	select {
	case <-.sendCalled:
		return true
	default:
		return false
	}
}

// hasStopped tells if stop has been called.
func ( *RTPSender) () bool {
	select {
	case <-.stopCalled:
		return true
	default:
		return false
	}
}

// Set a SSRC for FEC and RTX if MediaEngine has them enabled
// If the remote doesn't support FEC or RTX we disable locally.
func ( *RTPSender) () {
	.mu.RLock()
	defer .mu.RUnlock()

	for ,  := range .trackEncodings {
		if !.api.mediaEngine.isRTXEnabled(.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
			.ssrcRTX = SSRC(0)
		}

		if !.api.mediaEngine.isFECEnabled(.kind, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) {
			.ssrcFEC = SSRC(0)
		}
	}
}