package freelist

import (
	"bytes"
	"fmt"
	"os"
	"slices"
	"sync"

	"gordenko.dev/dima/qb/bin"
)

const (
	pageSize       = 1024
	crcSize        = 4
	ptrSize        = 4
	pointersOnPage = (pageSize - crcSize) / ptrSize
	flushTreshold  = 1000 //= int(float(pageSize/ptrSize) * 1.3) // кількість вільних сторінок коли вже треба писати на диск
)

type FreeList struct {
	mutex sync.Mutex
	file  *os.File
	pages int
	//free     *roaring.Bitmap
	//reserved *roaring.Bitmap
	free     []uint32
	reserved []uint32
}

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

func (s *FreeList) Restore(serialized []byte) error {
	if (len(serialized) % ptrSize) != 0 {
		return fmt.Errorf("wrong size")
	}
	for i := 0; i < len(serialized); i += ptrSize {
		s.free = append(s.free, bin.GetUint32(serialized[i:]))
	}
	return nil
}

func (s *FreeList) AddPages(pageNumbers []uint32) {
	if len(pageNumbers) == 0 {
		return
	}
	s.mutex.Lock()
	s.free = append(s.free, pageNumbers...)
	if len(s.free) > flushTreshold {
		err := s.save()
		if err != nil {
			// ABORT
		}
	}
	s.mutex.Unlock()
}

func (s *FreeList) save() error {
	buf := make([]byte, pageSize)
	//w := bytes.NewBuffer(nil)
	//s.mutex.Lock()
	i := crcSize
	for _, pageNo := range s.free[:pointersOnPage] {
		bin.PutUint32(buf[i:], pageNo)
		i += ptrSize
	}
	// fix checksum
	fileOffset := int64(s.pages-1) * pageSize
	n, err := s.file.WriteAt(buf, fileOffset)
	if err != nil {
		return err
	}
	if n != pageSize {
		return fmt.Errorf("written size %d bytes not equal page size %d", n, pageSize)
	}
	s.free = s.free[pointersOnPage:]
	s.pages++
	return nil
}

func (s *FreeList) loadLastPage() error {
	var (
		buf    = make([]byte, pageSize)
		offset = int64(s.pages-1) * pageSize
	)
	n, err := s.file.ReadAt(buf, offset)
	if err != nil {
		return fmt.Errorf("file.Seek: %s", err)
	}
	if n != s.pages {
		return fmt.Errorf("read size %d bytes not equal page size %d", n, pageSize)
	}
	// check crc
	//var pageNumbers []uint32
	for i := crcSize; i < len(buf); i += ptrSize {
		s.free = append(s.free, bin.GetUint32(buf[i:]))
	}
	s.pages--
	return nil
}

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

	if len(s.free) == 0 {
		if s.pages == 0 {
			return
		}
		if err := s.loadLastPage(); err != nil {
			// ABORT
			return
		}
	}
	lastIdx := len(s.free)
	pageNo = s.free[lastIdx]
	s.free = s.free[:lastIdx]
	s.reserved = append(s.reserved, pageNo)
	return
}

// Удаляет ранее зарезервированные страницы
func (s *FreeList) DeleteReservedPages(pageNumbers []uint32) {
	var cleared []uint32
	s.mutex.Lock()
	for _, reservedPageNo := range s.reserved {
		if slices.Contains(pageNumbers, reservedPageNo) {
			// delete
		} else {
			cleared = append(cleared, reservedPageNo)
		}
	}
	s.reserved = cleared
	s.mutex.Unlock()
}

func (s *FreeList) Serialize() ([]byte, error) {
	w := bytes.NewBuffer(nil)
	s.mutex.Lock()
	for _, pageNo := range s.free {
		bin.WriteUint32(w, pageNo)
	}
	for _, pageNo := range s.reserved {
		bin.WriteUint32(w, pageNo)
	}
	s.mutex.Unlock()
	return w.Bytes(), nil
}
