package tsm1

import (
	"fmt"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func makeIntegerValues(count int, min, max int64) IntegerValues {
	vals := make(IntegerValues, count)

	ts := min
	inc := (max - min) / int64(count)

	for i := 0; i < count; i++ {
		vals[i] = NewRawIntegerValue(ts, 0)
		ts += inc
	}

	return vals
}

func makeIntegerValuesFromSlice(t []int64) IntegerValues {
	iv := make(IntegerValues, len(t))
	for i, v := range t {
		iv[i] = NewRawIntegerValue(v, 0)
	}
	return iv
}

func TestIntegerValues_FindRangeNoValues(t *testing.T) {
	var vals IntegerValues
	l, r := vals.FindRange(0, 100)
	if exp := -1; l != exp {
		t.Errorf("invalid l; exp=%d, got=%d", exp, l)
	}
	if exp := -1; r != exp {
		t.Errorf("invalid r; exp=%d, got=%d", exp, r)
	}
}

func TestIntegerValues_FindRange(t *testing.T) {
	vals := makeIntegerValuesFromSlice([]int64{10, 11, 13, 15, 17, 20, 21})

	cases := []struct {
		min, max int64
		l, r     int
	}{
		{12, 20, 2, 5},
		{22, 40, -1, -1},
		{1, 9, -1, -1},
		{1, 10, 0, 0},
		{1, 11, 0, 1},
		{15, 15, 3, 3},
	}

	for _, tc := range cases {
		t.Run(fmt.Sprintf("%d→%d", tc.min, tc.max), func(t *testing.T) {
			l, r := vals.FindRange(tc.min, tc.max)
			if l != tc.l {
				t.Errorf("left: got %d, exp %d", l, tc.l)
			}
			if r != tc.r {
				t.Errorf("right: got %d, exp %d", r, tc.r)
			}
		})
	}
}

func TestIntegerValues_Exclude(t *testing.T) {
	cases := []struct {
		n        string
		min, max int64
		exp      []int64
	}{
		{"excl bad range", 18, 11, []int64{10, 12, 14, 16, 18}},
		{"excl none-lo", 0, 9, []int64{10, 12, 14, 16, 18}},
		{"excl none-hi", 19, 30, []int64{10, 12, 14, 16, 18}},
		{"excl first", 0, 10, []int64{12, 14, 16, 18}},
		{"excl last", 18, 20, []int64{10, 12, 14, 16}},
		{"excl all but first and last", 12, 16, []int64{10, 18}},
		{"excl none in middle", 13, 13, []int64{10, 12, 14, 16, 18}},
		{"excl middle", 14, 14, []int64{10, 12, 16, 18}},
		{"excl suffix", 16, 18, []int64{10, 12, 14}},
	}

	for _, tc := range cases {
		t.Run(fmt.Sprintf("%s[%d,%d]", tc.n, tc.min, tc.max), func(t *testing.T) {
			vals := makeIntegerValues(5, 10, 20)
			vals = vals.Exclude(tc.min, tc.max)
			var got []int64
			for _, v := range vals {
				got = append(got, v.UnixNano())
			}
			opt := cmp.AllowUnexported(IntegerValue{})
			if !cmp.Equal(tc.exp, got, opt) {
				t.Error(cmp.Diff(tc.exp, got, opt))
			}
		})
	}
}

func TestIntegerValues_Include(t *testing.T) {
	cases := []struct {
		n        string
		min, max int64
		exp      []int64
	}{
		{"incl none-lo", 0, 9, nil},
		{"incl none-hi", 19, 30, nil},
		{"incl first", 0, 10, []int64{10}},
		{"incl last", 18, 20, []int64{18}},
		{"incl all but first and last", 12, 16, []int64{12, 14, 16}},
		{"incl none in middle", 13, 13, nil},
		{"incl middle", 14, 14, []int64{14}},
	}

	for _, tc := range cases {
		t.Run(fmt.Sprintf("%s[%d,%d]", tc.n, tc.min, tc.max), func(t *testing.T) {
			vals := makeIntegerValues(5, 10, 20)
			vals = vals.Include(tc.min, tc.max)
			var got []int64
			for _, v := range vals {
				got = append(got, v.UnixNano())
			}
			opt := cmp.AllowUnexported(IntegerValue{})
			if !cmp.Equal(tc.exp, got, opt) {
				t.Error(cmp.Diff(tc.exp, got, opt))
			}
		})
	}
}

func benchExclude(b *testing.B, vals IntegerValues, min, max int64) {
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		vals.Exclude(min, max)
	}
}

