#ifdef INTELLISENSE_DIRECTIVES
#	pragma once
#	include "context_cracking.h"
#	include "linkage.h"
#	include "macros.h"
#	include "platform.h"
#	include "base_types.h"
#	include "memory.h"
#endif

// This provides an alterntive memory strategy to HMH/Casey Muratori/RJF styled arenas
// The library is derived from zpl-c which in-turn 
// is related to the gb headers an thus the Odin-lang memory strategy
// Users can override the underlying memory allocator used, even for the HMH arena memory strategy.

#ifndef MD__ONES
#define MD__ONES          ( scast( GEN_NS usize, - 1) / MD_U8_MAX )
#define MD__HIGHS         ( MD__ONES * ( MD_U8_MAX / 2 + 1 ) )
#define MD__HAS_ZERO( x ) ( ( ( x ) - MD__ONES ) & ~( x ) & MD__HIGHS )
#endif

// Return value of allocator_type
typedef U64 AllocatorType;
enum
{
	AllocatorType_Heap   = 0, // Genreal heap allocator
	AllocatorType_VArena = 1, // Arena allocator backed by virtual address space
	AllocatorType_FArena = 2, // Fixed arena backed back by a fixed size block of memory (usually a byte slice)
	AllocatorType_Arena  = 3, // Composite arena used originally by RAD Debugger & Metadesk
};

typedef U32 AllocatorMode;
enum AllocatorMode
{
	AllocatorMode_Alloc,
	AllocatorMode_Free,
	AllocatorMode_FreeAll,
	AllocatorMode_Resize,
	AllocatorMode_QueryType,
	AllocatorMode_QuerySupport,
};
typedef U64 AllocatorQueryFlags;
enum
{
	AllocatorQuery_Alloc         = (1 << 0),
	AllocatorQuery_Free          = (1 << 1),
	AllocatorQuery_FreeAll       = (1 << 2),
	AllocatorQuery_Resize        = (1 << 3), // Supports both grow and shrink
	AllocatorQuery_ResizeShrink  = (1 << 4),
	AllocatorQuery_ResizeGrow    = (1 << 5),
};

typedef void*(AllocatorProc)( void* allocator_data, AllocatorMode type, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags );

typedef struct AllocatorInfo AllocatorInfo;
struct AllocatorInfo
{
	AllocatorProc* proc;
	void*          data;
};

// Overridable by the user by defining MD_OVERRIDE_DEFAULT_ALLOCATOR
AllocatorInfo default_allocator();

enum AllocFlag
{
	ALLOCATOR_FLAG_CLEAR_TO_ZERO = (1 << 0),
};

#ifndef MD_DEFAULT_MEMORY_ALIGNMENT
#	define MD_DEFAULT_MEMORY_ALIGNMENT ( 2 * size_of( void* ) )
#endif

#ifndef MD_DEFAULT_ALLOCATOR_FLAGS
#	define MD_DEFAULT_ALLOCATOR_FLAGS ( ALLOCATOR_FLAG_CLEAR_TO_ZERO )
#endif

// Retrieve which type of allocator
AllocatorType allocator_type(AllocatorInfo a);
// Retreive which modes the allocator supports
AllocatorQueryFlags allocator_query_support(AllocatorInfo a);

// Allocate memory with default alignment.
void* alloc( AllocatorInfo a, SSIZE size );

// Allocate memory with specified alignment.
void* alloc_align( AllocatorInfo a, SSIZE size, SSIZE alignment );

// Free allocated memory.
void alloc_free( AllocatorInfo a, void* ptr );

// Free all memory allocated by an allocator.
void free_all( AllocatorInfo a );

// Resize an allocated memory.
void* resize( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size );

// Resize an allocated memory with specified alignment.
void* resize_align( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size, SSIZE alignment );

#ifndef alloc_item
// Allocate memory for an item.
#define alloc_item(allocator, Type)           (Type*)memory_zero(alloc(allocator,  size_of(Type)), size_of(Type))
// Allocate memory for an item.
#define alloc_item_no_zero( allocator, Type ) (Type*)            alloc(allocator,  size_of(Type))
#endif

#ifndef alloc_array
// Allocate memory for an array of items.
#define alloc_array( allocator_, Type, count )         (Type*)memory_zero(alloc( allocator_, size_of(Type) * (count) ), size_of(Type) * (count))
// Allocate memory for an array of items. (Don't zero initialize)
#define alloc_array_no_zero( allocator_, Type, count ) (Type*)            alloc( allocator_, size_of(Type) * (count) )
#endif

// Allocate/Resize memory using default options.

// Use this if you don't need a "fancy" resize allocation
void* default_resize_align( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size, SSIZE alignment );

#ifdef MD_HEAP_ANALYSIS
/* heap memory analysis tools */
/* define GEN_HEAP_ANALYSIS to enable this feature */
/* call zpl_heap_stats_init at the beginning of the entry point */
/* you can call zpl_heap_stats_check near the end of the execution to validate any possible leaks */
MD_API void  heap_stats_init( void );
MD_API SSIZE heap_stats_used_memory( void );
MD_API SSIZE heap_stats_alloc_count( void );
MD_API void  heap_stats_check( void );
#endif

