//go:build (amd64 || arm64) && !generic && !generic32 && !generic64
// +build amd64 arm64
// +build !generic
// +build !generic32
// +build !generic64

package ec256

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"math/big"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// r^{-1} mod p
var rModInverseP *big.Int
var rModInverseN *big.Int
var p *big.Int
var n *big.Int

func init() {
	rModInverseP = new(big.Int)
	rModInverseP.SetInt64(1)
	rModInverseP.Lsh(rModInverseP, 256)
	rModInverseP.ModInverse(rModInverseP, c256.P)

	rModInverseN = new(big.Int)
	rModInverseN.SetInt64(1)
	rModInverseN.Lsh(rModInverseN, 256)
	rModInverseN.ModInverse(rModInverseN, c256.N)

	p = new(big.Int)
	p.Set(c256.P)
	n = new(big.Int)
	n.Set(c256.N)
}

func randUint64(a []uint64) {
	buf := make([]byte, 8)
	for i := range a {
		rand.Read(buf)
		a[i] = binary.LittleEndian.Uint64(buf)
	}
}

func assertEqual(a, b interface{}) {
	switch a.(type) {
	case *big.Int:
		if a.(*big.Int).Cmp(b.(*big.Int)) != 0 {
			panic("assert equal failed")
		}
	case []uint64:
		aa := a.([]uint64)
		bb := b.([]uint64)
		for i := 0; i < len(aa); i++ {
			if aa[i] != bb[i] {
				panic("assert equal failed")
			}
		}
	default:
		panic("unknown type")
	}
}

func print(a []uint64) {
	for _, x := range a {
		fmt.Printf("%016x ", x)
	}
	fmt.Println("")
}

func toBig(in []uint64) *big.Int {
	out := new(big.Int)
	for i := len(in) - 1; i >= 0; i-- {
		out.Lsh(out, 64)
		out.Add(out, new(big.Int).SetUint64(in[i]))
	}
	return out
}

// Functions implemented in c256_asm_*64.s
// Montgomery multiplication modulo P256
func c256MulOfGo(res, in1, in2 []uint64) {
	int1 := toBig(in1)
	int2 := toBig(in2)
	int1.Mul(int1, int2)
	int1.Mul(int1, rModInverseP)
	int1.Mod(int1, p)
	fromBig(res, int1)
}

// Montgomery square modulo P256, repeated n times (n >= 1)
func c256SqrOfGo(res, in []uint64, n int) {
	copy(res, in)
	for i := 0; i < n; i++ {
		c256MulOfGo(res, res, res)
	}
}

// Montgomery multiplication by 1
func c256FromMontOfGo(res, in []uint64) {
	int1 := toBig(in)
	int1.Mul(int1, rModInverseP)
	int1.Mod(int1, p)
	fromBig(res, int1)
}

// iff cond == 1  val <- -val
func c256NegCondOfGo(val []uint64, cond int) {
	if cond == 1 {
		int1 := toBig(val)
		int1.Sub(p, int1)

		int1.Mod(int1, p)
		fromBig(val, int1)
	}
}

// Montgomery multiplication modulo Ord(G)
func c256OrdMulOfGo(res, in1, in2 []uint64) {
	int1 := toBig(in1)
	int2 := toBig(in2)
	int1.Mul(int1, int2)
	int1.Mul(int1, rModInverseN)
	int1.Mod(int1, n)
	fromBig(res, int1)
}

// Montgomery square modulo Ord(G), repeated n times
func c256OrdSqrOfGo(res, in []uint64, n int) {
	copy(res, in)

	for i := 0; i < n; i++ {
		c256OrdMulOfGo(res, res, res)
	}
}

// the key step of mont-mul, res = in + p * in[0]
// res:5, in:4
func c256MulPOfGo(res, in []uint64) {
	int1 := toBig(in)
	r := new(big.Int)
	r.Mul(new(big.Int).SetUint64(in[0]), p)
	r.Add(r, int1)
	fromBig(res, r)
}

