package eip

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"io"
	"math/big"
	"testing"
)

type fieldElement interface{}

type field interface {
	new() fieldElement
	debugElement(fe fieldElement)
	limbSize() int
	byteSize() int
	fromBytes(in []byte) (fieldElement, error)
	toBytes(a fieldElement) []byte
	isZero(a fieldElement) bool
	isOne(a fieldElement) bool
	zero() fieldElement
	p() *big.Int
	one() fieldElement
	equal(a, b fieldElement) bool
	rand(r io.Reader) fieldElement
	add(c, a, b fieldElement)
	sub(c, a, b fieldElement)
	double(c, a fieldElement)
	neg(c, a fieldElement)
	mul(c, a, b fieldElement)
	square(c, a fieldElement)
	exp(c, a fieldElement, e *big.Int)
	inverse(c, a fieldElement) bool
	sqrt(c, a fieldElement) bool
}

type fqTest struct {
	*fq
}

func (t fqTest) new() fieldElement {
	return t.fq.new()
}

func (t fqTest) debugElement(in fieldElement) {
	t.fq.debugElement(in.(fe))
}

func (f *fq) debugElement(in fe) {
	fmt.Println(f.toString(in))
}

func (t fqTest) byteSize() int {
	return t.fq.byteSize()
}

func (t fqTest) limbSize() int {
	return t.fq.limbSize
}

func (t fqTest) fromBytes(in []byte) (fieldElement, error) {
	return t.fq.fromBytes(in)
}

func (t fqTest) toBytes(in fieldElement) []byte {
	return t.fq.toBytes(in.(fe))
}

func (t fqTest) isZero(in fieldElement) bool {
	return t.fq.isZero(in.(fe))
}

func (t fqTest) isOne(in fieldElement) bool {
	return t.fq.isOne(in.(fe))
}

func (t fqTest) zero() fieldElement {
	return t.fq.zero
}

func (t fqTest) one() fieldElement {
	return t.fq.one
}

func (t fqTest) p() *big.Int {
	return t.fq.modulus()
}

func (t fqTest) equal(a, b fieldElement) bool {
	return t.fq.equal(a.(fe), b.(fe))
}

func (t fqTest) rand(r io.Reader) fieldElement {
	return t.fq.rand(r)
}

func (t fqTest) add(c, a, b fieldElement) {
	t.fq.add(c.(fe), a.(fe), b.(fe))
}

func (t fqTest) sub(c, a, b fieldElement) {
	t.fq.sub(c.(fe), a.(fe), b.(fe))
}

func (t fqTest) mul(c, a, b fieldElement) {
	t.fq.mul(c.(fe), a.(fe), b.(fe))
}

func (t fqTest) double(c, a fieldElement) {
	t.fq.double(c.(fe), a.(fe))
}

func (t fqTest) neg(c, a fieldElement) {
	t.fq.neg(c.(fe), a.(fe))
}

func (t fqTest) square(c, a fieldElement) {
	t.fq.square(c.(fe), a.(fe))
}

func (t fqTest) exp(c, a fieldElement, e *big.Int) {
	t.fq.exp(c.(fe), a.(fe), e)
}

func (t fqTest) inverse(c, a fieldElement) bool {
	return t.fq.inverse(c.(fe), a.(fe))
}

func (t fqTest) sqrt(c, a fieldElement) bool {
	return t.fq.sqrt(c.(fe), a.(fe))
}

type fq2Test struct {
	*fq2
}

func (t fq2Test) new() fieldElement {
	return t.fq2.new()
}

func (t fq2Test) debugElement(in fieldElement) {
	t.fq2.debugElement(in.(*fe2))
}

func (f *fq2) debugElement(in *fe2) {
	fmt.Println(f.toString(in))
}

func (t fq2Test) byteSize() int {
	return t.fq2.byteSize()
}

func (t fq2Test) limbSize() int {
	return t.fq2.f.limbSize
}

func (t fq2Test) fromBytes(in []byte) (fieldElement, error) {
	return t.fq2.fromBytes(in)
}

func (t fq2Test) toBytes(in fieldElement) []byte {
	return t.fq2.toBytes(in.(*fe2))
}

func (t fq2Test) isZero(in fieldElement) bool {
	return t.fq2.isZero(in.(*fe2))
}

func (t fq2Test) isOne(in fieldElement) bool {
	return t.fq2.isOne(in.(*fe2))
}

func (t fq2Test) zero() fieldElement {
	return t.fq2.zero()
}

func (t fq2Test) one() fieldElement {
	return t.fq2.one()
}

func (t fq2Test) p() *big.Int {
	return t.f.modulus()
}

func (t fq2Test) equal(a, b fieldElement) bool {
	return t.fq2.equal(a.(*fe2), b.(*fe2))
}

func (t fq2Test) rand(r io.Reader) fieldElement {
	return t.fq2.rand(r)
}

func (t fq2Test) add(c, a, b fieldElement) {
	t.fq2.add(c.(*fe2), a.(*fe2), b.(*fe2))
}

func (t fq2Test) sub(c, a, b fieldElement) {
	t.fq2.sub(c.(*fe2), a.(*fe2), b.(*fe2))
}

func (t fq2Test) mul(c, a, b fieldElement) {
	t.fq2.mul(c.(*fe2), a.(*fe2), b.(*fe2))
}

func (t fq2Test) double(c, a fieldElement) {
	t.fq2.double(c.(*fe2), a.(*fe2))
}

func (t fq2Test) neg(c, a fieldElement) {
	t.fq2.neg(c.(*fe2), a.(*fe2))
}

