//
// Copyright Quilibrium, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

package curves

import (
	"crypto/subtle"
	"errors"
	"fmt"
	"io"
	"math/big"

	"github.com/cloudflare/circl/ecc/goldilocks"
	"github.com/cloudflare/circl/math/fp448"
	"golang.org/x/crypto/sha3"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
)

type ScalarEd448 struct {
	value *goldilocks.Scalar
}

type PointEd448 struct {
	value *goldilocks.Point
}

var gscOne = goldilocks.Scalar{
	1, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
}

var ed448Order = goldilocks.Scalar{
	0xf3, 0x44, 0x58, 0xab, 0x92, 0xc2, 0x78, 0x23,
	0x55, 0x8f, 0xc5, 0x8d, 0x72, 0xc2, 0x6c, 0x21,
	0x90, 0x36, 0xd6, 0xae, 0x49, 0xdb, 0x4e, 0xc4,
	0xe9, 0x23, 0xca, 0x7c, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
}

func (s *ScalarEd448) Random(reader io.Reader) Scalar {
	if reader == nil {
		return nil
	}
	var seed [57]byte
	_, _ = reader.Read(seed[:])
	return s.Hash(seed[:])
}

func (s *ScalarEd448) Hash(bytes []byte) Scalar {
	raw := [114]byte{}
	h := sha3.NewShake256()
	_, _ = h.Write(bytes)
	_, _ = h.Read(raw[:])
	value := &goldilocks.Scalar{}
	raw[0] &= 0xFC
	raw[55] |= 0x80
	raw[56] = 0x00
	value.FromBytes(raw[:57])

	return &ScalarEd448{value}
}

func (s *ScalarEd448) Zero() Scalar {
	return &ScalarEd448{
		value: &goldilocks.Scalar{},
	}
}

func (s *ScalarEd448) One() Scalar {
	value := &goldilocks.Scalar{}
	value.FromBytes(gscOne[:])
	return &ScalarEd448{value}
}

func (s *ScalarEd448) IsZero() bool {
	i := byte(0)
	for _, b := range s.value {
		i |= b
	}
	return i == 0
}

func (s *ScalarEd448) IsOne() bool {
	data := s.value
	i := byte(0)
	for j := 1; j < len(data); j++ {
		i |= data[j]
	}
	return i == 0 && data[0] == 1
}

func (s *ScalarEd448) IsOdd() bool {
	return s.value[0]&1 == 1
}

func (s *ScalarEd448) IsEven() bool {
	return s.value[0]&1 == 0
}

func (s *ScalarEd448) New(input int) Scalar {
	var data [56]byte
	i := input
	if input < 0 {
		i = -input
	}

	data[0] = byte(i)
	data[1] = byte(i >> 8)
	data[2] = byte(i >> 16)
	data[3] = byte(i >> 24)
	value := &goldilocks.Scalar{}
	value.FromBytes(data[:])

	if input < 0 {
		value.Neg()
	}

	return &ScalarEd448{
		value,
	}
}

func (s *ScalarEd448) Cmp(rhs Scalar) int {
	r := s.Sub(rhs)
	if r != nil && r.IsZero() {
		return 0
	} else {
		return -2
	}
}

func (s *ScalarEd448) Square() Scalar {
	value := &goldilocks.Scalar{}
	value.Mul(s.value, s.value)
	return &ScalarEd448{value}
}

func (s *ScalarEd448) Double() Scalar {
	value := &goldilocks.Scalar{}
	value.Add(s.value, s.value)
	return &ScalarEd448{value}
}

func (s *ScalarEd448) Invert() (Scalar, error) {
	ret := new(big.Int)
	order := new(big.Int)
	buf := internal.ReverseScalarBytes(s.value[:])
	orderBuf := internal.ReverseScalarBytes(ed448Order[:])
	ret.SetBytes(buf)
	order.SetBytes(orderBuf)
	value := &goldilocks.Scalar{}
	ret = ret.ModInverse(ret, order)
	value.FromBytes(internal.ReverseScalarBytes(ret.Bytes()))
	return &ScalarEd448{value}, nil
}

func (s *ScalarEd448) Sqrt() (Scalar, error) {
	return nil, errors.New("not supported")
}

func (s *ScalarEd448) Cube() Scalar {
	value := &goldilocks.Scalar{}
	value.Mul(s.value, s.value)
	value.Mul(value, s.value)
	return &ScalarEd448{value}
}

