/*
 * EliasDB
 *
 * Copyright 2016 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package pageview

import (
	"devt.de/krotik/eliasdb/storage/file"
	"devt.de/krotik/eliasdb/storage/paging/view"
	"devt.de/krotik/eliasdb/storage/util"
)

// OFFSET_COUNT / OFFSET_DATA declared in freephysicalslotpage

/*
FreeLogicalSlotPage data structure
*/
type FreeLogicalSlotPage struct {
	*SlotInfoPage
	maxSlots               uint16 // Max number of slots
	prevFoundFreeSlot      uint16 // Previous found free slot
	prevFoundAllocatedSlot uint16 // Previous allocated slot
}

/*
NewFreeLogicalSlotPage creates a new page which can manage free slots.
*/
func NewFreeLogicalSlotPage(record *file.Record) *FreeLogicalSlotPage {
	checkFreeLogicalSlotPageMagic(record)

	maxSlots := (len(record.Data()) - OffsetData) / util.LocationSize

	return &FreeLogicalSlotPage{NewSlotInfoPage(record), uint16(maxSlots), 0, 0}
}

/*
checkFreeLogicalSlotPageMagic checks if the magic number at the beginning of
the wrapped record is valid.
*/
func checkFreeLogicalSlotPageMagic(record *file.Record) bool {
	magic := record.ReadInt16(0)

	if magic == view.ViewPageHeader+view.TypeFreeLogicalSlotPage {
		return true
	}
	panic("Unexpected header found in FreeLogicalSlotPage")
}

/*
MaxSlots returns the maximum number of slots which can be allocated.
*/
func (flsp *FreeLogicalSlotPage) MaxSlots() uint16 {
	return flsp.maxSlots
}

/*
FreeSlotCount returns the number of free slots on this page.
*/
func (flsp *FreeLogicalSlotPage) FreeSlotCount() uint16 {
	return flsp.Record.ReadUInt16(OffsetCount)
}

/*
SlotInfoLocation returns contents of a stored slotinfo as a location. Lookup is via a
given slotinfo id.
*/
func (flsp *FreeLogicalSlotPage) SlotInfoLocation(slotinfo uint16) uint64 {
	offset := flsp.slotinfoToOffset(slotinfo)
	return util.PackLocation(flsp.SlotInfoRecord(offset), flsp.SlotInfoOffset(offset))
}

/*
AllocateSlotInfo allocates a place for a slotinfo and returns the offset for it.
*/
func (flsp *FreeLogicalSlotPage) AllocateSlotInfo(slotinfo uint16) uint16 {
	offset := flsp.slotinfoToOffset(slotinfo)

	// Set slotinfo to initial values

	flsp.SetSlotInfo(offset, 1, 1)

	// Increase counter for allocated slotinfos

	flsp.Record.WriteUInt16(OffsetCount, flsp.FreeSlotCount()+1)

	// Update prevFoundAllocatedSlot if necessary

	if slotinfo < flsp.prevFoundAllocatedSlot {
		flsp.prevFoundAllocatedSlot = slotinfo
	}

	return offset
}

/*
ReleaseSlotInfo releases a place for a slotinfo and return its offset.
*/
func (flsp *FreeLogicalSlotPage) ReleaseSlotInfo(slotinfo uint16) uint16 {
	offset := flsp.slotinfoToOffset(slotinfo)

	// Set slotinfo to empty values

	flsp.SetSlotInfo(offset, 0, 0)

	// Decrease counter for allocated slotinfos

	flsp.Record.WriteUInt16(OffsetCount, flsp.FreeSlotCount()-1)

	// Update prevFoundFreeSlot if necessary

	if slotinfo < flsp.prevFoundFreeSlot {
		flsp.prevFoundFreeSlot = slotinfo
	}

	return offset
}

/*
FirstFreeSlotInfo returns the id for the first available slotinfo or -1 if
nothing is available.
*/
func (flsp *FreeLogicalSlotPage) FirstFreeSlotInfo() int {
	for flsp.prevFoundFreeSlot < flsp.maxSlots {
		if !flsp.isAllocatedSlot(flsp.prevFoundFreeSlot) {
			return int(flsp.prevFoundFreeSlot)
		}
		flsp.prevFoundFreeSlot++
	}
	return -1
}

/*
FirstAllocatedSlotInfo returns the id for the first allocated slotinfo or -1 if
nothing is allocated.
*/
func (flsp *FreeLogicalSlotPage) FirstAllocatedSlotInfo() int {
	for flsp.prevFoundAllocatedSlot < flsp.maxSlots {
		if flsp.isAllocatedSlot(flsp.prevFoundAllocatedSlot) {
			return int(flsp.prevFoundAllocatedSlot)
		}
		flsp.prevFoundAllocatedSlot++
	}
	return -1
}

/*
isAllocatedSlot checks if a given slotinfo is allocated.
*/
func (flsp *FreeLogicalSlotPage) isAllocatedSlot(slotinfo uint16) bool {
	offset := flsp.slotinfoToOffset(slotinfo)
	return flsp.SlotInfoRecord(offset) != 0 || flsp.SlotInfoOffset(offset) != 0
}

/*
slotinfoToOffset converts a slotinfo number into an offset on the record.
*/
func (flsp *FreeLogicalSlotPage) slotinfoToOffset(slotinfo uint16) uint16 {
	return OffsetData + slotinfo*util.LocationSize
}
