// Copyright (c) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package identity

import (
	"context"
	"crypto"

	"storj.io/common/pkcrypto"
	"storj.io/common/storj"
)

// GenerateKey generates a private key with a node id with difficulty at least
// minDifficulty. No parallelism is used.
func GenerateKey(ctx context.Context, minDifficulty uint16, version storj.IDVersion) (
	k crypto.PrivateKey, id storj.NodeID, err error) {
	defer mon.Task()(&ctx)(&err)

	var d uint16
	for {
		err = ctx.Err()
		if err != nil {
			break
		}
		k, err = version.NewPrivateKey()
		if err != nil {
			break
		}

		var pubKey crypto.PublicKey
		pubKey, err = pkcrypto.PublicKeyFromPrivate(k)
		if err != nil {
			break
		}

		id, err = NodeIDFromKey(pubKey, version)
		if err != nil {
			break
		}
		d, err = id.Difficulty()
		if err != nil {
			break
		}
		if d >= minDifficulty {
			return k, id, nil
		}
	}
	return k, id, storj.ErrNodeID.Wrap(err)
}

// GenerateCallback indicates that key generation is done when done is true.
// if err != nil key generation will stop with that error.
type GenerateCallback func(crypto.PrivateKey, storj.NodeID) (done bool, err error)

// GenerateKeys continues to generate keys until found returns done == false,
// or the ctx is canceled.
func GenerateKeys(ctx context.Context, minDifficulty uint16, concurrency int, version storj.IDVersion, found GenerateCallback) (err error) {
	defer mon.Task()(&ctx)(&err)
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	errchan := make(chan error, concurrency)

	for range concurrency {
		go func() {
			for {
				k, id, err := GenerateKey(ctx, minDifficulty, version)
				if err != nil {
					errchan <- err
					return
				}

				done, err := found(k, id)
				if err != nil {
					errchan <- err
					return
				}
				if done {
					errchan <- nil
					return
				}
			}
		}()
	}

	// we only care about the first error. the rest of the errors will be
	// context cancellation errors
	return <-errchan
}
