package encoding

import (
	"fmt"
	"testing"
)

func BenchmarkMarshalUint64(b *testing.B) {
	b.ReportAllocs()
	b.SetBytes(1)
	b.RunParallel(func(pb *testing.PB) {
		var dst []byte
		var sink uint64
		for pb.Next() {
			dst = MarshalUint64(dst[:0], sink)
			sink += uint64(len(dst))
		}
		Sink.Add(sink)
	})
}

func BenchmarkUnmarshalUint64(b *testing.B) {
	b.ReportAllocs()
	b.SetBytes(1)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		for pb.Next() {
			v := UnmarshalUint64(testMarshaledUint64Data)
			sink += v
		}
		Sink.Add(sink)
	})
}

func BenchmarkMarshalInt64(b *testing.B) {
	b.ReportAllocs()
	b.SetBytes(1)
	b.RunParallel(func(pb *testing.PB) {
		var dst []byte
		var sink uint64
		for pb.Next() {
			dst = MarshalInt64(dst[:0], int64(sink))
			sink += uint64(len(dst))
		}
		Sink.Add(sink)
	})
}

func BenchmarkUnmarshalInt64(b *testing.B) {
	b.ReportAllocs()
	b.SetBytes(1)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		for pb.Next() {
			v := UnmarshalInt64(testMarshaledInt64Data)
			sink += uint64(v)
		}
		Sink.Add(sink)
	})
}

func BenchmarkMarshalVarUint64s(b *testing.B) {
	b.Run("up-to-(1<<7)-1", func(b *testing.B) {
		benchmarkMarshalVarUint64s(b, (1<<7)-1)
	})
	b.Run("up-to-(1<<14)-1", func(b *testing.B) {
		benchmarkMarshalVarUint64s(b, (1<<14)-1)
	})
	b.Run("up-to-(1<<28)-1", func(b *testing.B) {
		benchmarkMarshalVarUint64s(b, (1<<28)-1)
	})
	b.Run("up-to-(1<<64)-1", func(b *testing.B) {
		benchmarkMarshalVarUint64s(b, (1<<64)-1)
	})
}

func benchmarkMarshalVarUint64s(b *testing.B, maxValue uint64) {
	const numsCount = 8000
	var data []uint64
	n := maxValue
	for i := 0; i < numsCount; i++ {
		if n > maxValue {
			n = maxValue
		}
		data = append(data, n)
		n--
	}
	b.ResetTimer()
	b.ReportAllocs()
	b.SetBytes(numsCount)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		var dst []byte
		for pb.Next() {
			dst = MarshalVarUint64s(dst[:0], data)
			sink += uint64(len(dst))
		}
		Sink.Add(sink)
	})
}

func BenchmarkMarshalVarInt64s(b *testing.B) {
	b.Run("up-to-(1<<6)-1", func(b *testing.B) {
		benchmarkMarshalVarInt64s(b, (1<<6)-1)
	})
	b.Run("up-to-(1<<13)-1", func(b *testing.B) {
		benchmarkMarshalVarInt64s(b, (1<<13)-1)
	})
	b.Run("up-to-(1<<27)-1", func(b *testing.B) {
		benchmarkMarshalVarInt64s(b, (1<<27)-1)
	})
	b.Run("up-to-(1<<63)-1", func(b *testing.B) {
		benchmarkMarshalVarInt64s(b, (1<<63)-1)
	})
}

func benchmarkMarshalVarInt64s(b *testing.B, maxValue int64) {
	const numsCount = 8000
	var data []int64
	n := maxValue
	for i := 0; i < numsCount; i++ {
		if n < -maxValue {
			n = maxValue
		}
		data = append(data, n)
		n--
	}
	b.ResetTimer()
	b.ReportAllocs()
	b.SetBytes(numsCount)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		var dst []byte
		for pb.Next() {
			dst = MarshalVarInt64s(dst[:0], data)
			sink += uint64(len(dst))
		}
		Sink.Add(sink)
	})
}

func BenchmarkUnmarshalVarUint64(b *testing.B) {
	b.Run("up-to-(1<<7)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64(b, (1<<7)-1)
	})
	b.Run("up-to-(1<<14)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64(b, (1<<14)-1)
	})
	b.Run("up-to-(1<<28)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64(b, (1<<28)-1)
	})
	b.Run("up-to-(1<<64)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64(b, (1<<64)-1)
	})
}

func benchmarkUnmarshalVarUint64(b *testing.B, maxValue uint64) {
	const numsCount = 8000
	var data []byte
	n := maxValue
	for i := 0; i < numsCount; i++ {
		if n > maxValue {
			n = maxValue
		}
		data = MarshalVarUint64(data, n)
		n--
	}
	b.ResetTimer()
	b.ReportAllocs()
	b.SetBytes(numsCount)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		for pb.Next() {
			src := data
			for len(src) > 0 {
				n, nSize := UnmarshalVarUint64(src)
				if nSize <= 0 {
					panic(fmt.Errorf("unexpected error"))
				}
				src = src[nSize:]
				sink += n
			}
		}
		Sink.Add(sink)
	})
}

func BenchmarkUnmarshalVarUint64s(b *testing.B) {
	b.Run("up-to-(1<<7)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64s(b, (1<<7)-1)
	})
	b.Run("up-to-(1<<14)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64s(b, (1<<14)-1)
	})
	b.Run("up-to-(1<<28)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64s(b, (1<<28)-1)
	})
	b.Run("up-to-(1<<64)-1", func(b *testing.B) {
		benchmarkUnmarshalVarUint64s(b, (1<<64)-1)
	})
}

