// Copyright 2014 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 intsets_test

import (
	"fmt"
	"log"
	"math/rand"
	"sort"
	"strings"
	"testing"

	"golang.org/x/tools/container/intsets"
)

func TestBasics(t *testing.T) {
	var s intsets.Sparse
	if len := s.Len(); len != 0 {
		t.Errorf("Len({}): got %d, want 0", len)
	}
	if s := s.String(); s != "{}" {
		t.Errorf("String({}): got %q, want \"{}\"", s)
	}
	if s.Has(3) {
		t.Errorf("Has(3): got true, want false")
	}
	if err := s.Check(); err != nil {
		t.Error(err)
	}

	if !s.Insert(3) {
		t.Errorf("Insert(3): got false, want true")
	}
	if max := s.Max(); max != 3 {
		t.Errorf("Max: got %d, want 3", max)
	}

	if !s.Insert(435) {
		t.Errorf("Insert(435): got false, want true")
	}
	if s := s.String(); s != "{3 435}" {
		t.Errorf("String({3 435}): got %q, want \"{3 435}\"", s)
	}
	if max := s.Max(); max != 435 {
		t.Errorf("Max: got %d, want 435", max)
	}
	if len := s.Len(); len != 2 {
		t.Errorf("Len: got %d, want 2", len)
	}

	if !s.Remove(435) {
		t.Errorf("Remove(435): got false, want true")
	}
	if s := s.String(); s != "{3}" {
		t.Errorf("String({3}): got %q, want \"{3}\"", s)
	}
}

// Insert, Len, IsEmpty, Hash, Clear, AppendTo.
func TestMoreBasics(t *testing.T) {
	set := new(intsets.Sparse)
	set.Insert(456)
	set.Insert(123)
	set.Insert(789)
	if set.Len() != 3 {
		t.Errorf("%s.Len: got %d, want 3", set, set.Len())
	}
	if set.IsEmpty() {
		t.Errorf("%s.IsEmpty: got true", set)
	}
	if !set.Has(123) {
		t.Errorf("%s.Has(123): got false", set)
	}
	if set.Has(1234) {
		t.Errorf("%s.Has(1234): got true", set)
	}
	got := set.AppendTo([]int{-1})
	if want := []int{-1, 123, 456, 789}; fmt.Sprint(got) != fmt.Sprint(want) {
		t.Errorf("%s.AppendTo: got %v, want %v", set, got, want)
	}

	set.Clear()

	if set.Len() != 0 {
		t.Errorf("Clear: got %d, want 0", set.Len())
	}
	if !set.IsEmpty() {
		t.Errorf("IsEmpty: got false")
	}
	if set.Has(123) {
		t.Errorf("%s.Has: got false", set)
	}
}

func TestTakeMin(t *testing.T) {
	var set intsets.Sparse
	set.Insert(456)
	set.Insert(123)
	set.Insert(789)
	set.Insert(-123)
	var got int
	for i, want := range []int{-123, 123, 456, 789} {
		if !set.TakeMin(&got) || got != want {
			t.Errorf("TakeMin #%d: got %d, want %d", i, got, want)
		}
	}
	if set.TakeMin(&got) {
		t.Errorf("%s.TakeMin returned true", &set)
	}
	if err := set.Check(); err != nil {
		t.Fatalf("check: %s: %#v", err, &set)
	}
}

func TestMinAndMax(t *testing.T) {
	values := []int{0, 456, 123, 789, -123} // elt 0 => empty set
	wantMax := []int{intsets.MinInt, 456, 456, 789, 789}
	wantMin := []int{intsets.MaxInt, 456, 123, 123, -123}

	var set intsets.Sparse
	for i, x := range values {
		if i != 0 {
			set.Insert(x)
		}
		if got, want := set.Min(), wantMin[i]; got != want {
			t.Errorf("Min #%d: got %d, want %d", i, got, want)
		}
		if got, want := set.Max(), wantMax[i]; got != want {
			t.Errorf("Max #%d: got %d, want %d", i, got, want)
		}
	}

	set.Insert(intsets.MinInt)
	if got, want := set.Min(), intsets.MinInt; got != want {
		t.Errorf("Min: got %d, want %d", got, want)
	}

	set.Insert(intsets.MaxInt)
	if got, want := set.Max(), intsets.MaxInt; got != want {
		t.Errorf("Max: got %d, want %d", got, want)
	}
}

