// Copyright 2014 Will Fitzgerald. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file tests bit sets

package bitset

import (
	"math/rand"
	"testing"
)

func BenchmarkSet(b *testing.B) {
	b.StopTimer()
	r := rand.New(rand.NewSource(0))
	sz := 100000
	s := New(uint(sz))
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		s.Set(uint(r.Int31n(int32(sz))))
	}
}

func BenchmarkGetTest(b *testing.B) {
	b.StopTimer()
	r := rand.New(rand.NewSource(0))
	sz := 100000
	s := New(uint(sz))
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		s.Test(uint(r.Int31n(int32(sz))))
	}
}

func BenchmarkSetExpand(b *testing.B) {
	b.StopTimer()
	sz := uint(100000)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		var s BitSet
		s.Set(sz)
	}
}

// go test -bench=Count
func BenchmarkCount(b *testing.B) {
	b.StopTimer()
	s := New(100000)
	for i := 0; i < 100000; i += 100 {
		s.Set(uint(i))
	}
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		s.Count()
	}
}

// go test -bench=Iterate
func BenchmarkIterate(b *testing.B) {
	b.StopTimer()
	s := New(10000)
	for i := 0; i < 10000; i += 3 {
		s.Set(uint(i))
	}
	b.StartTimer()
	for j := 0; j < b.N; j++ {
		c := uint(0)
		for i, e := s.NextSet(0); e; i, e = s.NextSet(i + 1) {
			c++
		}
	}
}

// go test -bench=SparseIterate
func BenchmarkSparseIterate(b *testing.B) {
	b.StopTimer()
	s := New(100000)
	for i := 0; i < 100000; i += 30 {
		s.Set(uint(i))
	}
	b.StartTimer()
	for j := 0; j < b.N; j++ {
		c := uint(0)
		for i, e := s.NextSet(0); e; i, e = s.NextSet(i + 1) {
			c++
		}
	}
}

// go test -bench=LemireCreate
// see http://lemire.me/blog/2016/09/22/swift-versus-java-the-bitset-performance-test/
func BenchmarkLemireCreate(b *testing.B) {
	for i := 0; i < b.N; i++ {
		bitmap := New(0) // we force dynamic memory allocation
		for v := uint(0); v <= 100000000; v += 100 {
			bitmap.Set(v)
		}
	}
}

// go test -bench=LemireCount
// see http://lemire.me/blog/2016/09/22/swift-versus-java-the-bitset-performance-test/
func BenchmarkLemireCount(b *testing.B) {
	bitmap := New(100000000)
	for v := uint(0); v <= 100000000; v += 100 {
		bitmap.Set(v)
	}
	b.ResetTimer()
	sum := uint(0)
	for i := 0; i < b.N; i++ {
		sum += bitmap.Count()
	}
	if sum == 0 { // added just to fool ineffassign
		return
	}
}

// go test -bench=LemireIterate
// see http://lemire.me/blog/2016/09/22/swift-versus-java-the-bitset-performance-test/
func BenchmarkLemireIterate(b *testing.B) {
	bitmap := New(100000000)
	for v := uint(0); v <= 100000000; v += 100 {
		bitmap.Set(v)
	}
	b.ResetTimer()
	sum := uint(0)
	for i := 0; i < b.N; i++ {
		for j, e := bitmap.NextSet(0); e; j, e = bitmap.NextSet(j + 1) {
			sum++
		}
	}
	if sum == 0 { // added just to fool ineffassign
		return
	}
}

// go test -bench=LemireIterateb
// see http://lemire.me/blog/2016/09/22/swift-versus-java-the-bitset-performance-test/
func BenchmarkLemireIterateb(b *testing.B) {
	bitmap := New(100000000)
	for v := uint(0); v <= 100000000; v += 100 {
		bitmap.Set(v)
	}
	b.ResetTimer()
	sum := uint(0)
	for i := 0; i < b.N; i++ {
		for j, e := bitmap.NextSet(0); e; j, e = bitmap.NextSet(j + 1) {
			sum += j
		}
	}

	if sum == 0 { // added just to fool ineffassign
		return
	}
}

// go test -bench=BenchmarkLemireIterateManyb
// see http://lemire.me/blog/2016/09/22/swift-versus-java-the-bitset-performance-test/
func BenchmarkLemireIterateManyb(b *testing.B) {
	bitmap := New(100000000)
	for v := uint(0); v <= 100000000; v += 100 {
		bitmap.Set(v)
	}
	buffer := make([]uint, 256)
	b.ResetTimer()
	sum := uint(0)
	for i := 0; i < b.N; i++ {
		j := uint(0)
		j, buffer = bitmap.NextSetMany(j, buffer)
		for ; len(buffer) > 0; j, buffer = bitmap.NextSetMany(j, buffer) {
			for k := range buffer {
				sum += buffer[k]
			}
			j++
		}
	}

	if sum == 0 { // added just to fool ineffassign
		return
	}
}

func setRnd(bits []uint64, halfings int) {
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := range bits {
		bits[i] = 0xFFFFFFFFFFFFFFFF
		for j := 0; j < halfings; j++ {
			bits[i] &= rnd.Uint64()
		}
	}
}

