package weightrand

import (
	"math/rand"
	"sort"
)

type WeightItem[T any] struct {
	Item   T
	Weight int64
	AccSum int64
}

type RandTable[T any] struct {
	allItems []*WeightItem[T]
	sum      int64
	needCalc bool
	hitLimit map[int]int
}

func NewRandTable[T any]() *RandTable[T] {
	wr := new(RandTable[T])
	wr.hitLimit = make(map[int]int)
	return wr
}

// 增加随机项，
func (rt *RandTable[T]) AddItem(item T, weight int64) {
	if weight < 0 {
		weight = 0
	}
	item1 := new(WeightItem[T])
	item1.Item = item
	item1.Weight = weight

	rt.allItems = append(rt.allItems, item1)
	rt.needCalc = true
}

// 添加需要限制命中次数的项
func (rt *RandTable[T]) AddLimitItem(item T, weight int64, limit int) {
	if weight < 0 {
		weight = 0
	}
	item1 := new(WeightItem[T])
	item1.Item = item
	item1.Weight = weight

	rt.allItems = append(rt.allItems, item1)
	l := len(rt.allItems)
	rt.hitLimit[l-1] = limit

	rt.needCalc = true
}

// 计算总权重
func (rt *RandTable[T]) calcSum() {
	rt.sum = 0

	for _, item := range rt.allItems {
		rt.sum += item.Weight
		item.AccSum = rt.sum
	}
}

func (rt *RandTable[T]) WeightSum() int64 {
	if rt.needCalc {
		rt.calcSum()
		rt.needCalc = false
	}
	return rt.sum
}

func (rt *RandTable[T]) Size() int {
	return len(rt.allItems)
}

// 如果没有合适项，返回该类型默认值 指针为nil，int为0
func (rt *RandTable[T]) randomItem() (*WeightItem[T], int) {
	if rt.needCalc {
		rt.calcSum()
		rt.needCalc = false
	}

	if rt.sum == 0 {
		return nil, 0
	}

	rnd := rand.Int63n(rt.sum)
	rnd += 1

	cnt := len(rt.allItems)
	index := sort.Search(cnt, func(i int) bool { return rt.allItems[i].AccSum >= rnd })

	if index < cnt {
		return rt.allItems[index], index
	}

	return nil, 0
}

// 如果没有合适项，返回该类型默认值 指针为nil，int为0
func (rt *RandTable[T]) GetRandomItem() (df T, ok bool) {
	v, index := rt.randomItem()
	if v != nil {
		df = v.Item
		ok = true

		// 命中项需要限制次数，当限制次数用完就不能命中
		if v2, ok := rt.hitLimit[index]; ok {
			v2 -= 1
			rt.hitLimit[index] = v2

			if v2 <= 0 {
				v.Weight = 0
				rt.needCalc = true
			}
		}
	}
	return
}

// 随机移除，并返回移除项；并非真的移除，只是把移除项的权重设为0，不会再随机到
func (rt *RandTable[T]) RandomRemoveItem() (df T, ok bool) {
	v, _ := rt.randomItem()
	if v != nil {
		df = v.Item
		ok = true

		v.Weight = 0
		rt.needCalc = true
	}
	return
}

// 增加权重，返回第几次增加， 从0开始计数
type IndexRandTable struct {
	allItems []int64
	sum      int64
}

func NewIndexRandTable(cap int) *IndexRandTable {
	wr := new(IndexRandTable)
	wr.allItems = make([]int64, 0, cap)
	return wr
}

func (srt *IndexRandTable) AddItem(weight int64) {
	if weight < 0 {
		weight = 0
	}
	srt.sum += weight
	srt.allItems = append(srt.allItems, srt.sum)
}

// 没有随机到时，返回-1，随机到返回第几次添加，从0开始
func (srt *IndexRandTable) GetRandomItem() int {

	if srt.sum <= 0 {
		return -1
	}

	rnd := rand.Int63n(srt.sum)
	rnd += 1

	cnt := len(srt.allItems)
	index := sort.Search(cnt, func(i int) bool { return srt.allItems[i] >= rnd })

	if index < cnt {
		return index
	}

	return -1
}
