package main

import (
	"errors"
	"fmt"
	"hash/fnv"
	"sync"
)

// Mix of heap and map
type StatSuperHeapPair struct {
	Key   string
	Code  int
	Value int64

	combinedKey uint64
}

type StatSuperHeapIdx map[uint64]int

type StatSuperHeap struct {
	Idx   StatSuperHeapIdx
	Heap  []StatSuperHeapPair
	Mutex *sync.Mutex
}

func CreateStatSuperHeap() *StatSuperHeap {
	return &StatSuperHeap{StatSuperHeapIdx{}, []StatSuperHeapPair{}, &sync.Mutex{}}
}

func (t *StatSuperHeap) Lock() {
	t.Mutex.Lock()
}

func (t *StatSuperHeap) Unlock() {
	t.Mutex.Unlock()
}

func (t *StatSuperHeap) Push(key string, code int, value int64) {
	t.Lock()
	pairIdx := -1

	hashedKey := fnv.New64()
	_, _ = fmt.Fprint(hashedKey, key)
	_, _ = fmt.Fprint(hashedKey, code)

	combinedKey := hashedKey.Sum64()

	if i, ok := t.Idx[combinedKey]; ok {
		pairIdx = i
		t.Heap[pairIdx].Value += value
	} else {
		t.Heap = append(t.Heap, StatSuperHeapPair{key, code, value, combinedKey})
		pairIdx = len(t.Heap) - 1
		t.Idx[combinedKey] = pairIdx
	}

	for {
		i := (pairIdx - 1) / 2

		if i == pairIdx || t.Heap[i].Value > t.Heap[pairIdx].Value {
			break
		}

		t.swap(i, pairIdx)
		pairIdx = i
	}
	t.Unlock()
}

func (t *StatSuperHeap) Pop() (StatSuperHeapPair, error) {
	if len(t.Heap) == 0 {
		return StatSuperHeapPair{"", 0, 0, 0}, errors.New("empty")
	}

	t.Lock()

	pairIdx := 0
	heapLen := len(t.Heap) - 1

	ret := t.Heap[pairIdx]

	t.Heap[pairIdx] = t.Heap[heapLen] // move last to top
	t.Heap = t.Heap[:heapLen]         // remove duplicated last

	for { // heapLen now equals to sliced array size
		nextIdx := pairIdx*2 + 1

		if nextIdx >= heapLen || nextIdx < 0 { // pairIdx is int, break loop on overflow
			break
		}

		rightIdx := pairIdx*2 + 2

		if rightIdx >= 0 && rightIdx < heapLen && t.Heap[nextIdx].Value < t.Heap[rightIdx].Value { // choose right subtree
			nextIdx = rightIdx
		}

		if t.Heap[nextIdx].Value < t.Heap[pairIdx].Value {
			break
		}

		t.swap(nextIdx, pairIdx)

		pairIdx = nextIdx
	}

	t.Unlock()

	return ret, nil
}

func (t *StatSuperHeap) swap(i int, j int) {
	iCombinedKey := t.Heap[i].combinedKey
	jCombinedKey := t.Heap[j].combinedKey

	t.Heap[i], t.Heap[j] = t.Heap[j], t.Heap[i]
	t.Idx[iCombinedKey], t.Idx[jCombinedKey] = j, i
}

func (t *StatSuperHeap) Copy() *StatSuperHeap {
	ret := CreateStatSuperHeap()

	t.Lock()

	heapLen := len(t.Heap)
	// replace this with nlargest from python heapq
	for i := 0; i < heapLen && i < 500; i++ {
		ret.Push(t.Heap[i].Key, t.Heap[i].Code, t.Heap[i].Value)
	}

	t.Unlock()

	return ret
}
