#ifndef RT_MEM_ARENA_H
#define RT_MEM_ARENA_H

#include "runtime.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct rt_arena_s {
    void *base;
    size_t at;
    size_t size;
    int needs_free;
} rt_arena;

typedef struct {
    int ok;
    rt_arena arena;
} rt_create_arena_result;

typedef struct {
    size_t at;
    rt_arena *arena;
} rt_temp_arena;

RT_DLLEXPORT rt_create_arena_result rtCreateArena(void *memory, size_t size);

RT_DLLEXPORT void rtReleaseArena(rt_arena *arena);

RT_DLLEXPORT void *rtArenaPush(rt_arena *arena, size_t n);

RT_DLLEXPORT void *rtArenaPushZero(rt_arena *arena, size_t n);

RT_DLLEXPORT void rtArenaPop(rt_arena *arena, size_t n);

RT_INLINE void rtArenaClear(rt_arena *arena) {
    arena->at = 0;
}

RT_INLINE rt_temp_arena rtBeginTempArena(rt_arena *arena) {
#ifndef __cplusplus
    return (rt_temp_arena){.at = arena->at, .arena = arena};
#else
    rt_temp_arena rp = {arena->at, arena};
    return rp;
#endif
}

RT_INLINE void rtEndTempArena(rt_temp_arena tmp) {
    RT_ASSERT(tmp.at <= tmp.arena->at,
              "Tried to rewind an arena to a point that lies beyond the last allocation.");
    tmp.arena->at = tmp.at;
}

/* Get a thread-local arena for temporary allocations.
 *
 * Since it's possible that such an arena was passed into a function via parameters, which might 
 * lead to conflicts - such as a called function inadvertently freeing its own "permanent" allocations,
 * we pass in arenas that are used for allocations lasting beyond the usage of the temporary arena.
 *
 * DO NOT call rtReleaseArena on a arena returned by this function. Instead return them via
 * rtReturnTemporaryArena()!
 */
RT_DLLEXPORT rt_temp_arena rtGetTemporaryArena(const rt_arena **permanent_arenas, int count);

RT_INLINE void rtReturnTemporaryArena(rt_temp_arena tmp) {
    rtEndTempArena(tmp);
}

/* Helper macros */
#define RT_ARENA_PUSH_STRUCT(_Arena, _Type)         (_Type*)rtArenaPush((_Arena), sizeof(_Type))
#define RT_ARENA_PUSH_STRUCT_ZERO(_Arena, _Type)    (_Type *)rtArenaPushZero((_Arena), sizeof(_Type))
#define RT_ARENA_POP_STRUCT(_Arena, _Type)          rtArenaPop((_Arena), sizeof(_Type))
#define RT_ARENA_PUSH_ARRAY(_Arena, _Type, _N)      (_Type*)rtArenaPush((_Arena), sizeof(_Type) * (_N))
#define RT_ARENA_PUSH_ARRAY_ZERO(_Arena, _Type, _N) (_Type*)rtArenaPushZero((_Arena), sizeof(_Type) * (_N))
#define RT_ARENA_POP_ARRAY(_Arena, _Type, _N)       rtArenaPop((_Arena), sizeof(_Type) * (_N)

#ifdef __cplusplus
}
#endif

#endif
