// mybasicAdapter.c -- my-basic engine adapter for the broker.
//
// A bank of slot-specific trampolines works around my-basic's native signature
// (no per-call userData; one interpreter userdata slot): each trampoline forwards
// to mbDispatch with its compile-time slot, which recovers the broker binding
// from the context's bank. The context pointer rides the interpreter userdata
// slot; the live argument cursor of the current native call is stashed in
// currentL so an exported BASIC routine can be evaluated through mb_eval_routine.

#include "mybasicAdapter.h"
#include "myBasic.h"

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

#define MB_BANK_SIZE     32
#define MB_INITIAL_ARGS  8
#define MB_NAME_MAX      128
#define MB_NATIVE_ERROR  SE_RN_FAILED_TO_OPERATE

typedef struct BindingT {
    const char *name;   // registry name; dispatched through calogCall so the actor
                        // layer marshals the native to the host thread
} BindingT;

typedef struct MyBasicRoutineT {
    CalogMyBasicT *context;
    mb_value_t       routine;
} MyBasicRoutineT;

struct CalogMyBasicT {
    struct mb_interpreter_t *bas;
    CalogT                 *broker;
    uint64_t                 ctxId;
    void                   **currentL;
    BindingT                 bank[MB_BANK_SIZE];
    int32_t                  bankCount;
};

static int32_t mbContextCount = 0;

static int32_t mbAggregateToCollDepth(CalogMyBasicT *context, void **l, const CalogAggT *aggregate, mb_value_t *out, int32_t depth);
static int32_t mbCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void    mbCallableRelease(CalogFnT *callable);
static int32_t mbDictToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth);
static int     mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l);
static int32_t mbFromValueDepth(CalogMyBasicT *context, void **l, const CalogValueT *value, mb_value_t *out, int32_t depth);
static int32_t mbListToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth);
static int     mbPrinter(struct mb_interpreter_t *s, const char *format, ...);
static void    mbReleaseSetValue(CalogMyBasicT *context, mb_value_t value);
static void    mbRetainBeforeSet(CalogMyBasicT *context, void **l, mb_value_t value);
static int32_t mbToValueDepth(CalogMyBasicT *context, void **l, const mb_value_t *value, CalogValueT *out, int32_t depth);
static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFnT **out);

// Generated trampoline bank (see file header). Each forwards its compile-time
// slot to mbDispatch. This block is intentionally exempt from the alphabetical
// ordering of the hand-written functions below.
#define MB_TRAMPOLINE(slot) static int mbTramp##slot(struct mb_interpreter_t *s, void **l) { return mbDispatch(slot, s, l); }
MB_TRAMPOLINE(0)
MB_TRAMPOLINE(1)
MB_TRAMPOLINE(2)
MB_TRAMPOLINE(3)
MB_TRAMPOLINE(4)
MB_TRAMPOLINE(5)
MB_TRAMPOLINE(6)
MB_TRAMPOLINE(7)
MB_TRAMPOLINE(8)
MB_TRAMPOLINE(9)
MB_TRAMPOLINE(10)
MB_TRAMPOLINE(11)
MB_TRAMPOLINE(12)
MB_TRAMPOLINE(13)
MB_TRAMPOLINE(14)
MB_TRAMPOLINE(15)
MB_TRAMPOLINE(16)
MB_TRAMPOLINE(17)
MB_TRAMPOLINE(18)
MB_TRAMPOLINE(19)
MB_TRAMPOLINE(20)
MB_TRAMPOLINE(21)
MB_TRAMPOLINE(22)
MB_TRAMPOLINE(23)
MB_TRAMPOLINE(24)
MB_TRAMPOLINE(25)
MB_TRAMPOLINE(26)
MB_TRAMPOLINE(27)
MB_TRAMPOLINE(28)
MB_TRAMPOLINE(29)
MB_TRAMPOLINE(30)
MB_TRAMPOLINE(31)
#undef MB_TRAMPOLINE

