// Copyright (c) Roman Atachiants and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

package bitmap

import (
	"fmt"
	"math"
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
)

/*
cpu: 13th Gen Intel(R) Core(TM) i7-13700K
BenchmarkBitmap/set-24         	655739137	         1.803 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/remove-24      	1000000000	         1.107 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/contains-24    	1000000000	         0.8975 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/clear-24       	  827574	      1487 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/ones-24        	  571444	      2088 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/min-24         	979591036	         1.252 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/max-24         	944884120	         1.229 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/min-zero-24    	991736356	         1.258 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/max-zero-24    	1000000000	         1.157 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/count-24       	  393440	      3086 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/count-to-24    	58537441	        20.20 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/clone-24       	  648651	      1875 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/and-24         	  685710	      1733 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/andnot-24      	  705882	      1709 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/or-24          	  705894	      1702 ns/op	       0 B/op	       0 allocs/op
BenchmarkBitmap/xor-24         	  705919	      1721 ns/op	       0 B/op	       0 allocs/op
*/
func BenchmarkBitmap(b *testing.B) {
	other := make(Bitmap, 1000000/64)
	other.Set(1000000)

	run(b, "set", func(index Bitmap) {
		index.Set(5000)
	})

	run(b, "remove", func(index Bitmap) {
		index.Remove(5000)
	})

	run(b, "contains", func(index Bitmap) {
		index.Contains(5000)
	})

	run(b, "clear", func(index Bitmap) {
		index.Clear()
	})

	run(b, "ones", func(index Bitmap) {
		index.Ones()
	})

	run(b, "min", func(index Bitmap) {
		index.Min()
	})

	run(b, "max", func(index Bitmap) {
		index.Max()
	})

	run(b, "min-zero", func(index Bitmap) {
		index.MinZero()
	})

	run(b, "max-zero", func(index Bitmap) {
		index.MaxZero()
	})

	run(b, "count", func(index Bitmap) {
		index.Count()
	})

	run(b, "count-to", func(index Bitmap) {
		index.CountTo(5001)
	})

	into := make(Bitmap, len(other))
	run(b, "clone", func(index Bitmap) {
		index.Clone(&into)
	})

	run(b, "and", func(index Bitmap) {
		index.And(other)
	})

	run(b, "andnot", func(index Bitmap) {
		index.AndNot(other)
	})

	run(b, "or", func(index Bitmap) {
		index.AndNot(other)
	})

	run(b, "xor", func(index Bitmap) {
		index.AndNot(other)
	})
}

/*
cpu: 13th Gen Intel(R) Core(TM) i7-13700K
BenchmarkMany/and4-noasm-24         	   66297	     18139 ns/op	       0 B/op	       0 allocs/op
BenchmarkMany/and4-naive-24         	  179106	      6803 ns/op	       0 B/op	       0 allocs/op
BenchmarkMany/and4-batch-24         	  258091	      4679 ns/op	      32 B/op	       1 allocs/op
*/
func BenchmarkMany(b *testing.B) {
	other := make(Bitmap, 1000000/64)
	other.Set(1000000)

	run(b, "and4-noasm", func(index Bitmap) {
		max := minlen(index, other, nil)
		index.shrink(max)
		and(index, max, other, nil)
		and(index, max, other, nil)
		and(index, max, other, nil)
		and(index, max, other, nil)
	})

	run(b, "and4-naive", func(index Bitmap) {
		index.And(other)
		index.And(other)
		index.And(other)
		index.And(other)
	})

	run(b, "and4-batch", func(index Bitmap) {
		index.And(other, other, other, other)
	})
}

func TestSetRemove(t *testing.T) {
	index := Bitmap{}
	for i := uint32(100); i < 200; i++ {
		index.Set(i)
		assert.True(t, index.Contains(i))
	}

	for i := uint32(150); i < 180; i++ {
		index.Remove(i)
		assert.False(t, index.Contains(i))
	}
}

