#pragma once
/// \file heap_allocator.h
/// A buddy allocator for a memory heap

#include <stddef.h>
#include "kutil/allocator.h"

namespace kutil {


/// Allocator for a given heap range
class heap_allocator :
	public allocator
{
public:
	/// Default constructor creates a valid but empty heap.
	heap_allocator();

	/// Constructor. The given memory area must already have been reserved.
	/// \arg start  Starting address of the heap
	/// \arg size   Size of the heap in bytes
	heap_allocator(uintptr_t start, size_t size);

	/// Allocate memory from the area managed.
	/// \arg length  The amount of memory to allocate, in bytes
	/// \returns     A pointer to the allocated memory, or nullptr if
	///              allocation failed.
	virtual void * allocate(size_t length) override;

	/// Free a previous allocation.
	/// \arg p  A pointer previously retuned by allocate()
	virtual void free(void *p) override;

	/// Minimum block size is (2^min_size). Must be at least 6.
	static const unsigned min_size = 6;

	/// Maximum block size is (2^max_size). Must be less than 64.
	static const unsigned max_size = 22;

protected:
	class mem_header;

	/// Ensure there is a block of a given size, recursively splitting
	/// \arg size   Size category of the block we want
	void ensure_block(unsigned size);

	/// Helper accessor for the list of blocks of a given size
	/// \arg size   Size category of the block we want
	/// \returns    A mutable reference to the head of the list
	mem_header *& get_free(unsigned size)  { return m_free[size - min_size]; }

	/// Helper to get a block of the given size, growing if necessary
	/// \arg size   Size category of the block we want
	/// \returns    A detached block of the given size
	mem_header * pop_free(unsigned size);

	uintptr_t m_next;
	size_t m_size;
	mem_header *m_free[max_size - min_size + 1];

	heap_allocator(const heap_allocator &) = delete;
};

} // namespace kutil
