package bn256

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

	"xdx.jelly/xgcl/gmath"
)

var twoTo256 = new(big.Int).Lsh(big.NewInt(1), 256)

func gfPFromBigInt(A *big.Int) *gfP {
	a := &gfP{}
	buf := make([]byte, 32)
	A.FillBytes(buf)
	a.Unmarshal(buf)
	montEncode(a, a)
	return a
}

func randomInt(r io.Reader, max *big.Int) *big.Int {
	a, _ := rand.Int(r, max)
	return a
}

func (e *gfP) random(r io.Reader) *gfP {
	e.Set(gfPFromBigInt(randomInt(r, P)))
	return e
}

func (e *gfP2) random(r io.Reader) *gfP2 {
	e.x.random(r)
	e.y.random(r)
	return e
}

func (f *gfP6) random(r io.Reader) *gfP6 {
	f.x.random(r)
	f.z.random(r)
	f.y.random(r)
	return f
}

func (f *gfP12) random(r io.Reader) *gfP12 {
	f.x.random(r)
	f.y.random(r)
	return f
}

func testOp(t *testing.T, arg1, arg2 *big.Int, op1 func(*big.Int, *big.Int, *big.Int), op2 func(*gfP, *gfP, *gfP), equal func(*gfP, *gfP) bool) {
	var a, b *gfP

	a = gfPFromBigInt(arg1)
	if arg2 != nil {
		b = gfPFromBigInt(arg2)
	}

	C := new(big.Int)
	op1(C, arg1, arg2)
	c1 := gfPFromBigInt(C)

	c2 := &gfP{}
	op2(c2, a, b)
	if !equal(c1, c2) {
		fmt.Println("failed:")
		fmt.Println("\ta =", a)
		fmt.Println("\tb =", b)
		fmt.Println("\tc1 =", c1)
		fmt.Println("\tc2 =", c2)
		t.Fatal()
	}
}

func TestGFpSqrt(t *testing.T) {
	for i := 0; i < 100000; i++ {
		a := randomInt(rand.Reader, twoTo256)
		a.Mul(a, a).Mod(a, N)
		testOp(t,
			big.NewInt(1),
			nil,
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.ModSqrt(a, P)
			},
			func(c *gfP, a *gfP, b *gfP) {
				c.Sqrt(a)
			},
			func(a *gfP, b *gfP) bool {
				c := &gfP{}
				gfpAdd(c, a, b)
				return *a == *b || *c == gfP{}
			},
		)
	}
}

func TestGFpNeg(t *testing.T) {
	a := &gfP{0xe56f9b27e351457c, 0x21f2934b1a7aeedb, 0xd603ab4ff58ec745, 0xb640000002a3a6f1}
	gfpNeg(a, a)
	if *a != (gfP{1, 0, 0, 0}) {
		t.Fatal()
	}

	for i := 0; i < 1000000; i++ {
		a := randomInt(rand.Reader, P)
		a.Mul(a, a).Mod(a, N)
		testOp(t,
			randomInt(rand.Reader, P),
			nil,
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.Neg(a).Mod(c, P)
			},
			func(c *gfP, a *gfP, b *gfP) {
				gfpNeg(c, a)
			},
			func(a *gfP, b *gfP) bool {
				return *a == *b
			},
		)
	}

}

func TestGFpAdd(t *testing.T) {
	for i := 0; i < 1000000; i++ {
		testOp(t,
			randomInt(rand.Reader, twoTo256),
			randomInt(rand.Reader, twoTo256),
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.Add(a, b).Mod(c, P)
			},
			func(c *gfP, a *gfP, b *gfP) {
				gfpAdd(c, a, b)
			},
			func(a *gfP, b *gfP) bool {
				return *a == *b
			},
		)
	}
}