MD_API void* heap_allocator_proc( void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags );

#ifndef heap
// The heap allocator backed by the platform vendor's malloc & free.
#define heap() (AllocatorInfo){ heap_allocator_proc, nullptr }
#endif

#ifndef md_malloc
// Helper to allocate memory using heap allocator.
#define md_malloc( sz ) alloc( heap(), sz )
#endif

#ifndef md_free
// Helper to free memory allocated by heap allocator.
#define md_free( ptr ) alloc_free( heap(), ptr )
#endif

/* Virtual Memory Arena
	This is separate from the composite arena used by HMH/Casey Muratori/RJF
	This arena stricly manages one reservation of the process's virtual address space.

	Segregating this from composite Arena style causes more moremoy to be used for "allocator headers", however it allows
	users of a library to have greater control over the allocation strategy used from their side instead of the library itself.

	Like with the composite Arena, the VArena has its struct as the header of the reserve of memory.
*/

#ifndef VARENA_DEFUALT_RESERVE
#define VARENA_DEFAULT_RESERVE MB(64)
#endif
#ifndef VARENA_DEFUALT_COMMIT
#define VARENA_DEFAULT_COMMIT  KB(64)
#endif

typedef U32 VArenaFlags;
enum
{
	VArenaFlag_LargePages = (1 << 0),
};

typedef struct VArenaParams VArenaParams;
struct VArenaParams
{
	U64         base_addr;
	VArenaFlags flags;
	U64         reserve_size;
	U64         commit_size;
};

typedef struct VArena VArena;
struct VArena
{
	SSIZE reserve_start;
	SSIZE reserve;
	SSIZE commit_size;
	SSIZE committed;
	SSIZE commit_used;
	VArenaFlags flags;
};

MD_API VArena* varena__alloc(VArenaParams params PARAM_DEFAULT);
#define varena_alloc(...) varena__alloc( (VArenaParams){__VA_ARGS__} )

MD_API void varena_commit (VArena* vm, SSIZE commit_size);
MD_API void varena_release(VArena* vm);

force_inline void varena_rewind(VArena* vm, SSIZE pos) { vm->commit_used = pos; }

MD_API void* varena_allocator_proc(void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags);

#define varena_allocator(vm) (AllocatorInfo) { varena_allocator_proc, vm }

typedef struct ByteSlice ByteSlice;
struct ByteSlice
{
	U8*   data;
	SSIZE len;
};

#define mem_to_byteslice(data, len) (ByteSlice){ (U8*)(data), (SSIZE)(len) }

// Fixed size arena
typedef struct FArena FArena;
struct FArena
{
	ByteSlice slice;
	SSIZE     used;
};

#define farena_from_byteslice(slice)  (FArena) { slice, 0 }
#define farena_from_memory(data, len) (FArena) { mem_to_byteslice(data, len), 0 }

MD_API void* farena_allocator_proc(void* allocator_data, AllocatorMode mode, SSIZE size, SSIZE alignment, void* old_memory, SSIZE old_size, U64 flags);

#define farena_allocator(arena) (AllocatorInfo){ farena_allocator_proc, & arena }

// Inlines

inline AllocatorType
allocator_type(AllocatorInfo a) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	return (AllocatorType) a.proc(a.data, AllocatorMode_QueryType, 0, 0, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS);
}

inline AllocatorQueryFlags
allocator_query_support(AllocatorInfo a) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	return (AllocatorType) a.proc(a.data, AllocatorMode_QuerySupport, 0, 0, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS);
}

inline void* 
alloc_align( AllocatorInfo a, SSIZE size, SSIZE alignment ) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	return a.proc( a.data, AllocatorMode_Alloc, size, alignment, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS );
}

inline void*
alloc( AllocatorInfo a, SSIZE size ) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	return alloc_align( a, size, MD_DEFAULT_MEMORY_ALIGNMENT );
}

inline void
alloc_free( AllocatorInfo a, void* ptr ) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	if ( ptr != nullptr ) {
		a.proc( a.data, AllocatorMode_Free, 0, 0, ptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS );
	}
}

inline void
free_all( AllocatorInfo a ) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	a.proc( a.data, AllocatorMode_FreeAll, 0, 0, nullptr, 0, MD_DEFAULT_ALLOCATOR_FLAGS );
}

inline void*
resize( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size ) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	return resize_align( a, ptr, old_size, new_size, MD_DEFAULT_ALLOCATOR_FLAGS );
}

inline void*
resize_align( AllocatorInfo a, void* ptr, SSIZE old_size, SSIZE new_size, SSIZE alignment ) {
	if (a.proc == nullptr) {
		a = default_allocator();
	}
	return a.proc( a.data, AllocatorMode_Resize, new_size, alignment, ptr, old_size, MD_DEFAULT_ALLOCATOR_FLAGS );
}
