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

package twcc

import (
	
	
	
	

	
	
	
)

// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor.
type SenderInterceptorFactory struct {
	opts []Option
}

var errClosed = errors.New("interceptor is closed")

// NewInterceptor constructs a new SenderInterceptor.
func ( *SenderInterceptorFactory) ( string) (interceptor.Interceptor, error) {
	 := &SenderInterceptor{
		log:        logging.NewDefaultLoggerFactory().NewLogger("twcc_sender_interceptor"),
		packetChan: make(chan packet),
		close:      make(chan struct{}),
		interval:   100 * time.Millisecond,
		startTime:  time.Now(),
	}

	for ,  := range .opts {
		 := ()
		if  != nil {
			return nil, 
		}
	}

	return , nil
}

// NewSenderInterceptor returns a new SenderInterceptorFactory configured with the given options.
func ( ...Option) (*SenderInterceptorFactory, error) {
	return &SenderInterceptorFactory{opts: }, nil
}

// SenderInterceptor sends transport wide congestion control reports as specified in:
// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
type SenderInterceptor struct {
	interceptor.NoOp

	log logging.LeveledLogger

	m     sync.Mutex
	wg    sync.WaitGroup
	close chan struct{}

	interval  time.Duration
	startTime time.Time

	recorder   *Recorder
	packetChan chan packet
}

// An Option is a function that can be used to configure a SenderInterceptor.
type Option func(*SenderInterceptor) error

// SendInterval sets the interval at which the interceptor
// will send new feedback reports.
func ( time.Duration) Option {
	return func( *SenderInterceptor) error {
		.interval = 

		return nil
	}
}

// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func ( *SenderInterceptor) ( interceptor.RTCPWriter) interceptor.RTCPWriter {
	.m.Lock()
	defer .m.Unlock()

	.recorder = NewRecorder(rand.Uint32()) // #nosec

	if .isClosed() {
		return 
	}

	.wg.Add(1)

	go .loop()

	return 
}

type packet struct {
	hdr            *rtp.Header
	sequenceNumber uint16
	arrivalTime    int64
	ssrc           uint32
}

// BindRemoteStream lets you modify any incoming RTP packets.
// It is called once for per RemoteStream. The returned method
// will be called once per rtp packet.
//
//nolint:cyclop
func ( *SenderInterceptor) (
	 *interceptor.StreamInfo,  interceptor.RTPReader,
) interceptor.RTPReader {
	var  uint8
	for ,  := range .RTPHeaderExtensions {
		if .URI == transportCCURI {
			 = uint8(.ID) //nolint:gosec // G115

			break
		}
	}
	if  == 0 { // Don't try to read header extension if ID is 0, because 0 is an invalid extension ID
		return 
	}

	return interceptor.RTPReaderFunc(
		func( []byte,  interceptor.Attributes) (int, interceptor.Attributes, error) {
			, ,  := .Read(, )
			if  != nil {
				return 0, nil, 
			}

			if  == nil {
				 = make(interceptor.Attributes)
			}
			,  := .GetRTPHeader([:])
			if  != nil {
				return 0, nil, 
			}
			var  rtp.TransportCCExtension
			if  := .GetExtension();  != nil {
				 = .Unmarshal()
				if  != nil {
					return 0, nil, 
				}

				 := packet{
					hdr:            ,
					sequenceNumber: .TransportSequence,
					arrivalTime:    time.Since(.startTime).Microseconds(),
					ssrc:           .SSRC,
				}
				select {
				case <-.close:
					return 0, nil, errClosed
				case .packetChan <- :
				}
			}

			return , , nil
		},
	)
}

// Close closes the interceptor.
func ( *SenderInterceptor) () error {
	defer .wg.Wait()
	.m.Lock()
	defer .m.Unlock()

	if !.isClosed() {
		close(.close)
	}

	return nil
}

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

func ( *SenderInterceptor) ( interceptor.RTCPWriter) {
	defer .wg.Done()

	select {
	case <-.close:
		return
	case  := <-.packetChan:
		.recorder.Record(.ssrc, .sequenceNumber, .arrivalTime)
	}

	 := time.NewTicker(.interval)
	for {
		select {
		case <-.close:
			.Stop()

			return
		case  := <-.packetChan:
			.recorder.Record(.ssrc, .sequenceNumber, .arrivalTime)

		case <-.C:
			// build and send twcc
			 := .recorder.BuildFeedbackPacket()
			if len() == 0 {
				continue
			}
			if ,  := .Write(, nil);  != nil {
				.log.Error(.Error())
			}
		}
	}
}