// KEKS/CM -- KEKS-encoded cryptographic messages
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

package ed25519blake2b

import (
	"crypto"
	"errors"
	"hash"
	"io"

	cmhash "go.cypherpunks.su/keks/cm/hash"
	cmblake2b "go.cypherpunks.su/keks/cm/hash/blake2b"
	"go.cypherpunks.su/keks/cm/hash/merkle"
	"go.cypherpunks.su/keks/cm/sign/ed25519-blake2b/ed25519"
	"go.cypherpunks.su/keks/cm/sign/mode"
	"golang.org/x/crypto/blake2b"
)

type Signer struct {
	Prv       *ed25519.PrivateKey
	prehasher *hash.Hash
	mode      mode.Mode
}

func (s *Signer) SetMode(m mode.Mode) error {
	switch m {
	case mode.Pure:
		s.mode = m
		return nil
	case mode.Prehash:
		s.mode = m
		h, err := blake2b.New512(nil)
		if err != nil {
			return err
		}
		s.prehasher = &h
		return nil
	case mode.Merkle:
		s.mode = m
		h := cmblake2b.NewMerkleHasher(merkle.DefaultChunkLen, cmhash.DefaultNumCPU)
		s.prehasher = &h
		return nil
	default:
		return errors.New("unsupported mode")
	}
}

func (s *Signer) Mode() mode.Mode {
	return s.mode
}

func (s *Signer) Algo() string {
	switch s.mode {
	case mode.Pure:
		return Ed25519BLAKE2b
	case mode.Prehash:
		return Ed25519PhBLAKE2b
	case mode.Merkle:
		return Ed25519PhBLAKE2bMerkle
	}
	return ""
}

func (s *Signer) Public() crypto.PublicKey {
	return s.Prv.PublicKey()
}

func (s *Signer) Sign(
	rand io.Reader,
	msg []byte,
	opts crypto.SignerOpts,
) (signature []byte, err error) {
	switch s.mode {
	case mode.Pure:
		return ed25519.Sign(s.Prv, msg), nil
	case mode.Prehash, mode.Merkle:
		return ed25519.SignPH(s.Prv, msg, "")
	default:
		panic("unsupported mode")
	}
}

func (s *Signer) Prehasher() *hash.Hash {
	return s.prehasher
}

func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
	if len(v) != 32 {
		err = errors.New("wrong ed25519 private key size")
		return
	}
	var p *ed25519.PrivateKey
	p, err = ed25519.NewPrivateKeyFromSeed(v)
	if err != nil {
		return
	}
	pub = p.PublicKey()
	prv = &Signer{Prv: p}
	return
}
