package mt19937

import "github.com/fiskerinc/cloud-services/pkg/logger"

type aliasTableUnit struct {
	weight float64
	index  int
}

type aliasTable struct {
	m_aliasTable []aliasTableUnit
	m_eng        *MT19937
}

func DistDiscrete(eng *MT19937, list []int) *aliasTable {
	if len(list) == 0 {
		logger.Error().Msg("list is not allowed empty!")
		return nil
	}
	var weightSum int
	for _, weight := range list {
		weightSum += weight
	}
	average := float64(weightSum) / float64(len(list))

	table := &aliasTable{
		m_aliasTable: make([]aliasTableUnit, len(list)),
		m_eng:        eng,
	}

	below_average := make([]aliasTableUnit, 0, len(list))
	above_average := make([]aliasTableUnit, 0, len(list))

	for i, weight := range list {
		val := float64(weight) / average
		unit := aliasTableUnit{
			weight: val,
			index:  i,
		}
		if val < 1 {
			below_average = append(below_average, unit)
		} else {
			above_average = append(above_average, unit)
		}
	}

	posA := 0
	posB := 0
	for posB < len(below_average) && posA < len(above_average) {
		table.m_aliasTable[below_average[posB].index] = aliasTableUnit{
			weight: below_average[posB].weight,
			index:  above_average[posA].index,
		}
		above_average[posA].weight -= (1 - below_average[posB].weight)
		if above_average[posA].weight < 1 {
			below_average[posB] = above_average[posA]
			posA++
		} else {
			posB++
		}
	}

	for ; posB < len(below_average); posB++ {
		table.m_aliasTable[below_average[posB].index].weight = float64(1)
	}
	for ; posA < len(above_average); posA++ {
		table.m_aliasTable[above_average[posA].index].weight = float64(1)
	}

	return table
}

func (dist *aliasTable) Discrete() int {
	result := int(DistInt64(dist.m_eng, 0, int64(len(dist.m_aliasTable)-1)).Int64())
	test := Dist01(dist.m_eng).Float64()
	if test < dist.m_aliasTable[result].weight {
		return result
	} else {
		return dist.m_aliasTable[result].index
	}
}
