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

package nack

import (
	

	
	
	
	
	
)

// ResponderInterceptorFactory is a interceptor.Factory for a ResponderInterceptor.
type ResponderInterceptorFactory struct {
	opts []ResponderOption
}

// NewInterceptor constructs a new ResponderInterceptor.
func ( *ResponderInterceptorFactory) ( string) (interceptor.Interceptor, error) {
	 := &ResponderInterceptor{
		streamsFilter: streamSupportNack,
		size:          1024,
		log:           logging.NewDefaultLoggerFactory().NewLogger("nack_responder"),
		streams:       map[uint32]*localStream{},
	}

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

	if .packetFactory == nil {
		.packetFactory = rtpbuffer.NewPacketFactoryCopy()
	}

	if ,  := rtpbuffer.NewRTPBuffer(.size);  != nil {
		return nil, 
	}

	return , nil
}

// ResponderInterceptor responds to nack feedback messages.
type ResponderInterceptor struct {
	interceptor.NoOp
	streamsFilter func(info *interceptor.StreamInfo) bool
	size          uint16
	log           logging.LeveledLogger
	packetFactory rtpbuffer.PacketFactory

	streams   map[uint32]*localStream
	streamsMu sync.Mutex
}

type localStream struct {
	rtpBuffer      *rtpbuffer.RTPBuffer
	rtpBufferMutex sync.RWMutex
	rtpWriter      interceptor.RTPWriter
}

// NewResponderInterceptor returns a new ResponderInterceptorFactor.
func ( ...ResponderOption) (*ResponderInterceptorFactory, error) {
	return &ResponderInterceptorFactory{}, nil
}

// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
// change in the future. The returned method will be called once per packet batch.
func ( *ResponderInterceptor) ( interceptor.RTCPReader) interceptor.RTCPReader {
	return interceptor.RTCPReaderFunc(func( []byte,  interceptor.Attributes) (int, interceptor.Attributes, error) {
		, ,  := .Read(, )
		if  != nil {
			return 0, nil, 
		}

		if  == nil {
			 = make(interceptor.Attributes)
		}
		,  := .GetRTCPPackets([:])
		if  != nil {
			return 0, nil, 
		}
		for ,  := range  {
			,  := .(*rtcp.TransportLayerNack)
			if ! {
				continue
			}

			go .resendPackets()
		}

		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 ( *ResponderInterceptor) (
	 *interceptor.StreamInfo,  interceptor.RTPWriter,
) interceptor.RTPWriter {
	if !.streamsFilter() {
		return 
	}

	// error is already checked in NewGeneratorInterceptor
	,  := rtpbuffer.NewRTPBuffer(.size)
	 := &localStream{
		rtpBuffer: ,
		rtpWriter: ,
	}
	.streamsMu.Lock()
	.streams[.SSRC] = 
	.streamsMu.Unlock()

	return interceptor.RTPWriterFunc(
		func( *rtp.Header,  []byte,  interceptor.Attributes) (int, error) {
			// If this packet doesn't belong to the main SSRC, do not add it to rtpBuffer
			if .SSRC != .SSRC {
				return .Write(, , )
			}

			,  := .packetFactory.NewPacket(, , .SSRCRetransmission, .PayloadTypeRetransmission)
			if  != nil {
				return 0, 
			}
			.rtpBufferMutex.Lock()
			defer .rtpBufferMutex.Unlock()

			.Add()

			return .Write(, , )
		},
	)
}

// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
func ( *ResponderInterceptor) ( *interceptor.StreamInfo) {
	.streamsMu.Lock()
	delete(.streams, .SSRC)
	.streamsMu.Unlock()
}

func ( *ResponderInterceptor) ( *rtcp.TransportLayerNack) {
	.streamsMu.Lock()
	,  := .streams[.MediaSSRC]
	.streamsMu.Unlock()
	if ! {
		return
	}

	for  := range .Nacks {
		.Nacks[].Range(func( uint16) bool {
			.rtpBufferMutex.Lock()
			defer .rtpBufferMutex.Unlock()

			if  := .rtpBuffer.Get();  != nil {
				if ,  := .rtpWriter.Write(.Header(), .Payload(), interceptor.Attributes{});  != nil {
					.log.Warnf("failed resending nacked packet: %+v", )
				}
				.Release()
			}

			return true
		})
	}
}