package fields

import (
	"bytes"
	"math/big"
)

// Fq12 uses the same algorithms than Fq2, but with [2][3][2]*big.Int data structure

// Fq12 is Field 12
type Fq12 struct {
	F          Fq6
	Fq2        Fq2
	NonResidue [2]*big.Int
}

// NewFq12 generates a new Fq12
func NewFq12(f Fq6, fq2 Fq2, nonResidue [2]*big.Int) Fq12 {
	fq12 := Fq12{
		f,
		fq2,
		nonResidue,
	}
	return fq12
}

// Zero returns a Zero value on the Fq12
func (fq12 Fq12) Zero() [2][3][2]*big.Int {
	return [2][3][2]*big.Int{fq12.F.Zero(), fq12.F.Zero()}
}

// One returns a One value on the Fq12
func (fq12 Fq12) One() [2][3][2]*big.Int {
	return [2][3][2]*big.Int{fq12.F.One(), fq12.F.Zero()}
}

func (fq12 Fq12) mulByNonResidue(a [3][2]*big.Int) [3][2]*big.Int {
	return [3][2]*big.Int{
		fq12.Fq2.Mul(fq12.NonResidue, a[2]),
		a[0],
		a[1],
	}
}

// Add performs an addition on the Fq12
func (fq12 Fq12) Add(a, b [2][3][2]*big.Int) [2][3][2]*big.Int {
	return [2][3][2]*big.Int{
		fq12.F.Add(a[0], b[0]),
		fq12.F.Add(a[1], b[1]),
	}
}

// Double performs a doubling on the Fq12
func (fq12 Fq12) Double(a [2][3][2]*big.Int) [2][3][2]*big.Int {
	return fq12.Add(a, a)
}

// Sub performs a subtraction on the Fq12
func (fq12 Fq12) Sub(a, b [2][3][2]*big.Int) [2][3][2]*big.Int {
	return [2][3][2]*big.Int{
		fq12.F.Sub(a[0], b[0]),
		fq12.F.Sub(a[1], b[1]),
	}
}

// Neg performs a negation on the Fq12
func (fq12 Fq12) Neg(a [2][3][2]*big.Int) [2][3][2]*big.Int {
	return fq12.Sub(fq12.Zero(), a)
}

// Mul performs a multiplication on the Fq12
func (fq12 Fq12) Mul(a, b [2][3][2]*big.Int) [2][3][2]*big.Int {
	// Multiplication and Squaring on Pairing-Friendly .pdf; Section 3 (Karatsuba)
	v0 := fq12.F.Mul(a[0], b[0])
	v1 := fq12.F.Mul(a[1], b[1])
	return [2][3][2]*big.Int{
		fq12.F.Add(v0, fq12.mulByNonResidue(v1)),
		fq12.F.Sub(
			fq12.F.Mul(
				fq12.F.Add(a[0], a[1]),
				fq12.F.Add(b[0], b[1])),
			fq12.F.Add(v0, v1)),
	}
}

func (fq12 Fq12) MulScalar(base [2][3][2]*big.Int, e *big.Int) [2][3][2]*big.Int {
	// for more possible implementations see g2.go file, at the function g2.MulScalar()

	res := fq12.Zero()
	rem := e
	exp := base

	for !bytes.Equal(rem.Bytes(), big.NewInt(int64(0)).Bytes()) {
		// if rem % 2 == 1
		if bytes.Equal(new(big.Int).Rem(rem, big.NewInt(int64(2))).Bytes(), big.NewInt(int64(1)).Bytes()) {
			res = fq12.Add(res, exp)
		}
		exp = fq12.Double(exp)
		rem = rem.Rsh(rem, 1) // rem = rem >> 1
	}
	return res
}

// Inverse returns the inverse on the Fq12
func (fq12 Fq12) Inverse(a [2][3][2]*big.Int) [2][3][2]*big.Int {
	t0 := fq12.F.Square(a[0])
	t1 := fq12.F.Square(a[1])
	t2 := fq12.F.Sub(t0, fq12.mulByNonResidue(t1))
	t3 := fq12.F.Inverse(t2)
	return [2][3][2]*big.Int{
		fq12.F.Mul(a[0], t3),
		fq12.F.Neg(fq12.F.Mul(a[1], t3)),
	}
}

// Div performs a division on the Fq12
func (fq12 Fq12) Div(a, b [2][3][2]*big.Int) [2][3][2]*big.Int {
	return fq12.Mul(a, fq12.Inverse(b))
}

// Square performs a square operation on the Fq12
func (fq12 Fq12) Square(a [2][3][2]*big.Int) [2][3][2]*big.Int {
	ab := fq12.F.Mul(a[0], a[1])

	return [2][3][2]*big.Int{
		fq12.F.Sub(
			fq12.F.Mul(
				fq12.F.Add(a[0], a[1]),
				fq12.F.Add(
					a[0],
					fq12.mulByNonResidue(a[1]))),
			fq12.F.Add(
				ab,
				fq12.mulByNonResidue(ab))),
		fq12.F.Add(ab, ab),
	}
}

func BigIsOdd(n *big.Int) bool {
	one := big.NewInt(int64(1))
	and := new(big.Int).And(n, one)
	return bytes.Equal(and.Bytes(), big.NewInt(int64(1)).Bytes())
}

func (fq12 Fq12) Exp(base [2][3][2]*big.Int, e *big.Int) [2][3][2]*big.Int {
	// TODO fix bottleneck

	res := fq12.One()
	rem := fq12.Fq2.F.Copy(e)
	exp := base

	// before := time.Now()
	for !bytes.Equal(rem.Bytes(), big.NewInt(int64(0)).Bytes()) {
		if BigIsOdd(rem) {
			res = fq12.Mul(res, exp)
		}
		exp = fq12.Square(exp)
		rem = new(big.Int).Rsh(rem, 1)
	}
	// fmt.Println("time elapsed:", time.Since(before))
	return res
}
func (fq12 Fq12) Affine(a [2][3][2]*big.Int) [2][3][2]*big.Int {
	return [2][3][2]*big.Int{
		fq12.F.Affine(a[0]),
		fq12.F.Affine(a[1]),
	}
}
func (fq12 Fq12) Equal(a, b [2][3][2]*big.Int) bool {
	return fq12.F.Equal(a[0], b[0]) && fq12.F.Equal(a[1], b[1])
}
