package bls12381

import (
	"io"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves/native"
)

// GtFieldBytes is the number of bytes needed to represent this field
const GtFieldBytes = 576

// Gt is the target group
type Gt fp12

// Random generates a random field element
func (gt *Gt) Random(reader io.Reader) (*Gt, error) {
	_, err := (*fp12)(gt).Random(reader)
	return gt, err
}

// FinalExponentiation performs a "final exponentiation" routine to convert the result
// of a Miller loop into an element of `Gt` with help of efficient squaring
// operation in the so-called `cyclotomic subgroup` of `Fq6` so that
// it can be compared with other elements of `Gt`.
func (gt *Gt) FinalExponentiation(a *Gt) *Gt {
	var t0, t1, t2, t3, t4, t5, t6, t fp12
	t0.FrobeniusMap((*fp12)(a))
	t0.FrobeniusMap(&t0)
	t0.FrobeniusMap(&t0)
	t0.FrobeniusMap(&t0)
	t0.FrobeniusMap(&t0)
	t0.FrobeniusMap(&t0)

	// Shouldn't happen since we enforce `a` to be non-zero but just in case
	_, wasInverted := t1.Invert((*fp12)(a))
	t2.Mul(&t0, &t1)
	t1.Set(&t2)
	t2.FrobeniusMap(&t2)
	t2.FrobeniusMap(&t2)
	t2.Mul(&t2, &t1)
	t1.cyclotomicSquare(&t2)
	t1.Conjugate(&t1)

	t3.cyclotomicExp(&t2)
	t4.cyclotomicSquare(&t3)
	t5.Mul(&t1, &t3)
	t1.cyclotomicExp(&t5)
	t0.cyclotomicExp(&t1)
	t6.cyclotomicExp(&t0)
	t6.Mul(&t6, &t4)
	t4.cyclotomicExp(&t6)
	t5.Conjugate(&t5)
	t4.Mul(&t4, &t5)
	t4.Mul(&t4, &t2)
	t5.Conjugate(&t2)
	t1.Mul(&t1, &t2)
	t1.FrobeniusMap(&t1)
	t1.FrobeniusMap(&t1)
	t1.FrobeniusMap(&t1)
	t6.Mul(&t6, &t5)
	t6.FrobeniusMap(&t6)
	t3.Mul(&t3, &t0)
	t3.FrobeniusMap(&t3)
	t3.FrobeniusMap(&t3)
	t3.Mul(&t3, &t1)
	t3.Mul(&t3, &t6)
	t.Mul(&t3, &t4)
	(*fp12)(gt).CMove((*fp12)(gt), &t, wasInverted)
	return gt
}

// IsZero returns 1 if gt == 0, 0 otherwise
func (gt *Gt) IsZero() int {
	return (*fp12)(gt).IsZero()
}

// IsOne returns 1 if gt == 1, 0 otherwise
func (gt *Gt) IsOne() int {
	return (*fp12)(gt).IsOne()
}

// SetOne gt = one
func (gt *Gt) SetOne() *Gt {
	(*fp12)(gt).SetOne()
	return gt
}

// Set copies a into gt
func (gt *Gt) Set(a *Gt) *Gt {
	gt.A.Set(&a.A)
	gt.B.Set(&a.B)
	return gt
}

// Bytes returns the Gt field byte representation
func (gt *Gt) Bytes() [GtFieldBytes]byte {
	var out [GtFieldBytes]byte
	t := gt.A.A.A.Bytes()
	copy(out[:FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.A.A.B.Bytes()
	copy(out[FieldBytes:2*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.A.B.A.Bytes()
	copy(out[2*FieldBytes:3*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.A.B.B.Bytes()
	copy(out[3*FieldBytes:4*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.A.C.A.Bytes()
	copy(out[4*FieldBytes:5*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.A.C.B.Bytes()
	copy(out[5*FieldBytes:6*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.B.A.A.Bytes()
	copy(out[6*FieldBytes:7*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.B.A.B.Bytes()
	copy(out[7*FieldBytes:8*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.B.B.A.Bytes()
	copy(out[8*FieldBytes:9*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.B.B.B.Bytes()
	copy(out[9*FieldBytes:10*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.B.C.A.Bytes()
	copy(out[10*FieldBytes:11*FieldBytes], internal.ReverseScalarBytes(t[:]))
	t = gt.B.C.B.Bytes()
	copy(out[11*FieldBytes:12*FieldBytes], internal.ReverseScalarBytes(t[:]))

	return out
}

// SetBytes attempts to convert a big-endian byte representation of
// a scalar into a `Gt`, failing if the input is not canonical.
func (gt *Gt) SetBytes(input *[GtFieldBytes]byte) (*Gt, int) {
	var t [FieldBytes]byte
	var valid [12]int
	copy(t[:], internal.ReverseScalarBytes(input[:FieldBytes]))
	_, valid[0] = gt.A.A.A.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[FieldBytes:2*FieldBytes]))
	_, valid[1] = gt.A.A.B.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[2*FieldBytes:3*FieldBytes]))
	_, valid[2] = gt.A.B.A.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[3*FieldBytes:4*FieldBytes]))
	_, valid[3] = gt.A.B.B.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[4*FieldBytes:5*FieldBytes]))
	_, valid[4] = gt.A.C.A.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[5*FieldBytes:6*FieldBytes]))
	_, valid[5] = gt.A.C.B.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[6*FieldBytes:7*FieldBytes]))
	_, valid[6] = gt.B.A.A.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[7*FieldBytes:8*FieldBytes]))
	_, valid[7] = gt.B.A.B.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[8*FieldBytes:9*FieldBytes]))
	_, valid[8] = gt.B.B.A.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[9*FieldBytes:10*FieldBytes]))
	_, valid[9] = gt.B.B.B.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[10*FieldBytes:11*FieldBytes]))
	_, valid[10] = gt.B.C.A.SetBytes(&t)
	copy(t[:], internal.ReverseScalarBytes(input[11*FieldBytes:12*FieldBytes]))
	_, valid[11] = gt.B.C.B.SetBytes(&t)

	return gt, valid[0] & valid[1] &
		valid[2] & valid[3] &
		valid[4] & valid[5] &
		valid[6] & valid[7] &
		valid[8] & valid[9] &
		valid[10] & valid[11]
}