func montReduceOfGo(res, a []uint64) {
	res1 := new(big.Int)
	a1 := toBig(a)
	res1.Mul(new(big.Int).SetUint64(a[0]), p)
	res1.Add(res1, a1)
	res1.Rsh(res1, 64)
	fromBig(res, res1)
}

func randomPoint() (*big.Int, *big.Int) {
	k, _ := rand.Int(rand.Reader, c256.N)
	return c256.ScalarMult(c256.Gx, c256.Gy, k.Bytes())
}

//	func TestMontReduceOfGo(t *testing.T) {
//		res1, res2, in1 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
//		_ = res1
//		for i := 0; i < 100000000; i++ {
//			randUint64(in1)
//			// in1 = []uint64{1, 1, 1, 1}
//			montReduceOfGo(res1, in1)
//			// montReduce(res2, in1)
//			// print(res1)
//			// print(res2)
//			// assertEqualUint(res1, res2, "")
//		}
//	}

func BenchmarkUint64IsZero(b *testing.B) {
	scalar := []uint64{1, 2, 3, 4}
	for i := 0; i < b.N; i++ {
		scalarIsZero(scalar)
	}
}
func TestC256Mul(t *testing.T) {
	for i := 0; i < 1000000; i++ {
		// for {
		res1, res2, in1, in2 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
		randUint64(in1)
		randUint64(in2)

		c256MulOfGo(res1, in1, in2)
		c256Mul(res2, in1, in2)
		assertEqual(res1, res2)
	}
}

// 使用p256:
// BenchmarkC256Mul-10    	82318298	        14.51 ns/op	       0 B/op	       0 allocs/op
// 修改不用nist p256:
// BenchmarkC256Mul-10    	87902702	        13.60 ns/op	       0 B/op	       0 allocs/op
func BenchmarkC256Mul(b *testing.B) {
	res, in1, in2 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
	randUint64(in1)
	randUint64(in2)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		c256Mul(res, in1, in2)
	}
}

func TestC256SqrBasic(t *testing.T) {
	res, zero, in := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
	c256Sqr(res, in, 1)
	assertEqual(res, zero)

	pplus1 := new(big.Int).Add(p, big.NewInt(1))
	fromBig(in, pplus1)
	c256Sqr(res, in, 1)
	rInv := toBig(res)
	assertEqual(rInv, rModInverseP)

	f32 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
	fromBig(in, f32)
	c256Sqr(res, in, 1)
	f32.Mul(f32, f32)
	f32.Mul(f32, rModInverseP)
	f32.Mod(f32, p)
	res2 := make([]uint64, 4)
	fromBig(res2, f32)
	assertEqual(res, res2)
}

func TestC256Sqr(t *testing.T) {
	for n := 1; n < 10; n++ {
		for i := 0; i < 100000; i++ {
			res1, res2, in := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
			randUint64(in)
			c256SqrOfGo(res1, in, n)
			c256Sqr(res2, in, n)
			assertEqual(res1, res2)
		}
	}
}

// 使用p256:
// BenchmarkC256Sqr-10    	93287706	        12.84 ns/op	       0 B/op	       0 allocs/op
// 修改不用nist p256:
// BenchmarkC256Sqr-10    	87514056	        11.72 ns/op	       0 B/op	       0 allocs/op
func BenchmarkC256Sqr(b *testing.B) {
	res, in := make([]uint64, 4), make([]uint64, 4)
	randUint64(in)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		c256Sqr(res, in, 1)
	}
}

func TestNegCond(t *testing.T) {
	res1, res2, in1 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
	randUint64(in1)
	copy(res1, in1)
	copy(res2, in1)

	c256NegCondOfGo(res1, 1)
	c256NegCond(res2, 1)
	assertEqual(res1, res2)
	c256NegCondOfGo(res1, 0)
	c256NegCond(res2, 0)
	assertEqual(res1, res2)
}

