// SPDX-License-Identifier: GPL-3.0-or-later

#include "aclk_query_queue.h"

#define MAX_QUERY_ENTRIES (512)

struct {
    aclk_query_t query_workers[MAX_QUERY_ENTRIES];
    int free_stack[MAX_QUERY_ENTRIES];
    bool in_free_stack[MAX_QUERY_ENTRIES];
    int top;
    SPINLOCK spinlock;
} queryPool;

// Initialize the query pool
__attribute__((constructor)) void init_query_pool()
{
    spinlock_init(&queryPool.spinlock);
    for (int i = 0; i < MAX_QUERY_ENTRIES; i++) {
        queryPool.free_stack[i] = i;
        queryPool.in_free_stack[i] = true;
        queryPool.query_workers[i].allocated = false;
    }
    queryPool.top = MAX_QUERY_ENTRIES;
}

static aclk_query_t *get_query()
{
    spinlock_lock(&queryPool.spinlock);
    if (queryPool.top == 0) {
        spinlock_unlock(&queryPool.spinlock);
        aclk_query_t *query = callocz(1, sizeof(aclk_query_t));
        query->allocated = true;
        return query;
    }
    int index = queryPool.free_stack[--queryPool.top];
    queryPool.in_free_stack[index] = false;
    memset(&queryPool.query_workers[index], 0, sizeof(aclk_query_t));
    spinlock_unlock(&queryPool.spinlock);
    return &queryPool.query_workers[index];
}

static void return_query(aclk_query_t *query)
{
    if (unlikely(query->allocated)) {
       freez(query);
       return;
    }
    spinlock_lock(&queryPool.spinlock);
    int index = (int) (query - queryPool.query_workers);
    if (index < 0 || index >= MAX_QUERY_ENTRIES) {
        spinlock_unlock(&queryPool.spinlock);
        return;  // Invalid (should not happen)
    }
    if (unlikely(queryPool.in_free_stack[index] || queryPool.top < 0 || queryPool.top >= MAX_QUERY_ENTRIES)) {
        spinlock_unlock(&queryPool.spinlock);
        return;
    }
    queryPool.in_free_stack[index] = true;
    queryPool.free_stack[queryPool.top++] = index;
    memset(query, 0, sizeof(aclk_query_t));
    spinlock_unlock(&queryPool.spinlock);
}

aclk_query_t *aclk_query_new(aclk_query_type_t type)
{
    aclk_query_t *query = get_query();
    query->type = type;
    now_monotonic_high_precision_timeval(&query->created_tv);
    return query;
}

void aclk_query_free(aclk_query_t *query)
{
    struct ctxs_checkpoint *cmd;
    switch (query->type) {
        case HTTP_API_V2:
            freez(query->data.http_api_v2.payload);
            if (query->data.http_api_v2.query != query->dedup_id)
                freez(query->data.http_api_v2.query);
            break;
        case ALERT_START_STREAMING:
            freez(query->data.node_id);
            break;
        case ALERT_CHECKPOINT:
            freez(query->data.node_id);
            freez(query->claim_id);
            break;
        case CREATE_NODE_INSTANCE:
            freez(query->data.node_id);
            freez(query->machine_guid);
            break;
        case ALARM_PROVIDE_CFG:
        case ALARM_SNAPSHOT:
        case REGISTER_NODE:
        case NODE_STATE_UPDATE:
        case UPDATE_NODE_INFO:
        case UPDATE_NODE_MANIFEST:
        case UPDATE_NODE_COLLECTORS:
        case CTX_SEND_SNAPSHOT:
        case CTX_SEND_SNAPSHOT_UPD:
            freez(query->data.bin_payload.payload);
            break;
        // keep following cases together
        case CTX_STOP_STREAMING:
        case CTX_CHECKPOINT:
            cmd = query->data.payload;
            freez(cmd->claim_id);
            freez(cmd->node_id);
            freez(cmd);
            break;

        default:
            break;
    }

    // Every query reaches this function on every path - executed, dropped before execution, or
    // dropped during shutdown - so reporting the manifest outcome here covers every way a manifest
    // can fail to reach the cloud without each of those sites having to know about it.
    if (query->type == UPDATE_NODE_MANIFEST)
        aclk_node_manifest_publish_result(&query->manifest);

    freez(query->dedup_id);
    freez(query->callback_topic);
    freez(query->msg_id);

    if (query->sync_completion)
        aclk_sync_completion_signal(query->sync_completion);
    return_query(query);
}
