// Copyright (c) 2019 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the GNU LGPLv3.0 license
// a copy of which can be found in the LICENSE file.

#include "common.h"
#include "vmem.h"

#define KB 1024ul
// Use 1MB at a time.
#define MB (KB * 1024ul)

static int allocations_done;
static Vmem arena;
uintptr_t arena_zero;
static Vmem char_arena;

#define START_VMEM_SIZE (sizeof(size_t) == 4 ? 1024 : 4096)

void memory_init(size_t max_mem)
{
	if (max_mem) vmem_set_max_limit(max_mem);

	vmem_init(&arena, START_VMEM_SIZE);
	vmem_init(&char_arena, START_VMEM_SIZE / 2);
	allocations_done = 0;
	arena_zero = (uintptr_t)arena.ptr;
	vmem_alloc(&arena, 16);
}

void memory_release()
{
	vmem_free(&arena);
	vmem_free(&char_arena);
}


void *calloc_string(size_t len)
{
	ASSERT(len > 0);
	allocations_done++;
	return vmem_alloc(&char_arena, len);
}


// Simple bump allocator with buckets.
void *calloc_arena(size_t mem)
{
	ASSERT(mem > 0);
	// Round to multiple of 16
	mem = (mem + 15U) & ~15ULL;
	allocations_done++;
	return vmem_alloc(&arena, mem);
}

void print_arena_status(void)
{
	printf("-- ARENA INFO -- \n");
	printf(" * Memory used:         %zu kb\n", arena.allocated / 1024);
	printf(" * Allocations:         %d\n", allocations_done);
	printf(" * String memory used:  %zu kb\n", char_arena.allocated / 1024);
}

void free_arena(void)
{
	vmem_free(&arena);
}


void run_arena_allocator_tests(void)
{
	printf("Begin arena allocator testing.\n");
	bool was_init = arena.ptr != NULL;
	if (!was_init) memory_init(0);
	memory_release();
	memory_init(0);
	ASSERT(calloc_arena(10) != calloc_arena(10) && "Expected different values...");
	printf("-- Tested basic allocation - OK.\n");
	ASSERT(arena.allocated == 48 && "Expected allocations rounded to next 16 bytes");
	(void)calloc_arena(1);
	ASSERT(arena.allocated == 64 && "Expected allocations rounded to next 16 bytes");
	printf("-- Tested allocation alignment - OK.\n");
	ASSERT(calloc_arena(1024 * 1024) != NULL && "Expected allocation to work");
	free_arena();
	ASSERT(arena.allocated == 0 && "Arena not freed?");
	printf("-- Test freeing arena - OK.\n");
	if (was_init) memory_init(0);
}

void *cmalloc(size_t size)
{
	void *ptr = malloc(size);
	if (!ptr) error_exit("Failed to malloc %zu bytes.", size);
	return ptr;
}

void *ccalloc(size_t size, size_t elements)
{
	void *ptr = calloc(size, elements);
	if (!ptr) error_exit("Failed to calloc %zu bytes.", size * elements);
	return ptr;
}