func (t fq2Test) square(c, a fieldElement) {
	t.fq2.square(c.(*fe2), a.(*fe2))
}

func (t fq2Test) exp(c, a fieldElement, e *big.Int) {
	t.fq2.exp(c.(*fe2), a.(*fe2), e)
}

func (t fq2Test) inverse(c, a fieldElement) bool {
	return t.fq2.inverse(c.(*fe2), a.(*fe2))
}

func (t fq2Test) mulByFq(c, a fieldElement, b fieldElement) {
	t.fq2.mulByFq(c.(*fe2), a.(*fe2), b.(fe))
}

func (t fq2Test) sqrt(c, a fieldElement) bool {
	return t.fq2.sqrt(c.(*fe2), a.(*fe2))
}

type fq3Test struct {
	*fq3
}

func (t fq3Test) new() fieldElement {
	return t.fq3.new()
}

func (t fq3Test) debugElement(in fieldElement) {
	t.fq3.debugElement(in.(*fe3))
}

func (f *fq3) debugElement(in *fe3) {
	fmt.Println(f.toString(in))
}

func (t fq3Test) byteSize() int {
	return t.fq3.byteSize()
}

func (t fq3Test) limbSize() int {
	return t.fq3.f.limbSize
}

func (t fq3Test) fromBytes(in []byte) (fieldElement, error) {
	return t.fq3.fromBytes(in)
}

func (t fq3Test) toBytes(in fieldElement) []byte {
	return t.fq3.toBytes(in.(*fe3))
}

func (t fq3Test) isZero(in fieldElement) bool {
	return t.fq3.isZero(in.(*fe3))
}

func (t fq3Test) isOne(in fieldElement) bool {
	return t.fq3.isOne(in.(*fe3))
}

func (t fq3Test) zero() fieldElement {
	return t.fq3.zero()
}

func (t fq3Test) one() fieldElement {
	return t.fq3.one()
}

func (t fq3Test) p() *big.Int {
	return t.f.modulus()
}

func (t fq3Test) equal(a, b fieldElement) bool {
	return t.fq3.equal(a.(*fe3), b.(*fe3))
}

func (t fq3Test) rand(r io.Reader) fieldElement {
	return t.fq3.rand(r)
}

func (t fq3Test) add(c, a, b fieldElement) {
	t.fq3.add(c.(*fe3), a.(*fe3), b.(*fe3))
}

func (t fq3Test) sub(c, a, b fieldElement) {
	t.fq3.sub(c.(*fe3), a.(*fe3), b.(*fe3))
}

func (t fq3Test) mul(c, a, b fieldElement) {
	t.fq3.mul(c.(*fe3), a.(*fe3), b.(*fe3))
}

func (t fq3Test) double(c, a fieldElement) {
	t.fq3.double(c.(*fe3), a.(*fe3))
}

func (t fq3Test) neg(c, a fieldElement) {
	t.fq3.neg(c.(*fe3), a.(*fe3))
}

func (t fq3Test) square(c, a fieldElement) {
	t.fq3.square(c.(*fe3), a.(*fe3))
}

func (t fq3Test) exp(c, a fieldElement, e *big.Int) {
	t.fq3.exp(c.(*fe3), a.(*fe3), e)
}

func (t fq3Test) inverse(c, a fieldElement) bool {
	return t.fq3.inverse(c.(*fe3), a.(*fe3))
}

func (t fq3Test) mulByFq(c, a fieldElement, b fieldElement) {
	t.fq3.mulByFq(c.(*fe3), a.(*fe3), b.(fe))
}

func (t fq3Test) sqrt(c, a fieldElement) bool {
	// return t.fq.sqrt(c.(*fe3), a.(*fe3))
	return true
}

type fq4Test struct {
	*fq4
}

func (t fq4Test) new() fieldElement {
	return t.fq4.new()
}

func (t fq4Test) debugElement(in fieldElement) {
	t.fq4.debugElement(in.(*fe4))
}

func (f *fq4) debugElement(in *fe4) {
	fmt.Println(f.toString(in))
}

func (t fq4Test) byteSize() int {
	return t.fq4.byteSize()
}

func (t fq4Test) limbSize() int {
	return t.fq4.f.f.limbSize
}

func (t fq4Test) fromBytes(in []byte) (fieldElement, error) {
	return t.fq4.fromBytes(in)
}

func (t fq4Test) toBytes(in fieldElement) []byte {
	return t.fq4.toBytes(in.(*fe4))
}

func (t fq4Test) isZero(in fieldElement) bool {
	return t.fq4.isZero(in.(*fe4))
}

func (t fq4Test) isOne(in fieldElement) bool {
	return t.fq4.isOne(in.(*fe4))
}

func (t fq4Test) zero() fieldElement {
	return t.fq4.zero()
}

func (t fq4Test) one() fieldElement {
	return t.fq4.one()
}

func (t fq4Test) p() *big.Int {
	return t.f.modulus()
}

func (t fq4Test) equal(a, b fieldElement) bool {
	return t.fq4.equal(a.(*fe4), b.(*fe4))
}

func (t fq4Test) rand(r io.Reader) fieldElement {
	return t.fq4.rand(r)
}

func (t fq4Test) add(c, a, b fieldElement) {
	t.fq4.add(c.(*fe4), a.(*fe4), b.(*fe4))
}

func (t fq4Test) sub(c, a, b fieldElement) {
	t.fq4.sub(c.(*fe4), a.(*fe4), b.(*fe4))
}

func (t fq4Test) mul(c, a, b fieldElement) {
	t.fq4.mul(c.(*fe4), a.(*fe4), b.(*fe4))
}