func TestEquals(t *testing.T) {
	var setX intsets.Sparse
	setX.Insert(456)
	setX.Insert(123)
	setX.Insert(789)

	if !setX.Equals(&setX) {
		t.Errorf("Equals(%s, %s): got false", &setX, &setX)
	}

	var setY intsets.Sparse
	setY.Insert(789)
	setY.Insert(456)
	setY.Insert(123)

	if !setX.Equals(&setY) {
		t.Errorf("Equals(%s, %s): got false", &setX, &setY)
	}

	setY.Insert(1)
	if setX.Equals(&setY) {
		t.Errorf("Equals(%s, %s): got true", &setX, &setY)
	}

	var empty intsets.Sparse
	if setX.Equals(&empty) {
		t.Errorf("Equals(%s, %s): got true", &setX, &empty)
	}

	// Edge case: some block (with offset=0) appears in X but not Y.
	setY.Remove(123)
	if setX.Equals(&setY) {
		t.Errorf("Equals(%s, %s): got true", &setX, &setY)
	}
}

// A pset is a parallel implementation of a set using both an intsets.Sparse
// and a built-in hash map.
type pset struct {
	hash map[int]bool
	bits intsets.Sparse
}

func makePset() *pset {
	return &pset{hash: make(map[int]bool)}
}

func (set *pset) add(n int) {
	prev := len(set.hash)
	set.hash[n] = true
	grewA := len(set.hash) > prev

	grewB := set.bits.Insert(n)

	if grewA != grewB {
		panic(fmt.Sprintf("add(%d): grewA=%t grewB=%t", n, grewA, grewB))
	}
}

func (set *pset) remove(n int) {
	prev := len(set.hash)
	delete(set.hash, n)
	shrankA := len(set.hash) < prev

	shrankB := set.bits.Remove(n)

	if shrankA != shrankB {
		panic(fmt.Sprintf("remove(%d): shrankA=%t shrankB=%t", n, shrankA, shrankB))
	}
}

func (set *pset) check(t *testing.T, msg string) {
	var eltsA []int
	for elt := range set.hash {
		eltsA = append(eltsA, int(elt))
	}
	sort.Ints(eltsA)

	eltsB := set.bits.AppendTo(nil)

	if a, b := fmt.Sprint(eltsA), fmt.Sprint(eltsB); a != b {
		t.Errorf("check(%s): hash=%s bits=%s (%s)", msg, a, b, &set.bits)
	}

	if err := set.bits.Check(); err != nil {
		t.Fatalf("Check(%s): %s: %#v", msg, err, &set.bits)
	}
}

// randomPset returns a parallel set of random size and elements.
func randomPset(prng *rand.Rand, maxSize int) *pset {
	set := makePset()
	size := int(prng.Int()) % maxSize
	for range size {
		// TODO(adonovan): benchmark how performance varies
		// with this sparsity parameter.
		n := int(prng.Int()) % 10000
		set.add(n)
	}
	return set
}

// TestRandomMutations performs the same random adds/removes on two
// set implementations and ensures that they compute the same result.
func TestRandomMutations(t *testing.T) {
	const debug = false

	set := makePset()
	prng := rand.New(rand.NewSource(0))
	for i := range 10000 {
		n := int(prng.Int())%2000 - 1000
		if i%2 == 0 {
			if debug {
				log.Printf("add %d", n)
			}
			set.add(n)
		} else {
			if debug {
				log.Printf("remove %d", n)
			}
			set.remove(n)
		}
		if debug {
			set.check(t, "post mutation")
		}
	}
	set.check(t, "final")
	if debug {
		log.Print(&set.bits)
	}
}

func TestLowerBound(t *testing.T) {
	// Use random sets of sizes from 0 to about 4000.
	prng := rand.New(rand.NewSource(0))
	for i := range uint(12) {
		x := randomPset(prng, 1<<i)
		for j := range 10000 {
			found := intsets.MaxInt
			for e := range x.hash {
				if e >= j && e < found {
					found = e
				}
			}
			if res := x.bits.LowerBound(j); res != found {
				t.Errorf("%s: LowerBound(%d)=%d, expected %d", &x.bits, j, res, found)
			}
		}
	}
}

