// 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 reflect_test

import (
	"fmt"
	. "reflect"
	"strconv"
	"testing"
	"time"
)

var sourceAll = struct {
	Bool         Value
	String       Value
	Bytes        Value
	NamedBytes   Value
	BytesArray   Value
	SliceAny     Value
	MapStringAny Value
}{
	Bool:         ValueOf(new(bool)).Elem(),
	String:       ValueOf(new(string)).Elem(),
	Bytes:        ValueOf(new([]byte)).Elem(),
	NamedBytes:   ValueOf(new(namedBytes)).Elem(),
	BytesArray:   ValueOf(new([32]byte)).Elem(),
	SliceAny:     ValueOf(new([]any)).Elem(),
	MapStringAny: ValueOf(new(map[string]any)).Elem(),
}

var sinkAll struct {
	RawBool   bool
	RawString string
	RawBytes  []byte
	RawInt    int
}

func BenchmarkBool(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawBool = sourceAll.Bool.Bool()
	}
}

func BenchmarkString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawString = sourceAll.String.String()
	}
}

func BenchmarkBytes(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawBytes = sourceAll.Bytes.Bytes()
	}
}

func BenchmarkNamedBytes(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawBytes = sourceAll.NamedBytes.Bytes()
	}
}

func BenchmarkBytesArray(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawBytes = sourceAll.BytesArray.Bytes()
	}
}

func BenchmarkSliceLen(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawInt = sourceAll.SliceAny.Len()
	}
}

func BenchmarkMapLen(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawInt = sourceAll.MapStringAny.Len()
	}
}

func BenchmarkStringLen(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawInt = sourceAll.String.Len()
	}
}

func BenchmarkArrayLen(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawInt = sourceAll.BytesArray.Len()
	}
}

func BenchmarkSliceCap(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sinkAll.RawInt = sourceAll.SliceAny.Cap()
	}
}

func BenchmarkDeepEqual(b *testing.B) {
	for _, bb := range deepEqualPerfTests {
		b.Run(ValueOf(bb.x).Type().String(), func(b *testing.B) {
			b.ReportAllocs()
			for i := 0; i < b.N; i++ {
				sink = DeepEqual(bb.x, bb.y)
			}
		})
	}
}

func BenchmarkMapsDeepEqual(b *testing.B) {
	m1 := map[int]int{
		1: 1, 2: 2,
	}
	m2 := map[int]int{
		1: 1, 2: 2,
	}
	for i := 0; i < b.N; i++ {
		DeepEqual(m1, m2)
	}
}

func BenchmarkIsZero(b *testing.B) {
	type Int4 struct {
		a, b, c, d int
	}
	type Int1024 struct {
		a [1024]int
	}
	type Int512 struct {
		a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16 [16]S
	}
	s := struct {
		ArrayComparable      [4]T
		ArrayIncomparable    [4]_Complex
		StructComparable     T
		StructIncomparable   _Complex
		ArrayInt_4           [4]int
		ArrayInt_1024        [1024]int
		ArrayInt_1024_NoZero [1024]int
		Struct4Int           Int4
		ArrayStruct4Int_1024 [256]Int4
		ArrayChanInt_1024    [1024]chan int
		StructInt_512        Int512
	}{}
	s.ArrayInt_1024_NoZero[512] = 1
	source := ValueOf(s)

	for i := 0; i < source.NumField(); i++ {
		name := source.Type().Field(i).Name
		value := source.Field(i)
		b.Run(name, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				sink = value.IsZero()
			}
		})
	}
}

func BenchmarkSetZero(b *testing.B) {
	source := ValueOf(new(struct {
		Bool      bool
		Int       int64
		Uint      uint64
		Float     float64
		Complex   complex128
		Array     [4]Value
		Chan      chan Value
		Func      func() Value
		Interface interface{ String() string }
		Map       map[string]Value
		Pointer   *Value
		Slice     []Value
		String    string
		Struct    Value
	})).Elem()

	for i := 0; i < source.NumField(); i++ {
		name := source.Type().Field(i).Name
		value := source.Field(i)
		zero := Zero(value.Type())
		b.Run(name+"/Direct", func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				value.SetZero()
			}
		})
		b.Run(name+"/CachedZero", func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				value.Set(zero)
			}
		})
		b.Run(name+"/NewZero", func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				value.Set(Zero(value.Type()))
			}
		})
	}
}

