// value.c -- CalogValueT / CalogAggT / CalogFnT lifecycle for the broker core.
//
// All cross-boundary data is by-value: calogValueCopy deep-copies aggregates and
// strings so no two owners (and, later, no two threads) ever share a heap
// pointer. The single exception is calogFnE: a function value is a refcounted
// CalogFnT handle that is shared by reference and only ever invoked, never
// inspected. Aggregates are required to be acyclic and bounded by
// CALOG_MAX_DEPTH; the copy path enforces the bound, and the free path relies
// on it.

#include "calogInternal.h"

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

#define BROKER_MIN_CAPACITY        8
#define CALLABLE_INITIAL_REFCOUNT  1

struct CalogFnT {
    CalogNativeFnT   fn;
    void            *userData;
    CalogReleaseFnT  release;
    CalogT          *runtime;      // owning runtime; its hooks route invoke/release
    uint64_t         ownerCtxId;   // the owning context's 64-bit id (0 = host)
    _Atomic int32_t  refCount;
    _Atomic bool     alive;
};

static int32_t aggregateCopyDepth(CalogAggT **out, const CalogAggT *src, int32_t depth);
static int32_t ensureCapacity(void **buffer, int64_t *cap, int64_t needed, size_t elemSize);
static int32_t valueCopyDepth(CalogValueT *dst, const CalogValueT *src, int32_t depth);


static int32_t aggregateCopyDepth(CalogAggT **out, const CalogAggT *src, int32_t depth) {
    CalogAggT *copy;
    int32_t     status;
    int64_t     index;

    *out = NULL;
    if (depth > CALOG_MAX_DEPTH) {
        return calogErrDepthE;
    }
    status = calogAggCreate(&copy, src->kind);
    if (status != calogOkE) {
        return status;
    }
    for (index = 0; index < src->arrayCount; index++) {
        CalogValueT element;
        status = valueCopyDepth(&element, &src->array[index], depth + 1);
        if (status != calogOkE) {
            calogAggFree(copy);
            return status;
        }
        status = calogAggPush(copy, &element);
        if (status != calogOkE) {
            calogValueFree(&element);
            calogAggFree(copy);
            return status;
        }
    }
    for (index = 0; index < src->pairCount; index++) {
        CalogValueT key;
        CalogValueT value;
        status = valueCopyDepth(&key, &src->pairs[index].key, depth + 1);
        if (status != calogOkE) {
            calogAggFree(copy);
            return status;
        }
        status = valueCopyDepth(&value, &src->pairs[index].value, depth + 1);
        if (status != calogOkE) {
            calogValueFree(&key);
            calogAggFree(copy);
            return status;
        }
        status = calogAggSet(copy, &key, &value);
        if (status != calogOkE) {
            calogValueFree(&key);
            calogValueFree(&value);
            calogAggFree(copy);
            return status;
        }
    }
    *out = copy;
    return calogOkE;
}


int32_t calogAggCreate(CalogAggT **out, CalogKindE kind) {
    CalogAggT *aggregate;

    *out = NULL;
    aggregate = (CalogAggT *)calloc(1, sizeof(*aggregate));
    if (aggregate == NULL) {
        return calogErrOomE;
    }
    aggregate->kind = kind;
    *out = aggregate;
    return calogOkE;
}


void calogAggFree(CalogAggT *aggregate) {
    int64_t index;

    if (aggregate == NULL) {
        return;
    }
    for (index = 0; index < aggregate->arrayCount; index++) {
        calogValueFree(&aggregate->array[index]);
    }
    for (index = 0; index < aggregate->pairCount; index++) {
        calogValueFree(&aggregate->pairs[index].key);
        calogValueFree(&aggregate->pairs[index].value);
    }
    free(aggregate->array);
    free(aggregate->pairs);
    free(aggregate);
}


CalogValueT *calogAggGet(CalogAggT *aggregate, const CalogValueT *key) {
    int64_t index;

    for (index = 0; index < aggregate->pairCount; index++) {
        if (calogValueEquals(&aggregate->pairs[index].key, key)) {
            return &aggregate->pairs[index].value;
        }
    }
    return NULL;
}


int32_t calogAggPush(CalogAggT *aggregate, CalogValueT *value) {
    void   *buffer;
    int32_t status;

    // Pun through a real void* lvalue, never (void **)&typedPointer, to avoid a
    // strict-aliasing violation when ensureCapacity stores the resized pointer.
    buffer = aggregate->array;
    status = ensureCapacity(&buffer, &aggregate->arrayCap, aggregate->arrayCount + 1, sizeof(CalogValueT));
    if (status != calogOkE) {
        return status;
    }
    aggregate->array = (CalogValueT *)buffer;
    calogValueMove(&aggregate->array[aggregate->arrayCount], value);
    aggregate->arrayCount++;
    return calogOkE;
}


