/************************************************************/ /**
*
*	@file: memory.c
*	@author: Martin Fouilleul
*	@date: 24/10/2019
*	@revision:
*
*****************************************************************/
#include "memory.h"
#include "macros.h"
#include "platform/platform.h"
#include "platform/platform_memory.h"

#if OC_PLATFORM_ORCA
enum
{
    OC_ARENA_DEFAULT_RESERVE_SIZE = 1 << 20
};
#else
enum
{
    OC_ARENA_DEFAULT_RESERVE_SIZE = 1 << 30
};
#endif

enum
{
    OC_ARENA_COMMIT_ALIGNMENT = 4 << 10
};

//--------------------------------------------------------------------------------
//NOTE(martin): memory arena
//--------------------------------------------------------------------------------

oc_arena_chunk* oc_arena_chunk_alloc(oc_arena* arena, u64 reserveSize)
{
    reserveSize = oc_align_up_pow2(reserveSize, OC_ARENA_COMMIT_ALIGNMENT);
    u64 commitSize = oc_align_up_pow2(sizeof(oc_arena_chunk), OC_ARENA_COMMIT_ALIGNMENT);

    char* mem = oc_base_reserve(arena->base, reserveSize);
    oc_base_commit(arena->base, mem, commitSize);

    oc_arena_chunk* chunk = (oc_arena_chunk*)mem;

    chunk->ptr = mem;
    chunk->cap = reserveSize;
    chunk->offset = sizeof(oc_arena_chunk);
    chunk->committed = commitSize;

    oc_list_push_back(&arena->chunks, &chunk->listElt);

    return (chunk);
}

void oc_arena_init(oc_arena* arena)
{
    oc_arena_init_with_options(arena, &(oc_arena_options){ 0 });
}

void oc_arena_init_with_options(oc_arena* arena, oc_arena_options* options)
{
    memset(arena, 0, sizeof(oc_arena));

    arena->base = options->base ? options->base : oc_base_allocator_default();

    u64 reserveSize = options->reserve ? (options->reserve + sizeof(oc_arena_chunk)) : OC_ARENA_DEFAULT_RESERVE_SIZE;

    arena->currentChunk = oc_arena_chunk_alloc(arena, reserveSize);
}

void oc_arena_cleanup(oc_arena* arena)
{
    oc_list_for_safe(&arena->chunks, chunk, oc_arena_chunk, listElt)
    {
        oc_base_release(arena->base, chunk, chunk->cap);
    }
    memset(arena, 0, sizeof(oc_arena));
}

void* oc_arena_push(oc_arena* arena, u64 size)
{
    oc_arena_chunk* chunk = arena->currentChunk;
    OC_ASSERT(chunk);

    u64 nextOffset = chunk->offset + size;
    u64 lastCap = chunk->cap;
    while(nextOffset > chunk->cap)
    {
        chunk = oc_list_next_entry(&arena->chunks, chunk, oc_arena_chunk, listElt);
        if(chunk)
        {
            nextOffset = chunk->offset + size;
            lastCap = chunk->cap;
        }
        else
        {
            break;
        }
    }
    if(!chunk)
    {
        u64 reserveSize = oc_max(lastCap * 1.5, size);

        chunk = oc_arena_chunk_alloc(arena, reserveSize);
        nextOffset = chunk->offset + size;
    }
    OC_ASSERT(nextOffset <= chunk->cap);

    arena->currentChunk = chunk;

    if(nextOffset > chunk->committed)
    {
        u64 nextCommitted = oc_align_up_pow2(nextOffset, OC_ARENA_COMMIT_ALIGNMENT);
        nextCommitted = oc_clamp_high(nextCommitted, chunk->cap);
        u64 commitSize = nextCommitted - chunk->committed;
        oc_base_commit(arena->base, chunk->ptr + chunk->committed, commitSize);
        chunk->committed = nextCommitted;
    }
    char* p = chunk->ptr + chunk->offset;
    chunk->offset += size;

    return (p);
}