func TestMovCond(t *testing.T) {
	res, in1, in2 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
	randUint64(in1)
	randUint64(in2)

	c256MovCond(res, in1, in2, 1)
	assertEqual(res, in1)

	c256MovCond(res, in1, in2, 0)
	assertEqual(res, in2)

	c256MovCond(res, in1, in2, 12345)
	assertEqual(res, in1)
}

func TestFromMont(t *testing.T) {
	for i := 0; i < 1000000; i++ {
		res1, res2, in1 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
		randUint64(in1)
		c256FromMontOfGo(res1, in1)
		c256FromMont(res2, in1)
		assertEqual(res1, res2)
	}
}

func TestOrdMul(t *testing.T) {
	for i := 0; i < 100000; i++ {
		res1, res2, in1, in2 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
		randUint64(in1)
		randUint64(in2)

		c256OrdMulOfGo(res1, in1, in2)
		c256OrdMul(res2, in1, in2)
		assertEqual(res1, res2)
	}

}

func TestOrdSqr(t *testing.T) {
	for k := 1; k < 10; k++ {
		for i := 0; i < 10000; i++ {
			res1, res2, in1, in2 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
			randUint64(in1)
			randUint64(in2)

			c256OrdSqrOfGo(res1, in1, k)
			c256OrdSqr(res2, in1, k)
			assertEqual(res1, res2)
		}
	}
}

func TestOrdInverse(t *testing.T) {
	for i := 0; i < 1000; i++ {
		k, _ := rand.Int(rand.Reader, c256.N)
		res1 := c256.Inverse(k)
		res2 := new(big.Int)
		res2.ModInverse(k, c256.N)

		res1.Mul(res1, k)
		res1.Mod(res1, c256.N)
		res2.Mul(res2, k)
		res2.Mod(res2, c256.N)

		assertEqual(res1, res2)
	}
}

func TestC256Inverse(t *testing.T) {
	for i := 0; i < 1000; i++ {
		res1, res2, in1, in2 := make([]uint64, 4), make([]uint64, 4), make([]uint64, 4), make([]uint64, 4)
		randUint64(in1)
		copy(in2, in1)

		int1 := toBig(in1)

		int1.ModInverse(int1, c256.P)
		int1.Lsh(int1, 256*2)
		int1.Mod(int1, c256.P)
		fromBig(res1, int1)

		c256Inverse(res2, in2)
		assertEqual(res1, res2)
	}
}

func TestPointAddAffineAsmG(t *testing.T) {
	var g1, g2, g3, g c256Point
	x1 := new(big.Int).Set(c256.Gx)
	y1 := new(big.Int).Set(c256.Gy)
	x2, y2 := c256.CurveParams.Add(x1, y1, x1, y1)
	x3, y3 := c256.CurveParams.Add(x1, y1, x2, y2)

	g1.c256PointFromAffine(c256.Gx, c256.Gy)
	g2.c256PointFromAffine(x2, y2)
	g3.c256PointFromAffine(x3, y3)

	c256PointAddAffineAsm(g.xyz[:], g1.xyz[:], g2.xyz[:], 0, 1, 1)
	x, y := g3.c256PointToAffine()
	assertEqual(x3, x)
	assertEqual(y3, y)
}

