package shachain

import (
	"io"

	"github.com/btcsuite/btcd/chaincfg/chainhash"
)

// Producer is an interface which serves as an abstraction over the data
// structure responsible for efficiently generating the secrets for a
// particular index based on a root seed.  The generation of secrets should be
// made in such way that secret store might efficiently store and retrieve the
// secrets. This is typically implemented as a tree-based PRF.
type Producer interface {
	// AtIndex produces a secret by evaluating using the initial seed and a
	// particular index.
	AtIndex(uint64) (*chainhash.Hash, error)

	// Encode writes a binary serialization of the Producer implementation
	// to the passed io.Writer.
	Encode(io.Writer) error
}

// RevocationProducer is an implementation of Producer interface using the
// shachain PRF construct. Starting with a single 32-byte element generated
// from a CSPRNG, shachain is able to efficiently generate a nearly unbounded
// number of secrets while maintaining a constant amount of storage. The
// original description of shachain can be found here:
// https://github.com/rustyrussell/ccan/blob/master/ccan/crypto/shachain/design.txt
// with supplementary material here:
// https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#per-commitment-secret-requirements
type RevocationProducer struct {
	// root is the element from which we may generate all hashes which
	// corresponds to the index domain [281474976710655,0].
	root *element
}

// A compile time check to ensure RevocationProducer implements the Producer
// interface.
var _ Producer = (*RevocationProducer)(nil)

// NewRevocationProducer creates new instance of shachain producer.
func NewRevocationProducer(root chainhash.Hash) *RevocationProducer {
	return &RevocationProducer{
		root: &element{
			index: rootIndex,
			hash:  root,
		}}
}

// NewRevocationProducerFromBytes deserializes an instance of a
// RevocationProducer encoded in the passed byte slice, returning a fully
// initialized instance of a RevocationProducer.
func NewRevocationProducerFromBytes(data []byte) (*RevocationProducer, error) {
	root, err := chainhash.NewHash(data)
	if err != nil {
		return nil, err
	}

	return &RevocationProducer{
		root: &element{
			index: rootIndex,
			hash:  *root,
		},
	}, nil
}

// AtIndex produces a secret by evaluating using the initial seed and a
// particular index.
//
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) AtIndex(v uint64) (*chainhash.Hash, error) {
	ind := newIndex(v)

	element, err := p.root.derive(ind)
	if err != nil {
		return nil, err
	}

	return &element.hash, nil
}

// Encode writes a binary serialization of the Producer implementation to the
// passed io.Writer.
//
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) Encode(w io.Writer) error {
	_, err := w.Write(p.root.hash[:])
	return err
}