// TestSetOperations exercises classic set operations: ∩ , ∪, \.
func TestSetOperations(t *testing.T) {
	prng := rand.New(rand.NewSource(0))

	// Use random sets of sizes from 0 to about 4000.
	// For each operator, we test variations such as
	// Z.op(X, Y), Z.op(X, Z) and Z.op(Z, Y) to exercise
	// the degenerate cases of each method implementation.
	for i := range uint(12) {
		X := randomPset(prng, 1<<i)
		Y := randomPset(prng, 1<<i)

		// TODO(adonovan): minimise dependencies between stanzas below.

		// Copy(X)
		C := makePset()
		C.bits.Copy(&Y.bits) // no effect on result
		C.bits.Copy(&X.bits)
		C.hash = X.hash
		C.check(t, "C.Copy(X)")
		C.bits.Copy(&C.bits)
		C.check(t, "C.Copy(C)")

		// U.Union(X, Y)
		U := makePset()
		U.bits.Union(&X.bits, &Y.bits)
		for n := range X.hash {
			U.hash[n] = true
		}
		for n := range Y.hash {
			U.hash[n] = true
		}
		U.check(t, "U.Union(X, Y)")

		// U.Union(X, X)
		U.bits.Union(&X.bits, &X.bits)
		U.hash = X.hash
		U.check(t, "U.Union(X, X)")

		// U.Union(U, Y)
		U = makePset()
		U.bits.Copy(&X.bits)
		U.bits.Union(&U.bits, &Y.bits)
		for n := range X.hash {
			U.hash[n] = true
		}
		for n := range Y.hash {
			U.hash[n] = true
		}
		U.check(t, "U.Union(U, Y)")

		// U.Union(X, U)
		U.bits.Copy(&Y.bits)
		U.bits.Union(&X.bits, &U.bits)
		U.check(t, "U.Union(X, U)")

		// U.UnionWith(U)
		U.bits.UnionWith(&U.bits)
		U.check(t, "U.UnionWith(U)")

		// I.Intersection(X, Y)
		I := makePset()
		I.bits.Intersection(&X.bits, &Y.bits)
		for n := range X.hash {
			if Y.hash[n] {
				I.hash[n] = true
			}
		}
		I.check(t, "I.Intersection(X, Y)")

		// I.Intersection(X, X)
		I.bits.Intersection(&X.bits, &X.bits)
		I.hash = X.hash
		I.check(t, "I.Intersection(X, X)")

		// I.Intersection(I, X)
		I.bits.Intersection(&I.bits, &X.bits)
		I.check(t, "I.Intersection(I, X)")

		// I.Intersection(X, I)
		I.bits.Intersection(&X.bits, &I.bits)
		I.check(t, "I.Intersection(X, I)")

		// I.Intersection(I, I)
		I.bits.Intersection(&I.bits, &I.bits)
		I.check(t, "I.Intersection(I, I)")

		// D.Difference(X, Y)
		D := makePset()
		D.bits.Difference(&X.bits, &Y.bits)
		for n := range X.hash {
			if !Y.hash[n] {
				D.hash[n] = true
			}
		}
		D.check(t, "D.Difference(X, Y)")

		// D.Difference(D, Y)
		D.bits.Copy(&X.bits)
		D.bits.Difference(&D.bits, &Y.bits)
		D.check(t, "D.Difference(D, Y)")

		// D.Difference(Y, D)
		D.bits.Copy(&X.bits)
		D.bits.Difference(&Y.bits, &D.bits)
		D.hash = make(map[int]bool)
		for n := range Y.hash {
			if !X.hash[n] {
				D.hash[n] = true
			}
		}
		D.check(t, "D.Difference(Y, D)")

		// D.Difference(X, X)
		D.bits.Difference(&X.bits, &X.bits)
		D.hash = nil
		D.check(t, "D.Difference(X, X)")

		// D.DifferenceWith(D)
		D.bits.Copy(&X.bits)
		D.bits.DifferenceWith(&D.bits)
		D.check(t, "D.DifferenceWith(D)")

		// SD.SymmetricDifference(X, Y)
		SD := makePset()
		SD.bits.SymmetricDifference(&X.bits, &Y.bits)
		for n := range X.hash {
			if !Y.hash[n] {
				SD.hash[n] = true
			}
		}
		for n := range Y.hash {
			if !X.hash[n] {
				SD.hash[n] = true
			}
		}
		SD.check(t, "SD.SymmetricDifference(X, Y)")

		// X.SymmetricDifferenceWith(Y)
		SD.bits.Copy(&X.bits)
		SD.bits.SymmetricDifferenceWith(&Y.bits)
		SD.check(t, "X.SymmetricDifference(Y)")

		// Y.SymmetricDifferenceWith(X)
		SD.bits.Copy(&Y.bits)
		SD.bits.SymmetricDifferenceWith(&X.bits)
		SD.check(t, "Y.SymmetricDifference(X)")

		// SD.SymmetricDifference(X, X)
		SD.bits.SymmetricDifference(&X.bits, &X.bits)
		SD.hash = nil
		SD.check(t, "SD.SymmetricDifference(X, X)")

		// SD.SymmetricDifference(X, Copy(X))
		X2 := makePset()
		X2.bits.Copy(&X.bits)
		SD.bits.SymmetricDifference(&X.bits, &X2.bits)
		SD.check(t, "SD.SymmetricDifference(X, Copy(X))")

		// Copy(X).SymmetricDifferenceWith(X)
		SD.bits.Copy(&X.bits)
		SD.bits.SymmetricDifferenceWith(&X.bits)
		SD.check(t, "Copy(X).SymmetricDifferenceWith(X)")
	}
}

