// JoeyLib lifecycle: init, shutdown, error reporting.
//
// jlInit stores configuration, allocates the library-owned screen
// surface, and asks the port HAL to set up the display mode.
// jlShutdown tears those down in reverse order.

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

#include "joey/core.h"
#include "codegenArenaInternal.h"
#include "inputInternal.h"
#include "port.h"
#include "spriteInternal.h"
#include "surfaceInternal.h"


// Default codegen arena size when jlConfigT.codegenBytes == 0.
// Per-platform because emitter code density differs ~4-8x across CPUs
// (PERF-AUDIT.md #39):
//
//   IIgs (65816): ~3-4 KB per all-opaque 32x32 sprite, so 8 KB fits
//   the typical working set and keeps the attrNoCross Memory Manager
//   request small. Requests above one 64 KB bank are clamped in
//   codegenArenaInit (#8).
//
//   Amiga/ST: the all-shift compiled sprites (NATIVE-PERF Phase 1)
//   emit one DRAW variant per x phase (8 on Amiga, 16 on ST) plus two
//   save/restore classes -- a 16x16 sprite's full set is ~13-19 KB and
//   a mixed 24x24 can reach ~25-50 KB, so 32 KB starved multi-sprite
//   apps into the degrade tier (aligned-only draws). 64 KB holds a
//   couple of full-variant sprites; jlSpriteCompile degrades per
//   sprite rather than failing when it still runs out. These ports
//   have real heaps.
//
//   DOS: two chunky shifts, ~1-6 KB per sprite; 32 KB is generous.
#if defined(JOEYLIB_PLATFORM_IIGS)
#define DEFAULT_CODEGEN_BYTES (8UL * 1024UL)
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST)
#define DEFAULT_CODEGEN_BYTES (64UL * 1024UL)
#else
#define DEFAULT_CODEGEN_BYTES (32UL * 1024UL)
#endif

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

static void clearError(void);

// coreSetError is the shared error setter (declared in surfaceInternal.h
// and called by surface.c etc.); its definition lives here next to the
// rest of the error-reporting state.

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

static jlConfigT  gConfig;
static bool         gInitialized = false;
static const char  *gLastError   = NULL;

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

static void clearError(void) {
    gLastError = NULL;
}


void coreSetError(const char *message) {
    gLastError = message;
}


// ----- Default large-allocation backing -----
//
// jlAlloc / jlFree route through jlpBigAlloc / jlpBigFree. Most ports have a
// normal heap, so the malloc/free generic default (src/generic/genericPort.c)
// serves them; only ports whose C heap is too small or per-allocation-capped
// (IIgs: bank-0 heap) define JL_HAS_BIG_ALLOC and override with their native
// allocator (IIgs Memory Manager NewHandle / DisposeHandle).


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

void *jlAlloc(uint32_t bytes) {
    return jlpBigAlloc(bytes);
}


void jlFree(void *p) {
    jlpBigFree(p);
}


bool jlInit(const jlConfigT *config) {
    clearError();

    if (gInitialized) {
        coreSetError("jlInit called while already initialized");
        return false;
    }

    if (config == NULL) {
        coreSetError("jlInit called with NULL config");
        return false;
    }

    memcpy(&gConfig, config, sizeof(gConfig));

    // jlpInit must run before stageAlloc: on IIgs the stage's pixel
    // buffer comes from jlpStageAllocPixels, which depends on shadow /
    // SHR setup that jlpInit performs.
    if (!jlpInit(&gConfig)) {
        const char *halMsg = jlpLastError();
        coreSetError(halMsg != NULL ? halMsg : "jlpInit failed");
        return false;
    }

    if (!stageAlloc()) {
        coreSetError("failed to allocate stage surface");
        jlpShutdown();
        return false;
    }

    if (!codegenArenaInit(gConfig.codegenBytes != 0 ? gConfig.codegenBytes
                                                    : DEFAULT_CODEGEN_BYTES)) {
        coreSetError("failed to allocate codegen arena");
        stageFree();
        jlpShutdown();
        return false;
    }

    // Empty the typed-character queue before the port arms its
    // keyboard: a shutdown/re-init cycle must not hand the app
    // characters typed during the previous run.
    jlInputCharReset();
    jlpInputInit();

    gInitialized = true;
    return true;
}


const char *jlLastError(void) {
    return gLastError;
}


const char *jlPlatformName(void) {
    return JOEYLIB_PLATFORM_NAME;
}


void jlShutdown(void) {
    if (!gInitialized) {
        return;
    }
    jlpInputShutdown();
    // Reset compiled-sprite state (slot pointers, routine offsets, patch
    // caches) in every registered sprite BEFORE the arena teardown frees
    // the slot structs -- see spriteSystemShutdown in spriteInternal.h
    // (PERF-AUDIT.md #34).
    spriteSystemShutdown();
    codegenArenaShutdown();
    stageFree();
    jlpShutdown();
    gInitialized = false;
    clearError();
}


const char *jlVersionString(void) {
    return JOEYLIB_VERSION_STRING;
}


void jlWaitVBL(void) {
    jlpWaitVBL();
}


uint16_t jlFrameCount(void) {
    return jlpFrameCount();
}


uint16_t jlFrameHz(void) {
    return jlpFrameHz();
}


uint32_t jlMillisElapsed(void) {
    return jlpMillisElapsed();
}