static const mb_func_t mbTrampTable[MB_BANK_SIZE] = {
    mbTramp0,  mbTramp1,  mbTramp2,  mbTramp3,  mbTramp4,  mbTramp5,  mbTramp6,  mbTramp7,
    mbTramp8,  mbTramp9,  mbTramp10, mbTramp11, mbTramp12, mbTramp13, mbTramp14, mbTramp15,
    mbTramp16, mbTramp17, mbTramp18, mbTramp19, mbTramp20, mbTramp21, mbTramp22, mbTramp23,
    mbTramp24, mbTramp25, mbTramp26, mbTramp27, mbTramp28, mbTramp29, mbTramp30, mbTramp31
};


static int32_t mbAggregateToCollDepth(CalogMyBasicT *context, void **l, const CalogAggT *aggregate, mb_value_t *out, int32_t depth) {
    mb_value_t coll;
    int32_t    status;
    int64_t    index;
    bool       asList;

    mb_make_nil(coll);
    if (depth > CALOG_MAX_DEPTH) {
        return calogErrDepthE;
    }
    asList = (aggregate->pairCount == 0 && aggregate->kind != calogMapE);
    coll.type = asList ? MB_DT_LIST : MB_DT_DICT;
    if (mb_init_coll(context->bas, l, &coll) != MB_FUNC_OK) {
        return calogErrOomE;
    }
    // mb_set_coll deep-copies a scalar or string key/value (so the copy we built
    // must be released afterwards) but stores a LIST/DICT by pointer WITHOUT
    // adding a reference (so that single reference now belongs to the parent and
    // must NOT be released here). mbReleaseSetValue encodes that distinction.
    for (index = 0; index < aggregate->arrayCount; index++) {
        mb_value_t element;
        mb_value_t key;
        mb_make_nil(element);
        status = mbFromValueDepth(context, l, &aggregate->array[index], &element, depth + 1);
        if (status != calogOkE) {
            mb_dispose_value(context->bas, coll);
            return status;
        }
        mb_make_int(key, (int_t)index);
        mbRetainBeforeSet(context, l, element);
        mb_set_coll(context->bas, l, coll, key, element);
        mbReleaseSetValue(context, element);
    }
    for (index = 0; index < aggregate->pairCount; index++) {
        mb_value_t key;
        mb_value_t element;
        mb_make_nil(key);
        mb_make_nil(element);
        status = mbFromValueDepth(context, l, &aggregate->pairs[index].key, &key, depth + 1);
        if (status != calogOkE) {
            mb_dispose_value(context->bas, coll);
            return status;
        }
        status = mbFromValueDepth(context, l, &aggregate->pairs[index].value, &element, depth + 1);
        if (status != calogOkE) {
            mb_dispose_value(context->bas, key);
            mb_dispose_value(context->bas, coll);
            return status;
        }
        mbRetainBeforeSet(context, l, element);
        mb_set_coll(context->bas, l, coll, key, element);
        mbReleaseSetValue(context, key);
        mbReleaseSetValue(context, element);
    }
    *out = coll;
    return calogOkE;
}


