// Copyright 2020 Consensys Software Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Code generated by consensys/gnark-crypto DO NOT EDIT

package fptower

// E6 is a degree three finite field extension of fp2
type E6 struct {
	B0, B1, B2 E2
}

// Equal returns true if z equals x, false otherwise
func (z *E6) Equal(x *E6) bool {
	return z.B0.Equal(&x.B0) && z.B1.Equal(&x.B1) && z.B2.Equal(&x.B2)
}

// SetString sets a E6 elmt from stringf
func (z *E6) SetString(s1, s2, s3, s4, s5, s6 string) *E6 {
	z.B0.SetString(s1, s2)
	z.B1.SetString(s3, s4)
	z.B2.SetString(s5, s6)
	return z
}

// Set Sets a E6 elmt form another E6 elmt
func (z *E6) Set(x *E6) *E6 {
	z.B0 = x.B0
	z.B1 = x.B1
	z.B2 = x.B2
	return z
}

// SetOne sets z to 1 in Montgomery form and returns z
func (z *E6) SetOne() *E6 {
	*z = E6{}
	z.B0.A0.SetOne()
	return z
}

// SetRandom set z to a random elmt
func (z *E6) SetRandom() (*E6, error) {
	if _, err := z.B0.SetRandom(); err != nil {
		return nil, err
	}
	if _, err := z.B1.SetRandom(); err != nil {
		return nil, err
	}
	if _, err := z.B2.SetRandom(); err != nil {
		return nil, err
	}
	return z, nil
}

// IsZero returns true if the two elements are equal, false otherwise
func (z *E6) IsZero() bool {
	return z.B0.IsZero() && z.B1.IsZero() && z.B2.IsZero()
}

func (z *E6) IsOne() bool {
	return z.B0.IsOne() && z.B1.IsZero() && z.B2.IsZero()
}

// Add adds two elements of E6
func (z *E6) Add(x, y *E6) *E6 {
	z.B0.Add(&x.B0, &y.B0)
	z.B1.Add(&x.B1, &y.B1)
	z.B2.Add(&x.B2, &y.B2)
	return z
}

// Neg negates the E6 number
func (z *E6) Neg(x *E6) *E6 {
	z.B0.Neg(&x.B0)
	z.B1.Neg(&x.B1)
	z.B2.Neg(&x.B2)
	return z
}

// Sub two elements of E6
func (z *E6) Sub(x, y *E6) *E6 {
	z.B0.Sub(&x.B0, &y.B0)
	z.B1.Sub(&x.B1, &y.B1)
	z.B2.Sub(&x.B2, &y.B2)
	return z
}

// Double doubles an element in E6
func (z *E6) Double(x *E6) *E6 {
	z.B0.Double(&x.B0)
	z.B1.Double(&x.B1)
	z.B2.Double(&x.B2)
	return z
}

// String puts E6 elmt in string form
func (z *E6) String() string {
	return (z.B0.String() + "+(" + z.B1.String() + ")*v+(" + z.B2.String() + ")*v**2")
}

// MulByNonResidue mul x by (0,1,0)
func (z *E6) MulByNonResidue(x *E6) *E6 {
	z.B2, z.B1, z.B0 = x.B1, x.B0, x.B2
	z.B0.MulByNonResidue(&z.B0)
	return z
}

// MulByE2 multiplies an element in E6 by an element in E2
func (z *E6) MulByE2(x *E6, y *E2) *E6 {
	var yCopy E2
	yCopy.Set(y)
	z.B0.Mul(&x.B0, &yCopy)
	z.B1.Mul(&x.B1, &yCopy)
	z.B2.Mul(&x.B2, &yCopy)
	return z
}

// MulBy12 multiplication by sparse element (0,b1,b2)
func (x *E6) MulBy12(b1, b2 *E2) *E6 {
	var t1, t2, c0, tmp, c1, c2 E2
	t1.Mul(&x.B1, b1)
	t2.Mul(&x.B2, b2)
	c0.Add(&x.B1, &x.B2)
	tmp.Add(b1, b2)
	c0.Mul(&c0, &tmp)
	c0.Sub(&c0, &t1)
	c0.Sub(&c0, &t2)
	c0.MulByNonResidue(&c0)
	c1.Add(&x.B0, &x.B1)
	c1.Mul(&c1, b1)
	c1.Sub(&c1, &t1)
	tmp.MulByNonResidue(&t2)
	c1.Add(&c1, &tmp)
	tmp.Add(&x.B0, &x.B2)
	c2.Mul(b2, &tmp)
	c2.Sub(&c2, &t2)
	c2.Add(&c2, &t1)

	x.B0 = c0
	x.B1 = c1
	x.B2 = c2

	return x
}

// MulBy01 multiplication by sparse element (c0,c1,0)
func (z *E6) MulBy01(c0, c1 *E2) *E6 {

	var a, b, tmp, t0, t1, t2 E2

	a.Mul(&z.B0, c0)
	b.Mul(&z.B1, c1)

	tmp.Add(&z.B1, &z.B2)
	t0.Mul(c1, &tmp)
	t0.Sub(&t0, &b)
	t0.MulByNonResidue(&t0)
	t0.Add(&t0, &a)

	tmp.Add(&z.B0, &z.B2)
	t2.Mul(c0, &tmp)
	t2.Sub(&t2, &a)
	t2.Add(&t2, &b)

	t1.Add(c0, c1)
	tmp.Add(&z.B0, &z.B1)
	t1.Mul(&t1, &tmp)
	t1.Sub(&t1, &a)
	t1.Sub(&t1, &b)

	z.B0.Set(&t0)
	z.B1.Set(&t1)
	z.B2.Set(&t2)

	return z
}

