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

package report

import (
	
	

	
	
	
	
)

// TickerFactory is a factory to create new tickers.
type TickerFactory func(d time.Duration) Ticker

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

// NewInterceptor constructs a new SenderInterceptor.
func ( *SenderInterceptorFactory) ( string) (interceptor.Interceptor, error) {
	 := &SenderInterceptor{
		interval: 1 * time.Second,
		now:      time.Now,
		newTicker: func( time.Duration) Ticker {
			return &timeTicker{time.NewTicker()}
		},
		log:   logging.NewDefaultLoggerFactory().NewLogger("sender_interceptor"),
		close: make(chan struct{}),
	}

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

	return , nil
}

// NewSenderInterceptor returns a new SenderInterceptorFactory.
func ( ...SenderOption) (*SenderInterceptorFactory, error) {
	return &SenderInterceptorFactory{}, nil
}

// SenderInterceptor interceptor generates sender reports.
type SenderInterceptor struct {
	interceptor.NoOp
	interval  time.Duration
	now       func() time.Time
	newTicker TickerFactory
	streams   sync.Map
	log       logging.LeveledLogger
	m         sync.Mutex
	wg        sync.WaitGroup
	close     chan struct{}
	started   chan struct{}

	useLatestPacket bool
}

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

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

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

	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()

	if .isClosed() {
		return 
	}

	.wg.Add(1)

	go .loop()

	return 
}

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

	 := .newTicker(.interval)
	defer .Stop()
	if .started != nil {
		// This lets us synchronize in tests to know whether the loop has begun or not.
		// It only happens if started was initialized, which should not occur in non-tests.
		close(.started)
	}
	for {
		select {
		case <-.Ch():
			 := .now()
			.streams.Range(func(,  interface{}) bool {
				if ,  := .(*senderStream); ! {
					.log.Warnf("failed to cast SenderInterceptor stream")
				} else if ,  := .Write(
					[]rtcp.Packet{.generateReport()}, interceptor.Attributes{},
				);  != nil {
					.log.Warnf("failed sending: %+v", )
				}

				return true
			})

		case <-.close:
			return
		}
	}
}

// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
// will be called once per rtp packet.
func ( *SenderInterceptor) (
	 *interceptor.StreamInfo,  interceptor.RTPWriter,
) interceptor.RTPWriter {
	 := newSenderStream(.SSRC, .ClockRate, .useLatestPacket)
	.streams.Store(.SSRC, )

	return interceptor.RTPWriterFunc(func( *rtp.Header,  []byte,  interceptor.Attributes) (int, error) {
		.processRTP(.now(), , )

		return .Write(, , )
	})
}

// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
func ( *SenderInterceptor) ( *interceptor.StreamInfo) {
	.streams.Delete(.SSRC)
}