func TestClear(t *testing.T) {
	index := Bitmap{}
	for i := uint32(0); i < 500; i++ {
		index.Set(i)
		assert.True(t, index.Contains(i))
	}

	index.Clear()
	index.Set(500)
	for i := uint32(0); i < 500; i++ {
		assert.False(t, index.Contains(i), i)
	}
	assert.True(t, index.Contains(500))
}

func TestAnd(t *testing.T) {
	a, b := Bitmap{}, Bitmap{}
	for i := uint32(0); i < 100; i += 2 {
		a.Set(i)
		b.Set(i)
	}

	a.And(b)
	assert.False(t, a.Contains(1))
	for i := uint32(0); i < 100; i += 2 {
		assert.True(t, a.Contains(i))
	}
}

func TestAndNot(t *testing.T) {
	a, b := Bitmap{}, Bitmap{}
	for i := uint32(0); i < 100; i += 2 {
		a.Set(i)
		b.Set(i)
	}

	a.AndNot(b)
	assert.False(t, a.Contains(1))
	for i := uint32(0); i < 100; i += 2 {
		assert.False(t, a.Contains(i))
	}
}

func TestAndNot_TheSameBitmap(t *testing.T) {
	var a Bitmap
	for i := uint32(0); i < 100; i += 2 {
		a.Set(i)
	}

	a.AndNot(a)

	for i := uint32(0); i < 100; i++ {
		assert.Equal(t, false, a.Contains(i), "for "+strconv.Itoa(int(i)))
	}
	assert.Equal(t, 0, a.Count())
}

func TestAndNot_DifferentBitmapSizes(t *testing.T) {
	var a, b, c, d Bitmap
	for i := uint32(0); i < 100; i += 2 {
		a.Set(i)
		c.Set(i)
	}

	for i := uint32(0); i < 200; i += 2 {
		b.Set(i)
		d.Set(i)
	}
	a.AndNot(b)
	d.AndNot(c)

	for i := uint32(0); i < 100; i++ {
		assert.Equal(t, false, a.Contains(i), "for "+strconv.Itoa(int(i)))
		assert.Equal(t, false, d.Contains(i), "for "+strconv.Itoa(int(i)))
	}
	for i := uint32(100); i < 200; i++ {
		assert.Equal(t, b.Contains(i), d.Contains(i), "for "+strconv.Itoa(int(i)))
	}
	assert.Equal(t, 0, a.Count())
	assert.Equal(t, 50, d.Count())
}

func TestOr(t *testing.T) {
	a, b := Bitmap{}, Bitmap{}
	for i := uint32(0); i < 100; i += 2 {
		b.Set(i)
	}

	a.Or(b)
	assert.False(t, a.Contains(1))
	for i := uint32(0); i < 100; i += 2 {
		assert.True(t, a.Contains(i))
	}
}

func TestOr_DifferentBitmapSizes(t *testing.T) {
	var a, b, c, d Bitmap
	for i := uint32(0); i < 100; i += 2 {
		a.Set(i)
		c.Set(i)
	}

	for i := uint32(0); i < 200; i += 2 {
		b.Set(i)
		d.Set(i)
	}
	a.Or(b)
	d.Or(c)

	for i := uint32(0); i < 200; i++ {
		assert.Equal(t, d.Contains(i), a.Contains(i), "for "+strconv.Itoa(int(i)))
	}
	assert.Equal(t, 100, a.Count())
	assert.Equal(t, 100, d.Count())
}

func TestXor(t *testing.T) {
	a, b := Bitmap{}, Bitmap{}
	for i := uint32(0); i < 100; i += 2 {
		b.Set(i)
	}

	a.Xor(b)
	assert.False(t, a.Contains(1))
	for i := uint32(0); i < 100; i += 2 {
		assert.True(t, a.Contains(i))
	}
}