func TestPointAddAffineAsm(t *testing.T) {
	var p1, p2 c256Point
	{
		x1, y1 := randomPoint()
		x2 := bigFromBase16("4071bba1f6624b6e9ac69b7109db9cac04e5bba76fdc954ebe375dfb2af6df2a")
		y2 := bigFromBase16("fffffffb00000005fffffffc00000002fffffffd00000006fffffff900000004")
		y2.Sub(p, y2)
		x3, y3 := c256.CurveParams.Add(x1, y1, x2, y2)

		y2.Sub(p, y2)
		p1.c256PointFromAffine(x1, y1)
		p2.c256PointFromAffine(x2, y2)
		// p2.y = 1, set to p+1
		p2.xyz[4] = 0
		p2.xyz[5] = 0xffffffff00000001
		p2.xyz[6] = 0xffffffffffffffff
		p2.xyz[7] = 0xfffffffeffffffff

		c256PointAddAffineAsm(p1.xyz[:], p1.xyz[:], p2.xyz[:], 1, 1, 1)
		x4, y4 := p1.c256PointToAffine()

		assertEqual(x3, x4)
		assertEqual(y3, y4)
	}

	{
		x1, y1 := randomPoint()
		x2 := bigFromBase16("4071bba1f6624b6e9ac69b7109db9cac04e5bba76fdc954ebe375dfb2af6df2a")
		y2 := bigFromBase16("fffffffb00000005fffffffc00000002fffffffd00000006fffffff900000004")
		x3, y3 := c256.CurveParams.Add(x1, y1, x2, y2)
		y2.Sub(p, y2)
		p1.c256PointFromAffine(x1, y1)
		p2.c256PointFromAffine(x2, y2)

		c256PointAddAffineAsm(p1.xyz[:], p1.xyz[:], p2.xyz[:], 1, 1, 1)
		x4, y4 := p1.c256PointToAffine()

		assertEqual(x3, x4)
		assertEqual(y3, y4)
	}

	for i := 0; i < 10000; i++ {
		x1, y1 := randomPoint()
		x2, y2 := randomPoint()
		x3, y3 := c256.CurveParams.Add(x1, y1, x2, y2)

		p1.c256PointFromAffine(x1, y1)
		p2.c256PointFromAffine(x2, y2)

		c256PointAddAffineAsm(p1.xyz[:], p1.xyz[:], p2.xyz[:], 0, 1, 1)
		x4, y4 := p1.c256PointToAffine()

		assertEqual(x3, x4)
		assertEqual(y3, y4)

		y2.Sub(p, y2)
		p1.c256PointFromAffine(x1, y1)
		p2.c256PointFromAffine(x2, y2)

		c256PointAddAffineAsm(p1.xyz[:], p1.xyz[:], p2.xyz[:], 1, 1, 1)
		x4, y4 = p1.c256PointToAffine()

		assertEqual(x3, x4)
		assertEqual(y3, y4)
	}

}

func BenchmarkPointAddAffineAsm(b *testing.B) {
	var res, p1, p2 c256Point
	x1, y1 := randomPoint()
	x2, y2 := randomPoint()

	p1.c256PointFromAffine(x1, y1)
	p2.c256PointFromAffine(x2, y2)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		c256PointAddAffineAsm(res.xyz[:], p1.xyz[:], p2.xyz[:], 1, 1, 1)
	}
}

func TestPointAddAffineAsmSpeed(t *testing.T) {
	var res, p1, p2 c256Point
	x1, y1 := randomPoint()
	x2, y2 := randomPoint()

	p1.c256PointFromAffine(x1, y1)
	p2.c256PointFromAffine(x2, y2)
	total := 100000
	begin := time.Now()
	for i := 0; i < total; i++ {
		c256PointAddAffineAsm(res.xyz[:], p1.xyz[:], p2.xyz[:], 1, 1, 1)
	}
	elaspe := time.Since(begin)
	fmt.Println("time: ", elaspe.Milliseconds(), "ms")
	fmt.Println(float64(total) / float64(elaspe.Milliseconds()) * 1000)
}