func TestGFpSub(t *testing.T) {
	for i := 0; i < 1000000; i++ {
		a := randomInt(rand.Reader, twoTo256)
		b := randomInt(rand.Reader, twoTo256)
		testOp(t,
			a,
			b.Sub(a, big.NewInt(1)),
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.Sub(a, b).Mod(c, P)
			},
			func(c *gfP, a *gfP, b *gfP) {
				gfpSub(c, a, b)
			},
			func(a *gfP, b *gfP) bool {
				return *a == *b
			},
		)
	}
}

func TestGFpHalf(t *testing.T) {
	for i := 0; i < 100000; i++ {
		testOp(t,
			randomInt(rand.Reader, P),
			nil,
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.Set(a)
				if c.Bit(0) == 1 {
					c.Add(a, P)
				}
				c.Rsh(c, 1)
			},
			func(c *gfP, a *gfP, b *gfP) {
				c.half(a)
			},
			func(a *gfP, b *gfP) bool {
				return *a == *b
			},
		)
	}

}

// FIXME: amd64平台不支持 a and b > p
func TestGFpMul(t *testing.T) {
	for i := 0; i < 100000; i++ {
		testOp(t,
			randomInt(rand.Reader, P),
			randomInt(rand.Reader, P),
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.Mul(a, b).Mod(c, P)
			},
			func(c *gfP, a *gfP, b *gfP) {
				gfpMul(c, a, b)
			},
			func(a *gfP, b *gfP) bool {
				return *a == *b
			},
		)

		testOp(t,
			randomInt(rand.Reader, twoTo256),
			randomInt(rand.Reader, twoTo256),
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.Mul(a, b).Mod(c, P)
			},
			func(c *gfP, a *gfP, b *gfP) {
				gfpMul(c, a, b)
			},
			func(a *gfP, b *gfP) bool {
				return *a == *b
			},
		)

		testOp(t,
			new(big.Int).Sub(twoTo256, big.NewInt(int64(i+1))),
			new(big.Int).Sub(twoTo256, big.NewInt(int64(i+2))),
			func(c *big.Int, a *big.Int, b *big.Int) {
				c.Mul(a, b).Mod(c, P)
			},
			func(c *gfP, a *gfP, b *gfP) {
				gfpMul(c, a, b)
			},
			func(a *gfP, b *gfP) bool {
				return *a == *b
			},
		)
	}
}

func TestSpeedGFp(t *testing.T) {
	a := newGFp(123)
	b := newGFp(123)
	c := newGFp(1)

	buf := make([]byte, 32)

	a.Marshal(buf)
	A := new(big.Int).SetBytes(buf)
	b.Marshal(buf)
	B := new(big.Int).SetBytes(buf)
	c.Marshal(buf)
	C := new(big.Int).SetBytes(buf)

	// fmt.Println(a, b, c)
	begin := time.Now()
	total := 10000000
	for i := 0; i < total; i++ {
		gfpMul(c, a, b)
	}
	elaspe := time.Since(begin)
	fmt.Println("time: ", elaspe.Milliseconds(), "ms")
	fmt.Println(int(float64(total) / float64(elaspe.Milliseconds()) * 1000))

	begin = time.Now()
	for i := 0; i < total; i++ {
		C.Mul(A, B)
	}
	elaspe = time.Since(begin)
	fmt.Println("time: ", elaspe.Milliseconds(), "ms")
	fmt.Println(int(float64(total) / float64(elaspe.Milliseconds()) * 1000))

}

func (e *gfP2) Exp(a *gfP2, power *big.Int) *gfP2 {
	sum := (&gfP2{}).SetOne()
	t := &gfP2{}

	for i := power.BitLen() - 1; i >= 0; i-- {
		t.Square(sum)
		if power.Bit(i) != 0 {
			sum.Mul(t, a)
		} else {
			sum.Set(t)
		}
	}

	e.Set(sum)
	return e
}

