package elgamal

import (
	"crypto/rand"
	"errors"
	"io"
	"math/big"

	"xdx.jelly/xgcl/grand"
)

var Reader = grand.Reader

type Params struct {
	isMont bool // Is Montgomery represent
	p      *big.Int
	q      *big.Int
	g      *big.Int
	pMont  *big.Int
	qMont  *big.Int
	gMont  *big.Int
}

func (p *Params) P() *big.Int {
	return p.p
}
func (p *Params) Q() *big.Int {
	return p.q
}
func (p *Params) G() *big.Int {
	return p.g
}

type PublicKey struct {
	y      big.Int
	Params *Params
}

type PrivateKey struct {
	x big.Int
	PublicKey
}
type Cipher struct {
	u big.Int
	v big.Int
}

func GenerateKey(rnd io.Reader, params *Params) (*PrivateKey, *PublicKey, error) {
	x, err := rand.Int(rnd, params.q)
	if err != nil {
		return nil, nil, err
	}
	y := new(big.Int).Exp(params.g, x, params.p)
	sk := &PrivateKey{
		x: *x,
		PublicKey: PublicKey{
			y:      *y,
			Params: P1024,
		},
	}
	pk := &sk.PublicKey
	return sk, pk, nil
}

// m = g^M mod p for some M
// rnd can be nil, io.Reader or []byte
func (pk *PublicKey) Encryption(m *big.Int, rnd any) (*Cipher, error) {
	var r *big.Int
	var err error
	p := pk.Params.p
	q := pk.Params.q
	g := pk.Params.g

	switch rnd := rnd.(type) {
	case nil:
		rnd = Reader
	case io.Reader:
		r, err = rand.Int(rnd, q)
		if err != nil {
			return nil, err
		}
	case []byte:
		r = new(big.Int).SetBytes(rnd)
		if r.Cmp(q) >= 0 {
			return nil, errors.New("random bytes too large")
		}
	default:
		return nil, errors.New("rnd must be io.Reader or []bytes")
	}

	c := &Cipher{}
	c.u.Exp(g, r, p)
	c.v.Exp(&pk.y, r, p)
	c.v.Mul(&c.v, m)
	return c, nil
}

func (sk *PrivateKey) Decryption(c *Cipher) (*big.Int, error) {
	p := sk.PublicKey.Params.p
	u := &c.u
	v := &c.v
	z := new(big.Int)
	z.Exp(u, &sk.x, p)
	z.ModInverse(z, p)
	z.Mul(z, v)
	z.Mod(z, p)
	return z, nil
}

func (c *Cipher) HomoMap(c1 *Cipher, c2 *Cipher) (*Cipher, error) {
	c.u.Mul(&c1.u, &c2.u)
	c.v.Mul(&c1.v, &c2.v)
	return c, nil
}