static int32_t mbCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    MyBasicRoutineT *routine;
    CalogMyBasicT *context;
    mb_value_t      *mbArgs;
    mb_value_t       ret;
    int32_t          index;
    int32_t          status;
    int              code;

    routine = (MyBasicRoutineT *)userData;
    context = routine->context;
    calogValueNil(result);
    if (context->currentL == NULL) {
        return calogFail(result, calogErrUnsupportedE, "BASIC routine invoked outside a serving frame");
    }
    mbArgs = NULL;
    if (argCount > 0) {
        mbArgs = (mb_value_t *)calloc((size_t)argCount, sizeof(mb_value_t));
        if (mbArgs == NULL) {
            return calogFail(result, calogErrOomE, "out of memory marshalling routine args");
        }
    }
    for (index = 0; index < argCount; index++) {
        mb_make_nil(mbArgs[index]);
        status = mbFromValueDepth(context, context->currentL, &args[index], &mbArgs[index], 0);
        if (status != calogOkE) {
            int32_t built;
            for (built = 0; built < index; built++) {
                mb_dispose_value(context->bas, mbArgs[built]);
            }
            free(mbArgs);
            return calogFail(result, status, "failed to marshal routine argument");
        }
    }
    mb_make_nil(ret);
    code = mb_eval_routine(context->bas, context->currentL, routine->routine, mbArgs, (unsigned)argCount, &ret);
    if (code == MB_FUNC_OK) {
        // Marshal the return value BEFORE releasing the arguments: a routine may
        // return one of its (borrowed) arguments, so result must be deep-copied
        // out first or it would alias a buffer we are about to free.
        status = mbToValueDepth(context, context->currentL, &ret, result, 0);
    } else {
        status = calogFail(result, calogErrArgE, "BASIC routine evaluation failed");
    }
    // mb_eval_routine BORROWS its arguments (the routine's scope marks them
    // is_ref and never frees them), so every marshalled arg is ours to release.
    for (index = 0; index < argCount; index++) {
        mb_dispose_value(context->bas, mbArgs[index]);
    }
    free(mbArgs);
    return status;
}


static void mbCallableRelease(CalogFnT *callable) {
    MyBasicRoutineT *routine;

    routine = (MyBasicRoutineT *)calogFnUserData(callable);
    free(routine);
}


static int32_t mbDictToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth) {
    CalogAggT *aggregate;
    mb_value_t *keys;
    int32_t     status;
    int32_t     index;
    int         count;

    calogValueNil(out);
    count = 0;
    mb_count_coll(context->bas, l, coll, &count);
    status = calogAggCreate(&aggregate, calogMapE);
    if (status != calogOkE) {
        return status;
    }
    keys = NULL;
    if (count > 0) {
        keys = (mb_value_t *)calloc((size_t)count, sizeof(mb_value_t));
        if (keys == NULL) {
            calogAggFree(aggregate);
            return calogErrOomE;
        }
    }
    mb_keys_of_coll(context->bas, l, coll, keys, count);
    for (index = 0; index < count; index++) {
        mb_value_t mbValue;
        CalogValueT     keyValue;
        CalogValueT     valValue;
        mb_make_nil(mbValue);
        mb_get_coll(context->bas, l, coll, keys[index], &mbValue);
        status = mbToValueDepth(context, l, &keys[index], &keyValue, depth + 1);
        if (status != calogOkE) {
            free(keys);
            calogAggFree(aggregate);
            return status;
        }
        status = mbToValueDepth(context, l, &mbValue, &valValue, depth + 1);
        if (status != calogOkE) {
            calogValueFree(&keyValue);
            free(keys);
            calogAggFree(aggregate);
            return status;
        }
        status = calogAggSet(aggregate, &keyValue, &valValue);
        if (status != calogOkE) {
            calogValueFree(&keyValue);
            calogValueFree(&valValue);
            free(keys);
            calogAggFree(aggregate);
            return status;
        }
    }
    free(keys);
    calogValueAgg(out, aggregate);
    return calogOkE;
}


