package models

type RingBuffer struct {
	Ed        int32 // write index, open index
	St        int32 // read index, closed index
	EdFrameId int32
	StFrameId int32
	N         int32
	Cnt       int32 // the count of valid elements in the buffer, used mainly to distinguish what "st == ed" means for "Pop" and "Get" methods
	Eles      []interface{}
}

func NewRingBuffer(n int32) *RingBuffer {
	return &RingBuffer{
		Ed:   0,
		St:   0,
		N:    n,
		Cnt:  0,
		Eles: make([]interface{}, n),
	}
}

func (rb *RingBuffer) Put(pItem interface{}) {
	for 0 < rb.Cnt && rb.Cnt >= rb.N {
		// Make room for the new element
		rb.Pop()
	}
	rb.Eles[rb.Ed] = pItem
	rb.EdFrameId++
	rb.Cnt++
	rb.Ed++
	if rb.Ed >= rb.N {
		rb.Ed -= rb.N // Deliberately not using "%" operator for performance concern
	}
}

func (rb *RingBuffer) Pop() interface{} {
	if 0 == rb.Cnt {
		return nil
	}
	pItem := rb.Eles[rb.St]
	rb.StFrameId++
	rb.Cnt--
	rb.St++
	if rb.St >= rb.N {
		rb.St -= rb.N
	}
	return pItem
}

func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
	if 0 == rb.Cnt {
		return nil
	}
	arrIdx := rb.St + offsetFromSt
	if rb.St < rb.Ed {
		// case#1: 0...st...ed...N-1
		if rb.St <= arrIdx && arrIdx < rb.Ed {
			return rb.Eles[arrIdx]
		}
	} else {
		// if rb.St >= rb.Ed
		// case#2: 0...ed...st...N-1
		if arrIdx >= rb.N {
			arrIdx -= rb.N
		}
		if arrIdx >= rb.St || arrIdx < rb.Ed {
			return rb.Eles[arrIdx]
		}
	}

	return nil
}

func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
	if frameId >= rb.EdFrameId || frameId < rb.StFrameId {
		return nil
	}
	return rb.GetByOffset(frameId - rb.StFrameId)
}