// Equal returns 1 if gt == rhs, 0 otherwise
func (gt *Gt) Equal(rhs *Gt) int {
	return (*fp12)(gt).Equal((*fp12)(rhs))
}

// Generator returns the base point
func (gt *Gt) Generator() *Gt {
	// pairing(&G1::generator(), &G2::generator())
	gt.Set((*Gt)(&fp12{
		A: fp6{
			A: fp2{
				A: fp{
					0x1972e433a01f85c5,
					0x97d32b76fd772538,
					0xc8ce546fc96bcdf9,
					0xcef63e7366d40614,
					0xa611342781843780,
					0x13f3448a3fc6d825,
				},
				B: fp{
					0xd26331b02e9d6995,
					0x9d68a482f7797e7d,
					0x9c9b29248d39ea92,
					0xf4801ca2e13107aa,
					0xa16c0732bdbcb066,
					0x083ca4afba360478,
				},
			},
			B: fp2{
				A: fp{
					0x59e261db0916b641,
					0x2716b6f4b23e960d,
					0xc8e55b10a0bd9c45,
					0x0bdb0bd99c4deda8,
					0x8cf89ebf57fdaac5,
					0x12d6b7929e777a5e,
				},
				B: fp{
					0x5fc85188b0e15f35,
					0x34a06e3a8f096365,
					0xdb3126a6e02ad62c,
					0xfc6f5aa97d9a990b,
					0xa12f55f5eb89c210,
					0x1723703a926f8889,
				},
			},
			C: fp2{
				A: fp{
					0x93588f2971828778,
					0x43f65b8611ab7585,
					0x3183aaf5ec279fdf,
					0xfa73d7e18ac99df6,
					0x64e176a6a64c99b0,
					0x179fa78c58388f1f,
				},
				B: fp{
					0x672a0a11ca2aef12,
					0x0d11b9b52aa3f16b,
					0xa44412d0699d056e,
					0xc01d0177221a5ba5,
					0x66e0cede6c735529,
					0x05f5a71e9fddc339,
				},
			},
		},
		B: fp6{
			A: fp2{
				A: fp{
					0xd30a88a1b062c679,
					0x5ac56a5d35fc8304,
					0xd0c834a6a81f290d,
					0xcd5430c2da3707c7,
					0xf0c27ff780500af0,
					0x09245da6e2d72eae,
				},
				B: fp{
					0x9f2e0676791b5156,
					0xe2d1c8234918fe13,
					0x4c9e459f3c561bf4,
					0xa3e85e53b9d3e3c1,
					0x820a121e21a70020,
					0x15af618341c59acc,
				},
			},
			B: fp2{
				A: fp{
					0x7c95658c24993ab1,
					0x73eb38721ca886b9,
					0x5256d749477434bc,
					0x8ba41902ea504a8b,
					0x04a3d3f80c86ce6d,
					0x18a64a87fb686eaa,
				},
				B: fp{
					0xbb83e71bb920cf26,
					0x2a5277ac92a73945,
					0xfc0ee59f94f046a0,
					0x7158cdf3786058f7,
					0x7cc1061b82f945f6,
					0x03f847aa9fdbe567,
				},
			},
			C: fp2{
				A: fp{
					0x8078dba56134e657,
					0x1cd7ec9a43998a6e,
					0xb1aa599a1a993766,
					0xc9a0f62f0842ee44,
					0x8e159be3b605dffa,
					0x0c86ba0d4af13fc2,
				},
				B: fp{
					0xe80ff2a06a52ffb1,
					0x7694ca48721a906c,
					0x7583183e03b08514,
					0xf567afdd40cee4e2,
					0x9a6d96d2e526a5fc,
					0x197e9f49861f2242,
				},
			},
		},
	}))
	return gt
}

