#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "../core_memory/core_memory_private.h"
#include "core_runtime_private.h"

#define HUNK_COM_ERROR_FATAL 0
#define HUNK_COM_ERROR_DROP 1
#define HUNK_DEFAULT_ALIGNMENT 32
#define HUNK_CLEAR_VALUE 0

static const char hunkAllocAlignUninitializedMessage[] =
    "\x15"
    "Hunk_AllocAlign: Hunk memory system not initialized";
static const char hunkAllocAlignFailedMessage[] =
    "\x15"
    "Hunk_AllocAlign failed on %i";
static const char hunkAllocLowAlignFailedMessage[] =
    "\x15"
    "Hunk_AllocLowAlign failed on %i";

void *Hunk_AllocAlign(size_t size,
                      int32_t alignment)
{
    uint8_t *block;

    alignment--;

    if (hunk_base == NULL) {
        Com_Error(HUNK_COM_ERROR_FATAL, hunkAllocAlignUninitializedMessage);
    }

#if SIZE_MAX > UINT32_MAX
    if (size > hunk.totalSize) {
        Hunk_MemInfo_f();
        Com_Error(HUNK_COM_ERROR_DROP, hunkAllocAlignFailedMessage,
                  (int32_t)size);
    }
#endif

    hunk.highUsed += size;
    hunk.highUsed = (hunk.highUsed + alignment) & ~alignment;
    hunk.highTemp = hunk.highUsed;

    if (HUNK_USAGE_EXCEEDS_TOTAL(hunk.highUsed, hunk.lowTemp)) {
        Hunk_MemInfo_f();
        Com_Error(HUNK_COM_ERROR_DROP, hunkAllocAlignFailedMessage, (int32_t)size);
    }

    block = hunk_base + hunk.totalSize - hunk.highUsed;
    com_hunkUsed = hunk.highUsed + hunk.lowUsed;
    memset(block, HUNK_CLEAR_VALUE, (size_t)size);

    return block;
}

void *Hunk_Alloc(size_t size)
{
    return Hunk_AllocAlign(size, HUNK_DEFAULT_ALIGNMENT);
}

void *Hunk_AllocLowAlign(size_t size,
                         int32_t alignment)
{
    uint8_t *block;
    size_t blockOffset;

    alignment--;

#if SIZE_MAX > UINT32_MAX
    if (size > hunk.totalSize) {
        Hunk_MemInfo_f();
        Com_Error(HUNK_COM_ERROR_DROP, hunkAllocLowAlignFailedMessage,
                  (int32_t)size);
    }
#endif

    hunk.lowUsed = (hunk.lowUsed + alignment) & ~alignment;
    blockOffset = hunk.lowUsed;
    hunk.lowUsed += size;
    hunk.lowTemp = hunk.lowUsed;

    if (HUNK_USAGE_EXCEEDS_TOTAL(hunk.highTemp, hunk.lowTemp)) {
        Hunk_MemInfo_f();
        Com_Error(HUNK_COM_ERROR_DROP, hunkAllocLowAlignFailedMessage, (int32_t)size);
    }

    block = hunk_base + blockOffset;
    com_hunkUsed = hunk.highUsed + hunk.lowUsed;
    memset(block, HUNK_CLEAR_VALUE, (size_t)size);

    return block;
}

void *Hunk_AllocLow(size_t size)
{
    return Hunk_AllocLowAlign(size, HUNK_DEFAULT_ALIGNMENT);
}
