package bn256

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"testing"
	"time"

	"xdx.jelly/xgcl/gmath"
)

func TestCurve(t *testing.T) {
	pt := &curvePoint{}
	pt.Set(curveGen)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("IsOnCurve failed!")
		return
	}

	pt.Double(curveGen)
	pt.MakeAffine()
	fmt.Println("Double of gen:", pt)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("Double failed!")
		return
	}

	pt.Add(pt, curveGen)
	fmt.Println("Triple of gen:", pt)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("Add failed!")
		return
	}

	OrderMinus1 := new(big.Int)
	OrderMinus1.Sub(N, gmath.BigInt1)

	pt.Mul(curveGen, OrderMinus1)
	pt.Neg(pt)
	pt.MakeAffine()
	if *pt != *curveGen {
		t.Fail()
		t.Log("Mul failed!")
		t.Log(pt)
		t.Log(curveGen)
		return
	}

	pt.Mul(curveGen, N)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("Mul N failed!")
		return
	}
	if !pt.IsInfinity() {
		t.Fail()
		t.Log("IsInfinity failed!")
		return
	}
}

func TestCurveBaseMul(t *testing.T) {
	pt0 := &curvePoint{}
	pt1 := &curvePoint{}
	for i := 0; ; i++ {
		k, _ := rand.Int(rand.Reader, N)
		pt0.Mul(curveGen, k)
		pt1.MulBase(k, curverBasePrecompted8)
		if !pt0.Equal(pt1) {
			t.Log(pt0)
			t.Log(pt1)
			t.Fail()
			return
		}
		if i%10000 == 0 {
			t.Log(i, "passed")
		}
	}
}

func BenchmarkCurveMul(b *testing.B) {
	//BenchmarkCurveMul-8        13298             85947 ns/op

	pt := &curvePoint{}
	k, _ := rand.Int(rand.Reader, N)
	for i := 0; i < b.N; i++ {
		pt.Mul(curveGen, k)
	}
}

func BenchmarkCurveBaseMul(b *testing.B) {
	//BenchmarkCurveBaseMul-8   	   28418	     40692 ns/op
	//BenchmarkCurveBaseMul-8   	   41059	     25595 ns/op
	pt := &curvePoint{}
	k, _ := rand.Int(rand.Reader, N)
	for i := 0; i < b.N; i++ {
		pt.MulBase(k, curverBasePrecompted8)
	}
}

func TestCurveSpeed(T *testing.T) {
	{
		// 11000 次/秒
		useLattice = true
		pt := &curvePoint{}
		k, _ := rand.Int(rand.Reader, N)
		begin := time.Now()
		total := 10000
		for i := 0; i < total; i++ {
			pt.Mul(curveGen, k)
		}
		elaspe := time.Since(begin)
		fmt.Println("time: ", elaspe.Milliseconds(), "ms")
		fmt.Println(float64(total) / float64(elaspe.Milliseconds()) * 1000)
	}
	{
		// 7300 次/秒
		useLattice = false
		pt := &curvePoint{}
		k, _ := rand.Int(rand.Reader, N)
		begin := time.Now()
		total := 10000
		for i := 0; i < total; i++ {
			pt.Mul(curveGen, k)
		}
		elaspe := time.Since(begin)
		fmt.Println("time: ", elaspe.Milliseconds(), "ms")
		fmt.Println(float64(total) / float64(elaspe.Milliseconds()) * 1000)
	}
}

func TestTwistCurve(t *testing.T) {
	pt := &twistPoint{}
	pt.Set(twistGen)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("IsOnCurve failed!")
		return
	}

	pt.Double(twistGen)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("Double failed!")
		return
	}

	pt.Add(pt, twistGen)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("Add failed!")
		return
	}

	OrderMinus1 := new(big.Int)
	OrderMinus1.Sub(N, gmath.BigInt1)

	pt.Mul(twistGen, OrderMinus1)
	pt.Neg(pt)
	pt.MakeAffine()
	if *pt != *twistGen {
		t.Fail()
		t.Log("Mul failed!")
		fmt.Println(pt)
		fmt.Println(twistGen)
		return
	}

	pt.Mul(twistGen, N)
	if !pt.IsOnCurve() {
		t.Fail()
		t.Log("Mul N failed!")
		return
	}
	if !pt.IsInfinity() {
		t.Fail()
		t.Log("IsInfinity failed!")
		return
	}
}

func TestTwistBaseMul(t *testing.T) {
	pt0 := &twistPoint{}
	pt1 := &twistPoint{}
	for i := 0; i < 1000000; i++ {
		k, _ := rand.Int(rand.Reader, N)
		pt0.Mul(twistGen, k)
		pt1.MulBase(k, twistBasePrecomputed8)
		if !pt0.Equal(pt1) {
			t.Log(pt0)
			t.Log(pt1)
			t.Fail()
			return
		}
		if i%10000 == 0 {
			t.Log(i, "passed")
		}
	}
}

func BenchmarkTwistBaseMul(b *testing.B) {
	//BenchmarkTwistBaseMul-8   	   11694	    101403 ns/op
	pt := &twistPoint{}
	k, _ := rand.Int(rand.Reader, N)
	for i := 0; i < b.N; i++ {
		pt.MulBase(k, twistBasePrecomputed8)
	}
}
func BenchmarkTwistCurve(b *testing.B) {
	//BenchmarkTwistCurve-8   	    3090	    350948 ns/op
	pt := &twistPoint{}
	k, _ := rand.Int(rand.Reader, N)
	for i := 0; i < b.N; i++ {
		pt.Mul(twistGen, k)
	}
}

func TestTwistCurveSpeed(T *testing.T) {
	{
		// 2800 次/秒
		useLattice = true
		pt := &twistPoint{}
		k, _ := rand.Int(rand.Reader, N)
		begin := time.Now()
		total := 10000
		for i := 0; i < total; i++ {
			pt.Mul(twistGen, k)
		}
		elaspe := time.Since(begin)
		fmt.Println("time: ", elaspe.Milliseconds(), "ms")
		fmt.Println(float64(total) / float64(elaspe.Milliseconds()) * 1000)
	}
	{
		// 1900 次/秒
		useLattice = false
		pt := &twistPoint{}
		k, _ := rand.Int(rand.Reader, N)
		begin := time.Now()
		total := 10000
		for i := 0; i < total; i++ {
			pt.Mul(twistGen, k)
		}
		elaspe := time.Since(begin)
		fmt.Println("time: ", elaspe.Milliseconds(), "ms")
		fmt.Println(float64(total) / float64(elaspe.Milliseconds()) * 1000)
	}
}