static int mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l) {
    CalogMyBasicT *context;
    BindingT        *binding;
    void            *userData;
    CalogValueT          *args;
    CalogValueT           result;
    mb_value_t       pushValue;
    void           **savedL;
    int32_t          argCount;
    int32_t          capacity;
    int32_t          index;
    int32_t          status;
    int              code;

    userData = NULL;
    mb_get_userdata(s, &userData);
    context = (CalogMyBasicT *)userData;
    binding = &context->bank[slot];

    code = mb_attempt_open_bracket(s, l);
    if (code != MB_FUNC_OK) {
        return code;
    }
    args = NULL;
    argCount = 0;
    capacity = 0;
    while (mb_has_arg(s, l)) {
        mb_value_t popped;
        mb_make_nil(popped);
        code = mb_pop_value(s, l, &popped);
        if (code != MB_FUNC_OK) {
            goto cleanupArgs;
        }
        if (argCount == capacity) {
            int32_t newCap;
            CalogValueT *grown;
            newCap = (capacity == 0) ? MB_INITIAL_ARGS : capacity * CALOG_GROWTH_FACTOR;
            grown = (CalogValueT *)realloc(args, (size_t)newCap * sizeof(CalogValueT));
            if (grown == NULL) {
                if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT) {
                    mb_dispose_value(context->bas, popped);
                }
                code = mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
                goto cleanupArgs;
            }
            args = grown;
            capacity = newCap;
        }
        status = mbToValueDepth(context, l, &popped, &args[argCount], 0);
        // A popped collection is owned by us (the consumer) and must be released
        // once marshalled; popped strings are borrowed interior pointers and
        // routine values are owned by their scope, so neither is disposed here.
        if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT) {
            mb_dispose_value(context->bas, popped);
        }
        if (status != calogOkE) {
            code = mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
            goto cleanupArgs;
        }
        argCount++;
    }
    code = mb_attempt_close_bracket(s, l);
    if (code != MB_FUNC_OK) {
        goto cleanupArgs;
    }

    // Route through calogCall so the actor layer marshals the native to the host thread
    // (like the other engines); currentL is published first so a host native that calls
    // back into a BASIC routine finds this context's serving frame.
    savedL = context->currentL;
    context->currentL = l;
    status = calogCall(context->broker, binding->name, args, argCount, &result);
    context->currentL = savedL;

    for (index = 0; index < argCount; index++) {
        calogValueFree(&args[index]);
    }
    free(args);
    args = NULL;

    if (status != calogOkE) {
        calogValueFree(&result);
        return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
    }
    mb_make_nil(pushValue);
    status = mbFromValueDepth(context, l, &result, &pushValue, 0);
    calogValueFree(&result);
    if (status != calogOkE) {
        return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
    }
    // mb_push_value borrows a string (the interpreter never frees it); mb_push_string
    // marks the buffer for lazy destruction. Collections are owned by mb_push_value.
    if (pushValue.type == MB_DT_STRING) {
        return mb_push_string(s, l, pushValue.value.string);
    }
    return mb_push_value(s, l, pushValue);

cleanupArgs:
    for (index = 0; index < argCount; index++) {
        calogValueFree(&args[index]);
    }
    free(args);
    return code;
}


static int32_t mbFromValueDepth(CalogMyBasicT *context, void **l, const CalogValueT *value, mb_value_t *out, int32_t depth) {
    char *duplicate;

    mb_make_nil(*out);
    switch (value->type) {
        case calogNilE:
            return calogOkE;
        case calogBoolE:
            mb_make_int(*out, value->as.b ? 1 : 0);
            return calogOkE;
        case calogIntE:
            if (value->as.i < INT32_MIN || value->as.i > INT32_MAX) {
                return calogErrRangeE;
            }
            mb_make_int(*out, (int_t)value->as.i);
            return calogOkE;
        case calogRealE:
            mb_make_real(*out, (real_t)value->as.r);
            return calogOkE;
        case calogStringE:
            duplicate = mb_memdup(value->as.s.bytes, (unsigned)(value->as.s.length + 1));
            if (duplicate == NULL) {
                return calogErrOomE;
            }
            mb_make_string(*out, duplicate);
            return calogOkE;
        case calogAggE:
            return mbAggregateToCollDepth(context, l, value->as.agg, out, depth);
        case calogFnE:
            return calogErrUnsupportedE;
    }
    return calogErrTypeE;
}


