package ope

import (
	"fmt"
	"math"
	"math/big"
	"testing"

	"github.com/stretchr/testify/assert"
	"xdx.jelly/xgcl/grand"
)

func choose(y int64, x int64) float64 {
	a, _ := new(big.Int).MulRange(y-x+1, y).Float64()
	b, _ := new(big.Int).MulRange(2, x).Float64()
	return a / b
}

func hyperProb(x int64, M int64, N int64, y int64) float64 {
	return choose(M, x) * choose(N-M, y-x) / choose(N, y)
}

func TestUniform(t *testing.T) {
	stats := make([]int, 10)
	for i := 0; i < 1000000; i++ {
		f := uniformRand(bytesToCoins(grand.GetRandom(4)))
		stats[int(f*10)]++
	}

	fmt.Println(stats)
}

func TestHyperSmall(t *testing.T) {
	M := int64(4)
	N := int64(10)
	y := int64(5)
	stats := make([]float64, min(y, M)+1)
	wants := make([]float64, min(y, M)+1)
	count := 1000000
	for i := 0; i < count; i++ {
		x := hypergeometricSmall(M, N, y, bytesToCoins(grand.GetRandom(4)))
		stats[x] += 1
	}

	for i := 0; i < len(stats); i++ {
		stats[i] = stats[i] / float64(count)
		wants[i] = hyperProb(int64(i), M, N, y)
	}
	if true {
		for i := 0; i < len(stats); i++ {
			fmt.Printf("%.2f ", stats[i])
		}
		fmt.Println()
		for i := 0; i < len(stats); i++ {
			fmt.Printf("%.2f ", wants[i])
		}
		fmt.Println()
	}
	epsilon := 0.001
	for i := 0; i < len(stats); i++ {
		assert.Less(t, math.Abs(stats[i]-wants[i]), epsilon)
	}
}

func TestHyperBig(t *testing.T) {
	M := int64(30)
	N := int64(100)
	y := int64(51)
	stats := make([]float64, min(y, M)+1)
	wants := make([]float64, min(y, M)+1)
	count := 1000000
	for i := 0; i < count; i++ {
		x := hypergeometricBig(M, N, y, bytesToCoins(grand.GetRandom(4)))
		stats[x] += 1
	}

	for i := 0; i < len(stats); i++ {
		stats[i] = stats[i] / float64(count)
		wants[i] = hyperProb(int64(i), M, N, y)
	}

	epsilon := 0.001
	for i := 0; i < len(stats); i++ {
		assert.Less(t, math.Abs(stats[i]-wants[i]), epsilon)
	}

}
