package mathext

import (
	"gogs.mikescher.com/BlackForestBytes/goext/exerr"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
)

func Sum[T langext.NumberConstraint](v []T) T {
	total := T(0)
	for _, v := range v {
		total += v
	}
	return total
}

func Mean[T langext.FloatConstraint](v []T) T {
	return Sum(v) / T(len(v))
}

func Median[T langext.FloatConstraint](v []T) T {
	if len(v)%2 == 1 {
		return v[len(v)/2]
	} else {
		return (v[len(v)/2-1] + v[len(v)/2]) / T(2)
	}
}

func ArrMin[T langext.OrderedConstraint](v []T) T {
	r := v[0]
	for _, val := range v {
		if val < r {
			r = val
		}
	}
	return r
}

func ArrMax[T langext.OrderedConstraint](v []T) T {
	r := v[0]
	for _, val := range v {
		if val > r {
			r = val
		}
	}
	return r
}

func MustPercentile[T langext.NumberConstraint](rawdata []T, percentile float64) T {
	v, err := Percentile(rawdata, percentile)
	if err != nil {
		panic(err)
	}
	return v
}

func Percentile[T langext.NumberConstraint](rawdata []T, percentile float64) (T, error) {
	v, err := FloatPercentile(rawdata, percentile)
	if err != nil {
		return T(0), err
	}
	return T(v), nil

}

func FloatPercentile[T langext.NumberConstraint](rawdata []T, percentile float64) (float64, error) {
	if len(rawdata) == 0 {
		return 0, exerr.New(exerr.TypeAssert, "no data to calculate percentile").Any("percentile", percentile).Build()
	}

	if percentile < 0 || percentile > 100 {
		return 0, exerr.New(exerr.TypeAssert, "percentile out of range").Any("percentile", percentile).Build()
	}

	data := langext.ArrCopy(rawdata)
	langext.Sort(data)

	idxFloat := float64(len(data)-1) * (percentile / float64(100))

	idxInt := int(idxFloat)

	// exact match on index
	if idxFloat == float64(idxInt) {
		return float64(data[idxInt]), nil
	}

	// linear interpolation
	v1 := data[idxInt]
	v2 := data[idxInt+1]

	weight := idxFloat - float64(idxInt)

	valFloat := (float64(v1) * (1 - weight)) + (float64(v2) * weight)

	return valFloat, nil

}