// Add adds this value to another value.
func (gt *Gt) Add(arg1, arg2 *Gt) *Gt {
	(*fp12)(gt).Mul((*fp12)(arg1), (*fp12)(arg2))
	return gt
}

// Double this value
func (gt *Gt) Double(a *Gt) *Gt {
	(*fp12)(gt).Square((*fp12)(a))
	return gt
}

// Sub subtracts the two values
func (gt *Gt) Sub(arg1, arg2 *Gt) *Gt {
	var t fp12
	t.Conjugate((*fp12)(arg2))
	(*fp12)(gt).Mul((*fp12)(arg1), &t)
	return gt
}

// Neg negates this value
func (gt *Gt) Neg(a *Gt) *Gt {
	(*fp12)(gt).Conjugate((*fp12)(a))
	return gt
}

// Mul multiplies this value by the input scalar
func (gt *Gt) Mul(a *Gt, s *native.Field) *Gt {
	var f, p fp12
	f.Set((*fp12)(a))
	bytes := s.Bytes()

	precomputed := [16]fp12{}
	precomputed[1].Set(&f)
	for i := 2; i < 16; i += 2 {
		precomputed[i].Square(&precomputed[i>>1])
		precomputed[i+1].Mul(&precomputed[i], &f)
	}
	for i := 0; i < 256; i += 4 {
		// Brouwer / windowing method. window size of 4.
		for j := 0; j < 4; j++ {
			p.Square(&p)
		}
		window := bytes[32-1-i>>3] >> (4 - i&0x04) & 0x0F
		p.Mul(&p, &precomputed[window])
	}
	(*fp12)(gt).Set(&p)
	return gt
}

// Square this value
func (gt *Gt) Square(a *Gt) *Gt {
	(*fp12)(gt).cyclotomicSquare((*fp12)(a))
	return gt
}

// Invert this value
func (gt *Gt) Invert(a *Gt) (*Gt, int) {
	_, wasInverted := (*fp12)(gt).Invert((*fp12)(a))
	return gt, wasInverted
}

func fp4Square(a, b, arg1, arg2 *fp2) {
	var t0, t1, t2 fp2

	t0.Square(arg1)
	t1.Square(arg2)
	t2.MulByNonResidue(&t1)
	a.Add(&t2, &t0)
	t2.Add(arg1, arg2)
	t2.Square(&t2)
	t2.Sub(&t2, &t0)
	b.Sub(&t2, &t1)
}

func (f *fp12) cyclotomicSquare(a *fp12) *fp12 {
	// Adaptation of Algorithm 5.5.4, Guide to Pairing-Based Cryptography
	// Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions
	// https://eprint.iacr.org/2009/565.pdf
	var z0, z1, z2, z3, z4, z5, t0, t1, t2, t3 fp2
	z0.Set(&a.A.A)
	z4.Set(&a.A.B)
	z3.Set(&a.A.C)
	z2.Set(&a.B.A)
	z1.Set(&a.B.B)
	z5.Set(&a.B.C)

	fp4Square(&t0, &t1, &z0, &z1)
	z0.Sub(&t0, &z0)
	z0.Double(&z0)
	z0.Add(&z0, &t0)

	z1.Add(&t1, &z1)
	z1.Double(&z1)
	z1.Add(&z1, &t1)

	fp4Square(&t0, &t1, &z2, &z3)
	fp4Square(&t2, &t3, &z4, &z5)

	z4.Sub(&t0, &z4)
	z4.Double(&z4)
	z4.Add(&z4, &t0)

	z5.Add(&z5, &t1)
	z5.Double(&z5)
	z5.Add(&z5, &t1)

	t0.MulByNonResidue(&t3)
	z2.Add(&z2, &t0)
	z2.Double(&z2)
	z2.Add(&z2, &t0)

	z3.Sub(&t2, &z3)
	z3.Double(&z3)
	z3.Add(&z3, &t2)

	f.A.A.Set(&z0)
	f.A.B.Set(&z4)
	f.A.C.Set(&z3)

	f.B.A.Set(&z2)
	f.B.B.Set(&z1)
	f.B.C.Set(&z5)
	return f
}

func (f *fp12) cyclotomicExp(a *fp12) *fp12 {
	var t fp12
	t.SetOne()
	foundOne := 0

	for i := 63; i >= 0; i-- {
		b := int((paramX >> i) & 1)
		if foundOne == 1 {
			t.cyclotomicSquare(&t)
		} else {
			foundOne = b
		}
		if b == 1 {
			t.Mul(&t, a)
		}
	}
	f.Conjugate(&t)
	return f
}