func TestXOr_DifferentBitmapSizes(t *testing.T) {
	var a, b, c, d Bitmap
	for i := uint32(0); i < 100; i += 2 {
		a.Set(i)
		c.Set(i)
	}

	for i := uint32(0); i < 200; i += 2 {
		b.Set(i)
		d.Set(i)
	}
	a.Xor(b)
	d.Xor(c)

	for i := uint32(0); i < 200; i++ {
		assert.Equal(t, d.Contains(i), a.Contains(i), "for "+strconv.Itoa(int(i)))
	}
	assert.Equal(t, 50, a.Count())
	assert.Equal(t, 50, d.Count())
}

func TestMin(t *testing.T) {
	{
		a := Bitmap{0x0, 0x0, 0xffffffffffffff00}
		v, ok := a.Min()
		assert.True(t, ok)
		assert.Equal(t, 64+64+8, int(v))
		assert.False(t, a.Contains(v-1))
		assert.True(t, a.Contains(v))
	}

	{
		a := Bitmap{0x0, 0x0}
		v, ok := a.Min()
		assert.False(t, ok)
		assert.Equal(t, 0, int(v))
	}
}

func TestMax(t *testing.T) {
	{
		a := Bitmap{0x0, 0x0, 0x00000000000000f0}
		v, ok := a.Max()
		assert.True(t, ok)
		assert.Equal(t, 64+64+7, int(v))

		assert.False(t, a.Contains(v-4))
		assert.True(t, a.Contains(v-3))
		assert.True(t, a.Contains(v-2))
		assert.True(t, a.Contains(v-1))
		assert.True(t, a.Contains(v))
		assert.False(t, a.Contains(v+1))
		assert.False(t, a.Contains(v+2))
	}

	{
		a := Bitmap{0x0, 0x0}
		v, ok := a.Max()
		assert.False(t, ok)
		assert.Equal(t, 0, int(v))
	}
}

func TestMinZero(t *testing.T) {
	{
		a := Bitmap{0xffffffffffffffff, 0xffffffffffffffff, 0xf0ffffffffffff0f}
		v, ok := a.MinZero()
		assert.True(t, ok)
		assert.Equal(t, 64+64+4, int(v))
		assert.False(t, a.Contains(v))
	}

	{
		a := Bitmap{0xffffffffffffffff, 0xffffffffffffffff}
		v, ok := a.MinZero()
		assert.False(t, ok)
		assert.Equal(t, 0, int(v))
	}
}

func TestMaxZero(t *testing.T) {
	{
		a := Bitmap{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffff0f}
		v, ok := a.MaxZero()
		assert.True(t, ok)
		assert.Equal(t, 64+64+7, int(v))
		assert.False(t, a.Contains(v))
	}

	{
		a := Bitmap{0xffffffffffffffff, 0xffffffffffffffff}
		v, ok := a.MaxZero()
		assert.False(t, ok)
		assert.Equal(t, 0, int(v))
	}
}

func TestCount(t *testing.T) {
	a := Bitmap{}
	assert.Equal(t, 0, a.Count())
	assert.Equal(t, 0, a.CountTo(math.MaxUint32))

	b := Bitmap{}
	b.Set(1)
	b.Set(2)
	b.Set(5)
	b.Set(6)
	b.Set(101)
	b.Set(102)
	b.Set(105)
	b.Set(106)

	assert.Equal(t, 8, b.Count())
	assert.Equal(t, 1, b.CountTo(2))
	assert.Equal(t, 2, b.CountTo(4))
	assert.Equal(t, 4, b.CountTo(100))
	assert.Equal(t, 4, b.CountTo(101))
	assert.Equal(t, 5, b.CountTo(102))
	assert.Equal(t, 8, b.CountTo(128))
	assert.Equal(t, 8, b.CountTo(math.MaxUint32))

	b.Set(127)

	assert.Equal(t, 9, b.CountTo(128))
}