int32_t calogAggSet(CalogAggT *aggregate, CalogValueT *key, CalogValueT *value) {
    void   *buffer;
    int32_t status;
    int64_t index;

    for (index = 0; index < aggregate->pairCount; index++) {
        if (calogValueEquals(&aggregate->pairs[index].key, key)) {
            calogValueFree(&aggregate->pairs[index].value);
            calogValueMove(&aggregate->pairs[index].value, value);
            calogValueFree(key);
            return calogOkE;
        }
    }
    buffer = aggregate->pairs;
    status = ensureCapacity(&buffer, &aggregate->pairCap, aggregate->pairCount + 1, sizeof(CalogPairT));
    if (status != calogOkE) {
        return status;
    }
    aggregate->pairs = (CalogPairT *)buffer;
    calogValueMove(&aggregate->pairs[aggregate->pairCount].key, key);
    calogValueMove(&aggregate->pairs[aggregate->pairCount].value, value);
    aggregate->pairCount++;
    return calogOkE;
}


int32_t calogFnCreate(CalogFnT **out, CalogT *runtime, CalogNativeFnT fn, void *userData, CalogReleaseFnT release, uint64_t ownerCtxId) {
    CalogFnT *callable;

    *out = NULL;
    callable = (CalogFnT *)calloc(1, sizeof(*callable));
    if (callable == NULL) {
        return calogErrOomE;
    }
    callable->fn         = fn;
    callable->userData   = userData;
    callable->release    = release;
    callable->runtime    = runtime;
    callable->ownerCtxId = ownerCtxId;
    atomic_init(&callable->refCount, CALLABLE_INITIAL_REFCOUNT);
    atomic_init(&callable->alive, true);
    *out = callable;
    return calogOkE;
}


void calogFnFinalize(CalogFnT *callable) {
    if (callable == NULL) {
        return;
    }
    // Last reference is gone: run the engine's closure release (which also frees the
    // engine's per-callable struct), then free the shell. The actor layer guarantees
    // this runs on the owner's thread (design.md sec 10), so the interpreter op is
    // safe; in the core (no actor) it runs inline.
    if (callable->release != NULL) {
        callable->release(callable);
    }
    free(callable);
}


CalogNativeFnT calogFnNative(const CalogFnT *callable) {
    return callable->fn;
}


int32_t calogFnInvoke(CalogFnT *callable, CalogValueT *args, int32_t argCount, CalogValueT *result) {
    calogValueNil(result);
    if (!atomic_load_explicit(&callable->alive, memory_order_acquire)) {
        return calogFail(result, calogErrNotFoundE, "callable owner no longer exists");
    }
    // The owning runtime's actor layer, if present, marshals a foreign-thread invoke
    // to the owner's thread; inline otherwise (a bare broker has no hook).
    if (callable->runtime != NULL && callable->runtime->invokeHook != NULL) {
        return callable->runtime->invokeHook(callable, args, argCount, result);
    }
    return callable->fn(args, argCount, result, callable->userData);
}


void calogFnMarkDead(CalogFnT *callable) {
    if (callable == NULL) {
        return;
    }
    atomic_store_explicit(&callable->alive, false, memory_order_release);
}


uint64_t calogFnOwner(const CalogFnT *callable) {
    return callable->ownerCtxId;
}


void calogFnRelease(CalogFnT *callable) {
    int32_t previous;

    if (callable == NULL) {
        return;
    }
    previous = atomic_fetch_sub_explicit(&callable->refCount, 1, memory_order_acq_rel);
    if (previous == 1) {
        // This drop took the count to zero. The owning runtime's actor layer (if
        // installed) routes the finalize to the owner's thread; otherwise inline.
        if (callable->runtime != NULL && callable->runtime->releaseHook != NULL) {
            callable->runtime->releaseHook(callable);
        } else {
            calogFnFinalize(callable);
        }
    }
}


void calogFnRetain(CalogFnT *callable) {
    if (callable == NULL) {
        return;
    }
    atomic_fetch_add_explicit(&callable->refCount, 1, memory_order_relaxed);
}


CalogT *calogFnRuntime(const CalogFnT *callable) {
    return callable->runtime;
}


void *calogFnUserData(const CalogFnT *callable) {
    return callable->userData;
}


static int32_t ensureCapacity(void **buffer, int64_t *cap, int64_t needed, size_t elemSize) {
    int64_t newCap;
    void   *resized;

    if (*cap >= needed) {
        return calogOkE;
    }
    newCap = (*cap == 0) ? BROKER_MIN_CAPACITY : *cap;
    while (newCap < needed) {
        if (newCap > INT64_MAX / CALOG_GROWTH_FACTOR) {
            return calogErrOomE;
        }
        newCap *= CALOG_GROWTH_FACTOR;
    }
    if ((uint64_t)newCap > (uint64_t)(SIZE_MAX / elemSize)) {
        return calogErrOomE;
    }
    resized = realloc(*buffer, (size_t)newCap * elemSize);
    if (resized == NULL) {
        return calogErrOomE;
    }
    *buffer = resized;
    *cap    = newCap;
    return calogOkE;
}


