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

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

	"go.cypherpunks.su/gogost/v6/gost3410"
	"go.cypherpunks.su/gogost/v6/gost34112012256"
	"go.cypherpunks.su/gogost/v6/gost34112012512"

	cmhash "go.cypherpunks.su/keks/cm/hash"
	hashgost "go.cypherpunks.su/keks/cm/hash/gost"
	"go.cypherpunks.su/keks/cm/hash/merkle"
	"go.cypherpunks.su/keks/cm/sign/mode"
)

type Signer struct {
	NewHasher func() hash.Hash
	Prv       *gost3410.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
		p := s.NewHasher()
		s.prehasher = &p
		return nil
	case mode.Merkle:
		s.mode = m
		p := hashgost.NewMerkleHasher(s.NewHasher, merkle.DefaultChunkLen, cmhash.DefaultNumCPU)
		s.prehasher = &p
		return nil
	default:
		return errors.New("unsupported mode")
	}
}

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

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

func (s *Signer) Algo() string {
	switch s.Prv.C.PointSize() {
	case 32:
		if s.mode == mode.Merkle {
			return GOST3410256AMerkle
		}
		return GOST3410256A
	case 64:
		if s.mode == mode.Merkle {
			return GOST3410512CMerkle
		}
		return GOST3410512C
	default:
		return ""
	}
}

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

func (s *Signer) Sign(
	rand io.Reader,
	msg []byte,
	opts crypto.SignerOpts,
) (signature []byte, err error) {
	var hsh []byte
	switch s.mode {
	case mode.Pure:
		h := s.NewHasher()
		h.Write(msg)
		hsh = h.Sum(nil)
	case mode.Prehash, mode.Merkle:
		hsh = msg
	default:
		panic("unsupported mode")
	}
	signature, err = s.Prv.SignDigest(hsh, rand)
	if err != nil {
		return
	}
	signature = append(signature[len(signature)/2:], signature[:len(signature)/2]...)
	return
}

func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
	signer := Signer{}
	switch len(v) {
	case 32:
		signer.NewHasher = gost34112012256.New
		signer.Prv, err = gost3410.NewPrivateKeyBE(
			gost3410.CurveIdtc26gost341012256paramSetA(), v,
		)
	case 64:
		signer.NewHasher = gost34112012512.New
		signer.Prv, err = gost3410.NewPrivateKeyBE(
			gost3410.CurveIdtc26gost341012512paramSetC(), v,
		)
	default:
		err = errors.New("unsupported GOST R 34.10 algorithm")
	}
	if err != nil {
		return
	}
	prv = &signer
	var pk *gost3410.PublicKey
	pk, err = signer.Prv.PublicKey()
	if err == nil {
		pub = pk.RawBE()
	}
	return
}