func BenchmarkIntegerValues_ExcludeNone_1000(b *testing.B) {
	benchExclude(b, makeIntegerValues(1000, 1000, 2000), 0, 500)
}

func BenchmarkIntegerValues_ExcludeMiddleHalf_1000(b *testing.B) {
	benchExclude(b, makeIntegerValues(1000, 1000, 2000), 1250, 1750)
}

func BenchmarkIntegerValues_ExcludeFirst_1000(b *testing.B) {
	benchExclude(b, makeIntegerValues(1000, 1000, 2000), 0, 1000)
}

func BenchmarkIntegerValues_ExcludeLast_1000(b *testing.B) {
	benchExclude(b, makeIntegerValues(1000, 1000, 2000), 1999, 2000)
}

func BenchmarkIntegerValues_ExcludeNone_10000(b *testing.B) {
	benchExclude(b, makeIntegerValues(10000, 10000, 20000), 00, 5000)
}

func BenchmarkIntegerValues_ExcludeMiddleHalf_10000(b *testing.B) {
	benchExclude(b, makeIntegerValues(10000, 10000, 20000), 12500, 17500)
}

func BenchmarkIntegerValues_ExcludeFirst_10000(b *testing.B) {
	benchExclude(b, makeIntegerValues(10000, 10000, 20000), 0, 10000)
}

func BenchmarkIntegerValues_ExcludeLast_10000(b *testing.B) {
	benchExclude(b, makeIntegerValues(10000, 10000, 20000), 19999, 20000)
}

func benchInclude(b *testing.B, vals IntegerValues, min, max int64) {
	tmp := append(IntegerValues{}, vals...)
	n := len(vals)
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		vals.Include(min, max)
		vals = vals[:n]
		copy(vals, tmp)
	}
}

func BenchmarkIntegerValues_IncludeNone_1000(b *testing.B) {
	benchInclude(b, makeIntegerValues(1000, 1000, 2000), 0, 500)
}

func BenchmarkIntegerValues_IncludeMiddleHalf_1000(b *testing.B) {
	benchInclude(b, makeIntegerValues(1000, 1000, 2000), 1250, 1750)
}

func BenchmarkIntegerValues_IncludeFirst_1000(b *testing.B) {
	benchInclude(b, makeIntegerValues(1000, 1000, 2000), 0, 1000)
}

func BenchmarkIntegerValues_IncludeLast_1000(b *testing.B) {
	benchInclude(b, makeIntegerValues(1000, 1000, 2000), 1999, 2000)
}

func BenchmarkIntegerValues_IncludeNone_10000(b *testing.B) {
	benchInclude(b, makeIntegerValues(10000, 10000, 20000), 00, 5000)
}

func BenchmarkIntegerValues_IncludeMiddleHalf_10000(b *testing.B) {
	benchInclude(b, makeIntegerValues(10000, 10000, 20000), 12500, 17500)
}

func BenchmarkIntegerValues_IncludeFirst_10000(b *testing.B) {
	benchInclude(b, makeIntegerValues(10000, 10000, 20000), 0, 10000)
}

func BenchmarkIntegerValues_IncludeLast_10000(b *testing.B) {
	benchInclude(b, makeIntegerValues(10000, 10000, 20000), 19999, 20000)
}
