// testPolyglot.c -- integration test: one broker, two engines.
//
// Demonstrates the whole point of the broker: a single C native registered once
// is callable from both Lua and my-basic, and a function written in Lua is
// callable from a my-basic program -- routed entirely through the broker, with
// neither engine aware the other exists.

#include "calogInternal.h"
#include "luaAdapter.h"
#include "mybasicAdapter.h"

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

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

#define LUA_CTX_ID 1
#define BASIC_CTX_ID 2

static CalogT         *broker      = NULL;
static CalogLuaT     *lua         = NULL;
static CalogMyBasicT *basic       = NULL;
static CalogFnT       *luaCallable = NULL;
static CalogValueT           recorded;
static int32_t          testsRun    = 0;
static int32_t          testsFailed = 0;

static int32_t basicRun(const char *source);
static int32_t callableTrampoline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void    checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeRecord(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void    testBasicCallsLua(void);
static void    testSharedNative(void);


static int32_t basicRun(const char *source) {
    int32_t status;

    if (basic != NULL) {
        calogMyBasicDestroy(basic);
        basic = NULL;
    }
    status = calogMyBasicCreate(&basic, broker, BASIC_CTX_ID);
    if (status != calogOkE) {
        return status;
    }
    calogMyBasicExpose(basic, "add");
    calogMyBasicExpose(basic, "record");
    calogMyBasicExpose(basic, "luaDouble");
    return calogMyBasicRun(basic, source);
}


static int32_t callableTrampoline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    return calogFnInvoke((CalogFnT *)userData, args, argCount, result);
}


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 nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    (void)userData;
    if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
        return calogFail(result, calogErrArgE, "add expects two integers");
    }
    calogValueInt(result, args[0].as.i + args[1].as.i);
    return calogOkE;
}


static int32_t nativeRecord(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    int32_t status;

    (void)userData;
    calogValueNil(result);
    calogValueFree(&recorded);
    if (argCount < 1) {
        return calogOkE;
    }
    status = calogValueCopy(&recorded, &args[0]);
    if (status != calogOkE) {
        return calogFail(result, status, "record copy failed");
    }
    return calogOkE;
}


static void testBasicCallsLua(void) {
    int32_t status;

    // luaDouble is implemented in Lua; my-basic calls it by name through the
    // broker with no knowledge that another language is involved.
    status = basicRun("record(luadouble(21))");
    CHECK(status == calogOkE, "BASIC calling a Lua function run");
    CHECK(recorded.type == calogIntE && recorded.as.i == 42, "BASIC invoked the Lua function via the broker");
}


static void testSharedNative(void) {
    int32_t status;

    status = calogLuaRun(lua, "record(add(2, 3))");
    CHECK(status == calogOkE, "shared native from Lua run");
    CHECK(recorded.type == calogIntE && recorded.as.i == 5, "one C native called from Lua");

    status = basicRun("record(add(10, 20))");
    CHECK(status == calogOkE, "shared native from BASIC run");
    CHECK(recorded.type == calogIntE && recorded.as.i == 30, "the same C native called from BASIC");
}


int main(void) {
    int32_t status;

    calogValueNil(&recorded);
    broker = calogBrokerCreate();
    if (broker == NULL) {
        printf("broker create failed\n");
        return 1;
    }
    calogRegister(broker, "add", nativeAdd, NULL);
    calogRegister(broker, "record", nativeRecord, NULL);

    status = calogLuaCreate(&lua, broker, LUA_CTX_ID);
    if (status != calogOkE) {
        printf("lua context create failed\n");
        return 1;
    }
    calogLuaExpose(lua, "add");
    calogLuaExpose(lua, "record");

    // Define a function in Lua, export it, and register it as a broker native so
    // any other language can call it.
    calogLuaRun(lua, "function luaDouble(x) return x * 2 end");
    status = calogLuaExport(lua, "luaDouble", &luaCallable);
    if (status != calogOkE) {
        printf("lua export failed\n");
        return 1;
    }
    calogRegister(broker, "luaDouble", callableTrampoline, luaCallable);

    testSharedNative();
    testBasicCallsLua();

    calogValueFree(&recorded);
    if (basic != NULL) {
        calogMyBasicDestroy(basic);
    }
    calogFnRelease(luaCallable);
    calogLuaDestroy(lua);
    calogBrokerDestroy(broker);

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