// broker.c -- the function registry and the uniform call entry point.
//
// The registry is an open-addressing hash table (power-of-two slot count, linear
// probing, FNV-1a hash). A native function is registered once and becomes
// callable by name from every engine. calogCall is the single dispatch point;
// the actor layer will later intercept it to route calls whose owning context is
// not the caller's, but in the core every call is invoked inline.

#define _POSIX_C_SOURCE 200809L

#include "calogInternal.h"

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

#define BROKER_INITIAL_SLOTS 16
#define BROKER_LOAD_PERCENT  70
#define BROKER_PERCENT_SCALE 100
#define FNV_OFFSET_BASIS     1469598103934665603ULL
#define FNV_PRIME            1099511628211ULL

// probeSlot masks the hash with slotCount - 1, which is only a full bit mask
// when slotCount is a power of two. Both the initial size and the growth factor
// must preserve that.
_Static_assert((BROKER_INITIAL_SLOTS & (BROKER_INITIAL_SLOTS - 1)) == 0, "registry slot count must be a power of two");
_Static_assert((CALOG_GROWTH_FACTOR & (CALOG_GROWTH_FACTOR - 1)) == 0, "registry growth factor must be a power of two");

static int32_t       brokerGrow(CalogT *broker);
static uint64_t      hashName(const char *name);
static CalogEntryT *probeSlot(CalogT *broker, const char *name);
static int32_t       registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline);


int32_t calogCall(CalogT *broker, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result) {
    CalogEntryT *entry;

    calogValueNil(result);
    entry = calogLookup(broker, name);
    if (entry == NULL) {
        return calogFail(result, calogErrNotFoundE, "no such function");
    }
    if (broker->routeHook != NULL) {
        return broker->routeHook(broker, entry, args, argCount, result);
    }
    return entry->fn(args, argCount, result, entry->userData);
}


CalogT *calogBrokerCreate(void) {
    CalogT *broker;

    broker = (CalogT *)calloc(1, sizeof(*broker));
    if (broker == NULL) {
        return NULL;
    }
    broker->slots = (CalogEntryT *)calloc(BROKER_INITIAL_SLOTS, sizeof(CalogEntryT));
    if (broker->slots == NULL) {
        free(broker);
        return NULL;
    }
    broker->slotCount  = BROKER_INITIAL_SLOTS;
    broker->entryCount = 0;
    return broker;
}


void calogBrokerDestroy(CalogT *broker) {
    int64_t index;

    if (broker == NULL) {
        return;
    }
    for (index = 0; index < broker->slotCount; index++) {
        free(broker->slots[index].name);
    }
    free(broker->slots);
    free(broker->engines);
    free(broker);
}


int32_t calogFail(CalogValueT *result, int32_t status, const char *message) {
    int32_t initStatus;

    calogValueFree(result);
    initStatus = calogValueString(result, message, (int64_t)strlen(message));
    if (initStatus != calogOkE) {
        calogValueNil(result);
    }
    return status;
}


void calogForEach(CalogT *broker, void (*visit)(const CalogEntryT *entry, void *ud), void *ud) {
    int64_t index;

    for (index = 0; index < broker->slotCount; index++) {
        if (broker->slots[index].name != NULL) {
            visit(&broker->slots[index], ud);
        }
    }
}


static int32_t brokerGrow(CalogT *broker) {
    CalogEntryT *oldSlots;
    CalogEntryT *newSlots;
    int64_t       oldCount;
    int64_t       newCount;
    int64_t       index;

    oldSlots = broker->slots;
    oldCount = broker->slotCount;
    newCount = oldCount * CALOG_GROWTH_FACTOR;
    newSlots = (CalogEntryT *)calloc((size_t)newCount, sizeof(CalogEntryT));
    if (newSlots == NULL) {
        return calogErrOomE;
    }
    broker->slots     = newSlots;
    broker->slotCount = newCount;
    for (index = 0; index < oldCount; index++) {
        CalogEntryT *target;
        if (oldSlots[index].name == NULL) {
            continue;
        }
        target  = probeSlot(broker, oldSlots[index].name);
        *target = oldSlots[index];
    }
    free(oldSlots);
    return calogOkE;
}


CalogEntryT *calogLookup(CalogT *broker, const char *name) {
    CalogEntryT *slot;

    slot = probeSlot(broker, name);
    if (slot->name == NULL) {
        return NULL;
    }
    return slot;
}


int32_t calogRegister(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) {
    return registerEntry(broker, name, fn, userData, false);
}


int32_t calogRegisterInline(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) {
    return registerEntry(broker, name, fn, userData, true);
}


static int32_t registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline) {
    CalogEntryT *slot;
    char        *nameCopy;
    int32_t      status;

    slot = probeSlot(broker, name);
    if (slot->name != NULL) {
        // Re-registration replaces the existing binding in place: allocation free,
        // so it never grows and never fails.
        slot->fn        = fn;
        slot->userData  = userData;
        slot->runInline = runInline;
        return calogOkE;
    }
    if ((broker->entryCount + 1) * BROKER_PERCENT_SCALE >= broker->slotCount * BROKER_LOAD_PERCENT) {
        status = brokerGrow(broker);
        if (status != calogOkE) {
            return status;
        }
        slot = probeSlot(broker, name);
    }
    nameCopy = strdup(name);
    if (nameCopy == NULL) {
        return calogErrOomE;
    }
    slot->name      = nameCopy;
    slot->fn        = fn;
    slot->userData  = userData;
    slot->runInline = runInline;
    broker->entryCount++;
    return calogOkE;
}


static uint64_t hashName(const char *name) {
    uint64_t hash;
    size_t   index;

    hash = FNV_OFFSET_BASIS;
    for (index = 0; name[index] != '\0'; index++) {
        hash ^= (uint64_t)(unsigned char)name[index];
        hash *= FNV_PRIME;
    }
    return hash;
}


static CalogEntryT *probeSlot(CalogT *broker, const char *name) {
    uint64_t mask;
    uint64_t index;

    mask  = (uint64_t)broker->slotCount - 1;
    index = hashName(name) & mask;
    while (broker->slots[index].name != NULL) {
        if (strcmp(broker->slots[index].name, name) == 0) {
            return &broker->slots[index];
        }
        index = (index + 1) & mask;
    }
    return &broker->slots[index];
}