func (t fq4Test) double(c, a fieldElement) {
	t.fq4.double(c.(*fe4), a.(*fe4))
}

func (t fq4Test) neg(c, a fieldElement) {
	t.fq4.neg(c.(*fe4), a.(*fe4))
}

func (t fq4Test) square(c, a fieldElement) {
	t.fq4.square(c.(*fe4), a.(*fe4))
}

func (t fq4Test) exp(c, a fieldElement, e *big.Int) {
	t.fq4.exp(c.(*fe4), a.(*fe4), e)
}

func (t fq4Test) inverse(c, a fieldElement) bool {
	return t.fq4.inverse(c.(*fe4), a.(*fe4))
}

func (t fq4Test) sqrt(c, a fieldElement) bool {
	// return t.fq.sqrt(c.(*fe4), a.(*fe4))
	return true
}

type fq6CTest struct {
	*fq6C
}

func (t fq6CTest) new() fieldElement {
	return t.fq6C.new()
}

func (t fq6CTest) debugElement(in fieldElement) {
	t.fq6C.debugElement(in.(*fe6C))
}

func (f *fq6C) debugElement(in *fe6C) {
	fmt.Println(f.toString(in))
}

func (t fq6CTest) byteSize() int {
	return t.fq6C.byteSize()
}

func (t fq6CTest) limbSize() int {
	return t.fq6C.f.f.limbSize
}

func (t fq6CTest) fromBytes(in []byte) (fieldElement, error) {
	return t.fq6C.fromBytes(in)
}

func (t fq6CTest) toBytes(in fieldElement) []byte {
	return t.fq6C.toBytes(in.(*fe6C))
}

func (t fq6CTest) isZero(in fieldElement) bool {
	return t.fq6C.isZero(in.(*fe6C))
}

func (t fq6CTest) isOne(in fieldElement) bool {
	return t.fq6C.isOne(in.(*fe6C))
}

func (t fq6CTest) zero() fieldElement {
	return t.fq6C.zero()
}

func (t fq6CTest) one() fieldElement {
	return t.fq6C.one()
}

func (t fq6CTest) p() *big.Int {
	return t.f.modulus()
}

func (t fq6CTest) equal(a, b fieldElement) bool {
	return t.fq6C.equal(a.(*fe6C), b.(*fe6C))
}

func (t fq6CTest) rand(r io.Reader) fieldElement {
	return t.fq6C.rand(r)
}

func (t fq6CTest) add(c, a, b fieldElement) {
	t.fq6C.add(c.(*fe6C), a.(*fe6C), b.(*fe6C))
}

func (t fq6CTest) sub(c, a, b fieldElement) {
	t.fq6C.sub(c.(*fe6C), a.(*fe6C), b.(*fe6C))
}

func (t fq6CTest) mul(c, a, b fieldElement) {
	t.fq6C.mul(c.(*fe6C), a.(*fe6C), b.(*fe6C))
}

func (t fq6CTest) double(c, a fieldElement) {
	t.fq6C.double(c.(*fe6C), a.(*fe6C))
}

func (t fq6CTest) neg(c, a fieldElement) {
	t.fq6C.neg(c.(*fe6C), a.(*fe6C))
}

func (t fq6CTest) square(c, a fieldElement) {
	t.fq6C.square(c.(*fe6C), a.(*fe6C))
}

func (t fq6CTest) exp(c, a fieldElement, e *big.Int) {
	t.fq6C.exp(c.(*fe6C), a.(*fe6C), e)
}

func (t fq6CTest) inverse(c, a fieldElement) bool {
	return t.fq6C.inverse(c.(*fe6C), a.(*fe6C))
}

func (t fq6CTest) sqrt(c, a fieldElement) bool {
	// return t.fq.sqrt(c.(*fe6C), a.(*fe6C))
	return true
}

type fq6QTest struct {
	*fq6Q
}

func (t fq6QTest) new() fieldElement {
	return t.fq6Q.new()
}

func (t fq6QTest) debugElement(in fieldElement) {
	t.fq6Q.debugElement(in.(*fe6Q))
}

func (f *fq6Q) debugElement(in *fe6Q) {
	fmt.Println(f.toString(in))
}

func (t fq6QTest) byteSize() int {
	return t.fq6Q.byteSize()
}

func (t fq6QTest) limbSize() int {
	return t.fq6Q.f.f.limbSize
}

func (t fq6QTest) fromBytes(in []byte) (fieldElement, error) {
	return t.fq6Q.fromBytes(in)
}

func (t fq6QTest) toBytes(in fieldElement) []byte {
	return t.fq6Q.toBytes(in.(*fe6Q))
}

func (t fq6QTest) isZero(in fieldElement) bool {
	return t.fq6Q.isZero(in.(*fe6Q))
}

func (t fq6QTest) isOne(in fieldElement) bool {
	return t.fq6Q.isOne(in.(*fe6Q))
}

func (t fq6QTest) zero() fieldElement {
	return t.fq6Q.zero()
}

func (t fq6QTest) one() fieldElement {
	return t.fq6Q.one()
}

func (t fq6QTest) p() *big.Int {
	return t.f.modulus()
}

func (t fq6QTest) equal(a, b fieldElement) bool {
	return t.fq6Q.equal(a.(*fe6Q), b.(*fe6Q))
}

func (t fq6QTest) rand(r io.Reader) fieldElement {
	return t.fq6Q.rand(r)
}

func (t fq6QTest) add(c, a, b fieldElement) {
	t.fq6Q.add(c.(*fe6Q), a.(*fe6Q), b.(*fe6Q))
}