func TestGrow(t *testing.T) {
	bitmap := make(Bitmap, 1, 5)
	bitmap[0] = 42

	assert.Equal(t, 1, len(bitmap))
	assert.Equal(t, 5, cap(bitmap))
	assert.Equal(t, Bitmap{42}, bitmap)

	bitmap.grow(0)
	assert.Equal(t, 1, len(bitmap))
	assert.Equal(t, 5, cap(bitmap))
	assert.Equal(t, Bitmap{42}, bitmap)

	bitmap.grow(4)
	assert.Equal(t, 5, len(bitmap))
	assert.Equal(t, 5, cap(bitmap))
	assert.Equal(t, Bitmap{42, 0, 0, 0, 0}, bitmap)

	bitmap.grow(5)
	assert.Equal(t, 6, len(bitmap))
	assert.Equal(t, Bitmap{42, 0, 0, 0, 0, 0}, bitmap)
	bitmap.Grow(6)
}

func TestAnd_DifferentBitmapSizes(t *testing.T) {
	var a, b, c, d Bitmap
	for i := uint32(0); i < 100; i += 2 {
		a.Set(i)
		c.Set(i)
	}

	for i := uint32(0); i < 200; i += 2 {
		b.Set(i)
		d.Set(i)
	}

	a.And(b)
	d.And(c)

	for i := uint32(0); i < 200; i++ {
		assert.Equal(t, a.Contains(i), d.Contains(i), "for "+strconv.Itoa(int(i)))
	}
	assert.Equal(t, 50, a.Count())
	assert.Equal(t, 50, d.Count())
}

func TestAnd_ConsecutiveAnd_DifferentBitmapSizes(t *testing.T) {
	var a, b, c Bitmap
	for i := uint32(0); i < 200; i += 2 {
		a.Set(i)
		c.Set(i)
	}

	for i := uint32(0); i < 100; i += 2 {
		b.Set(i)
	}

	a.And(b)
	a.And(c)

	for i := uint32(0); i < 200; i++ {
		assert.Equal(t, a.Contains(i), b.Contains(i), "for "+strconv.Itoa(int(i)))
	}
	assert.Equal(t, 50, a.Count())
}

func TestResizeBitmap(t *testing.T) {
	assert.Equal(t, 1, resize(100, 0))
	assert.Equal(t, 2, resize(100, 1))
	assert.Equal(t, 4, resize(100, 2))
	assert.Equal(t, 16, resize(100, 11))
	assert.Equal(t, 256, resize(100, 255))
	assert.Equal(t, 1232, resize(100, 1000))
	assert.Equal(t, 1232, resize(200, 1000))
	assert.Equal(t, 1232, resize(512, 1000))
	assert.Equal(t, 1213, resize(500, 1000)) // Inconsistent
	assert.Equal(t, 22504, resize(512, 20000))
	assert.Equal(t, 28322, resize(22504, 22600))
}

func TestMinInteger(t *testing.T) {
	tests := [][3]int{
		{10, 20, 10},
		{20, 10, 10},
		{0, 10, 0},
		{10, 0, 0},
		{10, 10, 10},
		{10, -10, -10},
		{-10, 10, -10},
		{-10, 0, -10},
		{-10, -10, -10},
	}

	for _, tc := range tests {
		assert.Equal(t, tc[2], minint(tc[0], tc[1]), fmt.Sprintf("min(%v, %v)", tc[0], tc[1]))
	}
}

func TestMaxInteger(t *testing.T) {
	tests := [][3]int{
		{10, 20, 20},
		{20, 10, 20},
		{0, 10, 10},
		{10, 0, 10},
		{10, 10, 10},
		{10, -10, 10},
		{-10, 10, 10},
		{-10, 0, 0},
		{-10, -10, -10},
	}

	for _, tc := range tests {
		assert.Equal(t, tc[2], maxint(tc[0], tc[1]), fmt.Sprintf("max(%v, %v)", tc[0], tc[1]))
	}
}

