package optimizelab

import (
	"bytes"
	"encoding/json"
	jsonv2 "encoding/json/v2"
	"fmt"
	"reflect"
	"strings"
	"sync"
	"testing"
)

var sliceSink []int
var intSink *int

func TestBehavior(t *testing.T) {
	for _, n := range []int{1, 16, 1024} {
		if !reflect.DeepEqual(GrowSlice(n), ReserveSlice(n)) {
			t.Fatal(n)
		}
	}
	parts := []string{"a", "中", "b"}
	if Build(parts, false) != Build(parts, true) {
		t.Fatal("builder")
	}
	for _, add := range []func(*Counter, []int){(*Counter).AddOneByOne, (*Counter).AddBatch} {
		c := new(Counter)
		var wg sync.WaitGroup
		for range 4 {
			wg.Go(func() { add(c, []int{1, 2, 3}) })
		}
		wg.Wait()
		if c.Value() != 24 {
			t.Fatal(c.Value())
		}
	}
	first, err := EncodePooled(Record{ID: 1})
	if err != nil {
		t.Fatal(err)
	}
	saved := bytes.Clone(first)
	for range 10 {
		if _, err := EncodePooled(Record{ID: 9}); err != nil {
			t.Fatal(err)
		}
	}
	if !bytes.Equal(first, saved) {
		t.Fatal("returned bytes were reused")
	}
	if DirectID(Record{ID: 7}) != ReflectID(Record{ID: 7}) {
		t.Fatal("reflect")
	}
}
func TestAllocationEvidence(t *testing.T) {
	a := testing.AllocsPerRun(20, func() { sliceSink = GrowSlice(1024) })
	b := testing.AllocsPerRun(20, func() { sliceSink = ReserveSlice(1024) })
	if b >= a {
		t.Fatalf("expected fewer allocations: %v %v", a, b)
	}
	fmt.Printf("slice allocations: grow=%.0f reserve=%.0f\n", a, b)
	a = testing.AllocsPerRun(20, func() { _ = Local(7) })
	b = testing.AllocsPerRun(20, func() { intSink = Escaped(7) })
	fmt.Printf("int allocations: local=%.0f escaped=%.0f\n", a, b)
	if a != 0 || b != 1 {
		t.Fatal(a, b)
	}
}
func TestBuilderCopyPanics(t *testing.T) {
	var b strings.Builder
	b.WriteString("a")
	c := b
	defer func() {
		if r := recover(); r == nil {
			t.Fatal("expected copied-builder panic")
		} else {
			fmt.Println("copy:", r)
		}
	}()
	c.WriteString("b")
}
func TestJSONDifferences(t *testing.T) {
	a, err := json.Marshal(Record{})
	if err != nil {
		t.Fatal(err)
	}
	b, err := jsonv2.Marshal(Record{})
	if err != nil {
		t.Fatal(err)
	}
	fmt.Println("v1:", string(a))
	fmt.Println("v2:", string(b))
	var x, y Record
	if err := json.Unmarshal([]byte(`{"ID":7}`), &x); err != nil {
		t.Fatal(err)
	}
	if err := jsonv2.Unmarshal([]byte(`{"ID":7}`), &y); err != nil {
		t.Fatal(err)
	}
	fmt.Printf("uppercase key: v1=%d v2=%d\n", x.ID, y.ID)
	if x.ID != 7 || y.ID != 0 {
		t.Fatal(x, y)
	}
	err1 := json.Unmarshal([]byte(`{"id":1,"id":2}`), &x)
	err2 := jsonv2.Unmarshal([]byte(`{"id":1,"id":2}`), &y)
	fmt.Printf("duplicate rejected: v1=%v v2=%v\n", err1 != nil, err2 != nil)
	if err1 != nil || err2 == nil {
		t.Fatal(err1, err2)
	}
}
func BenchmarkSlice(b *testing.B) {
	for _, name := range []string{"grow", "reserve"} {
		b.Run(name, func(b *testing.B) {
			b.ReportAllocs()
			for b.Loop() {
				if name == "grow" {
					sliceSink = GrowSlice(1024)
				} else {
					sliceSink = ReserveSlice(1024)
				}
			}
		})
	}
}
func BenchmarkBuilder(b *testing.B) {
	parts := make([]string, 128)
	for i := range parts {
		parts[i] = "go中"
	}
	for _, reserve := range []bool{false, true} {
		b.Run(fmt.Sprint(reserve), func(b *testing.B) {
			b.ReportAllocs()
			for b.Loop() {
				_ = Build(parts, reserve)
			}
		})
	}
}
func BenchmarkHot(b *testing.B) {
	parts := []string{"alpha", "中", "omega"}
	for b.Loop() {
		_ = Build(parts, true)
	}
}

func TestZeroLengthSemantics(t *testing.T) {
	fmt.Printf("zero length nil: grow=%v reserve=%v\n", GrowSlice(0) == nil, ReserveSlice(0) == nil)
	if GrowSlice(0) != nil || ReserveSlice(0) == nil {
		t.Fatal("unexpected zero length behavior")
	}
}