func benchmarkUnmarshalVarUint64s(b *testing.B, maxValue uint64) {
	const numsCount = 8000
	var data []byte
	n := maxValue
	for i := 0; i < numsCount; i++ {
		if n > maxValue {
			n = maxValue
		}
		data = MarshalVarUint64(data, n)
		n--
	}
	b.ResetTimer()
	b.ReportAllocs()
	b.SetBytes(numsCount)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		dst := make([]uint64, numsCount)
		for pb.Next() {
			tail, err := UnmarshalVarUint64s(dst, data)
			if err != nil {
				panic(fmt.Errorf("unexpected error: %w", err))
			}
			if len(tail) > 0 {
				panic(fmt.Errorf("unexpected non-empty tail with len=%d: %X", len(tail), tail))
			}
			sink += uint64(len(dst))
		}
		Sink.Add(sink)
	})
}

func BenchmarkUnmarshalVarInt64(b *testing.B) {
	b.Run("up-to-(1<<6)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64(b, (1<<6)-1)
	})
	b.Run("up-to-(1<<13)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64(b, (1<<13)-1)
	})
	b.Run("up-to-(1<<27)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64(b, (1<<27)-1)
	})
	b.Run("up-to-(1<<63)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64(b, (1<<63)-1)
	})
}

func benchmarkUnmarshalVarInt64(b *testing.B, maxValue int64) {
	const numsCount = 8000
	var data []byte
	n := maxValue
	for i := 0; i < numsCount; i++ {
		if n < -maxValue {
			n = maxValue
		}
		data = MarshalVarInt64(data, n)
		n--
	}
	b.ResetTimer()
	b.ReportAllocs()
	b.SetBytes(numsCount)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		for pb.Next() {
			src := data
			for len(src) > 0 {
				n, nSize := UnmarshalVarInt64(src)
				if nSize <= 0 {
					panic(fmt.Errorf("unexpected error"))
				}
				src = src[nSize:]
				sink += uint64(n)
			}
		}
		Sink.Add(sink)
	})
}

func BenchmarkUnmarshalVarInt64s(b *testing.B) {
	b.Run("up-to-(1<<6)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64s(b, (1<<6)-1)
	})
	b.Run("up-to-(1<<13)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64s(b, (1<<13)-1)
	})
	b.Run("up-to-(1<<27)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64s(b, (1<<27)-1)
	})
	b.Run("up-to-(1<<63)-1", func(b *testing.B) {
		benchmarkUnmarshalVarInt64s(b, (1<<63)-1)
	})
}

func benchmarkUnmarshalVarInt64s(b *testing.B, maxValue int64) {
	const numsCount = 8000
	var data []byte
	n := maxValue
	for i := 0; i < numsCount; i++ {
		if n < -maxValue {
			n = maxValue
		}
		data = MarshalVarInt64(data, n)
		n--
	}
	b.ResetTimer()
	b.ReportAllocs()
	b.SetBytes(numsCount)
	b.RunParallel(func(pb *testing.PB) {
		var sink uint64
		dst := make([]int64, numsCount)
		for pb.Next() {
			tail, err := UnmarshalVarInt64s(dst, data)
			if err != nil {
				panic(fmt.Errorf("unexpected error: %w", err))
			}
			if len(tail) > 0 {
				panic(fmt.Errorf("unexpected non-empty tail with len=%d: %X", len(tail), tail))
			}
			sink += uint64(len(dst))
		}
		Sink.Add(sink)
	})
}

func BenchmarkMarshalVarUint64(b *testing.B) {
	b.Run("small-ints", func(b *testing.B) {
		benchmarkMarshalVarUint64(b, []uint64{1, 2, 3, 4, 5, 67, 127})
	})
	b.Run("big-ints", func(b *testing.B) {
		benchmarkMarshalVarUint64(b, []uint64{12355, 89832432, 8989843, 8989989, 883443, 9891233, 8232434342})
	})
}

func benchmarkMarshalVarUint64(b *testing.B, a []uint64) {
	b.ReportAllocs()
	b.SetBytes(int64(len(a)))
	b.RunParallel(func(pb *testing.PB) {
		var buf []byte
		var sink uint64
		for pb.Next() {
			buf = buf[:0]
			for _, n := range a {
				buf = MarshalVarUint64(buf, n)
			}
			sink += uint64(len(buf))
		}
		Sink.Add(sink)
	})
}

func BenchmarkMarshalVarInt64(b *testing.B) {
	b.Run("small-ints", func(b *testing.B) {
		benchmarkMarshalVarInt64(b, []int64{1, 2, 3, -4, 5, -60, 63})
	})
	b.Run("big-ints", func(b *testing.B) {
		benchmarkMarshalVarInt64(b, []int64{12355, -89832432, 8989843, -8989989, 883443, -9891233, 8232434342})
	})
}

func benchmarkMarshalVarInt64(b *testing.B, a []int64) {
	b.ReportAllocs()
	b.SetBytes(int64(len(a)))
	b.RunParallel(func(pb *testing.PB) {
		var buf []byte
		var sink uint64
		for pb.Next() {
			buf = buf[:0]
			for _, n := range a {
				buf = MarshalVarInt64(buf, n)
			}
			sink += uint64(len(buf))
		}
		Sink.Add(sink)
	})
}

var testMarshaledInt64Data = MarshalInt64(nil, 1234567890)
var testMarshaledUint64Data = MarshalUint64(nil, 1234567890)