// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package kheap

import (
	"container/heap"
)

// HeapItem is an interface type implemented by objects stored in the ScoreHeap
type HeapItem interface {
	Data() interface{} // The data object
	Score() float64    // Score to use as the sort criteria
}

// A ScoreHeap implements heap.Interface and is a min heap
// that keeps the top K elements by Score. Push can be called
// with an arbitrary number of values but only the top K are stored
type ScoreHeap struct {
	items    []HeapItem
	capacity int
}

func NewScoreHeap(capacity uint32) *ScoreHeap {
	return &ScoreHeap{capacity: int(capacity)}
}

func (pq ScoreHeap) Len() int { return len(pq.items) }

func (pq ScoreHeap) Less(i, j int) bool {
	return pq.items[i].Score() < pq.items[j].Score()
}

func (pq ScoreHeap) Swap(i, j int) {
	pq.items[i], pq.items[j] = pq.items[j], pq.items[i]
}

// Push implements heap.Interface and only stores
// the top K elements by Score
func (pq *ScoreHeap) Push(x interface{}) {
	item := x.(HeapItem)
	if len(pq.items) < pq.capacity {
		pq.items = append(pq.items, item)
	} else {
		// Pop the lowest scoring element if this item's Score is
		// greater than the min Score so far
		minIndex := 0
		min := pq.items[minIndex]
		if item.Score() > min.Score() {
			// Replace min and heapify
			pq.items[minIndex] = item
			heap.Fix(pq, minIndex)
		}
	}
}

// Pop implements heap.Interface and returns the top K scoring elements in
// increasing order of Score. Callers must reverse the order of returned
// elements to get the top K scoring elements in descending order.
func (pq *ScoreHeap) Pop() interface{} {
	old := pq.items
	n := len(old)
	item := old[n-1]
	pq.items = old[0 : n-1]
	return item
}

// GetItemsReverse returns the items in this min heap in reverse order
// sorted by score descending
func (pq *ScoreHeap) GetItemsReverse() []interface{} {
	ret := make([]interface{}, pq.Len())
	i := pq.Len() - 1
	for pq.Len() > 0 {
		item := heap.Pop(pq)
		ret[i] = item
		i--
	}
	return ret
}