func (t fq6QTest) sub(c, a, b fieldElement) {
	t.fq6Q.sub(c.(*fe6Q), a.(*fe6Q), b.(*fe6Q))
}

func (t fq6QTest) mul(c, a, b fieldElement) {
	t.fq6Q.mul(c.(*fe6Q), a.(*fe6Q), b.(*fe6Q))
}

func (t fq6QTest) double(c, a fieldElement) {
	t.fq6Q.double(c.(*fe6Q), a.(*fe6Q))
}

func (t fq6QTest) neg(c, a fieldElement) {
	t.fq6Q.neg(c.(*fe6Q), a.(*fe6Q))
}

func (t fq6QTest) square(c, a fieldElement) {
	t.fq6Q.square(c.(*fe6Q), a.(*fe6Q))
}

func (t fq6QTest) exp(c, a fieldElement, e *big.Int) {
	t.fq6Q.exp(c.(*fe6Q), a.(*fe6Q), e)
}

func (t fq6QTest) inverse(c, a fieldElement) bool {
	return t.fq6Q.inverse(c.(*fe6Q), a.(*fe6Q))
}

func (t fq6QTest) sqrt(c, a fieldElement) bool {
	// return t.fq.sqrt(c.(*fe6Q), a.(*fe6Q))
	return true
}

type fq12Test struct {
	*fq12
}

func (t fq12Test) new() fieldElement {
	return t.fq12.new()
}

func (t fq12Test) debugElement(in fieldElement) {
	t.fq12.debugElement(in.(*fe12))
}

func (f *fq12) debugElement(in *fe12) {
	fmt.Println(f.toString(in))
}

func (t fq12Test) byteSize() int {
	return t.fq12.byteSize()
}

func (t fq12Test) limbSize() int {
	return t.fq12.f.f.f.limbSize
}

func (t fq12Test) fromBytes(in []byte) (fieldElement, error) {
	return t.fq12.fromBytes(in)
}

func (t fq12Test) toBytes(in fieldElement) []byte {
	return t.fq12.toBytes(in.(*fe12))
}

func (t fq12Test) isZero(in fieldElement) bool {
	return t.fq12.isZero(in.(*fe12))
}

func (t fq12Test) isOne(in fieldElement) bool {
	return t.fq12.isOne(in.(*fe12))
}

func (t fq12Test) zero() fieldElement {
	return t.fq12.zero()
}

func (t fq12Test) one() fieldElement {
	return t.fq12.one()
}

func (t fq12Test) p() *big.Int {
	return t.f.modulus()
}

func (t fq12Test) equal(a, b fieldElement) bool {
	return t.fq12.equal(a.(*fe12), b.(*fe12))
}

func (t fq12Test) rand(r io.Reader) fieldElement {
	return t.fq12.rand(r)
}

func (t fq12Test) add(c, a, b fieldElement) {
	t.fq12.add(c.(*fe12), a.(*fe12), b.(*fe12))
}

func (t fq12Test) sub(c, a, b fieldElement) {
	t.fq12.sub(c.(*fe12), a.(*fe12), b.(*fe12))
}

func (t fq12Test) mul(c, a, b fieldElement) {
	t.fq12.mul(c.(*fe12), a.(*fe12), b.(*fe12))
}

func (t fq12Test) double(c, a fieldElement) {
	t.fq12.double(c.(*fe12), a.(*fe12))
}

func (t fq12Test) neg(c, a fieldElement) {
	t.fq12.neg(c.(*fe12), a.(*fe12))
}

func (t fq12Test) square(c, a fieldElement) {
	t.fq12.square(c.(*fe12), a.(*fe12))
}

func (t fq12Test) exp(c, a fieldElement, e *big.Int) {
	t.fq12.exp(c.(*fe12), a.(*fe12), e)
}

func (t fq12Test) inverse(c, a fieldElement) bool {
	return t.fq12.inverse(c.(*fe12), a.(*fe12))
}

func (t fq12Test) sqrt(c, a fieldElement) bool {
	// return t.fq.sqrt(c.(*fe12), a.(*fe12))
	return true
}

func randFq(limbSize int) *fq {
	var offset int
	t, err := rand.Int(rand.Reader, new(big.Int).SetUint64(64))
	if err != nil {
		panic(err)
	}
	offset = int(t.Uint64())
	byteLen := limbSize * 8
	bitLen := (limbSize-1)*64 + offset
	if bitLen < 32 {
		bitLen = 32
	}
	pbig, err := rand.Prime(rand.Reader, bitLen)
	if err != nil {
		panic(err)
	}
	rawpbytes := pbig.Bytes()
	pbytes := make([]byte, byteLen)
	copy(pbytes[byteLen-len(rawpbytes):], pbig.Bytes())
	field, err := newField(pbytes)
	if err != nil {
		panic(err)
	}
	if limbSize < 4 {
		if field.limbSize != 4 {
			panic("bad random field construction")
		}
	} else {
		if field.limbSize != limbSize {
			panic("bad random field construction")
		}
	}
	return field
}

func resolveLimbSize(bitSize int) int {
	size := (bitSize / 64)
	if bitSize%64 != 0 {
		size += 1
	}
	return size
}