// TestUnionWithChanged checks the 'changed' result of UnionWith.
func TestUnionWithChanged(t *testing.T) {
	setOf := func(elems ...int) *intsets.Sparse {
		s := new(intsets.Sparse)
		for _, elem := range elems {
			s.Insert(elem)
		}
		return s
	}

	checkUnionWith := func(x, y *intsets.Sparse) {
		xstr := x.String()
		prelen := x.Len()
		changed := x.UnionWith(y)
		if (x.Len() > prelen) != changed {
			t.Errorf("%s.UnionWith(%s) => %s, changed=%t", xstr, y, x, changed)
		}
	}

	// The case marked "!" is a regression test for Issue 50352,
	// which spuriously returned true when y ⊂ x.

	// same block
	checkUnionWith(setOf(1, 2), setOf(1, 2))
	checkUnionWith(setOf(1, 2, 3), setOf(1, 2)) // !
	checkUnionWith(setOf(1, 2), setOf(1, 2, 3))
	checkUnionWith(setOf(1, 2), setOf())

	// different blocks
	checkUnionWith(setOf(1, 1000000), setOf(1, 1000000))
	checkUnionWith(setOf(1, 2, 1000000), setOf(1, 2))
	checkUnionWith(setOf(1, 2), setOf(1, 2, 1000000))
	checkUnionWith(setOf(1, 1000000), setOf())
}

func TestIntersectionWith(t *testing.T) {
	// Edge cases: the pairs (1,1), (1000,2000), (8000,4000)
	// exercise the <, >, == cases in IntersectionWith that the
	// TestSetOperations data is too dense to cover.
	var X, Y intsets.Sparse
	X.Insert(1)
	X.Insert(1000)
	X.Insert(8000)
	Y.Insert(1)
	Y.Insert(2000)
	Y.Insert(4000)
	X.IntersectionWith(&Y)
	if got, want := X.String(), "{1}"; got != want {
		t.Errorf("IntersectionWith: got %s, want %s", got, want)
	}
}

func TestIntersects(t *testing.T) {
	prng := rand.New(rand.NewSource(0))

	for i := range uint(12) {
		X, Y := randomPset(prng, 1<<i), randomPset(prng, 1<<i)
		x, y := &X.bits, &Y.bits

		// test the slow way
		var z intsets.Sparse
		z.Copy(x)
		z.IntersectionWith(y)

		if got, want := x.Intersects(y), !z.IsEmpty(); got != want {
			t.Errorf("Intersects(%s, %s): got %v, want %v (%s)", x, y, got, want, &z)
		}

		// make it false
		a := x.AppendTo(nil)
		for _, v := range a {
			y.Remove(v)
		}

		if got, want := x.Intersects(y), false; got != want {
			t.Errorf("Intersects: got %v, want %v", got, want)
		}

		// make it true
		if x.IsEmpty() {
			continue
		}
		i := prng.Intn(len(a))
		y.Insert(a[i])

		if got, want := x.Intersects(y), true; got != want {
			t.Errorf("Intersects: got %v, want %v", got, want)
		}
	}
}

func TestSubsetOf(t *testing.T) {
	prng := rand.New(rand.NewSource(0))

	for i := range uint(12) {
		X, Y := randomPset(prng, 1<<i), randomPset(prng, 1<<i)
		x, y := &X.bits, &Y.bits

		// test the slow way
		var z intsets.Sparse
		z.Copy(x)
		z.DifferenceWith(y)

		if got, want := x.SubsetOf(y), z.IsEmpty(); got != want {
			t.Errorf("SubsetOf: got %v, want %v", got, want)
		}

		// make it true
		y.UnionWith(x)

		if got, want := x.SubsetOf(y), true; got != want {
			t.Errorf("SubsetOf: got %v, want %v", got, want)
		}

		// make it false
		if x.IsEmpty() {
			continue
		}
		a := x.AppendTo(nil)
		i := prng.Intn(len(a))
		y.Remove(a[i])

		if got, want := x.SubsetOf(y), false; got != want {
			t.Errorf("SubsetOf: got %v, want %v", got, want)
		}
	}
}