// go test -bench=BenchmarkFlorianUekermannIterateMany
func BenchmarkFlorianUekermannIterateMany(b *testing.B) {
	var input = make([]uint64, 68)
	setRnd(input, 4)
	var bitmap = From(input)
	buffer := make([]uint, 256)
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		var last, batch = bitmap.NextSetMany(0, buffer)
		for len(batch) > 0 {
			for _, idx := range batch {
				checksum += idx
			}
			last, batch = bitmap.NextSetMany(last+1, batch)
		}
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

func BenchmarkFlorianUekermannIterateManyReg(b *testing.B) {
	var input = make([]uint64, 68)
	setRnd(input, 4)
	var bitmap = From(input)
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		for j, e := bitmap.NextSet(0); e; j, e = bitmap.NextSet(j + 1) {
			checksum += j
		}
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

// function provided by FlorianUekermann
func good(set []uint64) (checksum uint) {
	for wordIdx, word := range set {
		var wordIdx = uint(wordIdx * 64)
		for word != 0 {
			var bitIdx = uint(trailingZeroes64(word))
			word ^= 1 << bitIdx
			var index = wordIdx + bitIdx
			checksum += index
		}
	}
	return checksum
}

func BenchmarkFlorianUekermannIterateManyComp(b *testing.B) {
	var input = make([]uint64, 68)
	setRnd(input, 4)
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		checksum += good(input)
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

/////// Mid density

// go test -bench=BenchmarkFlorianUekermannLowDensityIterateMany
func BenchmarkFlorianUekermannLowDensityIterateMany(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 50000; i++ {
		input[rnd.Uint64()%1000000] = 1
	}
	var bitmap = From(input)
	buffer := make([]uint, 256)
	b.ResetTimer()
	var sum = uint(0)
	for i := 0; i < b.N; i++ {
		j := uint(0)
		j, buffer = bitmap.NextSetMany(j, buffer)
		for ; len(buffer) > 0; j, buffer = bitmap.NextSetMany(j, buffer) {
			for k := range buffer {
				sum += buffer[k]
			}
			j++
		}
	}
	if sum == 0 { // added just to fool ineffassign
		return
	}
}

func BenchmarkFlorianUekermannLowDensityIterateManyReg(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 50000; i++ {
		input[rnd.Uint64()%1000000] = 1
	}
	var bitmap = From(input)
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		for j, e := bitmap.NextSet(0); e; j, e = bitmap.NextSet(j + 1) {
			checksum += j
		}
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

func BenchmarkFlorianUekermannLowDensityIterateManyComp(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 50000; i++ {
		input[rnd.Uint64()%1000000] = 1
	}
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		checksum += good(input)
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

/////// Mid density

// go test -bench=BenchmarkFlorianUekermannMidDensityIterateMany
func BenchmarkFlorianUekermannMidDensityIterateMany(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 3000000; i++ {
		input[rnd.Uint64()%1000000] |= uint64(1) << (rnd.Uint64() % 64)
	}
	var bitmap = From(input)
	buffer := make([]uint, 256)
	b.ResetTimer()
	sum := uint(0)
	for i := 0; i < b.N; i++ {
		j := uint(0)
		j, buffer = bitmap.NextSetMany(j, buffer)
		for ; len(buffer) > 0; j, buffer = bitmap.NextSetMany(j, buffer) {
			for k := range buffer {
				sum += buffer[k]
			}
			j++
		}
	}

	if sum == 0 { // added just to fool ineffassign
		return
	}
}

func BenchmarkFlorianUekermannMidDensityIterateManyReg(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 3000000; i++ {
		input[rnd.Uint64()%1000000] |= uint64(1) << (rnd.Uint64() % 64)
	}
	var bitmap = From(input)
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		for j, e := bitmap.NextSet(0); e; j, e = bitmap.NextSet(j + 1) {
			checksum += j
		}
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

func BenchmarkFlorianUekermannMidDensityIterateManyComp(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 3000000; i++ {
		input[rnd.Uint64()%1000000] |= uint64(1) << (rnd.Uint64() % 64)
	}
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		checksum += good(input)
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

////////// High density

func BenchmarkFlorianUekermannMidStrongDensityIterateMany(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 20000000; i++ {
		input[rnd.Uint64()%1000000] |= uint64(1) << (rnd.Uint64() % 64)
	}
	var bitmap = From(input)
	buffer := make([]uint, 256)
	b.ResetTimer()
	sum := uint(0)
	for i := 0; i < b.N; i++ {
		j := uint(0)
		j, buffer = bitmap.NextSetMany(j, buffer)
		for ; len(buffer) > 0; j, buffer = bitmap.NextSetMany(j, buffer) {
			for k := range buffer {
				sum += buffer[k]
			}
			j++
		}
	}

	if sum == 0 { // added just to fool ineffassign
		return
	}
}

func BenchmarkFlorianUekermannMidStrongDensityIterateManyReg(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 20000000; i++ {
		input[rnd.Uint64()%1000000] |= uint64(1) << (rnd.Uint64() % 64)
	}
	var bitmap = From(input)
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		for j, e := bitmap.NextSet(0); e; j, e = bitmap.NextSet(j + 1) {
			checksum += j
		}
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}

func BenchmarkFlorianUekermannMidStrongDensityIterateManyComp(b *testing.B) {
	var input = make([]uint64, 1000000)
	var rnd = rand.NewSource(0).(rand.Source64)
	for i := 0; i < 20000000; i++ {
		input[rnd.Uint64()%1000000] |= uint64(1) << (rnd.Uint64() % 64)
	}
	b.ResetTimer()
	var checksum = uint(0)
	for i := 0; i < b.N; i++ {
		checksum += good(input)
	}
	if checksum == 0 { // added just to fool ineffassign
		return
	}
}