func BenchmarkField(t *testing.B) {
	var limbSize int
	if targetNumberOfLimb > 0 {
		limbSize = targetNumberOfLimb
	} else {
		return
	}
	field := randFq(limbSize)
	if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize != limbSize {
		t.Fatalf("bad field construction")
	}
	bitSize := limbSize * 64
	a := field.rand(rand.Reader)
	b := field.rand(rand.Reader)
	c := field.new()
	t.Run(fmt.Sprintf("%d_add", bitSize), func(t *testing.B) {
		for i := 0; i < t.N; i++ {
			field.add(c, a, b)
		}
	})
	t.Run(fmt.Sprintf("%d_double", bitSize), func(t *testing.B) {
		for i := 0; i < t.N; i++ {
			field.double(c, a)
		}
	})
	t.Run(fmt.Sprintf("%d_sub", bitSize), func(t *testing.B) {
		for i := 0; i < t.N; i++ {
			field.sub(c, a, b)
		}
	})
	t.Run(fmt.Sprintf("%d_mul", bitSize), func(t *testing.B) {
		for i := 0; i < t.N; i++ {
			field.mul(c, a, b)
		}
	})
	t.Run(fmt.Sprintf("%d_cmp", bitSize), func(t *testing.B) {
		for i := 0; i < t.N; i++ {
			field.cmp(a, b)
		}
	})
}

func TestFqShift(t *testing.T) {
	two := big.NewInt(2)
	for limbSize := from; limbSize < to+1; limbSize++ {
		t.Run(fmt.Sprintf("%d_shift", limbSize*64), func(t *testing.T) {
			field := randFq(limbSize)
			a := field.rand(rand.Reader)
			bi := field.toBigNoTransform(a)
			da := field.new()
			field.copy(da, a)
			field.div_two(da)
			dbi := new(big.Int).Div(bi, two)
			dbi_2 := field.toBigNoTransform(da)
			if dbi.Cmp(dbi_2) != 0 {
				t.Fatalf("bad div 2 operation")
			}
			ma := field.new()
			field.copy(ma, a)
			field.mul_two(ma)
			mbi := new(big.Int).Mul(bi, two)
			mbi_2 := field.toBigNoTransform(ma)
			if mbi.Cmp(mbi_2) != 0 {
				t.Fatalf("bad mul 2 operation")
			}
		})
	}
}

func TestFqCompare(t *testing.T) {
	for limbSize := from; limbSize < to+1; limbSize++ {
		t.Run(fmt.Sprintf("%d_compare", limbSize*64), func(t *testing.T) {
			field := randFq(limbSize)
			if field.cmp(field.r, field.r) != 0 {
				t.Fatalf("r == r (cmp)")
			}
			if !field.equal(field.r, field.r) {
				t.Fatalf("r == r (equal)")
			}
			if field.equal(field.p, field.r) {
				t.Fatalf("p != r")
			}
			if field.equal(field.r, field.zero) {
				t.Fatalf("r != 0")
			}
			if !field.equal(field.zero, field.zero) {
				t.Fatalf("0 == 0")
			}
			if field.cmp(field.p, field.r) != 1 {
				t.Fatalf("p > r")
			}
			if field.cmp(field.r, field.p) != -1 {
				t.Fatalf("r < p")
			}
			if is_even(field.p) {
				t.Fatalf("p is not even")
			}
		})
	}
}

func TestFqCopy(t *testing.T) {
	for limbSize := from; limbSize < to+1; limbSize++ {
		t.Run(fmt.Sprintf("%d_copy", limbSize*64), func(t *testing.T) {
			field := randFq(limbSize)
			a := field.rand(rand.Reader)
			b := field.new()
			field.copy(b, a)
			if !field.equal(a, b) {
				t.Fatalf("copy operation fails")
			}
		})
	}
}

func TestFqSerialization(t *testing.T) {
	for limbSize := from; limbSize < to+1; limbSize++ {
		t.Run(fmt.Sprintf("%d_serialization", limbSize*64), func(t *testing.T) {
			field := randFq(limbSize)
			if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize != limbSize {
				t.Fatalf("bad field construction\n")
			}
			// demont(r) == 1
			b0 := make([]byte, field.byteSize())
			b0[len(b0)-1] = byte(1)
			b1 := field.toBytes(field.r)
			if !bytes.Equal(b0, b1) {
				t.Fatalf("demont(r) must be equal to 1\n")
			}
			// is a => modulus should not be valid
			_, err := field.fromBytes(field.pbig.Bytes())
			if err == nil {
				t.Fatalf("a number eq or larger than modulus must not be valid")
			}
			for i := 0; i < fuz; i++ {
				field := randFq(limbSize)
				if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize != limbSize {
					t.Fatalf("bad field construction")
				}
				// bytes
				b0 := randBytes(field.pbig)
				if USE_4LIMBS_FOR_LOWER_LIMBS && len(b0) < 32 {
					b0 = padBytes(b0, 32)
				}
				a0, err := field.fromBytes(b0)
				if err != nil {
					t.Fatal(err)
				}
				if USE_4LIMBS_FOR_LOWER_LIMBS && len(b0) < 32 {
					b0 = padBytes(b0, 32)
				}
				b1 = field.toBytes(a0)
				if !bytes.Equal(b0, b1) {
					t.Fatalf("bad serialization (bytes)")
				}
				// string
				s := field.toString(a0)
				a1, err := field.fromString(s)
				if err != nil {
					t.Fatal(err)
				}
				if !field.equal(a0, a1) {
					t.Fatalf("bad serialization (str)")
				}
				// big int
				a0, err = field.fromBytes(b0)
				if err != nil {
					t.Fatal(err)
				}
				bi := field.toBig(a0)
				a1, err = field.fromBig(bi)
				if err != nil {
					t.Fatal(err)
				}
				if !field.equal(a0, a1) {
					t.Fatalf("bad serialization (big.Int)")
				}
				// bytes dense
				b0 = field.toBytesDense(a0)
				a1, err = field.fromBytes(padBytes(b0, field.byteSize()))
				if err != nil {
					t.Fatal(err)
				}
				if !field.equal(a0, a1) {
					t.Fatalf("bad serialization (dense)")
				}
			}
		})
	}
}

