package cachekv_test

import (
	"testing"

	dbm "github.com/tendermint/tm-db"

	"github.com/cosmos/cosmos-sdk/store/cachekv"
	"github.com/cosmos/cosmos-sdk/store/dbadapter"
)

var sink interface{}

const defaultValueSizeBz = 1 << 12

// This benchmark measures the time of iterator.Next() when the parent store is blank
func benchmarkBlankParentIteratorNext(b *testing.B, keysize int) {
	mem := dbadapter.Store{DB: dbm.NewMemDB()}
	kvstore := cachekv.NewStore(mem)
	// Use a singleton for value, to not waste time computing it
	value := randSlice(defaultValueSizeBz)
	// Use simple values for keys, pick a random start,
	// and take next b.N keys sequentially after.]
	startKey := randSlice(32)

	// Add 1 to avoid issues when b.N = 1
	keys := generateSequentialKeys(startKey, b.N+1)
	for _, k := range keys {
		kvstore.Set(k, value)
	}

	b.ReportAllocs()
	b.ResetTimer()

	iter := kvstore.Iterator(keys[0], keys[b.N])
	defer iter.Close()

	for _ = iter.Key(); iter.Valid(); iter.Next() {
		// deadcode elimination stub
		sink = iter
	}
}

// Benchmark setting New keys to a store, where the new keys are in sequence.
func benchmarkBlankParentAppend(b *testing.B, keysize int) {
	mem := dbadapter.Store{DB: dbm.NewMemDB()}
	kvstore := cachekv.NewStore(mem)

	// Use a singleton for value, to not waste time computing it
	value := randSlice(32)
	// Use simple values for keys, pick a random start,
	// and take next b.N keys sequentially after.
	startKey := randSlice(32)

	keys := generateSequentialKeys(startKey, b.N)

	b.ReportAllocs()
	b.ResetTimer()

	for _, k := range keys {
		kvstore.Set(k, value)
	}
}

// Benchmark setting New keys to a store, where the new keys are random.
// the speed of this function does not depend on the values in the parent store
func benchmarkRandomSet(b *testing.B, keysize int) {
	mem := dbadapter.Store{DB: dbm.NewMemDB()}
	kvstore := cachekv.NewStore(mem)

	// Use a singleton for value, to not waste time computing it
	value := randSlice(defaultValueSizeBz)
	keys := generateRandomKeys(keysize, b.N)

	b.ReportAllocs()
	b.ResetTimer()

	for _, k := range keys {
		kvstore.Set(k, value)
	}

	iter := kvstore.Iterator(keys[0], keys[b.N])
	defer iter.Close()

	for _ = iter.Key(); iter.Valid(); iter.Next() {
		// deadcode elimination stub
		sink = iter
	}
}

// Benchmark creating an iterator on a parent with D entries,
// that are all deleted in the cacheKV store.
// We essentially are benchmarking the cacheKV iterator creation & iteration times
// with the number of entries deleted in the parent.
func benchmarkIteratorOnParentWithManyDeletes(b *testing.B, numDeletes int) {
	mem := dbadapter.Store{DB: dbm.NewMemDB()}

	// Use a singleton for value, to not waste time computing it
	value := randSlice(32)
	// Use simple values for keys, pick a random start,
	// and take next D keys sequentially after.
	startKey := randSlice(32)
	keys := generateSequentialKeys(startKey, numDeletes)
	// setup parent db with D keys.
	for _, k := range keys {
		mem.Set(k, value)
	}
	kvstore := cachekv.NewStore(mem)
	// Delete all keys from the cache KV store.
	// The keys[1:] is to keep at least one entry in parent, due to a bug in the SDK iterator design.
	// Essentially the iterator will never be valid, in that it should never run.
	// However, this is incompatible with the for loop structure the SDK uses, hence
	// causes a panic. Thus we do keys[1:].
	for _, k := range keys[1:] {
		kvstore.Delete(k)
	}

	b.ReportAllocs()
	b.ResetTimer()

	iter := kvstore.Iterator(keys[0], keys[b.N])
	defer iter.Close()

	for _ = iter.Key(); iter.Valid(); iter.Next() {
		// deadcode elimination stub
		sink = iter
	}
}

func BenchmarkBlankParentIteratorNextKeySize32(b *testing.B) {
	benchmarkBlankParentIteratorNext(b, 32)
}

func BenchmarkBlankParentAppendKeySize32(b *testing.B) {
	benchmarkBlankParentAppend(b, 32)
}

func BenchmarkSetKeySize32(b *testing.B) {
	benchmarkRandomSet(b, 32)
}

func BenchmarkIteratorOnParentWith1MDeletes(b *testing.B) {
	benchmarkIteratorOnParentWithManyDeletes(b, 1_000_000)
}
