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

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

package webrtc

import (
	
	

	
	
	
)

// trackBinding is a single bind for a Track
// Bind can be called multiple times, this stores the
// result for a single bind call so that it can be used when writing.
type trackBinding struct {
	id                          string
	ssrc, ssrcRTX, ssrcFEC      SSRC
	payloadType, payloadTypeRTX PayloadType
	writeStream                 TrackLocalWriter
}

// TrackLocalStaticRTP  is a TrackLocal that has a pre-set codec and accepts RTP Packets.
// If you wish to send a media.Sample use TrackLocalStaticSample.
type TrackLocalStaticRTP struct {
	mu                sync.RWMutex
	bindings          []trackBinding
	codec             RTPCodecCapability
	payloader         func(RTPCodecCapability) (rtp.Payloader, error)
	id, rid, streamID string
	rtpTimestamp      *uint32
}

// NewTrackLocalStaticRTP returns a TrackLocalStaticRTP.
func (
	 RTPCodecCapability,
	,  string,
	 ...func(*TrackLocalStaticRTP),
) (*TrackLocalStaticRTP, error) {
	 := &TrackLocalStaticRTP{
		codec:    ,
		bindings: []trackBinding{},
		id:       ,
		streamID: ,
	}

	for ,  := range  {
		()
	}

	return , nil
}

// WithRTPStreamID sets the RTP stream ID for this TrackLocalStaticRTP.
func ( string) func(*TrackLocalStaticRTP) {
	return func( *TrackLocalStaticRTP) {
		.rid = 
	}
}

// WithPayloader allows the user to override the Payloader.
func ( func(RTPCodecCapability) (rtp.Payloader, error)) func(*TrackLocalStaticRTP) {
	return func( *TrackLocalStaticRTP) {
		.payloader = 
	}
}

// WithRTPTimestamp set the initial RTP timestamp for the track.
func ( uint32) func(*TrackLocalStaticRTP) {
	return func( *TrackLocalStaticRTP) {
		.rtpTimestamp = &
	}
}

// Bind is called by the PeerConnection after negotiation is complete
// This asserts that the code requested is supported by the remote peer.
// If so it sets up all the state (SSRC and PayloadType) to have a call.
func ( *TrackLocalStaticRTP) ( TrackLocalContext) (RTPCodecParameters, error) {
	.mu.Lock()
	defer .mu.Unlock()

	 := RTPCodecParameters{RTPCodecCapability: .codec}
	if ,  := codecParametersFuzzySearch(
		,
		.CodecParameters(),
	);  != codecMatchNone {
		.bindings = append(.bindings, trackBinding{
			ssrc:           .SSRC(),
			ssrcRTX:        .SSRCRetransmission(),
			ssrcFEC:        .SSRCForwardErrorCorrection(),
			payloadType:    .PayloadType,
			payloadTypeRTX: findRTXPayloadType(.PayloadType, .CodecParameters()),
			writeStream:    .WriteStream(),
			id:             .ID(),
		})

		return , nil
	}

	return RTPCodecParameters{}, ErrUnsupportedCodec
}

// Unbind implements the teardown logic when the track is no longer needed. This happens
// because a track has been stopped.
func ( *TrackLocalStaticRTP) ( TrackLocalContext) error {
	.mu.Lock()
	defer .mu.Unlock()

	for  := range .bindings {
		if .bindings[].id == .ID() {
			.bindings[] = .bindings[len(.bindings)-1]
			.bindings = .bindings[:len(.bindings)-1]

			return nil
		}
	}

	return ErrUnbindFailed
}

// ID is the unique identifier for this Track. This should be unique for the
// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
// and StreamID would be 'desktop' or 'webcam'.
func ( *TrackLocalStaticRTP) () string { return .id }

// StreamID is the group this track belongs too. This must be unique.
func ( *TrackLocalStaticRTP) () string { return .streamID }

// RID is the RTP stream identifier.
func ( *TrackLocalStaticRTP) () string { return .rid }

// Kind controls if this TrackLocal is audio or video.
func ( *TrackLocalStaticRTP) () RTPCodecType {
	switch {
	case strings.HasPrefix(.codec.MimeType, "audio/"):
		return RTPCodecTypeAudio
	case strings.HasPrefix(.codec.MimeType, "video/"):
		return RTPCodecTypeVideo
	default:
		return RTPCodecType(0)
	}
}

// Codec gets the Codec of the track.
func ( *TrackLocalStaticRTP) () RTPCodecCapability {
	return .codec
}

// packetPool is a pool of packets used by WriteRTP and Write below
// nolint:gochecknoglobals
var rtpPacketPool = sync.Pool{
	New: func() interface{} {
		return &rtp.Packet{}
	},
}

func resetPacketPoolAllocation( *rtp.Packet) {
	* = rtp.Packet{}
	rtpPacketPool.Put()
}

func getPacketAllocationFromPool() *rtp.Packet {
	 := rtpPacketPool.Get()

	return .(*rtp.Packet) //nolint:forcetypeassert
}

// WriteRTP writes a RTP Packet to the TrackLocalStaticRTP
// If one PeerConnection fails the packets will still be sent to
// all PeerConnections. The error message will contain the ID of the failed
// PeerConnections so you can remove them.
func ( *TrackLocalStaticRTP) ( *rtp.Packet) error {
	 := getPacketAllocationFromPool()

	defer resetPacketPoolAllocation()

	* = *

	return .writeRTP()
}

