// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package container

import (
	"sort"
)

// RingBuffer is a generic ring buffer implementation that contains
// sequential data (i.e. such as time ordered data).
// RingBuffer is implemented using slices. From testing, this should
// be fast than linked-list implementations, and also allows for efficient
// indexing of ordered data.
type RingBuffer struct {
	buffer  []any
	next    int // index of ring buffer head.
	maxSize int
}

// NewRingBuffer constructs a new ring buffer for a given buffer size.
func NewRingBuffer(bufferSize int) *RingBuffer {
	return &RingBuffer{
		buffer:  make([]any, 0, bufferSize),
		maxSize: bufferSize,
	}
}

func (eb *RingBuffer) isFull() bool {
	return len(eb.buffer) >= eb.maxSize
}

func (eb *RingBuffer) incr() {
	eb.next = (eb.next + 1) % eb.maxSize
}

// Add adds an element to the buffer.
func (eb *RingBuffer) Add(e any) {
	if eb.maxSize == 0 {
		return
	}
	if eb.isFull() {
		eb.buffer[eb.next] = e
		eb.incr()
		return
	}
	eb.incr()
	eb.buffer = append(eb.buffer, e)
}

func (eb *RingBuffer) dumpWithCallback(callback func(v any)) {
	for i := range eb.buffer {
		callback(eb.at(i))
	}
}

func (eb *RingBuffer) at(i int) any {
	return eb.buffer[eb.mapIndex(i)]
}

// firstValidIndex returns the first **absolute** index in the buffer that satisfies
// isValid.
// note: this value needs to be mapped before indexing the buffer.
func (eb *RingBuffer) firstValidIndex(isValid func(any) bool) int {
	return sort.Search(len(eb.buffer), func(i int) bool {
		return isValid(eb.at(i))
	})
}

// IterateValid calls the callback on each element of the buffer, starting with
// the first element in the buffer that satisfies "isValid".
func (eb *RingBuffer) IterateValid(isValid func(any) bool, callback func(any)) {
	startIndex := eb.firstValidIndex(isValid)
	l := len(eb.buffer) - startIndex
	for i := range l {
		index := eb.mapIndex(startIndex + i)
		callback(eb.buffer[index])
	}
}

// maps index in [0:len(buffer)) to the actual index in buffer.
func (eb *RingBuffer) mapIndex(indexOffset int) int {
	ret := (eb.next + indexOffset) % len(eb.buffer)
	return ret
}

// Compact clears out invalidated elements in the buffer.
// This may require copying the entire buffer.
// It is assumed that if buffer[i] is invalid then every entry [0...i-1] is also not valid.
func (eb *RingBuffer) Compact(isValid func(any) bool) {
	if len(eb.buffer) == 0 {
		return
	}
	startIndex := eb.firstValidIndex(isValid)
	// In this case, we compact the entire buffer.
	if startIndex >= len(eb.buffer) {
		eb.buffer = []any{}
		eb.next = 0
		return
	}

	mappedStart := eb.mapIndex(startIndex) // mapped start is the new index 0 of our buffer.
	// new length will be how long the current buffer is, minus the absolute starting index.
	newBufferLength := len(eb.buffer) - startIndex
	// case where the head index is to the left of the tail index.
	// e.x. [... head, tail, ...]
	// mappedStart + newBufferLength is the upper bound of the new buffer list
	// if we don't have to worry about mapping.
	//
	// e.x. [mappedStart:mappedStart+newBufferLength] <- this is our new buffer.
	//
	// If this value is less than or equal to the length then we don't need
	// to worry about any part of the list wrapping around.
	if mappedStart+newBufferLength > len(eb.buffer) {
		// now we can find the actual end index, by offsetting the startIndex
		// by the length and mapping it.
		// [... startIndex+newBufferLen ... startIndex ...]
		end := eb.mapIndex(startIndex + newBufferLength)
		tmp := make([]any, len(eb.buffer[:end]))
		copy(tmp, eb.buffer[:end])

		eb.buffer = eb.buffer[mappedStart:]
		eb.buffer = append(eb.buffer, tmp...)

		// at this point the buffer is such that the 0th element
		// maps to the 0th index in the buffer array.
		eb.next = len(eb.buffer)
		if eb.isFull() {
			eb.next = eb.next % eb.maxSize
		}
		return
	}
	// otherwise, the head is to the right of the tail.
	begin := mappedStart
	end := mappedStart + newBufferLength
	eb.buffer = eb.buffer[begin:end]
	eb.next = len(eb.buffer)
	if eb.isFull() {
		eb.next = eb.next % eb.maxSize
	}
}

// Iterate is a convenience function over IterateValid that iterates
// all elements in the buffer.
func (eb *RingBuffer) Iterate(callback func(any)) {
	eb.IterateValid(func(e any) bool { return true }, callback)
}

// Size returns the size of the buffer.
func (eb *RingBuffer) Size() int {
	return len(eb.buffer)
}
