package blobovnicza

import (
	"encoding/binary"
	"fmt"
	"strconv"

	"go.etcd.io/bbolt"
)

const firstBucketBound = uint64(32 * 1 << 10) // 32KB

func stringifyBounds(lower, upper uint64) string {
	return fmt.Sprintf("[%s:%s]",
		stringifyByteSize(lower),
		stringifyByteSize(upper),
	)
}

func stringifyByteSize(sz uint64) string {
	return strconv.FormatUint(sz, 10)
}

func bucketKeyFromBounds(upperBound uint64) []byte {
	buf := make([]byte, binary.MaxVarintLen64)

	ln := binary.PutUvarint(buf, upperBound)

	return buf[:ln]
}

func bucketForSize(sz uint64) []byte {
	return bucketKeyFromBounds(upperPowerOfTwo(sz))
}

func upperPowerOfTwo(v uint64) (upperBound uint64) {
	for upperBound = firstBucketBound; upperBound < v; upperBound *= 2 {
	}

	return
}

func (b *Blobovnicza) incSize(sz uint64) {
	b.filled.Add(sz)
}

func (b *Blobovnicza) decSize(sz uint64) {
	b.filled.Sub(sz)
}

func (b *Blobovnicza) full() bool {
	return b.filled.Load() >= b.fullSizeLimit
}

func (b *Blobovnicza) syncFullnessCounter() error {
	err := b.boltDB.View(func(tx *bbolt.Tx) error {
		sz := uint64(0)

		if err := b.iterateBucketKeys(func(lower, upper uint64, key []byte) (bool, error) {
			buck := tx.Bucket(key)
			if buck == nil {
				return false, fmt.Errorf("bucket not found %s", stringifyBounds(lower, upper))
			}

			sz += uint64(buck.Stats().KeyN) * (upper - lower)

			return false, nil
		}); err != nil {
			return err
		}

		b.filled.Store(sz)

		return nil
	})
	if err != nil {
		return fmt.Errorf("could not sync fullness counter: %w", err)
	}
	return nil
}