// Copyright 2022 The Go 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 benchfmt

import (
	"fmt"
	"reflect"
	"testing"
)

func TestResultSetConfig(t *testing.T) {
	r := &Result{}
	check := func(want ...string) {
		t.Helper()
		var kv []string
		for _, cfg := range r.Config {
			kv = append(kv, fmt.Sprintf("%s: %s", cfg.Key, cfg.Value))
		}
		if !reflect.DeepEqual(want, kv) {
			t.Errorf("want %q, got %q", want, kv)
		}

		// Check the index.
		for i, cfg := range r.Config {
			gotI, ok := r.ConfigIndex(cfg.Key)
			if !ok {
				t.Errorf("key %s missing from index", cfg.Key)
			} else if i != gotI {
				t.Errorf("key %s index: want %d, got %d", cfg.Key, i, gotI)
			}
		}
		if len(r.Config) != len(r.configPos) {
			t.Errorf("index size mismatch: %d file configs, %d index length", len(r.Config), len(r.configPos))
		}
	}

	// Basic key additions.
	check()
	r.SetConfig("a", "b")
	check("a: b")
	r.SetConfig("x", "y")
	check("a: b", "x: y")
	r.SetConfig("z", "w")
	check("a: b", "x: y", "z: w")

	// Update value.
	r.SetConfig("x", "z")
	check("a: b", "x: z", "z: w")

	// Delete key.
	r.SetConfig("a", "") // Check swapping
	check("z: w", "x: z")
	r.SetConfig("x", "") // Last key
	check("z: w")
	r.SetConfig("c", "") // Non-existent
	check("z: w")

	// Add key after deletion.
	r.SetConfig("c", "d")
	check("z: w", "c: d")
}

func TestResultGetConfig(t *testing.T) {
	r := &Result{}
	check := func(key, want string) {
		t.Helper()
		got := r.GetConfig(key)
		if want != got {
			t.Errorf("for key %s: want %s, got %s", key, want, got)
		}
	}
	check("x", "")
	r.SetConfig("x", "y")
	check("x", "y")
	r.SetConfig("a", "b")
	check("a", "b")
	check("x", "y")
	r.SetConfig("a", "")
	check("a", "")
	check("x", "y")

	// Test a literal.
	r = &Result{
		Config: []Config{{Key: "a", Value: []byte("b")}},
	}
	check("a", "b")
	check("x", "")
}

func TestResultValue(t *testing.T) {
	r := &Result{
		Values: []Value{{42, "ns/op", 42e-9, "sec/op"}, {24, "B/op", 0, ""}},
	}
	check := func(unit string, want float64) {
		t.Helper()
		got, ok := r.Value(unit)
		if !ok {
			t.Errorf("missing unit %s", unit)
		} else if want != got {
			t.Errorf("for unit %s: want %v, got %v", unit, want, got)
		}
	}
	check("ns/op", 42)
	check("B/op", 24)
	_, ok := r.Value("B/sec")
	if ok {
		t.Errorf("unexpectedly found unit %s", "B/sec")
	}
}

func TestBaseName(t *testing.T) {
	check := func(fullName string, want string) {
		t.Helper()
		got := string(Name(fullName).Base())
		if got != want {
			t.Errorf("BaseName(%q) = %q, want %q", fullName, got, want)
		}
	}
	check("Test", "Test")
	check("Test-42", "Test")
	check("Test/foo", "Test")
	check("Test/foo-42", "Test")
}

func TestNameParts(t *testing.T) {
	check := func(fullName string, base string, parts ...string) {
		t.Helper()
		got, gotParts := Name(fullName).Parts()
		fail := string(got) != base
		if len(gotParts) != len(parts) {
			fail = true
		} else {
			for i := range parts {
				if parts[i] != string(gotParts[i]) {
					fail = true
				}
			}
		}
		if fail {
			t.Errorf("FullName(%q) = %q, %q, want %q, %q", fullName, got, gotParts, base, parts)
		}
	}
	check("Test", "Test")
	// Gomaxprocs
	check("Test-42", "Test", "-42")
	// Subtests
	check("Test/foo", "Test", "/foo")
	check("Test/foo=42/bar=24", "Test", "/foo=42", "/bar=24")
	// Both
	check("Test/foo=123-42", "Test", "/foo=123", "-42")
	// Looks like gomaxprocs, but isn't
	check("Test/foo-bar", "Test", "/foo-bar")
	check("Test/foo-1/bar", "Test", "/foo-1", "/bar")
	// Trailing slash
	check("Test/foo/", "Test", "/foo", "/")
	// Empty name
	check("", "")
	check("/a/b", "", "/a", "/b")
}