// BenchmarkZero overlaps some with BenchmarkSetZero,
// but the inputs are set up differently to exercise
// different optimizations.
func BenchmarkZero(b *testing.B) {
	type bm struct {
		name    string
		zero    Value
		nonZero Value
		size    int
	}
	type Small struct {
		A    int64
		B, C bool
	}
	type Big struct {
		A    int64
		B, C bool
		D    [1008]byte
	}
	entry := func(name string, zero any, nonZero any) bm {
		return bm{name, ValueOf(zero), ValueOf(nonZero).Elem(), int(TypeOf(zero).Size())}
	}
	nonZeroTime := func() *time.Time { t := time.Now(); return &t }

	bms := []bm{
		entry("ByteArray", [16]byte{}, &[16]byte{1}),
		entry("ByteArray", [64]byte{}, &[64]byte{1}),
		entry("ByteArray", [1024]byte{}, &[1024]byte{1}),
		entry("BigStruct", Big{}, &Big{A: 1}),
		entry("SmallStruct", Small{}, &Small{A: 1}),
		entry("SmallStructArray", [4]Small{}, &[4]Small{0: {A: 1}}),
		entry("SmallStructArray", [64]Small{}, &[64]Small{0: {A: 1}}),
		entry("Time", time.Time{}, nonZeroTime()),
	}

	for _, bm := range bms {
		b.Run(fmt.Sprintf("IsZero/%s/size=%d", bm.name, bm.size), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				bm.zero.IsZero()
			}
		})
	}
	for _, bm := range bms {
		b.Run(fmt.Sprintf("SetZero/%s/size=%d", bm.name, bm.size), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				bm.nonZero.Set(bm.zero)
			}
		})
	}
}

func BenchmarkSelect(b *testing.B) {
	channel := make(chan int)
	close(channel)
	var cases []SelectCase
	for i := 0; i < 8; i++ {
		cases = append(cases, SelectCase{
			Dir:  SelectRecv,
			Chan: ValueOf(channel),
		})
	}
	for _, numCases := range []int{1, 4, 8} {
		b.Run(strconv.Itoa(numCases), func(b *testing.B) {
			b.ReportAllocs()
			for i := 0; i < b.N; i++ {
				_, _, _ = Select(cases[:numCases])
			}
		})
	}
}

func BenchmarkCall(b *testing.B) {
	fv := ValueOf(func(a, b string) {})
	b.ReportAllocs()
	b.RunParallel(func(pb *testing.PB) {
		args := []Value{ValueOf("a"), ValueOf("b")}
		for pb.Next() {
			fv.Call(args)
		}
	})
}

type myint int64

func (i *myint) inc() {
	*i = *i + 1
}

func BenchmarkCallMethod(b *testing.B) {
	b.ReportAllocs()
	z := new(myint)

	v := ValueOf(z.inc)
	for i := 0; i < b.N; i++ {
		v.Call(nil)
	}
}

func BenchmarkCallArgCopy(b *testing.B) {
	byteArray := func(n int) Value {
		return Zero(ArrayOf(n, TypeOf(byte(0))))
	}
	sizes := [...]struct {
		fv  Value
		arg Value
	}{
		{ValueOf(func(a [128]byte) {}), byteArray(128)},
		{ValueOf(func(a [256]byte) {}), byteArray(256)},
		{ValueOf(func(a [1024]byte) {}), byteArray(1024)},
		{ValueOf(func(a [4096]byte) {}), byteArray(4096)},
		{ValueOf(func(a [65536]byte) {}), byteArray(65536)},
	}
	for _, size := range sizes {
		bench := func(b *testing.B) {
			args := []Value{size.arg}
			b.SetBytes(int64(size.arg.Len()))
			b.ResetTimer()
			b.RunParallel(func(pb *testing.PB) {
				for pb.Next() {
					size.fv.Call(args)
				}
			})
		}
		name := fmt.Sprintf("size=%v", size.arg.Len())
		b.Run(name, bench)
	}
}