// MulBy1 multiplication of E6 by sparse element (0, c1, 0)
func (z *E6) MulBy1(c1 *E2) *E6 {

	var b, tmp, t0, t1 E2
	b.Mul(&z.B1, c1)

	tmp.Add(&z.B1, &z.B2)
	t0.Mul(c1, &tmp)
	t0.Sub(&t0, &b)
	t0.MulByNonResidue(&t0)

	tmp.Add(&z.B0, &z.B1)
	t1.Mul(c1, &tmp)
	t1.Sub(&t1, &b)

	z.B0.Set(&t0)
	z.B1.Set(&t1)
	z.B2.Set(&b)

	return z
}

// Mul sets z to the E6 product of x,y, returns z
func (z *E6) Mul(x, y *E6) *E6 {
	// Algorithm 13 from https://eprint.iacr.org/2010/354.pdf
	var t0, t1, t2, c0, c1, c2, tmp E2
	t0.Mul(&x.B0, &y.B0)
	t1.Mul(&x.B1, &y.B1)
	t2.Mul(&x.B2, &y.B2)

	c0.Add(&x.B1, &x.B2)
	tmp.Add(&y.B1, &y.B2)
	c0.Mul(&c0, &tmp).Sub(&c0, &t1).Sub(&c0, &t2).MulByNonResidue(&c0).Add(&c0, &t0)

	c1.Add(&x.B0, &x.B1)
	tmp.Add(&y.B0, &y.B1)
	c1.Mul(&c1, &tmp).Sub(&c1, &t0).Sub(&c1, &t1)
	tmp.MulByNonResidue(&t2)
	c1.Add(&c1, &tmp)

	tmp.Add(&x.B0, &x.B2)
	c2.Add(&y.B0, &y.B2).Mul(&c2, &tmp).Sub(&c2, &t0).Sub(&c2, &t2).Add(&c2, &t1)

	z.B0.Set(&c0)
	z.B1.Set(&c1)
	z.B2.Set(&c2)

	return z
}

// Square sets z to the E6 product of x,x, returns z
func (z *E6) Square(x *E6) *E6 {

	// Algorithm 16 from https://eprint.iacr.org/2010/354.pdf
	var c4, c5, c1, c2, c3, c0 E2
	c4.Mul(&x.B0, &x.B1).Double(&c4)
	c5.Square(&x.B2)
	c1.MulByNonResidue(&c5).Add(&c1, &c4)
	c2.Sub(&c4, &c5)
	c3.Square(&x.B0)
	c4.Sub(&x.B0, &x.B1).Add(&c4, &x.B2)
	c5.Mul(&x.B1, &x.B2).Double(&c5)
	c4.Square(&c4)
	c0.MulByNonResidue(&c5).Add(&c0, &c3)
	z.B2.Add(&c2, &c4).Add(&z.B2, &c5).Sub(&z.B2, &c3)
	z.B0.Set(&c0)
	z.B1.Set(&c1)

	return z
}

// Inverse an element in E6
//
// if x == 0, sets and returns z = x
func (z *E6) Inverse(x *E6) *E6 {
	// Algorithm 17 from https://eprint.iacr.org/2010/354.pdf
	// step 9 is wrong in the paper it's t1-t4
	var t0, t1, t2, t3, t4, t5, t6, c0, c1, c2, d1, d2 E2
	t0.Square(&x.B0)
	t1.Square(&x.B1)
	t2.Square(&x.B2)
	t3.Mul(&x.B0, &x.B1)
	t4.Mul(&x.B0, &x.B2)
	t5.Mul(&x.B1, &x.B2)
	c0.MulByNonResidue(&t5).Neg(&c0).Add(&c0, &t0)
	c1.MulByNonResidue(&t2).Sub(&c1, &t3)
	c2.Sub(&t1, &t4)
	t6.Mul(&x.B0, &c0)
	d1.Mul(&x.B2, &c1)
	d2.Mul(&x.B1, &c2)
	d1.Add(&d1, &d2).MulByNonResidue(&d1)
	t6.Add(&t6, &d1)
	t6.Inverse(&t6)
	z.B0.Mul(&c0, &t6)
	z.B1.Mul(&c1, &t6)
	z.B2.Mul(&c2, &t6)

	return z
}

// BatchInvertE6 returns a new slice with every element inverted.
// Uses Montgomery batch inversion trick
//
// if a[i] == 0, returns result[i] = a[i]
func BatchInvertE6(a []E6) []E6 {
	res := make([]E6, len(a))
	if len(a) == 0 {
		return res
	}

	zeroes := make([]bool, len(a))
	var accumulator E6
	accumulator.SetOne()

	for i := 0; i < len(a); i++ {
		if a[i].IsZero() {
			zeroes[i] = true
			continue
		}
		res[i].Set(&accumulator)
		accumulator.Mul(&accumulator, &a[i])
	}

	accumulator.Inverse(&accumulator)

	for i := len(a) - 1; i >= 0; i-- {
		if zeroes[i] {
			continue
		}
		res[i].Mul(&res[i], &accumulator)
		accumulator.Mul(&accumulator, &a[i])
	}

	return res
}

func (z *E6) Select(cond int, caseZ *E6, caseNz *E6) *E6 {
	//Might be able to save a nanosecond or two by an aggregate implementation

	z.B0.Select(cond, &caseZ.B0, &caseNz.B0)
	z.B1.Select(cond, &caseZ.B1, &caseNz.B1)
	z.B2.Select(cond, &caseZ.B2, &caseNz.B2)

	return z
}

func (z *E6) Div(x *E6, y *E6) *E6 {
	var r E6
	r.Inverse(y).Mul(x, &r)
	return z.Set(&r)
}