func (s *ScalarEd448) Add(rhs Scalar) Scalar {
	r, ok := rhs.(*ScalarEd448)
	if ok {
		value := &goldilocks.Scalar{}
		value.Add(s.value, r.value)
		return &ScalarEd448{value}
	} else {
		return nil
	}
}

func (s *ScalarEd448) Sub(rhs Scalar) Scalar {
	r, ok := rhs.(*ScalarEd448)
	if ok {
		value := &goldilocks.Scalar{}
		value.Sub(s.value, r.value)
		return &ScalarEd448{value}
	} else {
		return nil
	}
}

func (s *ScalarEd448) Mul(rhs Scalar) Scalar {
	r, ok := rhs.(*ScalarEd448)
	if ok {
		value := &goldilocks.Scalar{}
		value.Mul(s.value, r.value)
		return &ScalarEd448{value}
	} else {
		return nil
	}
}

func (s *ScalarEd448) MulAdd(y, z Scalar) Scalar {
	yy, ok := y.(*ScalarEd448)
	if !ok {
		return nil
	}
	zz, ok := z.(*ScalarEd448)
	if !ok {
		return nil
	}

	value := &goldilocks.Scalar{}
	value.Mul(s.value, yy.value)
	value.Add(value, zz.value)
	return &ScalarEd448{value}
}

func (s *ScalarEd448) Div(rhs Scalar) Scalar {
	r, ok := rhs.(*ScalarEd448)
	if ok {
		value, err := r.Invert()
		if err != nil {
			return nil
		}
		i, _ := value.(*ScalarEd448)
		i.value.Mul(i.value, s.value)
		return &ScalarEd448{value: i.value}
	} else {
		return nil
	}
}

func (s *ScalarEd448) Neg() Scalar {
	value := &goldilocks.Scalar{}
	copy(value[:], s.value[:])
	value.Neg()
	return &ScalarEd448{value}
}

func (s *ScalarEd448) SetBigInt(x *big.Int) (Scalar, error) {
	if x == nil {
		return nil, fmt.Errorf("invalid value")
	}

	order := new(big.Int)
	orderBuf := internal.ReverseScalarBytes(ed448Order[:])
	order.SetBytes(orderBuf)
	var v big.Int
	buf := v.Mod(x, order).Bytes()
	var rBuf [56]byte
	copy(rBuf[:], internal.ReverseScalarBytes(buf))
	value := &goldilocks.Scalar{}
	value.FromBytes(rBuf[:])
	return &ScalarEd448{value}, nil
}

func (s *ScalarEd448) BigInt() *big.Int {
	var ret big.Int
	buf := internal.ReverseScalarBytes(s.value[:])
	return ret.SetBytes(buf)
}

func (s *ScalarEd448) Bytes() []byte {
	return s.value[:]
}

// SetBytes takes input a 56-byte long array and returns a Ed448 scalar.
// The input must be 56-byte long and must be a reduced bytes.
func (s *ScalarEd448) SetBytes(input []byte) (Scalar, error) {
	if len(input) != 56 {
		return nil, fmt.Errorf("invalid byte sequence")
	}
	value := &goldilocks.Scalar{}
	value.FromBytes(input[:])

	return &ScalarEd448{value}, nil
}

// SetBytesWide takes input a 56-byte long byte array, reduce it and return an
// Ed448 scalar. If bytes is not of the right length, it returns nil and an
// error
func (s *ScalarEd448) SetBytesWide(bytes []byte) (Scalar, error) {
	if len(bytes) != 56 {
		return nil, fmt.Errorf("invalid byte sequence")
	}
	value := &goldilocks.Scalar{}
	value.FromBytes(bytes[:])

	return &ScalarEd448{value}, nil
}

// This function takes an input x and sets s = x, where x is a 56-byte
// little-endian encoding of s, then it returns the corresponding Ed448 scalar.
// If the input is not a canonical encoding of s, it returns nil and an error.
func (s *ScalarEd448) SetBytesCanonical(bytes []byte) (Scalar, error) {
	return s.SetBytes(bytes)
}

func (s *ScalarEd448) Point() Point {
	return new(PointEd448).Identity()
}

func (s *ScalarEd448) Clone() Scalar {
	value := &goldilocks.Scalar{}
	value.FromBytes(s.value[:])
	return &ScalarEd448{value}
}

func (s *ScalarEd448) MarshalBinary() ([]byte, error) {
	return scalarMarshalBinary(s)
}

func (s *ScalarEd448) UnmarshalBinary(input []byte) error {
	sc, err := scalarUnmarshalBinary(input)
	if err != nil {
		return err
	}
	ss, ok := sc.(*ScalarEd448)
	if !ok {
		return fmt.Errorf("invalid scalar")
	}
	s.value = ss.value
	return nil
}