func TestPointAddAsm(t *testing.T) {
	var res, p1, p2 c256Point
	x1, y1 := randomPoint()
	x2 := new(big.Int).Set(x1)
	y2 := new(big.Int).Set(y1)
	y2.Sub(p, y2)

	p1.c256PointFromAffine(x1, y1)
	p2.c256PointFromAffine(x2, y2)
	c256PointAddAsm(res.xyz[:], p1.xyz[:], p2.xyz[:])
	x, y := res.c256PointToAffine()
	assertEqual(x, big.NewInt(0))
	assertEqual(y, big.NewInt(0))

	for i := 0; i < 1000; i++ {

		k1, _ := rand.Int(rand.Reader, c256.N)
		k2, _ := rand.Int(rand.Reader, c256.N)

		x1, y1 := c256.CurveParams.ScalarMult(c256.Gx, c256.Gy, k1.Bytes())
		x2, y2 := c256.CurveParams.ScalarMult(c256.Gx, c256.Gy, k2.Bytes())
		x3, y3 := c256.CurveParams.Add(x1, y1, x2, y2)

		p1.c256PointFromAffine(x1, y1)
		p2.c256PointFromAffine(x2, y2)
		c256PointAddAsm(res.xyz[:], p1.xyz[:], p2.xyz[:])

		x4, y4 := res.c256PointToAffine()

		assertEqual(x3, x4)
		assertEqual(y3, y4)
	}
}

func TestPointDoubleAsm(t *testing.T) {
	for i := 0; i < 1000; i++ {
		var res1, res2, p1 c256Point
		k1, _ := rand.Int(rand.Reader, c256.N)

		x1, y1 := c256.CurveParams.ScalarMult(c256.Gx, c256.Gy, k1.Bytes())
		x3, y3 := c256.CurveParams.Double(x1, y1)
		res2.c256PointFromAffine(x3, y3)

		p1.c256PointFromAffine(x1, y1)
		c256PointDoubleAsm(res1.xyz[:], p1.xyz[:])

		x4, y4 := res1.c256PointToAffine()

		assertEqual(x3, x4)
		assertEqual(y3, y4)
	}
}

// / test for Curve interface
func TestIsOnCurve(t *testing.T) {
	if !c256.IsOnCurve(c256.Gx, c256.Gy) {
		t.Fail()
	}
}

func TestPointAdd(t *testing.T) {
	x1, y1 := randomPoint()
	x3, y3 := c256.CurveParams.Add(x1, y1, x1, y1)
	x4, y4 := c256.Add(x1, y1, x1, y1)
	assertEqual(x3, x4)
	assertEqual(y3, y4)

	x2 := new(big.Int).Set(x1)
	y2 := new(big.Int).Set(y1)
	y2.Sub(p, y2)
	x3, y3 = c256.CurveParams.Add(x1, y1, x2, y2)
	x4, y4 = c256.Add(x1, y1, x2, y2)
	assertEqual(x3, x4)
	assertEqual(y3, y4)

	for i := 0; i < 1000; i++ {
		k1, _ := rand.Int(rand.Reader, c256.N)
		k2, _ := rand.Int(rand.Reader, c256.N)
		x1, y1 := c256.CurveParams.ScalarMult(c256.Gx, c256.Gy, k1.Bytes())
		x2, y2 := c256.CurveParams.ScalarMult(c256.Gx, c256.Gy, k2.Bytes())
		x3, y3 := c256.CurveParams.Add(x1, y1, x2, y2)
		x4, y4 := c256.Add(x1, y1, x2, y2)
		assertEqual(x3, x4)
		assertEqual(y3, y4)
	}
}

func BenchmarkPointDouble(b *testing.B) {
	x, y := randomPoint()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// BenchmarkPointDouble-8   	  278118	      4096 ns/op	     192 B/op	       4 allocs/op
		c256.Double(x, y)

		// BenchmarkPointDouble-8   	  186952	      6471 ns/op	    3961 B/op	      52 allocs/op
		// c256.CurveParams.Double(x, y)
	}
}
func BenchmarkPointAdd(b *testing.B) {
	x1, y1 := randomPoint()
	x2, y2 := randomPoint()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		//BenchmarkPointAdd-8   	  273103	      4229 ns/op	     192 B/op	       4 allocs/op
		c256.Add(x1, y1, x2, y2)
		// c256.Add(x1, y1, x1, y1)

		// BenchmarkPointAdd-8   	  175370	      7210 ns/op	    4881 B/op	      65 allocs/op
		// c256.CurveParams.Add(x1, y1, x2, y2)
	}
}

