package battle

const (
	RING_BUFF_CONSECUTIVE_SET     = int32(0)
	RING_BUFF_NON_CONSECUTIVE_SET = int32(1)
	RING_BUFF_FAILED_TO_SET       = int32(2)
)

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) GetArrIdxByOffset(offsetFromSt int32) int32 {
	if 0 == rb.Cnt || 0 > offsetFromSt {
		return -1
	}
	arrIdx := rb.St + offsetFromSt
	if rb.St < rb.Ed {
		// case#1: 0...st...ed...N-1
		if rb.St <= arrIdx && arrIdx < rb.Ed {
			return 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 arrIdx
		}
	}

	return -1
}

func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
	arrIdx := rb.GetArrIdxByOffset(offsetFromSt)
	if -1 == arrIdx {
		return nil
	}
	return rb.Eles[arrIdx]
}

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

// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly.
func (rb *RingBuffer) SetByFrameId(pItem interface{}, frameId int32) (int32, int32, int32) {
	oldStFrameId, oldEdFrameId := rb.StFrameId, rb.EdFrameId
	if frameId < oldStFrameId {
		return RING_BUFF_FAILED_TO_SET, oldStFrameId, oldEdFrameId
	}
	// By now "rb.StFrameId <= frameId"
	if oldEdFrameId > frameId {
		arrIdx := rb.GetArrIdxByOffset(frameId - rb.StFrameId)
		if -1 != arrIdx {
			rb.Eles[arrIdx] = pItem
			return RING_BUFF_CONSECUTIVE_SET, oldStFrameId, oldEdFrameId
		}
	}

	// By now "rb.EdFrameId <= frameId"
	ret := RING_BUFF_CONSECUTIVE_SET
	if oldEdFrameId < frameId {
		rb.St, rb.Ed = 0, 0
		rb.StFrameId, rb.EdFrameId = frameId, frameId
		rb.Cnt = 0
		ret = RING_BUFF_NON_CONSECUTIVE_SET
	}

	// By now "rb.EdFrameId == frameId"
	rb.Put(pItem)

	return ret, oldStFrameId, oldEdFrameId
}