func TestFqAdditionCrossAgainstBigInt(t *testing.T) {
	for limbSize := from; limbSize < to+1; limbSize++ {
		t.Run(fmt.Sprintf("%d_addition_cross", limbSize*64), func(t *testing.T) {
			for i := 0; i < fuz; i++ {
				field := randFq(limbSize)
				if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize != limbSize {
					t.Fatalf("Bad field construction")
				}
				a := field.rand(rand.Reader)
				b := field.rand(rand.Reader)
				c := field.new()
				big_a := field.toBig(a)
				big_b := field.toBig(b)
				big_c := new(big.Int)
				field.add(c, a, b)
				out_1 := field.toBytes(c)
				out_2 := padBytes(big_c.Add(big_a, big_b).Mod(big_c, field.pbig).Bytes(), field.byteSize())
				if !bytes.Equal(out_1, out_2) {
					t.Fatalf("cross test against big.Int is not satisfied A")
				}
				field.double(c, a)
				out_1 = field.toBytes(c)
				out_2 = padBytes(big_c.Add(big_a, big_a).Mod(big_c, field.pbig).Bytes(), field.byteSize())
				if !bytes.Equal(out_1, out_2) {
					t.Fatalf("cross test against big.Int is not satisfied B")
				}
				field.sub(c, a, b)
				out_1 = field.toBytes(c)
				out_2 = padBytes(big_c.Sub(big_a, big_b).Mod(big_c, field.pbig).Bytes(), field.byteSize())
				if !bytes.Equal(out_1, out_2) {
					t.Fatalf("cross test against big.Int is not satisfied C")
				}
				field.neg(c, a)
				out_1 = field.toBytes(c)
				out_2 = padBytes(big_c.Neg(big_a).Mod(big_c, field.pbig).Bytes(), field.byteSize())
				if !bytes.Equal(out_1, out_2) {
					t.Fatalf("cross test against big.Int is not satisfied D")
				}
			}
		})
	}
}

func TestFqMultiplicationCrossAgainstBigInt(t *testing.T) {
	for limbSize := from; limbSize < to+1; limbSize++ {
		t.Run(fmt.Sprintf("%d_multiplication_cross", limbSize*64), func(t *testing.T) {
			for i := 0; i < fuz; i++ {
				field := randFq(limbSize)
				if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize != limbSize {
					t.Fatalf("bad field construction")
				}
				a := field.rand(rand.Reader)
				b := field.rand(rand.Reader)
				c := field.new()
				big_a := field.toBig(a)
				big_b := field.toBig(b)
				big_c := new(big.Int)
				field.mul(c, a, b)
				out_1 := field.toBytes(c)
				out_2 := padBytes(big_c.Mul(big_a, big_b).Mod(big_c, field.pbig).Bytes(), field.byteSize())
				if !bytes.Equal(out_1, out_2) {
					t.Fatalf("cross test against big.Int is not satisfied")
				}
			}
		})
	}
}

func randFq2(limbSize int) *fq2 {
	fq := randFq(limbSize)
	fq2, err := newFq2(fq, nil)
	if err != nil {
		panic(err)
	}
	// find a non residue
	for {
		k := fq.rand(rand.Reader)
		if !fq.isNonResidue(k, 2) {
			fq.copy(fq2.nonResidue, k)
			break
		}
	}
	fq2.calculateFrobeniusCoeffs()
	return fq2
}

func randFq3(limbSize int) *fq3 {
	var fq3 *fq3
	var err error
	for {
		fq := randFq(limbSize)
		fq3, err = newFq3(fq, nil)
		if err != nil {
			panic(err)
		}
		k := fq.rand(rand.Reader)
		if !fq.isNonResidue(k, 3) {
			fq.copy(fq3.nonResidue, k)
			break
		}
	}
	return fq3
}

func randFq4(limbSize int) *fq4 {
	var fq4 *fq4
	var fq2 *fq2
	var err error
	for {
		fq := randFq(limbSize)
		fq2, err = newFq2(fq, nil)
		if err != nil {
			panic(err)
		}
		k := fq.rand(rand.Reader)
		if !fq.isNonResidue(k, 4) {
			fq.copy(fq2.nonResidue, k)
			break
		}
	}
	for {
		fq4, err = newFq4(fq2, nil)
		if err != nil {
			panic(err)
		}
		k := fq2.rand(rand.Reader)
		if !fq2.isNonResidue(k, 2) {
			fq2.copy(fq4.nonResidue, k)
			break
		}
	}
	return fq4
}

func randFq6Q(limbSize int) *fq6Q {
	var fq6 *fq6Q
	var fq3 *fq3
	var err error
	for {
		fq := randFq(limbSize)
		fq3, err = newFq3(fq, nil)
		if err != nil {
			panic(err)
		}
		k := fq.rand(rand.Reader)
		if !fq.isNonResidue(k, 6) {
			fq.copy(fq3.nonResidue, k)
			break
		}
	}
	for {
		fq6, err = newFq6Quadratic(fq3, nil)
		if err != nil {
			panic(err)
		}
		k := fq3.rand(rand.Reader)
		if !fq3.isNonResidue(k, 2) {
			fq3.copy(fq6.nonResidue, k)
			break
		}
	}
	return fq6
}