static int32_t mbListToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth) {
    CalogAggT *aggregate;
    int32_t     status;
    int32_t     index;
    int         count;

    calogValueNil(out);
    count = 0;
    mb_count_coll(context->bas, l, coll, &count);
    status = calogAggCreate(&aggregate, calogListE);
    if (status != calogOkE) {
        return status;
    }
    for (index = 0; index < count; index++) {
        mb_value_t idx;
        mb_value_t element;
        CalogValueT     elementValue;
        mb_make_int(idx, (int_t)index);
        mb_make_nil(element);
        mb_get_coll(context->bas, l, coll, idx, &element);
        status = mbToValueDepth(context, l, &element, &elementValue, depth + 1);
        if (status != calogOkE) {
            calogAggFree(aggregate);
            return status;
        }
        status = calogAggPush(aggregate, &elementValue);
        if (status != calogOkE) {
            calogValueFree(&elementValue);
            calogAggFree(aggregate);
            return status;
        }
    }
    calogValueAgg(out, aggregate);
    return calogOkE;
}


static int mbPrinter(struct mb_interpreter_t *s, const char *format, ...) {
    va_list args;
    int     written;

    (void)s;
    va_start(args, format);
    written = vprintf(format, args);
    va_end(args);
    return written;
}


static void mbReleaseSetValue(CalogMyBasicT *context, mb_value_t value) {
    // Release a value we handed to mb_set_coll: scalars/strings were copied, so
    // dispose ours; a LIST/DICT was stored by pointer without a reference, so the
    // parent now owns it and we must not touch it.
    if (value.type == MB_DT_LIST || value.type == MB_DT_DICT) {
        return;
    }
    mb_dispose_value(context->bas, value);
}


static void mbRetainBeforeSet(CalogMyBasicT *context, void **l, mb_value_t value) {
    // mb_set_coll stores a LIST/DICT by pointer WITHOUT adding a reference, so the
    // parent would not own it and would leak it on teardown. Add the reference
    // here; mbReleaseSetValue then deliberately does not dispose collections, so
    // the parent holds the single owning reference. Scalars/strings are copied by
    // mb_set_coll and need no reference.
    if (value.type == MB_DT_LIST || value.type == MB_DT_DICT) {
        mb_ref_value(context->bas, l, value);
    }
}


static int32_t mbToValueDepth(CalogMyBasicT *context, void **l, const mb_value_t *value, CalogValueT *out, int32_t depth) {
    CalogFnT *callable;
    int32_t    status;

    calogValueNil(out);
    if (depth > CALOG_MAX_DEPTH) {
        return calogErrDepthE;
    }
    switch (value->type) {
        case MB_DT_NIL:
            return calogOkE;
        case MB_DT_INT:
            calogValueInt(out, (int64_t)value->value.integer);
            return calogOkE;
        case MB_DT_REAL:
            calogValueReal(out, (double)value->value.float_point);
            return calogOkE;
        case MB_DT_STRING:
            return calogValueString(out, value->value.string, value->value.string != NULL ? (int64_t)strlen(value->value.string) : 0);
        case MB_DT_LIST:
            return mbListToAggregate(context, l, *value, out, depth);
        case MB_DT_DICT:
            return mbDictToAggregate(context, l, *value, out, depth);
        case MB_DT_ROUTINE:
            status = mbWrapRoutine(context, *value, &callable);
            if (status != calogOkE) {
                return status;
            }
            calogValueFn(out, callable);
            return calogOkE;
        default:
            return calogErrUnsupportedE;
    }
}


