// Codegen arena: free-list allocator with adjacent-hole coalescing
// and manual compaction. See codegenArenaInternal.h for the contract.

#include "joey/platform.h"

#if defined(JOEYLIB_PLATFORM_IIGS)
// On the IIgs the arena holds 65816 machine code that callers JSL
// into. A normal C heap allocation returns memory from bank 0 (system
// RAM), so JSL'ing into it lands on whatever happens to live there ->
// instant crash. Memory Manager NewHandle with
// attrFixed | attrLocked | attrPage | attrNoCross gives us a fixed
// page-aligned region in a single bank we can safely jump into.
// The IIgs toolbox bindings live in <iigs/toolbox.h>. Handles are typed
// as void* and the owner ID comes from MMStartUp(); the Memory Manager
// attribute bits we use are defined here (values from Apple's Memory
// Manager reference).
#include <iigs/toolbox.h>
#define attrPage    0x0004
#define attrNoCross 0x0010
#define attrNoSpec  0x0020
#define attrFixed   0x4000
#define attrLocked  0x8000
#endif

#include <stdlib.h>
#include <string.h>

#include "codegenArenaInternal.h"



// ----- Module state -----

// gCodegenArenaBase / gCodegenArenaBaseAddr are non-static so jlSpriteCompile.c can read them
// directly via extern instead of paying a JSL/RTL per access through
// the codegenArenaBase() / codegenArenaBaseAddr() wrappers. Both are
// set once at codegenArenaInit and never moved (the underlying
// Memory Manager handle is locked-in-place on IIgs). Callers MUST
// treat them as read-only.
uint8_t    *gCodegenArenaBase     = NULL;
// gCodegenArenaBaseAddr mirrors gCodegenArenaBase as a 24-bit absolute
// address that JSL targets read directly (captured by copying the
// handle pointer's raw bytes -- see codegenArenaInit).
uint32_t    gCodegenArenaBaseAddr = 0;
static uint32_t    gTotalBytes = 0;
static uint32_t    gUsedBytes  = 0;
static ArenaSlotT *gFirstSlot  = NULL;
#if defined(JOEYLIB_PLATFORM_IIGS)
static void       *gCodegenArenaBaseHandle = NULL;
#endif


// ----- Prototypes -----

static ArenaSlotT *newSlot(uint32_t offset, uint32_t size, bool used);
static void        coalesceWithNext(ArenaSlotT *slot);


// ----- Internal helpers (alphabetical) -----

// If `slot` is free and slot->next is also free, merge them into a
// single free slot. Caller must already have ensured slot is free.
static void coalesceWithNext(ArenaSlotT *slot) {
    ArenaSlotT *victim;

    if (slot == NULL || slot->next == NULL) {
        return;
    }
    if (slot->used || slot->next->used) {
        return;
    }
    victim      = slot->next;
    slot->size += victim->size;
    slot->next  = victim->next;
    if (slot->next != NULL) {
        slot->next->prev = slot;
    }
    free(victim);
}


static ArenaSlotT *newSlot(uint32_t offset, uint32_t size, bool used) {
    ArenaSlotT *s;

    s = (ArenaSlotT *)malloc(sizeof(ArenaSlotT));
    if (s == NULL) {
        return NULL;
    }
    s->offset = offset;
    s->size   = size;
    s->used   = used;
    s->next   = NULL;
    s->prev   = NULL;
    return s;
}


// ----- Public-internal API (alphabetical) -----

ArenaSlotT *codegenArenaAlloc(uint32_t bytes) {
    ArenaSlotT *slot;
    ArenaSlotT *remainder;

    if (gCodegenArenaBase == NULL || bytes == 0) {
        return NULL;
    }
    for (slot = gFirstSlot; slot != NULL; slot = slot->next) {
        if (slot->used || slot->size < bytes) {
            continue;
        }
        // First fit. If there's slack, split: shrink this slot to
        // exactly `bytes` and insert a new free slot for the rest.
        if (slot->size > bytes) {
            remainder = newSlot(slot->offset + bytes, slot->size - bytes, false);
            if (remainder == NULL) {
                return NULL;
            }
            remainder->prev = slot;
            remainder->next = slot->next;
            if (slot->next != NULL) {
                slot->next->prev = remainder;
            }
            slot->next = remainder;
            slot->size = bytes;
        }
        slot->used = true;
        gUsedBytes += bytes;
        return slot;
    }
    return NULL;
}


// codegenArenaBase() / codegenArenaBaseAddr() are now header-only
// macros that read gCodegenArenaBase / gCodegenArenaBaseAddr
// directly, so the C function bodies that used to live here are
// gone. The wrappers cost ~30 cyc per call on IIgs and were hit
// 3x per sprite frame.


uint32_t codegenArenaBytesTotal(void) {
    return gTotalBytes;
}


uint32_t codegenArenaBytesUsed(void) {
    return gUsedBytes;
}


