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

// build when !amd64 AND !arm64 OR generic32 OR generic64

package ec256

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

func BenchmarkScalarMultc256(b *testing.B) {
	b.ResetTimer()
	// _, x, y, _ := elliptic.GenerateKey(c256, rand.Reader)
	// priv, _, _, _ := elliptic.GenerateKey(c256, rand.Reader)

	priv, _ := new(big.Int).SetString("115792089210356248762697446949407573529996955224135760342422259061068512044369", 10)
	bb := priv.Bytes()
	b.ReportAllocs()
	b.StartTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			// c256.ScalarMult(c256.Gx, c256.Gy, bb)
			c256.ScalarBaseMult(bb)
		}
	})
}

func TestPointMul(t *testing.T) {
	priv, _ := new(big.Int).SetString("115792089210356248762697446949407573529996955224135760342422259061068512044369", 10)
	bb := priv.Bytes()
	cnt := 5000
	start := time.Now()
	for i := 0; i < cnt; i++ {
		// c256.ScalarMult(c256.Gx, c256.Gy, bb)
		c256.ScalarBaseMult(bb)
	}
	end := time.Now()
	elapsed := end.Sub(start)
	fmt.Printf("SM2 Scalar Mul Point: %d PerSec\n", int(float64(cnt)/elapsed.Seconds()))
}

func TestReduceCarry(t *testing.T) {
	// fmt.Printf("%08x\n", 1<<29-1-2<<21)
	var inout [c256Limbs]uint32
	var temp [c256Limbs]uint32
	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < c256Limbs; i++ {
		temp[i] = uint32(rnd.Int31()) & 0xFFFFFFF
		inout[i] = temp[i]
	}
	var carry uint32 = 5
	c256ReduceCarry(&inout, carry)
	// for _, n := range inout {
	// 	fmt.Printf("0x%08x, ", n)
	// }
	ret := c256ToBig(&inout)
	fmt.Println(ret.Text(16))

	s := c256ToBig(&temp)
	r := big.NewInt(int64(carry))
	r.Lsh(r, 257)
	s.Add(s, r)
	s.Mod(s, c256.P)
	// c256FromBig(&inout, s)
	fmt.Println(s.Text(16))
	// c256FromBig(&inout, s)
	// for _, n := range inout {
	// 	fmt.Printf("0x%08x, ", n)
	// }
	ret.Sub(ret, s)
	fmt.Println(ret)
}

func TestReduceDegree(t *testing.T) {

	for j := uint64(0); j < 100000000; j++ {
		if j%1000000 == 0 {
			fmt.Println(j/10000, "万次pass")
		}
		var in [c256Limbs]uint32 //= [c256Limbs]uint32{0x1604a25, 0x6d1db34, 0x140458b9, 0xd3371b7, 0x79446ec, 0xd2bca28, 0xb98f19b, 0xc227f7c, 0xcaed5c}
		var out [c256Limbs]uint32
		var temp [c256Limbs]uint32 //= [c256Limbs]uint32{0xdb99003, 0x964a8c3, 0x1f7dc5a9, 0xc9db569, 0x1893e838, 0xeecb116, 0xca9ff4f, 0x68bd063, 0x11e538bf}

		rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
		for i := 0; i < c256Limbs; i++ {
			if i%2 == 0 {
				temp[i] = uint32(rnd.Int()) & 0x1FFFFFFF
			} else {
				temp[i] = uint32(rnd.Int()) & 0xFFFFFFF
			}
			// fmt.Printf("0x%x,", temp[i])
		}

		for i := 0; i < c256Limbs; i++ {
			if i%2 == 0 {
				in[i] = uint32(rnd.Int31()) & 0x1FFFFFFF
			} else {
				in[i] = uint32(rnd.Int31()) & 0xFFFFFFF
			}
			// fmt.Printf("0x%x,", in[i])
		}

		ret := c256ToBig(&temp)
		// fmt.Println("a:=  ", ret.Text(16))
		// ret = c256ToBig(&in)
		// fmt.Println("b:=  ", ret.Text(16))

		c256Mul(&out, &in, &temp)
		ret = c256ToBig(&out)

		ret.Mod(ret, c256.P)
		// fmt.Println("a*b=", ret.Text(16))

		s := c256ToBig(&temp)
		s.Mul(s, c256ToBig(&in))
		s.Mul(s, c256RInverse)
		s.Mod(s, c256.P)

		// ret.Mod(ret, c256.P)
		if ret.Cmp(s) != 0 {
			fmt.Println("failed")
			fmt.Println(ret.Text(16))
			fmt.Println(s.Text(16))
			fmt.Println("in:", in)
			fmt.Println("temp:", temp)
			fmt.Println("diff:", ret.Sub(ret, s).Text(16))
			return
		}
		// ret.Sub(ret, s)
		// fmt.Println("?0=", ret.Text(16))
	}
	fmt.Println("test over")
}

