package goproxy_test

import (
	"bytes"
	"crypto/rsa"
	"encoding/binary"
	"io"
	"math"
	"math/rand"
	"testing"

	"github.com/elazarl/goproxy"
)

type RandSeedReader struct {
	r rand.Rand
}

func (r *RandSeedReader) Read(b []byte) (n int, err error) {
	for i := range b {
		b[i] = byte(r.r.Int() & 0xFF)
	}
	return len(b), nil
}

func TestCounterEncDifferentConsecutive(t *testing.T) {
	k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)
	fatalOnErr(err, "rsa.GenerateKey", t)
	c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
	fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
	for i := 0; i < 100*1000; i++ {
		var a, b int64
		binary.Read(&c, binary.BigEndian, &a)
		binary.Read(&c, binary.BigEndian, &b)
		if a == b {
			t.Fatal("two consecutive equal int64", a, b)
		}
	}
}

func TestCounterEncIdenticalStreams(t *testing.T) {
	k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)
	fatalOnErr(err, "rsa.GenerateKey", t)
	c1, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
	fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
	c2, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
	fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
	nout := 1000
	out1, out2 := make([]byte, nout), make([]byte, nout)
	io.ReadFull(&c1, out1)
	tmp := out2[:]
	rand.Seed(0xFF43109)
	for len(tmp) > 0 {
		n := 1 + rand.Intn(256)
		if n > len(tmp) {
			n = len(tmp)
		}
		n, err := c2.Read(tmp[:n])
		fatalOnErr(err, "CounterEncryptorRand.Read", t)
		tmp = tmp[n:]
	}
	if !bytes.Equal(out1, out2) {
		t.Error("identical CSPRNG does not produce the same output")
	}
}

func stddev(data []int) float64 {
	var sum, sum_sqr float64 = 0, 0
	for _, h := range data {
		sum += float64(h)
		sum_sqr += float64(h) * float64(h)
	}
	n := float64(len(data))
	variance := (sum_sqr - ((sum * sum) / n)) / (n - 1)
	return math.Sqrt(variance)
}

func TestCounterEncStreamHistogram(t *testing.T) {
	k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128)
	fatalOnErr(err, "rsa.GenerateKey", t)
	c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog"))
	fatalOnErr(err, "NewCounterEncryptorRandFromKey", t)
	nout := 100 * 1000
	out := make([]byte, nout)
	io.ReadFull(&c, out)
	refhist := make([]int, 512)
	for i := 0; i < nout; i++ {
		refhist[rand.Intn(256)]++
	}
	hist := make([]int, 512)
	for _, b := range out {
		hist[int(b)]++
	}
	refstddev, stddev := stddev(refhist), stddev(hist)
	// due to lack of time, I guestimate
	t.Logf("ref:%v - act:%v = %v", refstddev, stddev, math.Abs(refstddev-stddev))
	if math.Abs(refstddev-stddev) >= 1 {
		t.Errorf("stddev of ref histogram different than regular PRNG: %v %v", refstddev, stddev)
	}
}
