module dlib.alloc;

import dlib.aliases;
import dlib.math;
import dlib.platform;
import dlib.util;

import std.stdio;
import core.stdc.string : memset;
import core.memory;

static Scratch g_scratch;
static u64     g_scratch_index;

const DEFAULT_ALIGNMENT = (void *).sizeof * 2;

struct Scratch
{
	Arena arena;
	bool  init;
}

struct ArenaPool
{
	u8*        mem;
	u64        pos;
	u64        length;
	ArenaPool* next;
}

struct Arena
{
	ArenaPool* first, last;
	u64        def_size;
}

struct TempArena
{
	Arena*     arena;
	u64        start_pos;
	ArenaPool* start_pool;
}

T*
MAlloc(T)()
{
	void* mem = MemAlloc(T.sizeof);
	return cast(T*)mem;
}

T[]
MAlloc(T)(u64 count)
{
	void* mem = MemAlloc(T.sizeof * count);
	return (cast(T*)mem)[0 .. count];
}

void
MFree(T)(T* ptr)
{
	MemFree(ptr, T.sizeof);
}

void
MFree(T)(T[] slice)
{
	MemFree(slice.ptr, cast(u64)slice.length * T.sizeof);
}

T*
Alloc(T)()
{
	void* mem = pureMalloc(T.sizeof);
	memset(mem, 0, T.sizeof);
	return (cast(T*)mem);
}

T[]
Alloc(T)(u64 count)
{
	void* mem = pureMalloc(T.sizeof * count);
	memset(mem, 0, T.sizeof * count);
	return (cast(T*)mem)[0 .. count];
}

T[]
Alloc(T)(T[] target)
{
	T[] arr = Alloc!(T)(target.length);
	arr[] = target[];
	return arr;
}

string
Alloc(string target)
{
	u8[] str = Alloc!(u8)(target.length);
	str[]    = cast(u8[])target[];
	return ConvToStr(str);
}

T[]
Alloc(T)(T[] target, u64 start, u64 len)
{
	T[] arr = Alloc!(T)(len);
	arr[0 .. $] = target[start .. start+len];
	return arr;
}

string
Alloc(string target, u64 start, u64 len)
{
	u8[] str    = Alloc!(u8)(len);
	str[0 .. $] = cast(u8[])target[start .. start+len];
	return ConvToStr(str);
}

T[]
Alloc(T)(u64 count, T set)
{
	T[] arr = Alloc!(T)(count);
	arr[] = set;
	return arr;
}

T[]
Realloc(T)(T[] arr, u64 count)
{
	void* mem = pureRealloc(arr.ptr, T.sizeof * count);
	return (cast(T*)mem)[0 .. count];
}

void
Free(T)(T[] arr)
{
	pureFree(arr.ptr);
}

void 
Free(T)(T* ptr)
{
	pureFree(ptr);
}

Arena 
CreateArena(u64 size)
{
	Arena arena = { 
		def_size: size,
	};

	AddArenaPool(&arena, size);

	return arena;
}

TempArena
BeginTempArena(Arena* arena)
{
	TempArena t = {
		arena: arena,
	};

	auto n = arena.first;
	for(;;)
	{
		if(n.next == null)
		{
			t.start_pool = n;
			t.start_pos  = n.pos;
			break;
		}

		n = n.next;
	}

	return t;
}

void
End(TempArena* t)
{
	bool resetting = false;
	for(auto n = t.arena.first; n != null; n = n.next)
	{
		if(t.start_pool == n)
		{
			n.pos = t.start_pos;
			resetting = true;
		}
		else if(resetting)
		{
			n.pos = 0;
		}
	}
}

T[]
Alloc(T)(TempArena* t, T[] target)
{
	return Alloc!(T)(t.arena, target);
}

T[]
Alloc(T)(TempArena* t, u64 count, T set)
{
	return Alloc!(T)(t.arena, count, set);
}

T[]
Alloc(T)(TempArena* t, u64 count)
{
	return Alloc!(T)(t.arena, count);
}

T[]
Alloc(T)(TempArena* t, T[] target)
{
	return Alloc!(T)(t.arena, target);
}

T[]
Alloc(T)(TempArena* t, T[] target, u64 start, u64 len)
{
	return Alloc!(T)(t.arena, target, start, len);
}

T*
Alloc(T)(TempArena* t)
{
	return Alloc!(T)(t.arena);
}