func BenchmarkPtrTo(b *testing.B) {
	// Construct a type with a zero ptrToThis.
	type T struct{ int }
	t := SliceOf(TypeOf(T{}))
	ptrToThis := ValueOf(t).Elem().FieldByName("PtrToThis")
	if !ptrToThis.IsValid() {
		b.Skipf("%v has no ptrToThis field; was it removed from rtype?", t) // TODO fix this at top of refactoring
		// b.Fatalf("%v has no ptrToThis field; was it removed from rtype?", t)
	}
	if ptrToThis.Int() != 0 {
		b.Fatalf("%v.ptrToThis unexpectedly nonzero", t)
	}
	b.ResetTimer()

	// Now benchmark calling PointerTo on it: we'll have to hit the ptrMap cache on
	// every call.
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			PointerTo(t)
		}
	})
}

type B1 struct {
	X int
	Y int
	Z int
}

func BenchmarkFieldByName1(b *testing.B) {
	t := TypeOf(B1{})
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			t.FieldByName("Z")
		}
	})
}

func BenchmarkFieldByName2(b *testing.B) {
	t := TypeOf(S3{})
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			t.FieldByName("B")
		}
	})
}

func BenchmarkFieldByName3(b *testing.B) {
	t := TypeOf(R0{})
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			t.FieldByName("X")
		}
	})
}

type S struct {
	i1 int64
	i2 int64
}

func BenchmarkInterfaceBig(b *testing.B) {
	v := ValueOf(S{})
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			v.Interface()
		}
	})
	b.StopTimer()
}

func BenchmarkInterfaceSmall(b *testing.B) {
	v := ValueOf(int64(0))
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			v.Interface()
		}
	})
}

func BenchmarkNew(b *testing.B) {
	v := TypeOf(XM{})
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			New(v)
		}
	})
}

func BenchmarkMap(b *testing.B) {
	type V *int
	type S string
	value := ValueOf((V)(nil))
	stringKeys := []string{}
	mapOfStrings := map[string]V{}
	uint64Keys := []uint64{}
	mapOfUint64s := map[uint64]V{}
	userStringKeys := []S{}
	mapOfUserStrings := map[S]V{}
	for i := 0; i < 100; i++ {
		stringKey := fmt.Sprintf("key%d", i)
		stringKeys = append(stringKeys, stringKey)
		mapOfStrings[stringKey] = nil

		uint64Key := uint64(i)
		uint64Keys = append(uint64Keys, uint64Key)
		mapOfUint64s[uint64Key] = nil

		userStringKey := S(fmt.Sprintf("key%d", i))
		userStringKeys = append(userStringKeys, userStringKey)
		mapOfUserStrings[userStringKey] = nil
	}

	tests := []struct {
		label          string
		m, keys, value Value
	}{
		{"StringKeys", ValueOf(mapOfStrings), ValueOf(stringKeys), value},
		{"Uint64Keys", ValueOf(mapOfUint64s), ValueOf(uint64Keys), value},
		{"UserStringKeys", ValueOf(mapOfUserStrings), ValueOf(userStringKeys), value},
	}

	for _, tt := range tests {
		b.Run(tt.label, func(b *testing.B) {
			b.Run("MapIndex", func(b *testing.B) {
				b.ReportAllocs()
				for i := 0; i < b.N; i++ {
					for j := tt.keys.Len() - 1; j >= 0; j-- {
						tt.m.MapIndex(tt.keys.Index(j))
					}
				}
			})
			b.Run("SetMapIndex", func(b *testing.B) {
				b.ReportAllocs()
				for i := 0; i < b.N; i++ {
					for j := tt.keys.Len() - 1; j >= 0; j-- {
						tt.m.SetMapIndex(tt.keys.Index(j), tt.value)
					}
				}
			})
		})
	}
}

func BenchmarkMapIterNext(b *testing.B) {
	m := ValueOf(map[string]int{"a": 0, "b": 1, "c": 2, "d": 3})
	it := m.MapRange()
	for i := 0; i < b.N; i++ {
		for it.Next() {
		}
		it.Reset(m)
	}
}
