// vim:fileencoding=utf-8:foldmethod=marker

#include "pr_pool_allocator.h"
#include "../../vendor/wapp/os/mem/mem_os.h"
#include <string.h>

// ---------------------------------------------------------------------------
// Intrusive free list node — reuses the first sizeof(void*) bytes of each slot
// ---------------------------------------------------------------------------

typedef struct PrPoolFreeNode PrPoolFreeNode;
struct PrPoolFreeNode {
	PrPoolFreeNode *next;
};

// ---------------------------------------------------------------------------
// Internal: allocate a new block and carve it into the free list
// ---------------------------------------------------------------------------

wp_intern b8 _prPoolGrow(PrPool *pool) {
	// Grow blocks array if full
	if (pool->block_count == pool->block_cap) {
		u64    new_cap  = pool->block_cap ? pool->block_cap * 2 : 4;
		u64    new_size = sizeof(void *) * new_cap;
		void **new_arr  = (void **)wpOsMemAlloc(NULL, new_size, WP_MEM_ACCESS_READ_WRITE,
		                                        WP_MEM_ALLOC_RESERVE, WP_MEM_INIT_UNINITIALISED);
		if (!new_arr) { return false; }

		if (pool->blocks) {
			memcpy(new_arr, pool->blocks, sizeof(void *) * pool->block_count);
			wpOsMemFree(pool->blocks, sizeof(void *) * pool->block_cap);
		}

		pool->blocks    = new_arr;
		pool->block_cap = new_cap;
	}

	// Allocate the block
	u64  block_bytes = pool->alloc_size * pool->block_slots;
	void *block      = wpOsMemAlloc(NULL, block_bytes, WP_MEM_ACCESS_READ_WRITE,
	                                WP_MEM_ALLOC_RESERVE, WP_MEM_INIT_UNINITIALISED);
	if (!block) { return false; }

	pool->blocks[pool->block_count++] = block;
	pool->total += pool->block_slots;

	// Carve into free list (in reverse so the first slot ends up on top)
	u8 *bytes = (u8 *)block;
	for (u64 i = pool->block_slots; i > 0; --i) {
		PrPoolFreeNode *node = (PrPoolFreeNode *)bytes;
		node->next           = (PrPoolFreeNode *)pool->free_list;
		pool->free_list      = node;
		bytes               += pool->alloc_size;
	}

	return true;
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

void prPoolInit(PrPool *pool, u64 slot_size, u64 initial_slots) {
	memset(pool, 0, sizeof(*pool));
	pool->slot_size  = slot_size;
	pool->alloc_size = slot_size < sizeof(PrPoolFreeNode) ? sizeof(PrPoolFreeNode) : slot_size;

	// Enforce minimum block size of 4096 bytes
	u64 min_slots = (4096 + pool->alloc_size - 1) / pool->alloc_size;
	pool->block_slots = initial_slots > min_slots ? initial_slots : min_slots;

	_prPoolGrow(pool);
}

void *prPoolAlloc(PrPool *pool) {
	// Grow if free list is empty
	if (!pool->free_list) {
		if (!_prPoolGrow(pool)) { return NULL; }
	}

	PrPoolFreeNode *node = (PrPoolFreeNode *)pool->free_list;
	pool->free_list = node->next;
	pool->active++;
	return node;
}

void prPoolFree(PrPool *pool, void *slot) {
	if (!slot) { return; }

	PrPoolFreeNode *node = (PrPoolFreeNode *)slot;
	node->next           = (PrPoolFreeNode *)pool->free_list;
	pool->free_list      = node;
	pool->active--;
}

void prPoolDestroy(PrPool *pool) {
	u64 block_bytes = pool->alloc_size * pool->block_slots;
	for (u64 i = 0; i < pool->block_count; ++i) {
		wpOsMemFree(pool->blocks[i], block_bytes);
	}
	if (pool->blocks) {
		wpOsMemFree(pool->blocks, sizeof(void *) * pool->block_cap);
	}
	memset(pool, 0, sizeof(*pool));
}

u64 prPoolTotalSlots(const PrPool *pool) {
	return pool->total;
}

u64 prPoolActiveSlots(const PrPool *pool) {
	return pool->active;
}