int32_t calogValueCopy(CalogValueT *dst, const CalogValueT *src) {
    return valueCopyDepth(dst, src, 0);
}


static int32_t valueCopyDepth(CalogValueT *dst, const CalogValueT *src, int32_t depth) {
    CalogAggT *aggregate;
    int32_t     status;

    calogValueNil(dst);
    switch (src->type) {
        case calogNilE:
            break;
        case calogBoolE:
            dst->as.b = src->as.b;
            break;
        case calogIntE:
            dst->as.i = src->as.i;
            break;
        case calogRealE:
            dst->as.r = src->as.r;
            break;
        case calogStringE:
            return calogValueString(dst, src->as.s.bytes, src->as.s.length);
        case calogAggE:
            status = aggregateCopyDepth(&aggregate, src->as.agg, depth);
            if (status != calogOkE) {
                return status;
            }
            calogValueAgg(dst, aggregate);
            return calogOkE;
        case calogFnE:
            calogFnRetain(src->as.fn);
            dst->as.fn = src->as.fn;
            break;
    }
    dst->type = src->type;
    return calogOkE;
}


// Equality used for map-key matching. Scalars and strings compare by value;
// aggregate and function values compare by pointer identity, so they are only
// useful as keys when the exact same handle is reused (a deep copy yields a new
// pointer). Real keys use IEEE semantics: NaN never matches and +0.0 == -0.0.
bool calogValueEquals(const CalogValueT *a, const CalogValueT *b) {
    if (a->type != b->type) {
        return false;
    }
    switch (a->type) {
        case calogNilE:
            return true;
        case calogBoolE:
            return a->as.b == b->as.b;
        case calogIntE:
            return a->as.i == b->as.i;
        case calogRealE:
            return a->as.r == b->as.r;
        case calogStringE:
            if (a->as.s.length != b->as.s.length) {
                return false;
            }
            return memcmp(a->as.s.bytes, b->as.s.bytes, (size_t)a->as.s.length) == 0;
        case calogAggE:
            return a->as.agg == b->as.agg;
        case calogFnE:
            return a->as.fn == b->as.fn;
    }
    return false;
}


void calogValueFree(CalogValueT *value) {
    if (value == NULL) {
        return;
    }
    switch (value->type) {
        case calogNilE:
        case calogBoolE:
        case calogIntE:
        case calogRealE:
            break;
        case calogStringE:
            free(value->as.s.bytes);
            break;
        case calogAggE:
            calogAggFree(value->as.agg);
            break;
        case calogFnE:
            calogFnRelease(value->as.fn);
            break;
    }
    calogValueNil(value);
}


void calogValueAgg(CalogValueT *value, CalogAggT *aggregate) {
    value->type   = calogAggE;
    value->as.agg = aggregate;
}


void calogValueBool(CalogValueT *value, bool b) {
    value->type = calogBoolE;
    value->as.b = b;
}


void calogValueFn(CalogValueT *value, CalogFnT *callable) {
    value->type        = calogFnE;
    value->as.fn = callable;
}


void calogValueInt(CalogValueT *value, int64_t i) {
    value->type = calogIntE;
    value->as.i = i;
}


void calogValueNil(CalogValueT *value) {
    memset(value, 0, sizeof(*value));
    value->type = calogNilE;
}


void calogValueReal(CalogValueT *value, double r) {
    value->type = calogRealE;
    value->as.r = r;
}


int32_t calogValueString(CalogValueT *value, const char *bytes, int64_t length) {
    char *buffer;

    if (length < 0) {
        calogValueNil(value);
        return calogErrRangeE;
    }
    if (bytes == NULL && length > 0) {
        calogValueNil(value);
        return calogErrArgE;
    }
    buffer = (char *)malloc((size_t)length + 1);
    if (buffer == NULL) {
        calogValueNil(value);
        return calogErrOomE;
    }
    if (length > 0) {
        memcpy(buffer, bytes, (size_t)length);
    }
    buffer[length]     = '\0';
    value->type        = calogStringE;
    value->as.s.bytes  = buffer;
    value->as.s.length = length;
    return calogOkE;
}


void calogValueMove(CalogValueT *dst, CalogValueT *src) {
    if (dst == src) {
        return;
    }
    *dst = *src;
    calogValueNil(src);
}


const char *calogTypeName(CalogTypeE type) {
    switch (type) {
        case calogNilE:
            return "nil";
        case calogBoolE:
            return "bool";
        case calogIntE:
            return "int";
        case calogRealE:
            return "real";
        case calogStringE:
            return "string";
        case calogAggE:
            return "aggregate";
        case calogFnE:
            return "function";
    }
    return "unknown";
}