void
AddArenaPool(Arena* arena, u64 size)
{
	u8* mem = Alloc!(u8)(size + ArenaPool.sizeof).ptr;

	ArenaPool* node = cast(ArenaPool*)mem;

	node.mem = (cast(u8*)mem) + ArenaPool.sizeof;
	node.pos = 0;
	node.length = size;

	assert(node.mem != null, "Unable to allocate memory for arena");

	SLLPushFront(arena, node, null);
}

T[]
Alloc(T)(Arena* arena, u64 count)
{
	void* mem = AllocAlign(arena, T.sizeof * count, DEFAULT_ALIGNMENT);
	memset(mem, 0, T.sizeof * count);
	return (cast(T*)mem)[0 .. count];
}

T[]
Alloc(T)(Arena* arena, T[] target)
{
	T[] arr = Alloc!(T)(arena, target.length);
	arr[]   = target[];
	return arr;
}

string
Alloc(Arena* arena, string target)
{
	u8[] str = Alloc!(u8)(arena, target.length);
	str[]    = cast(u8[])target[];
	return ConvToStr(str);
}

T[]
Alloc(T)(Arena* arena, T[] target, u64 start, u64 len)
{
	T[] arr     = Alloc!(T)(arena, len);
	arr[0 .. $] = target[start .. start+len];
	return arr;
}

string
Alloc(Arena* arena, string target, u64 start, u64 len)
{
	u8[] str    = Alloc!(u8)(arena, len);
	str[0 .. $] = cast(u8[])target[start .. start+len];
	return ConvToStr(str);
}

T[]
Alloc(T)(Arena* arena, u64 count, T set)
{
	T[] arr = Alloc!(T)(arena, count);
	arr[]   = set;
	return arr;
}

T* 
Alloc(T)(Arena* arena)
{
	void* mem = AllocAlign(arena, T.sizeof, DEFAULT_ALIGNMENT);
	memset(mem, 0, T.sizeof);
	return cast(T*)mem;
};

void* 
AllocAlign(Arena* arena, u64 size, u64 alignment)
{
	void* ptr = null;

	uintptr mem_pos, current, offset;
	ArenaPool* node = arena.first;
	while (true)
	{
		if(node == null)
		{
			if(size > arena.def_size)
			{
				size += arena.def_size;
			}

			AddArenaPool(arena, Max(size, arena.def_size));
			node = arena.first;
		}

		mem_pos = cast(uintptr)node.mem;
		current = mem_pos + node.pos;
		offset = AlignPow2(current, alignment) - mem_pos;

		if(offset+size <= node.length)
		{
			break;
		}

		node = node.next;
	}
	
	ptr = &node.mem[offset];
	node.pos = offset+size;

	return ptr;
};

void
Reset(Arena* arena)
{
	ArenaPool* node = arena.first;
	while (node != null)
	{
		node.pos = 0;
		node = node.next;
	}
}

void
Free(Arena* arena)
{
	ArenaPool* node = arena.first;
	ArenaPool* next;
	while (node != null)
	{
		next = node.next;
		Free(node);
		node = next;
	}
}

void
ResetScratch(u64 size)
{
	if(!g_scratch.init)
	{
		g_scratch.arena = CreateArena(size);
		g_scratch.init = true;
	}

	Reset(&g_scratch.arena);
}

T*
ScratchAlloc(T)()
{
	return Alloc!(T)(&g_scratch.arena);
}

T[]
ScratchAlloc(T)(u64 count)
{
	return Alloc!(T)(&g_scratch.arena, count);
}

T[]
ScratchAlloc(T)(T[] target)
{
	T[] arr = ScratchAlloc!(T)(target.length);
	arr[] = target[];
	return arr;
}

string
ScratchAlloc(string target)
{
	u8[] str = ScratchAlloc!(u8)(target.length);
	str[]    = cast(u8[])target[];
	return ConvToStr(str);
}

T[]
ScratchAlloc(T)(T[] target, u64 start, u64 len)
{
	T[] arr     = ScratchAlloc!(T)(len);
	arr[0 .. $] = target[start .. start+len];
	return arr;
}

string
ScratchAlloc(string target, u64 start, u64 len)
{
	u8[] str    = ScratchAlloc!(u8)(len);
	str[0 .. $] = cast(u8[])target[start .. start+len];
	return ConvToStr(str);
}

T[]
ScratchAlloc(T)(u64 count, T set)
{
	T[] arr = ScratchAlloc!(T)(count);
	arr[] = set;
	return arr;
}

version(DLIB_TEST) unittest
{
	{
		u64[5] arr = [1, 2, 3, 4, 5];

		u64[] copy = Alloc!(u64)(arr);

		assert(arr == copy);
	}

	{
		Arena a = CreateArena(64);

		u64[] arr = Alloc!(u64)(&a, 128);
	}
}
