#include "mvgal_shader_translate.h"

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

/* ------------------------------------------------------------------ */
/*  Hash — simple 64-bit mixing of uint32_t words                     */
/* ------------------------------------------------------------------ */

uint64_t mvgal_spirv_hash(const uint32_t *code, size_t word_count)
{
    uint64_t h = 0xcbf29ce484222325ULL;  /* FNV-1a offset basis */
    for (size_t i = 0; i < word_count; ++i) {
        uint32_t w = code[i];
        /* fold each byte through the FNV-1a step */
        for (int b = 0; b < 4; ++b) {
            h ^= (uint64_t)(w & 0xff);
            h *= 0x100000001b3ULL;        /* FNV-1a prime */
            w >>= 8;
        }
    }
    return h;
}

/* ------------------------------------------------------------------ */
/*  Cache — open-addressing, linear probing                           */
/* ------------------------------------------------------------------ */

void mvgal_spirv_cache_init(mvgal_spirv_cache_t *cache)
{
    memset(cache, 0, sizeof(*cache));
}

void mvgal_spirv_cache_destroy(mvgal_spirv_cache_t *cache)
{
    for (size_t i = 0; i < MVGAL_SPIRV_CAPACITY; ++i) {
        if (cache->entries[i].occupied) {
            free(cache->entries[i].code);
            cache->entries[i].code = NULL;
            cache->entries[i].occupied = false;
        }
    }
    cache->count = 0;
}

bool mvgal_spirv_cache_insert(mvgal_spirv_cache_t *cache,
                              const uint32_t *code,
                              size_t word_count,
                              VkShaderModule module)
{
    if (cache->count >= MVGAL_SPIRV_CAPACITY)
        return false;

    uint64_t h = mvgal_spirv_hash(code, word_count);
    size_t idx = (size_t)(h & MVGAL_SPIRV_CACHE_MASK);
    bool is_new = false;

    /* linear probe */
    while (cache->entries[idx].occupied) {
        /* if same hash already present — update in-place */
        if (cache->entries[idx].hash == h) {
            free(cache->entries[idx].code);
            cache->entries[idx].code = NULL;
            goto store;
        }
        idx = (idx + 1) & MVGAL_SPIRV_CACHE_MASK;
    }
    is_new = true;

store:
    cache->entries[idx].hash       = h;
    cache->entries[idx].word_count = word_count;
    cache->entries[idx].module     = module;
    cache->entries[idx].occupied   = true;

    cache->entries[idx].code = malloc(word_count * sizeof(uint32_t));
    if (cache->entries[idx].code != NULL)
        memcpy(cache->entries[idx].code, code, word_count * sizeof(uint32_t));

    if (is_new)
        cache->count++;
    return true;
}

VkShaderModule mvgal_spirv_cache_lookup(mvgal_spirv_cache_t *cache,
                                        const uint32_t *code,
                                        size_t word_count)
{
    uint64_t h = mvgal_spirv_hash(code, word_count);
    size_t idx = (size_t)(h & MVGAL_SPIRV_CACHE_MASK);

    for (size_t i = 0; i < MVGAL_SPIRV_CAPACITY; ++i) {
        size_t probe = (idx + i) & MVGAL_SPIRV_CACHE_MASK;
        if (!cache->entries[probe].occupied)
            return VK_NULL_HANDLE;
        if (cache->entries[probe].hash == h &&
            cache->entries[probe].word_count == word_count &&
            memcmp(cache->entries[probe].code, code,
                   word_count * sizeof(uint32_t)) == 0) {
            return cache->entries[probe].module;
        }
    }
    return VK_NULL_HANDLE;
}

/* ------------------------------------------------------------------ */
/*  Capture / route stubs                                              */
/* ------------------------------------------------------------------ */

void mvgal_shader_capture(mvgal_spirv_cache_t *cache,
                          const uint32_t *code,
                          size_t word_count,
                          VkShaderModule module)
{
    mvgal_spirv_cache_insert(cache, code, word_count, module);
}

int mvgal_shader_route(mvgal_vendor_t vendor,
                       const uint32_t *spirv,
                       size_t word_count,
                       uint32_t **vendor_code,
                       size_t *vendor_code_size)
{
#ifdef MVGAL_ENABLE_SPIRV_OPT
    /* ---- Step 1: Optimise SPIR-V with SPIRV-Tools ---- */
    void *opt = mvgal_spirv_opt_create();
    if (!opt) return -1;

    uint32_t *opt_spirv = NULL;
    size_t   opt_words  = 0;
    if (mvgal_spirv_optimize(opt, spirv, word_count,
                             &opt_spirv, &opt_words) != 0) {
        mvgal_spirv_opt_destroy(opt);
        return -1;
    }
    mvgal_spirv_opt_destroy(opt);

    /* ---- Step 2: Map vendor → SPIRV-Cross backend ---- */
    mvgal_spirv_backend_t backend;
    switch (vendor) {
    case MVGAL_VENDOR_AMD:
    case MVGAL_VENDOR_NVIDIA:
    case MVGAL_VENDOR_INTEL:
    case MVGAL_VENDOR_MOORE_THREADS:
    case MVGAL_VENDOR_QUALCOMM:
    case MVGAL_VENDOR_ARM:
    case MVGAL_VENDOR_BROADCOM:
        backend = MVGAL_SPIRV_BACKEND_GLSL;
        break;
    default:
        free(opt_spirv);
        return -1;
    }

    /* ---- Step 3: Decompile to vendor source ---- */
    char *source = mvgal_spirv_decompile(opt_spirv, opt_words, backend);
    free(opt_spirv);
    if (!source) return -1;

    /*
     * Return source string via vendor_code / vendor_code_size.
     * The caller receives a strdup'd C string cast to uint32_t*;
     * it must free() the pointer when done.
     */
    *vendor_code      = (uint32_t *)source;
    *vendor_code_size = strlen(source) + 1;   /* include NUL terminator */
    return 0;

#else
    (void)vendor;
    (void)spirv;
    (void)word_count;
    (void)vendor_code;
    (void)vendor_code_size;
    return -1;
#endif
}
