package random

import (
	"crypto/rand"
	"encoding/hex"
	"io"
	"math"
	math_rand "math/rand"
)

// Interface for our random number generator. We need this
// to fake random  values in tests.
type RandomProvider interface {
	Bytes(bytes int) ([]byte, error)
	HexDigest(bytes int) (string, error)
}

// Real random values, used in production
type CSPRNG struct{}

// Return n-bytes of random values from the CSPRNG.
func (c *CSPRNG) Bytes(bytes int) ([]byte, error) {
	n := make([]byte, bytes)

	// get bytes-bit random number from /dev/urandom
	_, err := io.ReadFull(rand.Reader, n)
	if err != nil {
		return nil, err
	}

	return n, nil
}

// Return n-bytes of random values from the CSPRNG but as a
// hex-encoded (base16) string.
func (c *CSPRNG) HexDigest(bytes int) (string, error) {
	return hexDigest(c, bytes)
}

// Fake random, used in tests. never use this in production!
type FakeRNG struct{}

// Fake random number generator, never use in production. Always
// returns a predictable sequence of bytes that looks like: 0x00,
// 0x01, 0x02, 0x03, ...
func (f *FakeRNG) Bytes(bytes int) ([]byte, error) {
	// create bytes long array
	b := make([]byte, bytes)

	for i := 0; i < len(b); i++ {
		b[i] = byte(i)
	}

	return b, nil
}

// Fake random number generator, never use in production. Always returns
// a predictable hex-encoded (base16) string that looks like "00010203..."
func (f *FakeRNG) HexDigest(bytes int) (string, error) {
	return hexDigest(f, bytes)
}

// SeededRNG returns bytes generated in a predictable sequence by package math/rand.
// Not cryptographically secure, not thread safe.
// Changes to Seed after the first call to Bytes or HexDigest
// will have no effect. The zero value of SeededRNG is ready to use,
// and will use a seed of 0.
type SeededRNG struct {
	Seed int64
	rand *math_rand.Rand
}

// Bytes conforms to the RandomProvider interface. Returns bytes
// generated by a math/rand.Rand.
func (r *SeededRNG) Bytes(bytes int) ([]byte, error) {
	if r.rand == nil {
		r.rand = math_rand.New(math_rand.NewSource(r.Seed))
	}
	b := make([]byte, bytes)
	for i := range b {
		b[i] = byte(r.rand.Intn(math.MaxUint8 + 1))
	}
	return b, nil
}

// HexDigest conforms to the RandomProvider interface. Returns
// a hex encoding of bytes generated by a math/rand.Rand.
func (r *SeededRNG) HexDigest(bytes int) (string, error) {
	return hexDigest(r, bytes)
}

func hexDigest(r RandomProvider, bytes int) (string, error) {
	b, err := r.Bytes(bytes)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}