func TestInverse(t *testing.T) {

	for i := 0; i < 100000; i++ {
		if i%10000 == 0 {
			fmt.Println(i, "pass")
		}
		var in [c256Limbs]uint32 //= [c256Limbs]uint32{0x1604a25, 0x6d1db34, 0x140458b9, 0xd3371b7, 0x79446ec, 0xd2bca28, 0xb98f19b, 0xc227f7c, 0xcaed5c}
		var out [c256Limbs]uint32

		rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
		for i := 0; i < c256Limbs; i++ {
			if i%2 == 0 {
				in[i] = uint32(rnd.Int()) & 0x1FFFFFFF
			} else {
				in[i] = uint32(rnd.Int()) & 0xFFFFFFF
			}
			// fmt.Printf("0x%x,", temp[i])
		}
		c256Invert(&out, &in) // in^(-1)*R
		outInt := c256ToBig(&out)
		outInt.Mod(outInt, c256.P)
		// fmt.Println(outInt.Text(16))

		inInt := c256ToBig(&in)         // in * R
		inInt.ModInverse(inInt, c256.P) // (in*R)^-1
		inInt.Lsh(inInt, 257+257)       // in^-1 * R
		inInt.Mod(inInt, c256.P)
		// fmt.Println(inInt.Text(16))

		if inInt.Cmp(outInt) != 0 {
			fmt.Println("Failed")
			fmt.Println(in)
			fmt.Println(new(big.Int).Sub(inInt, outInt).Text(16))
			return
		}
	}
}

func TestGenTable32(t *testing.T) {
	//   Index  |  Index (binary) | Value
	//       0  |           0000  | 0G (all zeros, omitted)
	//       1  |           0001  | G
	//       2  |           0010  | 2**64G
	//       3  |           0011  | 2**64G + G
	//       4  |           0100  | 2**128G
	//       5  |           0101  | 2**128G + G
	//       6  |           0110  | 2**128G + 2**64G
	//       7  |           0111  | 2**128G + 2**64G + G
	//       8  |           1000  | 2**192G
	//       9  |           1001  | 2**192G + G
	//      10  |           1010  | 2**192G + 2**64G
	//      11  |           1011  | 2**192G + 2**64G + G
	//      12  |           1100  | 2**192G + 2**128G
	//      13  |           1101  | 2**192G + 2**128G + G
	//      14  |           1110  | 2**192G + 2**128G + 2**64G
	//      15  |           1111  | 2**192G + 2**128G + 2**64G + G
	//
	// The second table follows the same style, but the terms are 2**32G,
	// 2**96G, 2**160G, 2**224G.
	for i := 1; i < 16; i++ {
		n := new(big.Int)
		one := new(big.Int).SetInt64(1)
		if i&0x08 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 192), n)
		}
		if i&0x04 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 128), n)
		}
		if i&0x02 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 64), n)
		}
		if i&0x01 > 0 {
			n.Add(one, n)
		}
		// fmt.Println(n.Text(16))
		x, y := c256.ScalarMult(c256.Gx, c256.Gy, n.Bytes())
		var xOut, yOut [c256Limbs]uint32
		c256FromBig(&xOut, x)
		c256FromBig(&yOut, y)
		for _, i := range xOut {
			fmt.Printf("0x%x, ", i)
		}
		fmt.Println()
		for _, i := range yOut {
			fmt.Printf("0x%x, ", i)
		}
		fmt.Println()
	}

	for i := 1; i < 16; i++ {
		n := new(big.Int)
		one := new(big.Int).SetInt64(1)
		if i&0x08 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 224), n)
		}
		if i&0x04 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 160), n)
		}
		if i&0x02 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 96), n)
		}
		if i&0x01 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 32), n)
		}
		// fmt.Println(n.Text(16))
		x, y := c256.ScalarMult(c256.Gx, c256.Gy, n.Bytes())
		var xOut, yOut [c256Limbs]uint32
		c256FromBig(&xOut, x)
		c256FromBig(&yOut, y)
		for _, i := range xOut {
			fmt.Printf("0x%x, ", i)
		}
		fmt.Println()
		for _, i := range yOut {
			fmt.Printf("0x%x, ", i)
		}
		fmt.Println()
	}
}

// c256FromBig sets out = R*in.
func c256FromBig64(out *[5]uint64, in *big.Int) {
	var bottom51Bits uint64 = 1<<51 - 1
	var bottom52Bits uint64 = 1<<52 - 1
	tmp := new(big.Int).Lsh(in, 257)
	tmp.Mod(tmp, c256.P)

	for i := 0; i < 5; i++ {
		if bits := tmp.Bits(); len(bits) > 0 {
			out[i] = uint64(bits[0]) & bottom51Bits
		} else {
			out[i] = 0
		}
		tmp.Rsh(tmp, 51)

		i++
		if i == 5 {
			break
		}

		if bits := tmp.Bits(); len(bits) > 0 {
			out[i] = uint64(bits[0]) & bottom52Bits
		} else {
			out[i] = 0
		}
		tmp.Rsh(tmp, 52)
	}
}

