// Copyright 2011 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 base

import (
	"fmt"
	"slices"
	"testing"
	"time"

	"golang.org/x/exp/rand"
)

func TestDefAppendSeparator(t *testing.T) {
	testCases := []struct {
		a, b, want string
	}{
		// Examples from the doc comments.
		{"black", "blue", "blb"},
		{"green", "", "green"},
		// Non-empty b values. The C++ Level-DB code calls these separators.
		{"", "2", ""},
		{"1", "2", "1"},
		{"1", "29", "2"},
		{"13", "19", "14"},
		{"13", "99", "2"},
		{"135", "19", "14"},
		{"1357", "19", "14"},
		{"1357", "2", "14"},
		{"13\xff", "14", "13\xff"},
		{"13\xff", "19", "14"},
		{"1\xff\xff", "19", "1\xff\xff"},
		{"1\xff\xff", "2", "1\xff\xff"},
		{"1\xff\xff", "9", "2"},
		// Empty b values. The C++ Level-DB code calls these successors.
		{"", "", ""},
		{"1", "", "1"},
		{"11", "", "11"},
		{"11\xff", "", "11\xff"},
		{"1\xff", "", "1\xff"},
		{"1\xff\xff", "", "1\xff\xff"},
		{"\xff", "", "\xff"},
		{"\xff\xff", "", "\xff\xff"},
		{"\xff\xff\xff", "", "\xff\xff\xff"},
	}
	for _, tc := range testCases {
		t.Run("", func(t *testing.T) {
			got := string(DefaultComparer.Separator(nil, []byte(tc.a), []byte(tc.b)))
			if got != tc.want {
				t.Errorf("a, b = %q, %q: got %q, want %q", tc.a, tc.b, got, tc.want)
			}
		})
	}
}

func TestAbbreviatedKey(t *testing.T) {
	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
	randBytes := func(size int) []byte {
		data := make([]byte, size)
		for i := range data {
			data[i] = byte(rng.Int() & 0xff)
		}
		return data
	}

	keys := make([][]byte, 10000)
	for i := range keys {
		keys[i] = randBytes(rng.Intn(16))
	}
	slices.SortFunc(keys, DefaultComparer.Compare)

	for i := 1; i < len(keys); i++ {
		last := DefaultComparer.AbbreviatedKey(keys[i-1])
		cur := DefaultComparer.AbbreviatedKey(keys[i])
		cmp := DefaultComparer.Compare(keys[i-1], keys[i])
		if cmp == 0 {
			if last != cur {
				t.Fatalf("expected equal abbreviated keys: %x[%x] != %x[%x]",
					last, keys[i-1], cur, keys[i])
			}
		} else {
			if last > cur {
				t.Fatalf("unexpected abbreviated key ordering: %x[%x] > %x[%x]",
					last, keys[i-1], cur, keys[i])
			}
		}
	}
}

func BenchmarkAbbreviatedKey(b *testing.B) {
	rng := rand.New(rand.NewSource(1449168817))
	randBytes := func(size int) []byte {
		data := make([]byte, size)
		for i := range data {
			data[i] = byte(rng.Int() & 0xff)
		}
		return data
	}
	keys := make([][]byte, 10000)
	for i := range keys {
		keys[i] = randBytes(8)
	}

	b.ResetTimer()
	var sum uint64
	for i := 0; i < b.N; i++ {
		j := i % len(keys)
		sum += DefaultComparer.AbbreviatedKey(keys[j])
	}

	if testing.Verbose() {
		// Ensure the compiler doesn't optimize away our benchmark.
		fmt.Println(sum)
	}
}
