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

package twcc

const (
	minCapacity        = 128
	maxNumberOfPackets = 1 << 15
)

// packetArrivalTimeMap is adapted from Chrome's implementation of TWCC, and keeps track
// of the arrival times of packets. It is used by the TWCC interceptor to build feedback
// packets.
// See https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/webrtc/modules/remote_bitrate_estimator/packet_arrival_map.h;drc=b5cd13bb6d5d157a5fbe3628b2dd1c1e106203c6
//
//nolint:lll
type packetArrivalTimeMap struct {
	// arrivalTimes is a circular buffer, where the packet with sequence number sn is stored
	// in slot sn % len(arrivalTimes)
	arrivalTimes []int64

	// The unwrapped sequence numbers for the range of valid sequence numbers in arrivalTimes.
	// beginSequenceNumber is inclusive, and endSequenceNumber is exclusive.
	beginSequenceNumber, endSequenceNumber int64
}

// AddPacket records the fact that the packet with sequence number sequenceNumber arrived
// at arrivalTime.
func ( *packetArrivalTimeMap) ( int64,  int64) {
	if .arrivalTimes == nil {
		// First packet
		.reallocate(minCapacity)
		.beginSequenceNumber = 
		.endSequenceNumber =  + 1
		.arrivalTimes[.index()] = 

		return
	}

	if  >= .beginSequenceNumber &&  < .endSequenceNumber {
		// The packet is within the buffer, no need to resize.
		.arrivalTimes[.index()] = 

		return
	}

	if  < .beginSequenceNumber {
		// The packet goes before the current buffer. Expand to add packet,
		// but only if it fits within the maximum number of packets.
		 := int(.endSequenceNumber - )
		if  > maxNumberOfPackets {
			// Don't expand the buffer back for this packet, as it would remove newer received
			// packets.
			return
		}
		.adjustToSize()
		.arrivalTimes[.index()] = 
		.setNotReceived(+1, .beginSequenceNumber)
		.beginSequenceNumber = 

		return
	}

	// The packet goes after the buffer.
	 :=  + 1

	if  >= .endSequenceNumber+maxNumberOfPackets {
		// All old packets have to be removed.
		.beginSequenceNumber = 
		.endSequenceNumber = 
		.arrivalTimes[.index()] = 

		return
	}

	if .beginSequenceNumber < -maxNumberOfPackets {
		// Remove oldest entries.
		.beginSequenceNumber =  - maxNumberOfPackets
	}

	.adjustToSize(int( - .beginSequenceNumber))

	// Packets can be received out of order. If this isn't the next expected packet,
	// add enough placeholders to fill the gap.
	.setNotReceived(.endSequenceNumber, )
	.endSequenceNumber = 
	.arrivalTimes[.index()] = 
}

func ( *packetArrivalTimeMap) (,  int64) {
	for  := ;  < ; ++ {
		.arrivalTimes[.index()] = -1
	}
}

// BeginSequenceNumber returns the first valid sequence number in the map.
func ( *packetArrivalTimeMap) () int64 {
	return .beginSequenceNumber
}

// EndSequenceNumber returns the first sequence number after the last valid sequence number in the map.
func ( *packetArrivalTimeMap) () int64 {
	return .endSequenceNumber
}

// FindNextAtOrAfter returns the sequence number and timestamp of the first received packet that has a sequence number
// greator or equal to sequenceNumber.
func ( *packetArrivalTimeMap) ( int64) (
	 int64,  int64,  bool,
) {
	for  = .Clamp();  < .endSequenceNumber; ++ {
		if  := .get();  >= 0 {
			return , , true
		}
	}

	return -1, -1, false
}

// EraseTo erases all elements from the beginning of the map until sequenceNumber.
func ( *packetArrivalTimeMap) ( int64) {
	if  < .beginSequenceNumber {
		return
	}
	if  >= .endSequenceNumber {
		// Erase all.
		.beginSequenceNumber = .endSequenceNumber

		return
	}
	// Remove some
	.beginSequenceNumber = 
	.adjustToSize(int(.endSequenceNumber - .beginSequenceNumber))
}

// RemoveOldPackets removes packets from the beginning of the map as long as they are before
// sequenceNumber and with an age older than arrivalTimeLimit.
func ( *packetArrivalTimeMap) ( int64,  int64) {
	 := min64(, .endSequenceNumber)
	for .beginSequenceNumber <  && .get(.beginSequenceNumber) <=  {
		.beginSequenceNumber++
	}
	.adjustToSize(int(.endSequenceNumber - .beginSequenceNumber))
}

// HasReceived returns whether a packet with the sequence number has been received.
func ( *packetArrivalTimeMap) ( int64) bool {
	return .get() >= 0
}

// Clamp returns sequenceNumber clamped to [beginSequenceNumber, endSequenceNumber].
func ( *packetArrivalTimeMap) ( int64) int64 {
	if  < .beginSequenceNumber {
		return .beginSequenceNumber
	}
	if .endSequenceNumber <  {
		return .endSequenceNumber
	}

	return 
}

func ( *packetArrivalTimeMap) ( int64) int64 {
	if  < .beginSequenceNumber ||  >= .endSequenceNumber {
		return -1
	}

	return .arrivalTimes[.index()]
}

func ( *packetArrivalTimeMap) ( int64) int {
	// Sequence number might be negative, and we always guarantee that arrivalTimes
	// length is a power of 2, so it's easier to use "&" instead of "%"
	return int( & int64(.capacity()-1))
}

func ( *packetArrivalTimeMap) ( int) {
	if  > .capacity() {
		 := .capacity()
		for  <  {
			 *= 2
		}
		.reallocate()
	}
	if .capacity() > maxInt(minCapacity, *4) {
		 := .capacity()
		for  >= 2*maxInt(, minCapacity) {
			 /= 2
		}
		.reallocate()
	}
}

func ( *packetArrivalTimeMap) () int {
	return len(.arrivalTimes)
}

func ( *packetArrivalTimeMap) ( int) {
	 := make([]int64, )
	for  := .beginSequenceNumber;  < .endSequenceNumber; ++ {
		[int(&(int64(-1)))] = .get()
	}
	.arrivalTimes = 
}