// 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 fext

import (
	"encoding/binary"
	"errors"
	"github.com/consensys/gnark-crypto/ecc/bls12-377/fr"
	"github.com/consensys/linea-monorepo/prover/maths/field"
	"math/big"
	"math/bits"
	"math/rand/v2"

	"strings"

	"github.com/bits-and-blooms/bitset"
)

const (
	frBytes = 32 // number of bytes needed to represent a Element
)

var RootPowers = []int{1, -11}

type Element = E2 // type alias

func NewElement(v1 uint64, v2 uint64) Element {
	z1 := fr.Element{v1}
	z1.Mul(&z1, &rSquare)

	z2 := fr.Element{v2}
	z2.Mul(&z2, &rSquare)
	return Element{z1, z2}
}

// NotEqual returns 0 if and only if z == x; constant-time
func (z *Element) NotEqual(x *Element) uint64 {
	if z.Equal(x) {
		return 0
	} else {
		return 1
	}
}

func Zero() Element {
	x := field.Zero()
	y := field.Zero()
	return Element{x, y}
}

// One returns 1
func One() Element {
	var one Element
	one.SetOne()
	return one
}

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

	zeroes := bitset.New(uint(len(a)))
	accumulator := One()

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

	accumulator.Inverse(&accumulator)

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

	return res
}

// Bytes returns the value of z as a big-endian byte array
func (z *Element) Bytes() (res [2 * frBytes]byte) {
	BigEndian.PutElement(&res, *z)
	return
}

// Marshal returns the value of z as a big-endian byte slice
func (z *Element) Marshal() []byte {
	b := z.Bytes()
	return b[:]
}

// Unmarshal is an alias for SetBytes, it sets z to the value of e.
func (z *Element) Unmarshal(e []byte) {
	z.SetBytes(e)
}

// SetBytes interprets e as the bytes of a big-endian unsigned integer,
// sets z to that value, and returns z.
func (z *Element) SetBytes(e []byte) *Element {
	if len(e) == 2*frBytes {
		// fast path
		vect := (*[2 * frBytes]byte)(e)
		v1, err1 := fr.BigEndian.Element((*[frBytes]byte)(vect[0:frBytes]))
		v2, err2 := fr.BigEndian.Element((*[frBytes]byte)(vect[frBytes : 2*frBytes]))
		if err1 == nil && err2 == nil {
			*z = Element{v1, v2}
			return z
		}
	}

	return z
}

// SetBytesCanonical interprets e as the bytes of a big-endian 32-byte integer.
// If e is not a 32-byte slice or encodes a value higher than q,
// SetBytesCanonical returns an error.
func (z *Element) SetBytesCanonical(e []byte) error {
	if len(e) != frBytes {
		return errors.New("invalid fr.Element encoding")
	}
	v, err := BigEndian.Element((*[2 * frBytes]byte)(e))
	if err != nil {
		return err
	}
	*z = v
	return nil
}

// setBigInt assumes 0 ⩽ v < q
func (z *Element) setBigInt(v1 *big.Int, v2 *big.Int) *Element {
	vBits := v1.Bits()

	if bits.UintSize == 64 {
		for i := 0; i < len(vBits); i++ {
			z.A0[i] = uint64(vBits[i])
		}
	} else {
		for i := 0; i < len(vBits); i++ {
			if i%2 == 0 {
				z.A0[i/2] = uint64(vBits[i])
			} else {
				z.A0[i/2] |= uint64(vBits[i]) << 32
			}
		}
	}
	ToMont(&z.A0)

	vBits = v2.Bits()

	if bits.UintSize == 64 {
		for i := 0; i < len(vBits); i++ {
			z.A1[i] = uint64(vBits[i])
		}
	} else {
		for i := 0; i < len(vBits); i++ {
			if i%2 == 0 {
				z.A1[i/2] = uint64(vBits[i])
			} else {
				z.A1[i/2] |= uint64(vBits[i]) << 32
			}
		}
	}
	ToMont(&z.A1)
	return z
}

// MarshalJSON returns json encoding of z (z.Text(10))
// If z == nil, returns null
func (z *Element) MarshalJSON() ([]byte, error) {
	if z == nil {
		return []byte("null"), nil
	}
	s := z.A0.Text(10)

	var sbb strings.Builder
	sbb.WriteByte('"')
	sbb.WriteString(s)
	sbb.WriteByte('"')

	s = z.A1.Text(10)
	/*
		if len(s) <= maxSafeBound {
			return []byte(s), nil
		}
	*/
	sbb.WriteByte('"')
	sbb.WriteString(s)
	sbb.WriteByte('"')
	return []byte(sbb.String()), nil
}

// A ByteOrder specifies how to convert byte slices into a Element
type ByteOrder interface {
	Element(*[frBytes]byte) (Element, error)
	PutElement(*[frBytes]byte, Element)
	String() string
}

// BigEndian is the big-endian implementation of ByteOrder and AppendByteOrder.
var BigEndian bigEndian

type bigEndian struct{}

