#if !defined(LD_CORE_ARENA_H_)
#define LD_CORE_ARENA_H_

#define AlignUp(x, a) (((x) + ((a) - 1)) & ~((a) - 1))
#define AlignDown(x, a) (((x)) & ~((a) - 1))

#define KB(x) ((U64) (x) << 10)
#define MB(x) ((U64) (x) << 20)
#define GB(x) ((U64) (x) << 30)

#if !defined(M_ARENA_CHAIN_RESERVE)
	#define M_ARENA_CHAIN_RESERVE MB(4)
#endif

function void *M_ZeroSize(void *base, U64 size);
function void *M_CopySize(void *dst, void *src, U64 size);

typedef U32 M_ArenaFlags;
enum {
	// Arena is fixed size, so will not chain
	M_ARENA_FIXED_SIZE = (1 << 0),
	// Don't clear allocation
	M_ARENA_NO_ZERO    = (1 << 1)

};

typedef union M_Arena M_Arena;
union M_Arena {
	struct {
		M_Arena *current;
		M_Arena *prev;

		U64 base;
		U64 limit;
		U64 offset;

		U64 committed;
		U64 increment;

		M_ArenaFlags flags;
	};

	U8 __pad[64];
};

typedef struct M_ArenaOpts M_ArenaOpts;
struct M_ArenaOpts {
	U64 initial;
	U64 increment;

	M_ArenaFlags flags;
};

typedef struct M_ArenaPushOpts M_ArenaPushOpts;
struct M_ArenaPushOpts {
	U64 count;
	U64 align;

	M_ArenaFlags flags;
};

function M_Arena *_M_ArenaAlloc(U64 limit, M_ArenaOpts *opts);
function void    *_M_ArenaPush(M_Arena *arena, U64 esize, M_ArenaPushOpts *opts);
function void    *_M_ArenaPushCopy(M_Arena *arena, void *src, U64 esize, M_ArenaPushOpts *opts);

#define M_ArenaAlloc(limit, ...) _M_ArenaAlloc(limit, &(M_ArenaOpts) { .initial = MB(1), .increment = KB(64), ##__VA_ARGS__ })
#define M_ArenaPush(arena, T, ...) (T *) _M_ArenaPush(arena, sizeof(T), &(M_ArenaPushOpts) { .count = 1, .align = Alignof(T), ##__VA_ARGS__ })
#define M_ArenaPushCopy(arena, T, src, ...) (T *) _M_ArenaPush(arena, src, sizeof(T), &(M_ArenaPushOpts) { .count = 1, .align = Alignof(T), ##__VA_ARGS__ })

function void M_ArenaReset(M_Arena *arena);
function void M_ArenaRelease(M_Arena *arena);

function U64 M_ArenaOffset(M_Arena *arena);

function void M_ArenaPop(M_Arena *arena, U64 offset);
function void M_ArenaPopSize(M_Arena *arena, U64 size);

// Temporary memory

typedef struct M_Temp M_Temp;
struct M_Temp {
	M_Arena *arena;
	U64 offset;
};

function M_Temp M_TempAcquire(U64 count, M_Arena **conflicts);
function void   M_TempRelease(M_Temp temp);

#define M_TempScope(n, c) for (M_Temp temp = M_TempAcquire(n, c); temp.arena != 0; M_TempRelease(temp), temp.arena = 0)

#endif  // LD_CORE_ARENA_H_