func randFq6C(limbSize int) *fq6C {
	var fq6 *fq6C
	var fq2 *fq2
	var err error
	for {
		fq := randFq(limbSize)
		fq2, err = newFq2(fq, nil)
		if err != nil {
			panic(err)
		}
		k := fq.rand(rand.Reader)
		if !fq.isNonResidue(k, 6) {
			fq.copy(fq2.nonResidue, k)
			break
		}
	}
	for {
		fq6, err = newFq6Cubic(fq2, nil)
		if err != nil {
			panic(err)
		}
		k := fq2.rand(rand.Reader)
		if !fq2.isNonResidue(k, 3) {
			fq2.copy(fq6.nonResidue, k)
			break
		}
	}
	return fq6
}

func randFq12(limbSize int) *fq12 {
	var fq6 *fq6C
	var fq2 *fq2
	var err error
	for {
		fq := randFq(limbSize)
		fq2, err = newFq2(fq, nil)
		if err != nil {
			panic(err)
		}
		k := fq.rand(rand.Reader)
		if !fq.isNonResidue(k, 12) {
			fq.copy(fq2.nonResidue, k)
			break
		}
	}
	for {
		fq6, err = newFq6Cubic(fq2, nil)
		if err != nil {
			panic(err)
		}
		k := fq2.rand(rand.Reader)
		if !fq2.isNonResidue(k, 6) {
			fq2.copy(fq6.nonResidue, k)
			break
		}
	}
	fq12, err := newFq12(fq6, nil)
	if err != nil {
		panic(err)
	}
	return fq12
}

var fields = []string{"FQ", "FQ2", "FQ3", "FQ4", "FQ6Q", "FQ6C", "FQ12"}

func randField(ext string, limbSize int) field {
	switch ext {
	case "FQ":
		return fqTest{randFq(limbSize)}
	case "FQ2":
		return fq2Test{randFq2(limbSize)}
	case "FQ3":
		return fq3Test{randFq3(limbSize)}
	case "FQ4":
		return fq4Test{randFq4(limbSize)}
	case "FQ6Q":
		return fq6QTest{randFq6Q(limbSize)}
	case "FQ6C":
		return fq6CTest{randFq6C(limbSize)}
	case "FQ12":
		return fq12Test{randFq12(limbSize)}
	default:
		panic("unknown extension")
	}
}

func TestFqSerializationGeneric(t *testing.T) {
	for _, ext := range fields {
		for limbSize := from; limbSize < to+1; limbSize++ {
			t.Run(fmt.Sprintf("%d_%s", limbSize*64, ext), func(t *testing.T) {
				field := randField(ext, limbSize)
				for i := 0; i < fuz; i++ {
					a0 := field.rand(rand.Reader)
					buf := field.toBytes(a0)
					a1, err := field.fromBytes(buf)
					if err != nil {
						t.Fatal(err)
					}
					if !field.equal(a0, a1) {
						t.Fatalf("bad serialization (bytes)")
					}
				}
			})
		}
	}
}

func TestFqAdditionProperties(t *testing.T) {
	for _, ext := range fields {
		for limbSize := from; limbSize < to+1; limbSize++ {
			t.Run(fmt.Sprintf("%d_%s", limbSize*64, ext), func(t *testing.T) {
				for i := 0; i < fuz; i++ {
					field := randField(ext, limbSize)
					zero := field.zero()
					if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize() != limbSize {
						t.Fatalf("bad field construction")
					}
					a := field.rand(rand.Reader)
					b := field.rand(rand.Reader)
					c_1 := field.new()
					c_2 := field.new()
					field.add(c_1, a, zero)
					if !field.equal(c_1, a) {
						t.Fatalf("a + 0 == a")
					}
					field.sub(c_1, a, zero)
					if !field.equal(c_1, a) {
						t.Fatalf("a - 0 == a")
					}
					field.double(c_1, zero)
					if !field.equal(c_1, zero) {
						t.Fatalf("2 * 0 == 0")
					}
					field.neg(c_1, zero)
					if !field.equal(c_1, zero) {
						t.Fatalf("-0 == 0")
					}
					field.sub(c_1, zero, a)
					field.neg(c_2, a)
					if !field.equal(c_1, c_2) {
						t.Fatalf("0-a == -a")
					}
					field.double(c_1, a)
					field.add(c_2, a, a)
					if !field.equal(c_1, c_2) {
						t.Fatalf("2 * a == a + a")
					}
					field.add(c_1, a, b)
					field.add(c_2, b, a)
					if !field.equal(c_1, c_2) {
						t.Fatalf("a + b = b + a")
					}
					field.sub(c_1, a, b)
					field.sub(c_2, b, a)
					field.neg(c_2, c_2)
					if !field.equal(c_1, c_2) {
						t.Fatalf("a - b = - ( b - a )")
					}
					c_x := field.rand(rand.Reader)
					field.add(c_1, a, b)
					field.add(c_1, c_1, c_x)
					field.add(c_2, a, c_x)
					field.add(c_2, c_2, b)
					if !field.equal(c_1, c_2) {
						t.Fatalf("(a + b) + c == (a + c ) + b")
					}
					field.sub(c_1, a, b)
					field.sub(c_1, c_1, c_x)
					field.sub(c_2, a, c_x)
					field.sub(c_2, c_2, b)
					if !field.equal(c_1, c_2) {
						t.Fatalf("(a - b) - c == (a - c ) -b")
					}
				}
			})
		}
	}
}