// Element interpret b is a big-endian 32-byte slice.
// If b encodes a value higher than q, Element returns error.
func (bigEndian) Element(b *[2 * frBytes]byte) (Element, error) {
	var z fr.Element
	z[0] = binary.BigEndian.Uint64((*b)[24:32])
	z[1] = binary.BigEndian.Uint64((*b)[16:24])
	z[2] = binary.BigEndian.Uint64((*b)[8:16])
	z[3] = binary.BigEndian.Uint64((*b)[0:8])

	var x fr.Element
	x[0] = binary.BigEndian.Uint64((*b)[frBytes+24 : frBytes+32])
	x[1] = binary.BigEndian.Uint64((*b)[frBytes+16 : frBytes+24])
	x[2] = binary.BigEndian.Uint64((*b)[frBytes+8 : frBytes+16])
	x[3] = binary.BigEndian.Uint64((*b)[frBytes+0 : frBytes+8])

	if !SmallerThanModulus(&z) {
		return Element{}, errors.New("invalid fr.Element encoding")
	}

	ToMont(&z)

	if !SmallerThanModulus(&x) {
		return Element{}, errors.New("invalid fr.Element encoding")
	}
	ToMont(&x)

	return Element{z, x}, nil
}

func (bigEndian) PutElement(b *[2 * frBytes]byte, e Element) {
	FromMont(&e.A0)
	binary.BigEndian.PutUint64((*b)[24:32], e.A0[0])
	binary.BigEndian.PutUint64((*b)[16:24], e.A0[1])
	binary.BigEndian.PutUint64((*b)[8:16], e.A0[2])
	binary.BigEndian.PutUint64((*b)[0:8], e.A0[3])

	FromMont(&e.A1)
	binary.BigEndian.PutUint64((*b)[frBytes+24:frBytes+32], e.A1[0])
	binary.BigEndian.PutUint64((*b)[frBytes+16:frBytes+24], e.A1[1])
	binary.BigEndian.PutUint64((*b)[frBytes+8:frBytes+16], e.A1[2])
	binary.BigEndian.PutUint64((*b)[frBytes+0:frBytes+8], e.A1[3])
}

func (bigEndian) String() string { return "BigEndian" }

// LittleEndian is the little-endian implementation of ByteOrder and AppendByteOrder.
var LittleEndian littleEndian

type littleEndian struct{}

func (littleEndian) Element(b *[2 * frBytes]byte) (Element, error) {
	var z Element
	z.A0[0] = binary.LittleEndian.Uint64((*b)[0:8])
	z.A0[1] = binary.LittleEndian.Uint64((*b)[8:16])
	z.A0[2] = binary.LittleEndian.Uint64((*b)[16:24])
	z.A0[3] = binary.LittleEndian.Uint64((*b)[24:32])

	if !SmallerThanModulus(&z.A0) {
		return Element{}, errors.New("invalid fr.Element encoding")
	}
	ToMont(&z.A0)

	z.A1[0] = binary.LittleEndian.Uint64((*b)[frBytes+0 : frBytes+8])
	z.A1[1] = binary.LittleEndian.Uint64((*b)[frBytes+8 : frBytes+16])
	z.A1[2] = binary.LittleEndian.Uint64((*b)[frBytes+16 : frBytes+24])
	z.A1[3] = binary.LittleEndian.Uint64((*b)[frBytes+24 : frBytes+32])

	ToMont(&z.A1)
	return z, nil
}

func (littleEndian) PutElement(b *[2 * frBytes]byte, e Element) {
	FromMont(&e.A0)
	binary.LittleEndian.PutUint64((*b)[0:8], e.A0[0])
	binary.LittleEndian.PutUint64((*b)[8:16], e.A0[1])
	binary.LittleEndian.PutUint64((*b)[16:24], e.A0[2])
	binary.LittleEndian.PutUint64((*b)[24:32], e.A0[3])

	FromMont(&e.A1)
	binary.LittleEndian.PutUint64((*b)[frBytes+0:frBytes+8], e.A1[0])
	binary.LittleEndian.PutUint64((*b)[frBytes+8:frBytes+16], e.A1[1])
	binary.LittleEndian.PutUint64((*b)[frBytes+16:frBytes+24], e.A1[2])
	binary.LittleEndian.PutUint64((*b)[frBytes+24:frBytes+32], e.A1[3])
}

func (littleEndian) String() string { return "LittleEndian" }

// linearComb z = xC * x + yC * y;
// 0 ≤ x, y < 2²⁵³
// |xC|, |yC| < 2⁶³
func (z *Element) linearComb(x *Element, xC int64, y *Element, yC int64) {
	var e1, e2 Element
	e1.Set(x)
	e2.Set(y)
	var i int64
	for i = 0; i < xC-1; i++ {
		e1.Add(&e1, x)
	}
	for i = 0; i < yC-1; i++ {
		e2.Add(&e1, y)
	}
	z.Add(&e1, &e2)
}

func (z *Element) SetFromBase(x *fr.Element) *Element {
	z.A0.Set(x)
	z.A1.SetZero()
	return z
}

func ExpToInt(z *Element, x Element, k int) *Element {
	if k == 0 {
		return z.SetOne()
	}

	if k < 0 {
		x.Inverse(&x)
		k = -k
	}

	z.Set(&x)

	for i := bits.Len(uint(k)) - 2; i >= 0; i-- {
		z.Square(z)
		if (k>>i)&1 == 1 {
			z.Mul(z, &x)
		}
	}

	return z
}

func PseudoRand(rng *rand.Rand) Element {
	x := field.PseudoRand(rng)
	y := field.PseudoRand(rng)
	result := new(Element).SetZero()
	return *result.Add(result, &Element{x, y})
}