// Exp set c = a^power and return c
func (f *gfP6) Exp(a *gfP6, power *big.Int) *gfP6 {
	sum := (&gfP6{}).SetOne()
	t := &gfP6{}

	for i := power.BitLen() - 1; i >= 0; i-- {
		t.Square(sum)
		if power.Bit(i) != 0 {
			sum.Mul(t, a)
		} else {
			sum.Set(t)
		}
	}

	f.Set(sum)
	return f
}
func TestGFp(t *testing.T) {
	one := newGFp(1)
	//one := &gfP{1}
	//two := &gfP{2}
	gfpMul(one, r2, one)
	gfpMul(one, &gfP{1}, one)
	montDecode(one, one)
	if *one != (gfP{1}) {
		t.Fail()
		fmt.Println(one)
		return
	}

	// a := (&gfP{}).random(rand.Reader)
	a := &gfP{0x0a1c7970e5df544d, 0xe74504e9a96b56cc, 0xcda02d92d4d62924, 0x7d2bc576fdf597d1}
	b := &gfP{}
	c := &gfP{}
	fmt.Println("Test Exponent")
	b.exp(a, pMinus1)
	//b.exp(a,[4]uint64{2,0,0,0})
	//fmt.Println("a:", b)
	//gfpMul(c,a,a)
	//fmt.Println("c:",c)
	//gfpSub(b,c,b)
	// montDecode(b, b)
	if *b != *newGFp(1) {
		t.Fail()
		fmt.Println(b)
		return
	}
	fmt.Println("pass")
	var rN1 = &gfP{0x0a1c7970e5df544d, 0xe74504e9a96b56cc, 0xcda02d92d4d62924, 0x7d2bc576fdf597d1}
	fmt.Println("Test Rn")
	gfpMul(b, rN1, r3)
	montDecode(b, b)
	if *b != (gfP{1}) {
		t.Fail()
		fmt.Println(b)
		return
	}
	fmt.Println("pass")

	fmt.Println("Test Invert")
	a = newGFp(1)
	c.Invert(a)
	fmt.Println(a, c)
	gfpMul(c, c, a)
	montDecode(c, c)
	if *c != (gfP{1}) {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")
}

func TestGfP2(t *testing.T) {

	p2Minus1 := new(big.Int)
	p2Minus1.Mul(P, P)
	p2Minus1.Sub(p2Minus1, gmath.BigInt1)

	a := (&gfP2{}).random(rand.Reader)
	b := &gfP2{}
	c := &gfP2{}

	fmt.Println("Test gfP2 mul and square")
	fmt.Println("a:  ", a)
	b.Mul(a, a)
	fmt.Println("a^2:", b)

	c.Square(a)
	fmt.Println("a*a:", c)
	if !c.Sub(c, b).IsZero() {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")

	fmt.Println("Test exponential")
	c.Exp(a, p2Minus1)
	if !c.IsOne() {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")

	fmt.Println("Test Invert")
	c.Invert(a)
	c.Mul(c, a)
	if !c.IsOne() {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")
}

func TestGFp2Sqrt(t *testing.T) {
	for i := 0; i < 100000; i++ {
		a := (&gfP2{}).random(rand.Reader)
		a.Mul(a, a)
		b := *a
		if !a.Sqrt(a) {
			t.Fatal()
		}
		a.Square(a)
		if *a != b {
			t.Fatal()
		}
		c := &gfP2{}
		if !c.Sqrt(a) {
			t.Fatal()
		}
		a.Square(c)
		if *a != b {
			t.Fatal()
		}
	}
}

func TestGfP6(t *testing.T) {
	p6Minus1 := new(big.Int)
	p6Minus1.Exp(P, gmath.BigInt6, nil)
	p6Minus1.Sub(p6Minus1, gmath.BigInt1)

	a := (&gfP6{}).random(rand.Reader)
	b := &gfP6{}
	c := &gfP6{}
	fmt.Println(a)

	fmt.Println("Test gfP6 mul and square")
	fmt.Println("a:  ", a)
	b.Mul(a, a)
	fmt.Println("a^2:", b)

	c.Square(a)
	fmt.Println("a*a:", c)
	if !c.Sub(c, b).IsZero() {
		t.Fail()
		fmt.Println(c)
		return
	}

	fmt.Println("Test exponential")

	c.Exp(a, p6Minus1)
	//c.Exp(a,gmath.BigInt1)
	if !c.IsOne() {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")

	fmt.Println("Test Invert")
	c.Invert(a)
	c.Mul(c, a)
	if !c.IsOne() {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")
}

func TestGfP12(t *testing.T) {
	p12Minus1 := new(big.Int)
	p12Minus1.Exp(P, gmath.BigInt12, nil)
	p12Minus1.Sub(p12Minus1, gmath.BigInt1)

	a := (&gfP12{}).random(rand.Reader)
	b := &gfP12{}
	c := &gfP12{}
	fmt.Println(a)

	fmt.Println("Test gfP12 mul and square")
	fmt.Println("a:  ", a)
	b.Mul(a, a)
	fmt.Println("a^2:", b)

	c.Square(a)
	fmt.Println("a*a:", c)
	if !c.Sub(c, b).IsZero() {
		t.Fail()
		fmt.Println(c)
		return
	}

	fmt.Println("Test exponential")

	c.Exp(a, p12Minus1)
	//c.Exp(a,gmath.BigInt1)
	if !c.IsOne() {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")

	fmt.Println("Test Invert")
	c.Invert(a)
	c.Mul(c, a)
	if !c.IsOne() {
		t.Fail()
		fmt.Println(c)
		return
	}
	fmt.Println("pass")

}

func TestFrobenius6(t *testing.T) {
	p2 := new(big.Int).Mul(P, P)
	a := (&gfP6{}).random(rand.Reader)
	b := &gfP6{}
	c := &gfP6{}
	b.Frobenius(a)
	c.Exp(a, P)
	if *b != *c {
		fmt.Println("a:", a)
		fmt.Println("b:", b)
		fmt.Println("c:", c)
		t.Fatal()
	}

	b.FrobeniusP2(a)
	c.Exp(a, p2)
	if *b != *c {
		t.Fatal()
	}

}

func TestFrobenius12(t *testing.T) {
	p2 := new(big.Int).Mul(P, P)
	a := (&gfP12{}).random(rand.Reader)
	b := &gfP12{}
	c := &gfP12{}

	fmt.Println("Test Frobenius on GFP12")
	b.Frobenius(a)
	c.Exp(a, P)
	if *b != *c {
		fmt.Println("a:", a)
		fmt.Println("b:", b)
		fmt.Println("c:", c)
		t.Fail()
		return
	}
	fmt.Println("pass")

	fmt.Println("Test Frobenius2 on GFP12")
	b.FrobeniusP2(a)
	c.Exp(a, p2)
	if *b != *c {
		t.Fail()
		return
	}
	fmt.Println("pass")
}

func present(p *gfP) string {

	return fmt.Sprintf("gfP{0x%x,0x%x,0x%x,0x%x}", p[0], p[1], p[2], p[3])
}

// ExampleGFP12Gen generate gfP12Gen
func TestGFP12Gen(t *testing.T) {
	e := Pair(&G1{*curveGen}, &G2{*twistGen}).p
	fmt.Printf(`
	var gfP12Gen *gfP12 = &gfP12{
		x: gfP6{
			x: gfP2{
				x: %s,
				y: %s,
			},
			y: gfP2{
				x: %s,
				y: %s,
			},
			z: gfP2{
				x: %s,
				y: %s,
			},
		},
		y: gfP6{
			x: gfP2{
				x: %s,
				y: %s,
			},
			y: gfP2{
				x: %s,
				y: %s,
			},
			z: gfP2{
				x: %s,
				y: %s,
			},
		},
	}
	`, present(&e.x.x.x),
		present(&e.x.x.y),
		present(&e.x.y.x),
		present(&e.x.y.y),
		present(&e.x.z.x),
		present(&e.x.z.y),
		present(&e.y.x.x),
		present(&e.y.x.y),
		present(&e.y.y.x),
		present(&e.y.y.y),
		present(&e.y.z.x),
		present(&e.y.z.y))
}