void codegenArenaCompact(void) {
    ArenaSlotT *slot;
    ArenaSlotT *next;
    ArenaSlotT *trailing;
    uint32_t    cursor;

    if (gCodegenArenaBase == NULL) {
        return;
    }
    cursor = 0;
    slot   = gFirstSlot;
    while (slot != NULL) {
        next = slot->next;
        if (slot->used) {
            if (slot->offset != cursor) {
                memmove(gCodegenArenaBase + cursor, gCodegenArenaBase + slot->offset, slot->size);
                slot->offset = cursor;
            }
            cursor += slot->size;
            slot = next;
            continue;
        }
        // Free slot: drop from the list. The caller-side ArenaSlotT*
        // for any free slot was already invalidated when it was
        // freed (coalesce released the struct); nothing live points
        // at it.
        if (slot->prev != NULL) {
            slot->prev->next = next;
        } else {
            gFirstSlot = next;
        }
        if (next != NULL) {
            next->prev = slot->prev;
        }
        free(slot);
        slot = next;
    }
    if (cursor < gTotalBytes) {
        trailing = newSlot(cursor, gTotalBytes - cursor, false);
        if (trailing == NULL) {
            return;     // Compaction succeeded; just skip the free-slot record.
        }
        trailing->prev = NULL;
        for (slot = gFirstSlot; slot != NULL && slot->next != NULL; slot = slot->next) {
            // walk to last
        }
        if (slot == NULL) {
            gFirstSlot = trailing;
        } else {
            slot->next     = trailing;
            trailing->prev = slot;
        }
    }
}


void codegenArenaFree(ArenaSlotT *slot) {
    if (slot == NULL || gCodegenArenaBase == NULL) {
        return;
    }
    if (!slot->used) {
        return;     // double-free; ignore
    }
    slot->used  = false;
    gUsedBytes -= slot->size;

    coalesceWithNext(slot);
    coalesceWithNext(slot->prev);
}


bool codegenArenaInit(uint32_t totalBytes) {
    if (gCodegenArenaBase != NULL) {
        return true;
    }
    if (totalBytes == 0) {
        return false;
    }
#if defined(JOEYLIB_PLATFORM_IIGS)
    // attrNoCross forbids the block from crossing a 64KB bank boundary,
    // so any request larger than one bank (65536 bytes) is unsatisfiable
    // and NewHandle would always return NULL. Clamp to a single bank.
    // The clamped request can still fail below -- a 64KB attrNoCross
    // block needs a completely free bank -- and the caller's error
    // message covers that.
    if (totalBytes > 65536UL) {
        totalBytes = 65536UL;
    }
    // MMStartUp() returns this application's master user ID (idempotent;
    // the system starts the Memory Manager before the app runs). A NULL
    // handle is the failure signal.
    {
        uint16_t ownerId;

        ownerId                 = MMStartUp();
        gCodegenArenaBaseHandle = NewHandle((unsigned long)totalBytes, ownerId,
                            attrFixed | attrLocked | attrPage | attrNoCross,
                            (void *)0);
    }
    if (gCodegenArenaBaseHandle == NULL) {
        return false;
    }
    HLock(gCodegenArenaBaseHandle);
    // Capture the 24-bit absolute address by copying the master
    // pointer's raw bytes; bytes[3] (bank-high/flags) is masked off.
    {
        void   *p;
        uint8_t bytes[4];

        p                     = *(void **)gCodegenArenaBaseHandle;
        gCodegenArenaBase     = (uint8_t *)p;
        memcpy(bytes, &p, 4);
        gCodegenArenaBaseAddr = (uint32_t)bytes[0]
                  | ((uint32_t)bytes[1] << 8)
                  | ((uint32_t)bytes[2] << 16);
    }
    if (gCodegenArenaBase == NULL) {
        DisposeHandle(gCodegenArenaBaseHandle);
        gCodegenArenaBaseHandle = NULL;
        return false;
    }
#else
    gCodegenArenaBase = (uint8_t *)malloc(totalBytes);
    if (gCodegenArenaBase == NULL) {
        return false;
    }
    gCodegenArenaBaseAddr = (uint32_t)gCodegenArenaBase;
#endif
    gFirstSlot = newSlot(0, totalBytes, false);
    if (gFirstSlot == NULL) {
#if defined(JOEYLIB_PLATFORM_IIGS)
        DisposeHandle(gCodegenArenaBaseHandle);
        gCodegenArenaBaseHandle = NULL;
#else
        free(gCodegenArenaBase);
#endif
        gCodegenArenaBase     = NULL;
        gCodegenArenaBaseAddr = 0;
        return false;
    }
    gTotalBytes = totalBytes;
    gUsedBytes  = 0;
    return true;
}


void codegenArenaShutdown(void) {
    ArenaSlotT *slot;
    ArenaSlotT *next;

    if (gCodegenArenaBase == NULL) {
        return;
    }
    for (slot = gFirstSlot; slot != NULL; slot = next) {
        next = slot->next;
        free(slot);
    }
#if defined(JOEYLIB_PLATFORM_IIGS)
    DisposeHandle(gCodegenArenaBaseHandle);
    gCodegenArenaBaseHandle = NULL;
#else
    free(gCodegenArenaBase);
#endif
    gCodegenArenaBase       = NULL;
    gCodegenArenaBaseAddr   = 0;
    gFirstSlot  = NULL;
    gTotalBytes = 0;
    gUsedBytes  = 0;
}