func TestPointDouble(t *testing.T) {
	for i := 0; i < 10000; i++ {
		k1, _ := rand.Int(rand.Reader, c256.N)

		x1, y1 := c256.CurveParams.ScalarMult(c256.Gx, c256.Gy, k1.Bytes())

		x3, y3 := c256.CurveParams.Double(x1, y1)
		x4, y4 := c256.Double(x1, y1)

		assertEqual(x3, x4)
		assertEqual(y3, y4)

	}
}

func TestScalarMult(t *testing.T) {
	k := new(big.Int).Set(c256.N)
	x, y := c256.ScalarMult(c256.Gx, c256.Gy, k.Bytes())
	zero := big.NewInt(0)
	assertEqual(x, zero)
	assertEqual(y, zero)

	for i := 0; i < 1000; i++ {
		k, _ := rand.Int(rand.Reader, c256.N)

		x1, y1 := c256.ScalarMult(c256.Gx, c256.Gy, k.Bytes())
		x2, y2 := c256.CurveParams.ScalarMult(c256.Gx, c256.Gy, k.Bytes())

		assertEqual(x1, x2)
		assertEqual(y1, y2)
	}
}

func TestScalarBaseMult(t *testing.T) {
	k := new(big.Int).Add(c256.N, big.NewInt(1))
	x1, y1 := c256.ScalarBaseMult(k.Bytes())
	assertEqual(x1, c256.Gx)
	assertEqual(y1, c256.Gy)

	for i := 0; i < 1000; i++ {
		k, _ := rand.Int(rand.Reader, c256.N)
		x1, y1 := c256.ScalarBaseMult(k.Bytes())
		x2, y2 := c256.CurveParams.ScalarBaseMult(k.Bytes())

		assertEqual(x1, x2)
		assertEqual(y1, y2)
	}
}

func TestScalarMultSpeed(t *testing.T) {
	k, _ := rand.Int(rand.Reader, c256.N)

	x1, y1 := c256.ScalarMult(c256.Gx, c256.Gy, k.Bytes())

	begin := time.Now()
	total := 100000
	for i := 0; i < total; i++ {
		c256.ScalarMult(x1, y1, k.Bytes())
	}
	elaspe := time.Since(begin)
	fmt.Println("time: ", elaspe.Milliseconds(), "ms")
	fmt.Println(float64(total) / float64(elaspe.Milliseconds()) * 1000)
}

func BenchmarkScalarMultSpeed(b *testing.B) {
	k, _ := rand.Int(rand.Reader, c256.N)

	x1, y1 := c256.ScalarMult(c256.Gx, c256.Gy, k.Bytes())
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		c256.ScalarMult(x1, y1, k.Bytes())
	}
}

func TestScalarBaseMultSpeed(t *testing.T) {
	k, _ := rand.Int(rand.Reader, c256.N)

	begin := time.Now()
	total := 100000
	for i := 0; i < total; i++ {
		c256.ScalarBaseMult(k.Bytes())
	}
	elaspe := time.Since(begin)
	fmt.Println("time: ", elaspe.Milliseconds(), "ms")
	fmt.Println(float64(total) / float64(elaspe.Milliseconds()) * 1000)
}

func BenchmarkScalarBaseMultSpeed(b *testing.B) {
	k, _ := rand.Int(rand.Reader, c256.N)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		c256.ScalarBaseMult(k.Bytes())
	}
}

// BenchmarkCombineMult-8   	   17679	     64513 ns/op	     320 B/op	       6 allocs/op
func BenchmarkCombineMult(b *testing.B) {
	x, y := randomPoint()
	k, _ := rand.Int(rand.Reader, c256.N)
	baseScalar := k.Bytes()
	k, _ = rand.Int(rand.Reader, c256.N)
	scalar := k.Bytes()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		CombinedMult(x, y, baseScalar, scalar)
	}
}

