package acl

import (
	"fmt"
	"testing"

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

func TestBits(t *testing.T) {
	num := uint32(0b10110)

	require.False(t, isBitSet(num, 0))
	require.True(t, isBitSet(num, 1))
	require.True(t, isBitSet(num, 2))
	require.False(t, isBitSet(num, 3))
	require.True(t, isBitSet(num, 4))
	require.False(t, isBitSet(num, 5))

	setBit(&num, 3)
	require.EqualValues(t, 0b11110, num)

	setBit(&num, 6)
	require.EqualValues(t, 0b1011110, num)

	resetBit(&num, 1)
	require.EqualValues(t, 0b1011100, num)
}

func TestOpBits(t *testing.T) {
	num := uint32(0b_1001_0101_1100_0011_0110_0111_1000_1111)

	require.Panics(t, func() { isOpBitSet(num, opZero, 0) })
	require.Panics(t, func() { isOpBitSet(num, opLast, 0) })

	cpNum := num

	require.Panics(t, func() { setOpBit(&num, opZero, 0) })
	require.EqualValues(t, cpNum, num)
	require.Panics(t, func() { setOpBit(&num, opLast, 0) })
	require.EqualValues(t, cpNum, num)

	for _, tc := range []struct {
		op   Op
		set  [4]bool   // is bit set (left-to-right)
		bits [4]uint32 // result of setting i-th bit (left-to-right) to zero num
	}{
		{
			op:  OpObjectHash,
			set: [4]bool{false, true, false, true},
			bits: [4]uint32{
				0b_0000_1000_0000_0000_0000_0000_0000_0000,
				0b_0000_0100_0000_0000_0000_0000_0000_0000,
				0b_0000_0010_0000_0000_0000_0000_0000_0000,
				0b_0000_0001_0000_0000_0000_0000_0000_0000,
			},
		},
		{
			op:  OpObjectRange,
			set: [4]bool{true, true, false, false},
			bits: [4]uint32{
				0b_0000_0000_1000_0000_0000_0000_0000_0000,
				0b_0000_0000_0100_0000_0000_0000_0000_0000,
				0b_0000_0000_0010_0000_0000_0000_0000_0000,
				0b_0000_0000_0001_0000_0000_0000_0000_0000,
			},
		},
		{
			op:  OpObjectSearch,
			set: [4]bool{false, false, true, true},
			bits: [4]uint32{
				0b_0000_0000_0000_1000_0000_0000_0000_0000,
				0b_0000_0000_0000_0100_0000_0000_0000_0000,
				0b_0000_0000_0000_0010_0000_0000_0000_0000,
				0b_0000_0000_0000_0001_0000_0000_0000_0000,
			},
		},
		{
			op:  OpObjectDelete,
			set: [4]bool{false, true, true, false},
			bits: [4]uint32{
				0b_0000_0000_0000_0000_1000_0000_0000_0000,
				0b_0000_0000_0000_0000_0100_0000_0000_0000,
				0b_0000_0000_0000_0000_0010_0000_0000_0000,
				0b_0000_0000_0000_0000_0001_0000_0000_0000,
			},
		},
		{
			op:  OpObjectPut,
			set: [4]bool{false, true, true, true},
			bits: [4]uint32{
				0b_0000_0000_0000_0000_0000_1000_0000_0000,
				0b_0000_0000_0000_0000_0000_0100_0000_0000,
				0b_0000_0000_0000_0000_0000_0010_0000_0000,
				0b_0000_0000_0000_0000_0000_0001_0000_0000,
			},
		},
		{
			op:  OpObjectHead,
			set: [4]bool{true, false, false, false},
			bits: [4]uint32{
				0b_0000_0000_0000_0000_0000_0000_1000_0000,
				0b_0000_0000_0000_0000_0000_0000_0100_0000,
				0b_0000_0000_0000_0000_0000_0000_0010_0000,
				0b_0000_0000_0000_0000_0000_0000_0001_0000,
			},
		},
		{
			op:  OpObjectGet,
			set: [4]bool{true, true, true, true},
			bits: [4]uint32{
				0b_0000_0000_0000_0000_0000_0000_0000_1000,
				0b_0000_0000_0000_0000_0000_0000_0000_0100,
				0b_0000_0000_0000_0000_0000_0000_0000_0010,
				0b_0000_0000_0000_0000_0000_0000_0000_0001,
			},
		},
	} {
		for i := range tc.set {
			require.EqualValues(t, tc.set[i], isOpBitSet(num, tc.op, uint8(len(tc.set)-1-i)),
				fmt.Sprintf("op %s, left bit #%d", tc.op, i),
			)
		}

		for i := range tc.bits {
			num := uint32(0)

			setOpBit(&num, tc.op, uint8(len(tc.bits)-1-i))

			require.EqualValues(t, tc.bits[i], num)
		}
	}
}