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

// this file allows to run benchmarks via go test
package benchmark

import (
	"database/sql"
	"flag"
	"fmt"
	"os"
	"runtime"
	"testing"

	"github.com/klauspost/cpuid/v2"
	_ "github.com/mattn/go-sqlite3"
	_ "modernc.org/sqlite"
)

var (
	// flag, allows to run each benchmark multiple times and average the results. this may provide more stable results between runs
	reps uint

	// flag, whether to use in-memory SQLite
	inMemory bool

	// benchmark funcs to execute
	funcs = []func(*testing.B, *sql.DB){
		benchCreateIndex,
		benchSelectOnStringComparison,
		benchSelectWithIndex,
		benchSelectWithoutIndex,
		benchInsert,
		benchInsertInTransaction,
		benchInsertIntoIndexed,
		benchInsertFromSelect,
		benchUpdateTextWithIndex,
		benchUpdateWithIndex,
		benchUpdateWithoutIndex,
		benchDeleteWithoutIndex,
		benchDeleteWithIndex,

		// due to very long run of this benchmark, it is disabled
		// benchDropTable,
	}
)

func TestMain(m *testing.M) {
	flag.UintVar(&reps, "rep", 1, "allows to run each benchmark multiple times and average the results. this may provide more stable results between runs")
	flag.BoolVar(&inMemory, "mem", false, "if set, use in-memory SQLite")
	flag.Parse()
	os.Exit(m.Run())
}

func TestBenchmarkSQLite(t *testing.T) {
	// print info about CPU and OS
	fmt.Println()
	fmt.Printf("goos:   %s\n", runtime.GOOS)
	fmt.Printf("goarch: %s\n", runtime.GOARCH)
	if cpu := cpuid.CPU.BrandName; cpu != "" {
		fmt.Printf("cpu:    %s\n", cpu)
	}
	fmt.Printf("repeat: %d time(s)\n", reps)
	fmt.Printf("in-memory SQLite: %v\n", inMemory)
	fmt.Println()

	// loop on functions
	for _, f := range funcs {

		var (
			nsPerOpCGo    avgVal
			nsPerOpPureGo avgVal
		)

		// run benchmark against different drivers
		for r := uint(0); r < reps; r++ {
			// -- run bench against Cgo --
			db := createDB(t, inMemory, "sqlite3")
			br := testing.Benchmark(func(b *testing.B) { f(b, db) })

			// contribue metric to average
			nsPerOpCGo.contribInt(br.NsPerOp())

			// close DB
			if err := db.Close(); err != nil {
				t.Fatal(err)
			}

			// -- run bench against Pure-go --
			db = createDB(t, inMemory, "sqlite")
			br = testing.Benchmark(func(b *testing.B) { f(b, db) })

			// contribue metric to average
			nsPerOpPureGo.contribInt(br.NsPerOp())
			// close DB
			if err := db.Close(); err != nil {
				t.Fatal(err)
			}
		}

		// print result row
		fmt.Printf("%-35s | %5.2fx | CGo: %7.3f ms/op | Pure-Go: %7.3f ms/op\n",
			toSnakeCase(getFuncName(f)),
			nsPerOpPureGo.val/nsPerOpCGo.val, // factor
			nsPerOpCGo.val/1e6,               // ms/op
			nsPerOpPureGo.val/1e6,            // ms/op
		)
	}
}