func TestFqMultiplicationProperties(t *testing.T) {
	for _, ext := range fields {
		for limbSize := from; limbSize < to+1; limbSize++ {
			t.Run(fmt.Sprintf("%d_%s", limbSize*64, ext), func(t *testing.T) {
				for i := 0; i < fuz; i++ {
					field := randField(ext, limbSize)
					if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize() != limbSize {
						t.Fatalf("bad field construction")
					}
					a := field.rand(rand.Reader)
					b := field.rand(rand.Reader)
					zero := field.zero()
					one := field.one()
					c_1 := field.new()
					c_2 := field.new()
					field.mul(c_1, a, zero)
					if !field.equal(c_1, zero) {
						t.Fatalf("a * 0 == 0")
					}
					field.mul(c_1, a, one)
					if !field.equal(c_1, a) {
						t.Fatalf("a * 1 == a")
					}
					field.mul(c_1, a, b)
					field.mul(c_2, b, a)
					if !field.equal(c_1, c_2) {
						t.Fatalf("a * b == b * a")
					}
					c_x := field.rand(rand.Reader)
					field.mul(c_1, a, b)
					field.mul(c_1, c_1, c_x)
					field.mul(c_2, c_x, b)
					field.mul(c_2, c_2, a)
					if !field.equal(c_1, c_2) {
						t.Fatalf("(a * b) * c == (a * c) * b")
					}
				}
			})
		}
	}
}

func TestFqExponentiation(t *testing.T) {
	for _, ext := range fields {
		for limbSize := from; limbSize < to+1; limbSize++ {
			t.Run(fmt.Sprintf("%d_%s", limbSize*64, ext), func(t *testing.T) {
				for i := 0; i < fuz; i++ {
					field := randField(ext, limbSize)
					if !USE_4LIMBS_FOR_LOWER_LIMBS && field.limbSize() != limbSize {
						t.Fatalf("bad field construction")
					}
					a := field.rand(rand.Reader)
					u := field.new()
					field.exp(u, a, big.NewInt(0))
					if !field.equal(u, field.one()) {
						t.Fatalf("a^0 == 1")
					}
					field.exp(u, a, big.NewInt(1))
					if !field.equal(u, a) {
						t.Fatalf("a^1 == a")
					}
					v := field.new()
					field.mul(u, a, a)
					field.mul(u, u, u)
					field.mul(u, u, u)
					field.exp(v, a, big.NewInt(8))
					if !field.equal(u, v) {
						t.Fatalf("((a^2)^2)^2 == a^8")
					}
					p := new(big.Int).Set(field.p())
					field.exp(u, a, p)
					if !field.equal(u, a) {
						t.Fatalf("a^p == a")
					}
					field.exp(u, a, p.Sub(p, big.NewInt(1)))
					if !field.equal(u, field.one()) {
						t.Fatalf("a^(p-1) == 1")
					}
				}
			})
		}
	}
}

func TestFqInversion(t *testing.T) {
	for _, ext := range fields {
		for limbSize := from; limbSize < to+1; limbSize++ {
			t.Run(fmt.Sprintf("%d_%s", limbSize*64, ext), func(t *testing.T) {
				for i := 0; i < fuz; i++ {
					field := randField(ext, limbSize)
					u := field.new()
					zero := field.zero()
					one := field.one()
					field.inverse(u, zero)
					if !field.equal(u, zero) {
						t.Fatalf("(0^-1) == 0)")
					}
					field.inverse(u, one)
					if !field.equal(u, one) {
						t.Fatalf("(1^-1) == 1)")
					}
					a := field.rand(rand.Reader)
					field.inverse(u, a)
					field.mul(u, u, a)
					if !field.equal(u, one) {
						t.Fatalf("(r*a) * r*(a^-1) == r)")
					}
					v := field.new()
					p := new(big.Int).Set(field.p())
					field.exp(u, a, p.Sub(p, big.NewInt(2)))
					field.inverse(v, a)
					if !field.equal(v, u) {
						t.Fatalf("a^(p-2) == a^-1")
					}
				}
			})
		}
	}
}

// func TestFqSquareRoot(t *testing.T) {
// 	fields = []string{"FQ"}
// 	for _, ext := range fields {
// 		for limbSize := from; limbSize < 4; limbSize++ {
// 			// for limbSize := from; limbSize < to+1; limbSize++ {
// 			t.Run(fmt.Sprintf("%d_%s", limbSize*64, ext), func(t *testing.T) {
// 				for i := 0; i < fuz; i++ {
// 					field := randField(ext, limbSize)
// 					u := field.new()
// 					zero := field.zero()
// 					one := field.one()
// 					field.sqrt(u, zero)
// 					if !field.equal(u, zero) {
// 						t.Errorf("(0^(1/2)) == 0)")
// 					}
// 					field.sqrt(u, one)
// 					if !field.equal(u, one) {
// 						// t.Errorf("(1^(1/2)) == 1)")
// 					}
// 					v, w, negA := field.new(), field.new(), field.new()
// 					a := field.rand(rand.Reader)
// 					field.neg(negA, a)
// 					field.square(u, a)
// 					field.square(w, negA)
// 					if !field.equal(w, u) {
// 						// TODO: why fails?
// 						// t.Errorf("square of r and -r is not same")
// 					}
// 					if hasRoot := field.sqrt(v, u); !hasRoot {
// 						// t.Errorf("elem has no square-root")
// 					}
// 					if !field.equal(a, v) && !field.equal(negA, v) {
// 						field.debugElement(a)
// 						field.debugElement(negA)
// 						field.debugElement(v)
// 						// t.Errorf("((r)^2)^(1/2) == r)")
// 					}
// 				}
// 			})
// 		}
// 	}
// }