func TestBitString(t *testing.T) {
	for _, test := range []struct {
		input []int
		want  string
	}{
		{nil, "0"},
		{[]int{0}, "1"},
		{[]int{0, 4, 5}, "110001"},
		{[]int{0, 7, 177}, "1" + strings.Repeat("0", 169) + "10000001"},
		{[]int{-3, 0, 4, 5}, "110001.001"},
		{[]int{-3}, "0.001"},
	} {
		var set intsets.Sparse
		for _, x := range test.input {
			set.Insert(x)
		}
		if got := set.BitString(); got != test.want {
			t.Errorf("BitString(%s) = %s, want %s", set.String(), got, test.want)
		}
	}
}

func TestFailFastOnShallowCopy(t *testing.T) {
	var x intsets.Sparse
	x.Insert(1)

	y := x // shallow copy (breaks representation invariants)
	defer func() {
		got := fmt.Sprint(recover())
		want := "interface conversion: interface {} is nil, not intsets.to_copy_a_sparse_you_must_call_its_Copy_method"
		if got != want {
			t.Errorf("shallow copy: recover() = %q, want %q", got, want)
		}
	}()
	_ = y.String() // panics
	t.Error("didn't panic as expected")
}

// -- Benchmarks -------------------------------------------------------

// TODO(adonovan):
// - Add benchmarks of each method.
// - Gather set distributions from pointer analysis.
// - Measure memory usage.

func benchmarkInsertProbeSparse(b *testing.B, size, spread int) {
	prng := rand.New(rand.NewSource(0))
	// Generate our insertions and probes beforehand (we don't want to benchmark
	// the prng).
	insert := make([]int, size)
	probe := make([]int, size*2)
	for i := range insert {
		insert[i] = prng.Int() % spread
	}
	for i := range probe {
		probe[i] = prng.Int() % spread
	}

	b.ResetTimer()
	var x intsets.Sparse
	for tries := 0; tries < b.N; tries++ {
		x.Clear()
		for _, n := range insert {
			x.Insert(n)
		}
		hits := 0
		for _, n := range probe {
			if x.Has(n) {
				hits++
			}
		}
		// Use the variable so it doesn't get optimized away.
		if hits > len(probe) {
			b.Fatalf("%d hits, only %d probes", hits, len(probe))
		}
	}
}

func BenchmarkInsertProbeSparse_2_10(b *testing.B) {
	benchmarkInsertProbeSparse(b, 2, 10)
}

func BenchmarkInsertProbeSparse_10_10(b *testing.B) {
	benchmarkInsertProbeSparse(b, 10, 10)
}

func BenchmarkInsertProbeSparse_10_1000(b *testing.B) {
	benchmarkInsertProbeSparse(b, 10, 1000)
}

func BenchmarkInsertProbeSparse_100_100(b *testing.B) {
	benchmarkInsertProbeSparse(b, 100, 100)
}

func BenchmarkInsertProbeSparse_100_10000(b *testing.B) {
	benchmarkInsertProbeSparse(b, 100, 1000)
}

func BenchmarkUnionDifferenceSparse(b *testing.B) {
	prng := rand.New(rand.NewSource(0))
	for tries := 0; tries < b.N; tries++ {
		var x, y, z intsets.Sparse
		for i := range 1000 {
			n := int(prng.Int()) % 100000
			if i%2 == 0 {
				x.Insert(n)
			} else {
				y.Insert(n)
			}
		}
		z.Union(&x, &y)
		z.Difference(&x, &y)
	}
}

func BenchmarkUnionDifferenceHashTable(b *testing.B) {
	prng := rand.New(rand.NewSource(0))
	for tries := 0; tries < b.N; tries++ {
		x, y, z := make(map[int]bool), make(map[int]bool), make(map[int]bool)
		for i := range 1000 {
			n := int(prng.Int()) % 100000
			if i%2 == 0 {
				x[n] = true
			} else {
				y[n] = true
			}
		}
		// union
		for n := range x {
			z[n] = true
		}
		for n := range y {
			z[n] = true
		}
		// difference
		z = make(map[int]bool)
		for n := range y {
			if !x[n] {
				z[n] = true
			}
		}
	}
}

func BenchmarkAppendTo(b *testing.B) {
	prng := rand.New(rand.NewSource(0))
	var x intsets.Sparse
	for range 1000 {
		x.Insert(int(prng.Int()) % 10000)
	}
	var space [1000]int
	for tries := 0; tries < b.N; tries++ {
		x.AppendTo(space[:0])
	}
}