func TestBoothW5(t *testing.T) {
	for i := uint(0); i < 64; i++ {
		sel, sign := boothW5(i)
		fmt.Println(i, "\t", sel, "\t", sign)
		_, _ = sel, sign
	}
}

func TestBoothW6(t *testing.T) {
	for i := uint(0); i < 128; i++ {
		sel, sign := boothW6(i)
		// fmt.Println(i, "\t", sel, "\t", sign)
		_, _ = sel, sign

	}
}

func TestSelectBase(t *testing.T) {
	var t0 c256Point
	c256SelectBase(t0.xyz[0:8], c256Precomputed[0][0:], 1)

}

func TestSelect(t *testing.T) {
	var t0 c256Point
	var precomp [16 * 4 * 3]uint64
	var p = c256Point{
		xyz: [12]uint64{0x715A4589334C74C7,
			0x8FE30BBFF2660BE1,
			0x5F9904466A39C994,
			0x32C4AE2C1F198119,
			0x02DF32E52139F0A0,
			0xD0A9877CC62A4740,
			0x59BDCEE36B692153,
			0xBC3736A2F4F6779C,
			1,
			0,
			0,
			0},
	}
	c256ToMont(p.xyz[:], p.xyz[:])
	c256Select(t0.xyz[:], precomp[:], 0)
	assertEqual(t0.xyz[:], make([]uint64, 12))
	equal := c256PointAddAsm(t0.xyz[:], t0.xyz[:], p.xyz[:])
	assert.Equal(t, equal, 1)
}

/*
TestBoothW5
0 	 0 	 0
1 	 1 	 0
2 	 1 	 0
3 	 2 	 0
4 	 2 	 0
5 	 3 	 0
6 	 3 	 0
7 	 4 	 0
8 	 4 	 0
9 	 5 	 0
10 	 5 	 0
11 	 6 	 0
12 	 6 	 0
13 	 7 	 0
14 	 7 	 0
15 	 8 	 0
16 	 8 	 0
17 	 9 	 0
18 	 9 	 0
19 	 10 	 0
20 	 10 	 0
21 	 11 	 0
22 	 11 	 0
23 	 12 	 0
24 	 12 	 0
25 	 13 	 0
26 	 13 	 0
27 	 14 	 0
28 	 14 	 0
29 	 15 	 0
30 	 15 	 0
31 	 16 	 0
32 	 16 	 1
33 	 15 	 1
34 	 15 	 1
35 	 14 	 1
36 	 14 	 1
37 	 13 	 1
38 	 13 	 1
39 	 12 	 1
40 	 12 	 1
41 	 11 	 1
42 	 11 	 1
43 	 10 	 1
44 	 10 	 1
45 	 9 	 1
46 	 9 	 1
47 	 8 	 1
48 	 8 	 1
49 	 7 	 1
50 	 7 	 1
51 	 6 	 1
52 	 6 	 1
53 	 5 	 1
54 	 5 	 1
55 	 4 	 1
56 	 4 	 1
57 	 3 	 1
58 	 3 	 1
59 	 2 	 1
60 	 2 	 1
61 	 1 	 1
62 	 1 	 1
63 	 0 	 1

*/

func TestPrintBaseMult(t *testing.T) {
	if false {
		for i, table := range c256Precomputed {
			for j := 0; j < 32; j++ {
				fmt.Printf("\t// [64^%d * %2d]G\n", i, j+1)
				fmt.Print("\t")
				for k := 0; k < 4; k++ {
					fmt.Printf("0x%016x", table[8*j+k])
					if k < 3 {
						fmt.Print(", ")
					} else {
						fmt.Println()
					}
				}
				fmt.Print("\t")
				for k := 4; k < 8; k++ {
					fmt.Printf("0x%016x", table[8*j+k])
					if k < 7 {
						fmt.Print(", ")
					} else {
						fmt.Println()
					}
				}
			}
			fmt.Println("\t//")
		}
	}
}