func (s *ScalarEd448) MarshalText() ([]byte, error) {
	return scalarMarshalText(s)
}

func (s *ScalarEd448) UnmarshalText(input []byte) error {
	sc, err := scalarUnmarshalText(input)
	if err != nil {
		return err
	}
	ss, ok := sc.(*ScalarEd448)
	if !ok {
		return fmt.Errorf("invalid scalar")
	}
	s.value = ss.value
	return nil
}

func (s *ScalarEd448) MarshalJSON() ([]byte, error) {
	return scalarMarshalJson(s)
}

func (s *ScalarEd448) UnmarshalJSON(input []byte) error {
	sc, err := scalarUnmarshalJson(input)
	if err != nil {
		return err
	}
	S, ok := sc.(*ScalarEd448)
	if !ok {
		return fmt.Errorf("invalid type")
	}
	s.value = S.value
	return nil
}

func (p *PointEd448) Random(reader io.Reader) Point {
	var seed [114]byte
	_, _ = reader.Read(seed[:])
	return p.Hash(seed[:])
}

func (p *PointEd448) Hash(bytes []byte) Point {
	hashBytes := make([]byte, 114)
	h := sha3.NewShake256()
	_, _ = h.Write(bytes)
	_, _ = h.Read(hashBytes[:])
	value := &goldilocks.Scalar{}
	hashBytes[0] &= 0xFC
	hashBytes[55] |= 0x80
	hashBytes[56] = 0x00
	value.FromBytes(hashBytes[:57])
	point := (goldilocks.Curve{}).ScalarBaseMult(value)
	return &PointEd448{value: point}
}

func (p *PointEd448) Identity() Point {
	return &PointEd448{
		value: (goldilocks.Curve{}).Identity(),
	}
}

func (p *PointEd448) Generator() Point {
	return &PointEd448{
		value: (goldilocks.Curve{}).Generator(),
	}
}

func (p *PointEd448) IsIdentity() bool {
	return p.Equal(p.Identity())
}

func (p *PointEd448) IsNegative() bool {
	// Negative points don't really exist in Ed448
	return false
}

func (p *PointEd448) IsOnCurve() bool {
	err := (goldilocks.Curve{}).Identity().UnmarshalBinary(
		p.ToAffineCompressed(),
	)
	return err == nil
}

func (p *PointEd448) Double() Point {
	value, err := p.value.MarshalBinary()
	if err != nil {
		return nil
	}

	clone := &goldilocks.Point{}
	if err := clone.UnmarshalBinary(value); err != nil {
		return nil
	}

	clone.Double()

	return &PointEd448{value: clone}
}

func (p *PointEd448) Scalar() Scalar {
	return new(ScalarEd448).Zero()
}

func (p *PointEd448) Neg() Point {
	value, err := p.value.MarshalBinary()
	if err != nil {
		return nil
	}

	clone := &goldilocks.Point{}
	if err := clone.UnmarshalBinary(value); err != nil {
		return nil
	}

	clone.Neg()

	return &PointEd448{value: clone}
}

func (p *PointEd448) Add(rhs Point) Point {
	if rhs == nil {
		return nil
	}
	r, ok := rhs.(*PointEd448)
	if ok {
		value, err := p.value.MarshalBinary()
		if err != nil {
			return nil
		}

		clone := &goldilocks.Point{}
		if err := clone.UnmarshalBinary(value); err != nil {
			return nil
		}

		clone.Add(r.value)

		return &PointEd448{value: clone}
	} else {
		return nil
	}
}

func (p *PointEd448) Sub(rhs Point) Point {
	if rhs == nil {
		return nil
	}
	r, ok := rhs.(*PointEd448)
	if ok {
		value, err := r.value.MarshalBinary()
		if err != nil {
			return nil
		}

		clone := &goldilocks.Point{}
		if err := clone.UnmarshalBinary(value); err != nil {
			return nil
		}

		clone.Neg()

		clone.Add(p.value)

		return &PointEd448{value: clone}
	} else {
		return nil
	}
}

func (p *PointEd448) Mul(rhs Scalar) Point {
	if rhs == nil {
		return nil
	}
	r, ok := rhs.(*ScalarEd448)
	if ok {
		value, err := p.value.MarshalBinary()
		if err != nil {
			return nil
		}

		clone := &goldilocks.Point{}
		if err := clone.UnmarshalBinary(value); err != nil {
			return nil
		}

		clone = (goldilocks.Curve{}).ScalarMult(r.value, clone)

		return &PointEd448{value: clone}
	} else {
		return nil
	}
}

