// Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.

package metamorphic

import "github.com/cockroachdb/pebble/internal/randvar"

type opType int

const (
	batchAbort opType = iota
	batchCommit
	dbCheckpoint
	dbClose
	dbCompact
	dbFlush
	dbRatchetFormatMajorVersion
	dbRestart
	iterClose
	iterFirst
	iterLast
	iterNext
	iterNextWithLimit
	iterNextPrefix
	iterCanSingleDelete
	iterPrev
	iterPrevWithLimit
	iterSeekGE
	iterSeekGEWithLimit
	iterSeekLT
	iterSeekLTWithLimit
	iterSeekPrefixGE
	iterSetBounds
	iterSetOptions
	newBatch
	newIndexedBatch
	newIter
	newIterUsingClone
	newSnapshot
	readerGet
	replicate
	snapshotClose
	writerApply
	writerDelete
	writerDeleteRange
	writerIngest
	writerMerge
	writerRangeKeyDelete
	writerRangeKeySet
	writerRangeKeyUnset
	writerSet
	writerSingleDelete
)

type config struct {
	// Weights for the operation mix to generate. ops[i] corresponds to the
	// weight for opType(i).
	ops []int

	// newPrefix configures the probability that when generating a new user key,
	// the generated key uses a new key prefix rather than an existing prefix
	// with a suffix.
	newPrefix float64
	// writeSuffixDist defines the distribution of key suffixes during writing.
	// It's a dynamic randvar to roughly emulate workloads with MVCC timestamps,
	// skewing towards most recent timestamps.
	writeSuffixDist randvar.Dynamic

	// numInstances defines the number of pebble instances created for this
	// metamorphic test run.
	numInstances int

	// TODO(peter): unimplemented
	// keyDist        randvar.Dynamic
	// keySizeDist    randvar.Static
	// valueSizeDist  randvar.Static
	// updateFrac     float64
	// lowerBoundFrac float64
	// upperBoundFrac float64
}

func (c config) withNewPrefixProbability(p float64) config {
	c.newPrefix = p
	return c
}

func (c config) withOpWeight(op opType, weight int) config {
	c.ops[op] = weight
	return c
}

var presetConfigs = []config{
	defaultConfig(),
	// Generate a configuration that helps exercise code paths dependent on many
	// versions of keys with the same prefixes. The default configuration does
	// not tend to generate many versions of the same key. Additionally, its
	// relatively high weight for deletion write operations makes it less likely
	// that we'll accumulate enough versions to exercise some code paths (eg,
	// see #2921 which requires >16 SETs for versions of the same prefix to
	// reside in a single block to exercise the code path).
	//
	// To encourage generation of many versions of the same keys, generate a new
	// prefix only 4% of the time when generating a new key. The remaining 96%
	// of new key generations will use an existing prefix. To keep the size of
	// the database growing, we also reduce the probability of delete write
	// operations significantly.
	defaultConfig().
		withNewPrefixProbability(0.04).
		withOpWeight(writerDeleteRange, 1).
		withOpWeight(writerDelete, 5).
		withOpWeight(writerSingleDelete, 5).
		withOpWeight(writerMerge, 0),
}

var multiInstancePresetConfig = multiInstanceConfig()

func defaultConfig() config {
	return config{
		// dbClose is not in this list since it is deterministically generated once, at the end of the test.
		ops: []int{
			batchAbort:                  5,
			batchCommit:                 5,
			dbCheckpoint:                1,
			dbCompact:                   1,
			dbFlush:                     2,
			dbRatchetFormatMajorVersion: 1,
			dbRestart:                   2,
			iterClose:                   5,
			iterFirst:                   100,
			iterLast:                    100,
			iterNext:                    100,
			iterNextWithLimit:           20,
			iterNextPrefix:              20,
			iterCanSingleDelete:         20,
			iterPrev:                    100,
			iterPrevWithLimit:           20,
			iterSeekGE:                  100,
			iterSeekGEWithLimit:         20,
			iterSeekLT:                  100,
			iterSeekLTWithLimit:         20,
			iterSeekPrefixGE:            100,
			iterSetBounds:               100,
			iterSetOptions:              10,
			newBatch:                    5,
			newIndexedBatch:             5,
			newIter:                     10,
			newIterUsingClone:           5,
			newSnapshot:                 10,
			readerGet:                   100,
			replicate:                   0,
			snapshotClose:               10,
			writerApply:                 10,
			writerDelete:                100,
			writerDeleteRange:           50,
			writerIngest:                100,
			writerMerge:                 100,
			writerRangeKeySet:           10,
			writerRangeKeyUnset:         10,
			writerRangeKeyDelete:        5,
			writerSet:                   100,
			writerSingleDelete:          50,
		},
		// Use a new prefix 75% of the time (and 25% of the time use an existing
		// prefix with an alternative suffix).
		newPrefix: 0.75,
		// Use a skewed distribution of suffixes to mimic MVCC timestamps. The
		// range will be widened whenever a suffix is found to already be in use
		// for a particular prefix.
		writeSuffixDist: mustDynamic(randvar.NewSkewedLatest(0, 1, 0.99)),
	}
}

func multiInstanceConfig() config {
	cfg := defaultConfig()
	cfg.ops[replicate] = 5
	// Single deletes and merges are disabled in multi-instance mode, as
	// replicateOp doesn't support them.
	cfg.ops[writerSingleDelete] = 0
	cfg.ops[writerMerge] = 0
	return cfg
}

func mustDynamic(dyn randvar.Dynamic, err error) randvar.Dynamic {
	if err != nil {
		panic(err)
	}
	return dyn
}