func TestBatched(t *testing.T) {
	const bits = 0b0011

	// Functions to test
	tests := []func(Bitmap) func(Bitmap, ...Bitmap){
		func(b Bitmap) func(Bitmap, ...Bitmap) {
			return b.And
		},
		func(b Bitmap) func(Bitmap, ...Bitmap) {
			return b.AndNot
		},
		func(b Bitmap) func(Bitmap, ...Bitmap) {
			return b.Or
		},
		func(b Bitmap) func(Bitmap, ...Bitmap) {
			return b.Xor
		},
	}

	for _, withHw := range []int{isAccelerated, isUnsupported} {
		for i, tc := range tests {
			t.Run(fmt.Sprintf("%v,avx=%v", i, withHw), func(t *testing.T) {
				hardware = withHw
				naive := func(n int) Bitmap {
					input := Bitmap{bits}
					tc(input)(Bitmap{bits})
					for i := 0; i < n; i++ {
						tc(input)(Bitmap{bits})
					}
					return input
				}

				for n := 0; n < 5; n++ {
					input := Bitmap{bits}
					other := Bitmap{bits}

					extra := make([]Bitmap, 0, n)
					for i := 0; i < n; i++ {
						extra = append(extra, Bitmap{bits})
					}

					tc(input)(other, extra...)
					assert.Equal(t, naive(n), input)
				}
			})
		}
	}
}

func TestEmptyAnd(t *testing.T) {
	var a, b Bitmap
	a.And(b)
	assert.Equal(t, 0, a.Count())
}

func TestEmptyAndNot(t *testing.T) {
	var a, b Bitmap
	a.AndNot(b)
	assert.Equal(t, 0, a.Count())
}

func TestEmptyOr(t *testing.T) {
	var a, b Bitmap
	a.Or(b)
	assert.Equal(t, 0, a.Count())
}

func TestEmptyXor(t *testing.T) {
	var a, b Bitmap
	a.Xor(b)
	assert.Equal(t, 0, a.Count())
}

func TestTruthTables_NoSIMD(t *testing.T) {
	hardware = isUnsupported
	testTruthTables(t)
}

func TestTruthTables_SIMD(t *testing.T) {
	hardware = isAccelerated
	testTruthTables(t)
}

func testTruthTables(t *testing.T) {
	{ // AND
		a := Bitmap{0b0011, 0b1011, 0b1100, 0b0000, 0b0011, 0b1011, 0b1100, 0b0000, 0b0011}
		a.And(Bitmap{0b0101, 0b1101, 0b1010, 0b1111, 0b0101, 0b1101, 0b1010, 0b1111, 0b0101})
		assert.Equal(t, 0b0001, int(a[0]))
		assert.Equal(t, 0b1001, int(a[1]))
		assert.Equal(t, 0b1000, int(a[2]))
		assert.Equal(t, 0b0000, int(a[3]))
		assert.Equal(t, 0b0001, int(a[4]))
		assert.Equal(t, 0b1001, int(a[5]))
		assert.Equal(t, 0b1000, int(a[6]))
		assert.Equal(t, 0b0000, int(a[7]))
		assert.Equal(t, 0b0001, int(a[8]))
	}
	{ // AND NOT
		a := Bitmap{0b0011, 0, 0, 0}
		a.AndNot(Bitmap{0b0101})
		assert.Equal(t, 0b0010, int(a[0]))
	}
	{ // OR
		a := Bitmap{0b0011, 0, 0, 0}
		a.Or(Bitmap{0b0101})
		assert.Equal(t, 0b0111, int(a[0]))
	}
	{ // XOR
		a := Bitmap{0b0011, 0, 0, 0}
		a.Xor(Bitmap{0b0101})
		assert.Equal(t, 0b0110, int(a[0]))
	}
}