// writeRTP is like WriteRTP, except that it may modify the packet p.
func ( *TrackLocalStaticRTP) ( *rtp.Packet) error {
	.mu.RLock()
	defer .mu.RUnlock()

	 := []error{}

	for ,  := range .bindings {
		.Header.SSRC = uint32(.ssrc)
		.Header.PayloadType = uint8(.payloadType)
		// b.writeStream.WriteRTP below expects header and payload separately, so value of Packet.PaddingSize
		// would be lost. Copy it to Packet.Header.PaddingSize to avoid that problem.
		if .PaddingSize != 0 && .Header.PaddingSize == 0 {
			.Header.PaddingSize = .PaddingSize
		}
		if ,  := .writeStream.WriteRTP(&.Header, .Payload);  != nil {
			 = append(, )
		}
	}

	return util.FlattenErrs()
}

// Write writes a RTP Packet as a buffer to the TrackLocalStaticRTP
// If one PeerConnection fails the packets will still be sent to
// all PeerConnections. The error message will contain the ID of the failed
// PeerConnections so you can remove them.
func ( *TrackLocalStaticRTP) ( []byte) ( int,  error) {
	 := getPacketAllocationFromPool()

	defer resetPacketPoolAllocation()

	if  = .Unmarshal();  != nil {
		return 0, 
	}

	return len(), .writeRTP()
}

// TrackLocalStaticSample is a TrackLocal that has a pre-set codec and accepts Samples.
// If you wish to send a RTP Packet use TrackLocalStaticRTP.
type TrackLocalStaticSample struct {
	packetizer rtp.Packetizer
	sequencer  rtp.Sequencer
	rtpTrack   *TrackLocalStaticRTP
	clockRate  float64
}

// NewTrackLocalStaticSample returns a TrackLocalStaticSample.
func (
	 RTPCodecCapability,
	,  string,
	 ...func(*TrackLocalStaticRTP),
) (*TrackLocalStaticSample, error) {
	,  := NewTrackLocalStaticRTP(, , , ...)
	if  != nil {
		return nil, 
	}

	return &TrackLocalStaticSample{
		rtpTrack: ,
	}, nil
}

// ID is the unique identifier for this Track. This should be unique for the
// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
// and StreamID would be 'desktop' or 'webcam'.
func ( *TrackLocalStaticSample) () string { return .rtpTrack.ID() }

// StreamID is the group this track belongs too. This must be unique.
func ( *TrackLocalStaticSample) () string { return .rtpTrack.StreamID() }

// RID is the RTP stream identifier.
func ( *TrackLocalStaticSample) () string { return .rtpTrack.RID() }

// Kind controls if this TrackLocal is audio or video.
func ( *TrackLocalStaticSample) () RTPCodecType { return .rtpTrack.Kind() }

// Codec gets the Codec of the track.
func ( *TrackLocalStaticSample) () RTPCodecCapability {
	return .rtpTrack.Codec()
}

// Bind is called by the PeerConnection after negotiation is complete
// This asserts that the code requested is supported by the remote peer.
// If so it setups all the state (SSRC and PayloadType) to have a call.
func ( *TrackLocalStaticSample) ( TrackLocalContext) (RTPCodecParameters, error) {
	,  := .rtpTrack.Bind()
	if  != nil {
		return , 
	}

	.rtpTrack.mu.Lock()
	defer .rtpTrack.mu.Unlock()

	// We only need one packetizer
	if .packetizer != nil {
		return , nil
	}

	 := .rtpTrack.payloader
	if  == nil {
		 = payloaderForCodec
	}

	,  := (.RTPCodecCapability)
	if  != nil {
		return , 
	}

	.sequencer = rtp.NewRandomSequencer()

	 := []rtp.PacketizerOption{}

	if .rtpTrack.rtpTimestamp != nil {
		 = append(, rtp.WithTimestamp(*.rtpTrack.rtpTimestamp))
	}

	.packetizer = rtp.NewPacketizerWithOptions(
		outboundMTU,
		,
		.sequencer,
		.ClockRate,
		...,
	)

	.clockRate = float64(.RTPCodecCapability.ClockRate)

	return , nil
}

// Unbind implements the teardown logic when the track is no longer needed. This happens
// because a track has been stopped.
func ( *TrackLocalStaticSample) ( TrackLocalContext) error {
	return .rtpTrack.Unbind()
}

// WriteSample writes a Sample to the TrackLocalStaticSample
// If one PeerConnection fails the packets will still be sent to
// all PeerConnections. The error message will contain the ID of the failed
// PeerConnections so you can remove them.
func ( *TrackLocalStaticSample) ( media.Sample) error {
	.rtpTrack.mu.RLock()
	 := .packetizer
	 := .clockRate
	.rtpTrack.mu.RUnlock()

	if  == nil {
		return nil
	}

	// skip packets by the number of previously dropped packets
	for  := uint16(0);  < .PrevDroppedPackets; ++ {
		.sequencer.NextSequenceNumber()
	}

	 := uint32(.Duration.Seconds() * )
	if .PrevDroppedPackets > 0 {
		.SkipSamples( * uint32(.PrevDroppedPackets))
	}
	 := .Packetize(.Data, )

	 := []error{}
	for ,  := range  {
		if  := .rtpTrack.WriteRTP();  != nil {
			 = append(, )
		}
	}

	return util.FlattenErrs()
}

// GeneratePadding writes padding-only samples to the TrackLocalStaticSample
// If one PeerConnection fails the packets will still be sent to
// all PeerConnections. The error message will contain the ID of the failed
// PeerConnections so you can remove them.
func ( *TrackLocalStaticSample) ( uint32) error {
	.rtpTrack.mu.RLock()
	 := .packetizer
	.rtpTrack.mu.RUnlock()

	if  == nil {
		return nil
	}

	 := .GeneratePadding()

	 := []error{}
	for ,  := range  {
		if  := .rtpTrack.WriteRTP();  != nil {
			 = append(, )
		}
	}

	return util.FlattenErrs()
}