func writePoint(sb *strings.Builder, p c256Point) {
	x, y := p.c256PointToAffine()
	p.c256PointFromAffine(x, y)
	for k := 0; k < 8; k++ {
		sb.WriteString(fmt.Sprintf("0x%016x, ", p.xyz[k]))
		if k == 3 {
			sb.WriteString("\n")
		}
	}
	sb.WriteString("\n")
}

func TestBaseTable(t *testing.T) {
	var sb strings.Builder

	const N = 8
	var G = c256Point{
		xyz: [12]uint64{0x715A4589334C74C7,
			0x8FE30BBFF2660BE1,
			0x5F9904466A39C994,
			0x32C4AE2C1F198119,
			0x02DF32E52139F0A0,
			0xD0A9877CC62A4740,
			0x59BDCEE36B692153,
			0xBC3736A2F4F6779C,
			1,
			0,
			0,
			0},
	}
	c256ToMont(G.xyz[:4], G.xyz[:4])
	c256ToMont(G.xyz[4:], G.xyz[4:])
	c256ToMont(G.xyz[8:], G.xyz[8:])

	var P, Q c256Point
	P = G
	for i := 0; i < 256/N; i++ {
		Q = P

		// P
		sb.WriteString(fmt.Sprintf("// [%d^%d]G\n", 1<<N, i))
		writePoint(&sb, Q)

		// 2P
		c256PointDoubleAsm(Q.xyz[:], Q.xyz[:])
		sb.WriteString(fmt.Sprintf("// [2 * %d^%d]G\n", 1<<N, i))
		writePoint(&sb, Q)

		for j := 3; j <= (1 << (N - 1)); j++ {
			// jP
			c256PointAddAsm(Q.xyz[:], Q.xyz[:], P.xyz[:])

			sb.WriteString(fmt.Sprintf("// [%d * %d^%d]G\n", j, 1<<N, i))
			writePoint(&sb, Q)
		}
		// the last round
		if i == 256/N-1 {
			for j := (1 << (N - 1)) + 1; j <= (1 << N); j++ {
				c256PointAddAsm(Q.xyz[:], Q.xyz[:], P.xyz[:])
				sb.WriteString(fmt.Sprintf("// [%d * %d^%d]G\n", j, 1<<N, i))
				writePoint(&sb, Q)
			}
		}
		c256PointDoubleAsm(P.xyz[:], Q.xyz[:])
	}

	os.WriteFile("/Users/fengwd/Files/Codes/go/src/xdx.jelly/xgcl/sm/sm2/tbl.txt", []byte(sb.String()), 0666)
}

func TestBaseTable3(t *testing.T) {
	var G = c256Point{
		xyz: [12]uint64{0x715A4589334C74C7,
			0x8FE30BBFF2660BE1,
			0x5F9904466A39C994,
			0x32C4AE2C1F198119,
			0x02DF32E52139F0A0,
			0xD0A9877CC62A4740,
			0x59BDCEE36B692153,
			0xBC3736A2F4F6779C,
			1,
			0,
			0,
			0},
	}
	c256ToMont(G.xyz[:4], G.xyz[:4])
	c256ToMont(G.xyz[4:], G.xyz[4:])
	c256ToMont(G.xyz[8:], G.xyz[8:])

	scalarReversed := []uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}
	var r, P c256Point
	P.c256BaseMult(scalarReversed)
	c256PointAddAsm(r.xyz[:], P.xyz[:], G.xyz[:])
	x, y := r.c256PointToAffine()
	r.c256PointFromAffine(x, y)

	for k := 0; k < 8; k++ {
		fmt.Printf("0x%016x, ", r.xyz[k])
	}
}