static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFnT **out) {
    MyBasicRoutineT *holder;
    int32_t          status;

    *out = NULL;
    holder = (MyBasicRoutineT *)malloc(sizeof(*holder));
    if (holder == NULL) {
        return calogErrOomE;
    }
    holder->context = context;
    holder->routine = routine;
    // A BASIC routine value is owned by its defining scope and is NOT separately
    // ref-counted here: mb_ref_value/mb_unref_value mishandle routine values and
    // corrupt the scope. The handle is therefore valid only while that scope is
    // alive -- i.e. while the context is serving (a native call is on the stack,
    // which the actor layer's parked serve frame guarantees).
    status = calogFnCreate(out, context->broker, mbCallableInvoke, holder, mbCallableRelease, context->ctxId);
    if (status != calogOkE) {
        free(holder);
        return status;
    }
    return calogOkE;
}


int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId) {
    CalogMyBasicT         *context;
    struct mb_interpreter_t *bas;

    *out = NULL;
    context = (CalogMyBasicT *)calloc(1, sizeof(*context));
    if (context == NULL) {
        return calogErrOomE;
    }
    if (mbContextCount == 0) {
        mb_init();
    }
    bas = NULL;
    if (mb_open(&bas) != MB_FUNC_OK || bas == NULL) {
        if (mbContextCount == 0) {
            mb_dispose();
        }
        free(context);
        return calogErrOomE;
    }
    context->bas      = bas;
    context->broker   = broker;
    context->ctxId    = ctxId;
    context->currentL = NULL;
    mbContextCount++;
    mb_set_userdata(bas, context);
    mb_set_printer(bas, mbPrinter);
    *out = context;
    return calogOkE;
}


void calogMyBasicDestroy(CalogMyBasicT *context) {
    if (context == NULL) {
        return;
    }
    if (context->bas != NULL) {
        mb_close(&context->bas);
    }
    free(context);
    mbContextCount--;
    if (mbContextCount == 0) {
        mb_dispose();
    }
}


int32_t calogMyBasicExportRoutine(CalogMyBasicT *context, const char *routineName, CalogFnT **out) {
    mb_value_t routine;
    char       upper[MB_NAME_MAX];
    size_t     index;

    *out = NULL;
    if (context->currentL == NULL) {
        return calogErrUnsupportedE;
    }
    // BASIC uppercases every identifier at parse time, but mb_get_routine matches
    // the raw name, so the lookup name must be uppercased to find "def d" as "D".
    for (index = 0; routineName[index] != '\0'; index++) {
        char ch;
        if (index + 1 >= sizeof(upper)) {
            return calogErrArgE;
        }
        ch = routineName[index];
        upper[index] = (ch >= 'a' && ch <= 'z') ? (char)(ch - 'a' + 'A') : ch;
    }
    upper[index] = '\0';
    mb_make_nil(routine);
    if (mb_get_routine(context->bas, context->currentL, upper, &routine) != MB_FUNC_OK) {
        return calogErrNotFoundE;
    }
    if (routine.type != MB_DT_ROUTINE) {
        return calogErrNotFoundE;
    }
    return mbWrapRoutine(context, routine, out);
}


int32_t calogMyBasicExpose(CalogMyBasicT *context, const char *name) {
    CalogEntryT *entry;
    int32_t       slot;

    entry = calogLookup(context->broker, name);
    if (entry == NULL) {
        return calogErrNotFoundE;
    }
    if (context->bankCount >= MB_BANK_SIZE) {
        return calogErrRangeE;
    }
    slot = context->bankCount;
    context->bank[slot].name = entry->name;
    if (mb_register_func(context->bas, name, mbTrampTable[slot]) == 0) {
        return calogErrArgE;
    }
    context->bankCount++;
    return calogOkE;
}


int32_t calogMyBasicRun(CalogMyBasicT *context, const char *source) {
    int code;

    code = mb_load_string(context->bas, source, true);
    if (code != MB_FUNC_OK) {
        fprintf(stderr, "my-basic load error: %d\n", code);
        return calogErrArgE;
    }
    code = mb_run(context->bas, true);
    if (code != MB_FUNC_OK) {
        fprintf(stderr, "my-basic run error: %d\n", code);
        return calogErrArgE;
    }
    return calogOkE;
}