func (p *PointEd448) Equal(rhs Point) bool {
	r, ok := rhs.(*PointEd448)
	if ok {
		return p.value.IsEqual(r.value)
	} else {
		return false
	}
}

func (p *PointEd448) Set(x, y *big.Int) (Point, error) {
	// check is identity
	xx := subtle.ConstantTimeCompare(x.Bytes(), []byte{})
	yy := subtle.ConstantTimeCompare(y.Bytes(), []byte{})
	if (xx | yy) == 1 {
		return p.Identity(), nil
	}

	xElem := &fp448.Elt{}
	yElem := &fp448.Elt{}
	copy(xElem[:], internal.ReverseScalarBytes(x.Bytes()))
	copy(yElem[:], internal.ReverseScalarBytes(y.Bytes()))

	point, err := goldilocks.FromAffine(xElem, yElem)
	if err != nil {
		return nil, err
	}
	return &PointEd448{value: point}, nil
}

func (p *PointEd448) ToAffineCompressed() []byte {
	affineCompressed, err := p.value.MarshalBinary()
	if err != nil {
		return nil
	}

	return affineCompressed
}

func (p *PointEd448) ToAffineUncompressed() []byte {
	x, y := p.value.ToAffine()
	var out [112]byte
	copy(out[:56], x[:])
	copy(out[56:], y[:])
	return out[:]
}

func (p *PointEd448) FromAffineCompressed(inBytes []byte) (Point, error) {
	pt := (&goldilocks.Point{})
	err := pt.UnmarshalBinary(inBytes)

	if err != nil {
		return nil, err
	}

	return &PointEd448{value: pt}, nil
}

func (p *PointEd448) FromAffineUncompressed(inBytes []byte) (Point, error) {
	if len(inBytes) != 112 {
		return nil, fmt.Errorf("invalid byte sequence")
	}

	x := &fp448.Elt{}
	copy(x[:], inBytes[:56])
	y := &fp448.Elt{}
	copy(y[:], inBytes[56:])

	value, err := goldilocks.FromAffine(x, y)
	if err != nil {
		return nil, err
	}
	return &PointEd448{value}, nil
}

func (p *PointEd448) CurveName() string {
	return ED448Name
}

func (p *PointEd448) SumOfProducts(points []Point, scalars []Scalar) Point {
	// Unfortunately the primitives don't have have multi-scalar mult
	// implementation so we're left to do it the slow way
	nScalars := make([]*ScalarEd448, len(scalars))
	nPoints := make([]*PointEd448, len(points))
	for i, sc := range scalars {
		s, ok := sc.(*ScalarEd448)
		if !ok {
			return nil
		}
		nScalars[i] = s
	}
	for i, pt := range points {
		pp, ok := pt.(*PointEd448)
		if !ok {
			return nil
		}
		nPoints[i] = pp
	}

	accum := p.Identity().(*PointEd448)
	for i, p := range nPoints {
		s := nScalars[i]
		accum = accum.Add(p.Mul(s)).(*PointEd448)
	}

	return &PointEd448{value: accum.value}
}

func (p *PointEd448) MarshalBinary() ([]byte, error) {
	return pointMarshalBinary(p)
}

func (p *PointEd448) UnmarshalBinary(input []byte) error {
	pt, err := pointUnmarshalBinary(input)
	if err != nil {
		return err
	}
	ppt, ok := pt.(*PointEd448)
	if !ok {
		return fmt.Errorf("invalid point")
	}
	p.value = ppt.value
	return nil
}

func (p *PointEd448) MarshalText() ([]byte, error) {
	return pointMarshalText(p)
}

func (p *PointEd448) UnmarshalText(input []byte) error {
	pt, err := pointUnmarshalText(input)
	if err != nil {
		return err
	}
	ppt, ok := pt.(*PointEd448)
	if !ok {
		return fmt.Errorf("invalid point")
	}
	p.value = ppt.value
	return nil
}

func (p *PointEd448) MarshalJSON() ([]byte, error) {
	return pointMarshalJson(p)
}

func (p *PointEd448) UnmarshalJSON(input []byte) error {
	pt, err := pointUnmarshalJson(input)
	if err != nil {
		return err
	}
	P, ok := pt.(*PointEd448)
	if !ok {
		return fmt.Errorf("invalid type")
	}
	p.value = P.value
	return nil
}