func TestGenTable64(t *testing.T) {
	//   Index  |  Index (binary) | Value
	//       0  |           0000  | 0G (all zeros, omitted)
	//       1  |           0001  | G
	//       2  |           0010  | 2**64G
	//       3  |           0011  | 2**64G + G
	//       4  |           0100  | 2**128G
	//       5  |           0101  | 2**128G + G
	//       6  |           0110  | 2**128G + 2**64G
	//       7  |           0111  | 2**128G + 2**64G + G
	//       8  |           1000  | 2**192G
	//       9  |           1001  | 2**192G + G
	//      10  |           1010  | 2**192G + 2**64G
	//      11  |           1011  | 2**192G + 2**64G + G
	//      12  |           1100  | 2**192G + 2**128G
	//      13  |           1101  | 2**192G + 2**128G + G
	//      14  |           1110  | 2**192G + 2**128G + 2**64G
	//      15  |           1111  | 2**192G + 2**128G + 2**64G + G
	//
	// The second table follows the same style, but the terms are 2**32G,
	// 2**96G, 2**160G, 2**224G.
	for i := 1; i < 16; i++ {
		n := new(big.Int)
		one := new(big.Int).SetInt64(1)
		if i&0x08 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 192), n)
		}
		if i&0x04 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 128), n)
		}
		if i&0x02 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 64), n)
		}
		if i&0x01 > 0 {
			n.Add(one, n)
		}
		// fmt.Println(n.Text(16))
		x, y := c256.ScalarMult(c256.Gx, c256.Gy, n.Bytes())
		var xOut, yOut [5]uint64
		c256FromBig64(&xOut, x)
		c256FromBig64(&yOut, y)
		for _, i := range xOut {
			fmt.Printf("0x%xLLU, ", i)
		}
		fmt.Println()
		for _, i := range yOut {
			fmt.Printf("0x%xLLU, ", i)
		}
		fmt.Println()
	}

	for i := 1; i < 16; i++ {
		n := new(big.Int)
		one := new(big.Int).SetInt64(1)
		if i&0x08 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 224), n)
		}
		if i&0x04 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 160), n)
		}
		if i&0x02 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 96), n)
		}
		if i&0x01 > 0 {
			n.Add(new(big.Int).SetInt64(1).Lsh(one, 32), n)
		}
		// fmt.Println(n.Text(16))
		x, y := c256.ScalarMult(c256.Gx, c256.Gy, n.Bytes())
		var xOut, yOut [5]uint64
		c256FromBig64(&xOut, x)
		c256FromBig64(&yOut, y)
		for _, i := range xOut {
			fmt.Printf("0x%xLLU, ", i)
		}
		fmt.Println()
		for _, i := range yOut {
			fmt.Printf("0x%xLLU, ", i)
		}
		fmt.Println()
	}
}
func TestPointMul2(t *testing.T) {
	n, _ := crand.Int(crand.Reader, c256.N)
	n.SetInt64(4)
	//n.Set(c256.N)
	//n.Sub(n, gmath.BigInt1)
	//x, y := c256.ScalarBaseMult(n.Bytes())
	//fmt.Println(x.Text(16), y.Text(16))
	// n.Set(c256.N)
	//xx, yy := c256.ScalarMult(c256.Gx, c256.Gy, n.Bytes())
	xx, yy := c256.ScalarBaseMult(n.Bytes())
	fmt.Println(xx.Text(16), yy.Text(16))
	//fmt.Println(xx.Text(16), yy.Text(16))

	// p := c256ToBig(&c256Zero31)
	// fmt.Println(p.Text(16))
}

// FIXME c256ScalarBaseMult error when scalar = 0
func TestZeroScaleBaseMult(t *testing.T) {
	n := new(big.Int)
	var scalarReversed [32]byte
	for i := 0; i < 32; i++ {
		scalarReversed[i] = 0xcc
	}

	c256GetScalar(&scalarReversed, n.Bytes())
	var x1, y1, z1 [c256Limbs]uint32
	var tmp [17]uint64
	c256PointDouble(&x1, &y1, &z1, &x1, &y1, &z1)
	c256ReduceDegree(&z1, tmp)
	c256ScalarBaseMult(&x1, &y1, &z1, &scalarReversed)

	for _, z := range z1 {
		if z != 0 {
			t.Fail()
		}
	}
}

func TestReduce(t *testing.T) {
	var tmp = [17]uint64{1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1}
	var out [9]uint32
	c256ReduceDegree(&out, tmp)
	for i := 0; i < 9; i++ {
		fmt.Println(out[i])
	}
}

func TestIssue52075(t *testing.T) {
	Gx, Gy := c256.Params().Gx, c256.Params().Gy
	scalar := make([]byte, 33)
	scalar[32] = 1
	x, y := c256.ScalarBaseMult(scalar)
	if x.Cmp(Gx) != 0 || y.Cmp(Gy) != 0 {
		t.Errorf("unexpected output (%v,%v)", x, y)
	}
	x, y = c256.ScalarMult(Gx, Gy, scalar)
	if x.Cmp(Gx) != 0 || y.Cmp(Gy) != 0 {
		t.Errorf("unexpected output (%v,%v)", x, y)
	}
}
