// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package structs

import (
	"fmt"

	"golang.org/x/exp/slices"
)

// Bitmap is a simple uncompressed bitmap
type Bitmap []byte

// NewBitmap returns a bitmap with up to size indexes
func NewBitmap(size uint) (Bitmap, error) {
	if size == 0 {
		return nil, fmt.Errorf("bitmap must be positive size")
	}
	if size&7 != 0 {
		return nil, fmt.Errorf("bitmap must be byte aligned")
	}
	b := make([]byte, size>>3)
	return Bitmap(b), nil
}

// Copy returns a copy of the Bitmap
func (b Bitmap) Copy() (Bitmap, error) {
	if b == nil {
		return nil, fmt.Errorf("can't copy nil Bitmap")
	}

	raw := make([]byte, len(b))
	copy(raw, b)
	return Bitmap(raw), nil
}

// Size returns the size of the bitmap
func (b Bitmap) Size() uint {
	return uint(len(b) << 3)
}

// Set is used to set the given index of the bitmap
func (b Bitmap) Set(idx uint) {
	bucket := idx >> 3
	mask := byte(1 << (idx & 7))
	b[bucket] |= mask
}

// Unset is used to unset the given index of the bitmap
func (b Bitmap) Unset(idx uint) {
	bucket := idx >> 3
	// Mask should be all ones minus the idx position
	offset := 1 << (idx & 7)
	mask := byte(offset ^ 0xff)
	b[bucket] &= mask
}

// Check is used to check the given index of the bitmap
func (b Bitmap) Check(idx uint) bool {
	bucket := idx >> 3
	mask := byte(1 << (idx & 7))
	return (b[bucket] & mask) != 0
}

// Clear is used to efficiently clear the bitmap
func (b Bitmap) Clear() {
	for i := range b {
		b[i] = 0
	}
}

// IndexesInRange returns the indexes in which the values are either set or unset based
// on the passed parameter in the passed range
func (b Bitmap) IndexesInRange(set bool, from, to uint) []int {
	var indexes []int
	for i := from; i <= to && i < b.Size(); i++ {
		c := b.Check(i)
		if c && set || !c && !set {
			indexes = append(indexes, int(i))
		}
	}

	return indexes
}

// IndexesInRangeFiltered returns the indexes in which the values are either set
// or unset based on the passed parameter in the passed range, and do not appear
// in the filter slice
func (b Bitmap) IndexesInRangeFiltered(set bool, from, to uint, filter []int) []int {
	var indexes []int
	for i := from; i <= to && i < b.Size(); i++ {
		c := b.Check(i)
		if c == set {
			if len(filter) < 1 || !slices.Contains(filter, int(i)) {
				indexes = append(indexes, int(i))
			}
		}
	}

	return indexes
}

// String represents the Bitmap the same as slice of the Bitmap's set values
func (b Bitmap) String() string {
	return fmt.Sprintf("%v", b.IndexesInRange(true, 0, b.Size()))
}
