// testLoad.c -- calogContextLoad across all four engines.
//
// Links every engine (built with all CALOG_WITH_* flags, set by the Makefile), so
// calogRegisterBuiltinEngines auto-registers Lua, JS, Squirrel, and my-basic, and each
// language is loaded by file extension. Also the first exercise of my-basic under the
// actor/context-thread model. Verifies: each extension loads and runs its script; the
// first registered engine wins when several files share a base name; a base that names
// no file returns NULL.

#define _POSIX_C_SOURCE 200809L

#include "calog.h"

#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// This test loads a .scm script, so it spins up an s7 interpreter, which keeps a small,
// bounded set of interned "permanent" strings that s7_free intentionally never reclaims
// (an s7 design characteristic, not a calog defect). Suppress exactly that allocation
// site so the leak check stays meaningful. LSan calls this weak hook automatically.
const char *__lsan_default_suppressions(void);
const char *__lsan_default_suppressions(void) {
    return "leak:make_permanent_string\n";
}

#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__)

#define PUMP_LIMIT 4000   // ~2s at 0.5ms/iter

static CalogT         *calog       = NULL;
static _Atomic int64_t  lastHit     = 0;
static int32_t          testsRun    = 0;
static int32_t          testsFailed = 0;

static void    checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t hit(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int64_t loadAndRun(const char *base);
static void    writeScript(const char *path, const char *content);


static void checkImpl(bool condition, const char *message, const char *file, int32_t line) {
    testsRun++;
    if (!condition) {
        testsFailed++;
        printf("FAIL %s:%d  %s\n", file, line, message);
    }
}


static int32_t hit(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    (void)userData;
    calogValueNil(result);
    if (argCount >= 1 && args[0].type == calogIntE) {
        atomic_store(&lastHit, args[0].as.i);
    }
    return calogOkE;
}


static void writeScript(const char *path, const char *content) {
    FILE *file;

    file = fopen(path, "wb");
    if (file != NULL) {
        fputs(content, file);
        fclose(file);
    }
}


// Load the script for base, pump until its hit() lands (or timeout), close, and return
// the reported value -- 0 if it never ran, -1 if no file matched at all.
static int64_t loadAndRun(const char *base) {
    CalogContextT  *context;
    struct timespec ts = { 0, 500000 };
    int32_t         i;

    atomic_store(&lastHit, 0);
    context = calogContextLoad(calog, base);
    if (context == NULL) {
        return -1;
    }
    for (i = 0; i < PUMP_LIMIT && atomic_load(&lastHit) == 0; i++) {
        calogPump(calog);
        nanosleep(&ts, NULL);
    }
    calogContextClose(context);
    return atomic_load(&lastHit);
}


int main(void) {
    calog = calogCreate();
    if (calog == NULL) {
        printf("calog create failed\n");
        return 1;
    }
    calogRegister(calog, "hit", hit, NULL);
    calogRegisterBuiltinEngines(calog);   // every engine the build linked (all seven)

    writeScript("clTest.lua", "hit(11)");
    CHECK(loadAndRun("clTest") == 11, "loaded and ran a .lua script by base name");
    remove("clTest.lua");

    writeScript("clTest.js", "hit(12)");
    CHECK(loadAndRun("clTest") == 12, "loaded and ran a .js script by base name");
    remove("clTest.js");

    writeScript("clTest.nut", "hit(13)");
    CHECK(loadAndRun("clTest") == 13, "loaded and ran a .nut Squirrel script by base name");
    remove("clTest.nut");

    writeScript("clTest.bas", "hit(14)");
    CHECK(loadAndRun("clTest") == 14, "loaded and ran a .bas my-basic script by base name");
    remove("clTest.bas");

    writeScript("clTest.scm", "(hit 15)");
    CHECK(loadAndRun("clTest") == 15, "loaded and ran a .scm Scheme script by base name");
    remove("clTest.scm");

    writeScript("clTest.wren", "Calog.call(\"hit\", [16])");
    CHECK(loadAndRun("clTest") == 16, "loaded and ran a .wren script by base name");
    remove("clTest.wren");

    writeScript("clTest.be", "hit(17)");
    CHECK(loadAndRun("clTest") == 17, "loaded and ran a .be Berry script by base name");
    remove("clTest.be");

    // First match wins: Lua is registered before my-basic, so .lua beats .bas.
    writeScript("clPrio.lua", "hit(21)");
    writeScript("clPrio.bas", "hit(22)");
    CHECK(loadAndRun("clPrio") == 21, "first registered engine wins when several files share a base");
    remove("clPrio.lua");
    remove("clPrio.bas");

    // No file with any registered extension exists -> NULL.
    CHECK(calogContextLoad(calog, "clNoSuchBase") == NULL, "calogContextLoad returns NULL when nothing matches");

    calogDestroy(calog);

    printf("\n%d checks, %d failed\n", testsRun, testsFailed);
    fflush(stdout);
    if (testsFailed != 0) {
        return 1;
    }
    return 0;
}
