package freelist

import (
	"fmt"
	"sync"

	"github.com/RoaringBitmap/roaring/v2"
)

type FreeList struct {
	mutex    sync.Mutex
	free     *roaring.Bitmap
	reserved *roaring.Bitmap
}

func New() *FreeList {
	return &FreeList{
		free:     roaring.New(),
		reserved: roaring.New(),
	}
}

func (s *FreeList) Restore(serialized []byte) error {
	err := s.free.UnmarshalBinary(serialized)
	if err != nil {
		return fmt.Errorf("UnmarshalBinary: %s", err)
	}
	return nil
}

func (s *FreeList) AddPages(pageNumbers []uint32) {
	if len(pageNumbers) == 0 {
		return
	}
	s.mutex.Lock()
	s.free.AddMany(pageNumbers)
	s.mutex.Unlock()
}

// ReserveDataPage - аллокатор резервирует страницу, но не удаляет до визова
// DeleteFromFree, ибо транзакция может не завершится, а между віделением страници
// и падением транзакции - будет создан init файл.
func (s *FreeList) ReservePage() (pageNo uint32) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if s.free.IsEmpty() {
		return
	}
	pageNo = s.free.Minimum()
	s.free.Remove(pageNo)
	s.reserved.Add(pageNo)
	return
}

// Удаляет ранее зарезервированные страницы
func (s *FreeList) DeletePages(pageNumbers []uint32) {
	s.mutex.Lock()
	for _, pageNo := range pageNumbers {
		s.reserved.Remove(pageNo)
		s.free.Remove(pageNo) // прокрута TransactionLog
	}
	s.mutex.Unlock()
}

func (s *FreeList) Serialize() ([]byte, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	tmp := roaring.Or(s.free, s.reserved)
	tmp.RunOptimize()
	return tmp.ToBytes()
}