void oc_arena_clear(oc_arena* arena)
{
    oc_list_for(&arena->chunks, chunk, oc_arena_chunk, listElt)
    {
        chunk->offset = sizeof(oc_arena_chunk);
    }
    arena->currentChunk = oc_list_first_entry(&arena->chunks, oc_arena_chunk, listElt);
}

oc_arena_scope oc_arena_scope_begin(oc_arena* arena)
{
    oc_arena_scope scope = { .arena = arena,
                             .chunk = arena->currentChunk,
                             .offset = arena->currentChunk->offset };
    return (scope);
}

void oc_arena_scope_end(oc_arena_scope scope)
{
    scope.arena->currentChunk = scope.chunk;
    scope.arena->currentChunk->offset = scope.offset;
}

//--------------------------------------------------------------------------------
//NOTE(martin): memory pool
//--------------------------------------------------------------------------------
void oc_pool_init(oc_pool* pool, u64 blockSize)
{
    oc_pool_init_with_options(pool, blockSize, &(oc_pool_options){ 0 });
}

void oc_pool_init_with_options(oc_pool* pool, u64 blockSize, oc_pool_options* options)
{
    oc_arena_init_with_options(&pool->arena, &(oc_arena_options){ .base = options->base, .reserve = options->reserve });
    pool->blockSize = oc_clamp_low(blockSize, sizeof(oc_list));
    oc_list_init(&pool->freeList);
}

void oc_pool_cleanup(oc_pool* pool)
{
    oc_arena_cleanup(&pool->arena);
    memset(pool, 0, sizeof(oc_pool));
}

void* oc_pool_alloc(oc_pool* pool)
{
    if(oc_list_empty(&pool->freeList))
    {
        return (oc_arena_push(&pool->arena, pool->blockSize));
    }
    else
    {
        return (oc_list_pop(&pool->freeList));
    }
}

void oc_pool_recycle(oc_pool* pool, void* ptr)
{
    oc_list_push(&pool->freeList, (oc_list_elt*)ptr);
}

void oc_pool_clear(oc_pool* pool)
{
    oc_arena_clear(&pool->arena);
    oc_list_init(&pool->freeList);
}

//--------------------------------------------------------------------------------
//NOTE(martin): per-thread scratch arena
//--------------------------------------------------------------------------------

enum
{
    OC_SCRATCH_POOL_SIZE = 8,
    OC_SCRATCH_DEFAULT_SIZE = 4096,
};

oc_thread_local oc_arena __scratchPool[OC_SCRATCH_POOL_SIZE] = { 0 };

static oc_arena* oc_scratch_at_index(int index)
{
    oc_arena* scratch = 0;

    if(index >= 0 && index < OC_SCRATCH_POOL_SIZE)
    {
        if(__scratchPool[index].base == 0)
        {
            oc_arena_options options = { .reserve = OC_SCRATCH_DEFAULT_SIZE };
            oc_arena_init_with_options(&__scratchPool[index], &options);
        }
        scratch = &__scratchPool[index];
    }
    return (scratch);
}

oc_arena* oc_scratch()
{
    return (oc_scratch_at_index(0));
}

ORCA_API oc_arena* oc_scratch_next(oc_arena* used)
{
    oc_arena* res = 0;
    if((used >= __scratchPool)
       && (used - __scratchPool < OC_SCRATCH_POOL_SIZE))
    {
        u64 index = used - __scratchPool;
        if(index + 1 < OC_SCRATCH_POOL_SIZE)
        {
            res = oc_scratch_at_index(index + 1);
        }
    }
    else
    {
        res = oc_scratch_at_index(0);
    }
    return (res);
}

ORCA_API oc_arena_scope oc_scratch_begin()
{
    oc_arena* scratch = oc_scratch();
    oc_arena_scope scope = oc_arena_scope_begin(scratch);
    return (scope);
}

ORCA_API oc_arena_scope oc_scratch_begin_next(oc_arena* used)
{
    oc_arena* scratch = oc_scratch_next(used);
    oc_arena_scope scope = oc_arena_scope_begin(scratch);
    return (scope);
}
