// serialize.c -- DVX BASIC module serialization
//
// Binary format (all little-endian, no alignment padding):
//
//   Header:
//     magic        4 bytes  "DBXM"
//     version      uint16   1
//     entryPoint   int32
//     globalCount  int32
//     codeLen      int32
//     constCount   int32
//     dataCount    int32
//     procCount    int32
//
//   code[]         codeLen bytes of raw bytecode
//
//   constants[]    constCount entries, each:
//                    len uint16 + len bytes of string data
//
//   dataPool[]     dataCount entries, each:
//                    type uint8 + value (type-dependent)
//
//   procs[]        procCount entries, each:
//                    codeAddr     int32
//                    paramCount   int32
//                    localCount   int32
//                    returnType   uint8
//                    isFunction   uint8
//                    nameLen      uint16 + nameLen bytes

#include "serialize.h"
#include "../compiler/opcodes.h"

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

// Module file header: 4-byte magic + uint16 version.  A matched pair
// -- read by basModuleDeserialize, written by basModuleSerialize --
// so both sides pull from the same definition.
#define BAS_MOD_MAGIC        "DBXM"
#define BAS_MOD_MAGIC_LEN    4
#define BAS_MOD_VERSION      1

// ============================================================
// Write helpers
// ============================================================

typedef struct {
    uint8_t *buf;
    int32_t  len;
    int32_t  cap;
} BufT;

typedef struct {
    const uint8_t *data;
    int32_t        len;
    int32_t        pos;
} ReaderT;


// Function prototypes (alphabetical)
void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen);
uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen);
BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen);
void basModuleFree(BasModuleT *mod);
uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen);
static void bufGrow(BufT *b, int32_t need);
static void bufInit(BufT *b);
static void bufWrite(BufT *b, const void *data, int32_t len);
static void bufWriteI32(BufT *b, int32_t v);
static void bufWriteStr(BufT *b, const char *s);
static void bufWriteU16(BufT *b, uint16_t v);
static void bufWriteU8(BufT *b, uint8_t v);
static double rF64(ReaderT *r);
static int32_t rI32(ReaderT *r);
static bool rOk(const ReaderT *r, int32_t need);
static char *rStr(ReaderT *r);
static uint16_t rU16(ReaderT *r);
static uint8_t rU8(ReaderT *r);

void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen) {
    if (!mod || !data || dataLen < 4) {
        return;
    }

    ReaderT r = { data, dataLen, 0 };

    // Debug variables
    mod->debugVarCount = rI32(&r);

    if (mod->debugVarCount > 0) {
        mod->debugVars = (BasDebugVarT *)calloc(mod->debugVarCount, sizeof(BasDebugVarT));

        for (int32_t i = 0; i < mod->debugVarCount; i++) {
            BasDebugVarT *v = &mod->debugVars[i];
            char *name     = rStr(&r);
            char *formName = rStr(&r);
            snprintf(v->name, BAS_MAX_PROC_NAME, "%s", name);
            snprintf(v->formName, BAS_MAX_PROC_NAME, "%s", formName);
            free(name);
            free(formName);
            v->scope     = rU8(&r);
            v->dataType  = rU8(&r);
            v->index     = rI32(&r);
            v->procIndex = rI32(&r);
        }
    }

}


uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen) {
    if (!mod || !outLen) {
        return NULL;
    }

    BufT b;
    bufInit(&b);

    // Debug variables
    bufWriteI32(&b, mod->debugVarCount);

    for (int32_t i = 0; i < mod->debugVarCount; i++) {
        const BasDebugVarT *v = &mod->debugVars[i];
        bufWriteStr(&b, v->name);
        bufWriteStr(&b, v->formName);
        bufWriteU8(&b, v->scope);
        bufWriteU8(&b, v->dataType);
        bufWriteI32(&b, v->index);
        bufWriteI32(&b, v->procIndex);
    }

    *outLen = b.len;
    return b.buf;
}


BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen) {
    if (!data || dataLen < 30) {
        return NULL;
    }

    ReaderT r = { data, dataLen, 0 };

    // Check magic
    if (memcmp(data, BAS_MOD_MAGIC, BAS_MOD_MAGIC_LEN) != 0) {
        return NULL;
    }

    r.pos = BAS_MOD_MAGIC_LEN;

    uint16_t version = rU16(&r);

    if (version != BAS_MOD_VERSION) {
        return NULL;
    }

    BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT));

    mod->entryPoint  = rI32(&r);
    mod->globalCount = rI32(&r);
    mod->codeLen     = rI32(&r);
    mod->constCount  = rI32(&r);
    mod->dataCount   = rI32(&r);
    mod->procCount   = rI32(&r);

    // Code
    if (!rOk(&r, mod->codeLen)) {
        free(mod);
        return NULL;
    }

    mod->code = (uint8_t *)malloc(mod->codeLen);
    memcpy(mod->code, r.data + r.pos, mod->codeLen);
    r.pos += mod->codeLen;

    // String constant pool
    mod->constants = (BasStringT **)calloc(mod->constCount, sizeof(BasStringT *));

    for (int32_t i = 0; i < mod->constCount; i++) {
        char *s = rStr(&r);
        mod->constants[i] = basStringNew(s, (int32_t)strlen(s));
        free(s);
    }

    // Data pool
    if (mod->dataCount > 0) {
        mod->dataPool = (BasValueT *)calloc(mod->dataCount, sizeof(BasValueT));

        for (int32_t i = 0; i < mod->dataCount; i++) {
            uint8_t type = rU8(&r);
            mod->dataPool[i].type = type;

            switch (type) {
                case BAS_TYPE_INTEGER:
                    mod->dataPool[i].intVal = (int16_t)rI32(&r);
                    break;

                case BAS_TYPE_LONG:
                    mod->dataPool[i].longVal = rI32(&r);
                    break;

                case BAS_TYPE_SINGLE: {
                    float f;

                    if (rOk(&r, 4)) {
                        memcpy(&f, r.data + r.pos, 4);
                        r.pos += 4;
                    } else {
                        f = 0.0f;
                    }

                    mod->dataPool[i].dblVal = (double)f;
                    break;
                }

                case BAS_TYPE_DOUBLE:
                    mod->dataPool[i].dblVal = rF64(&r);
                    break;

                case BAS_TYPE_STRING: {
                    char *s = rStr(&r);
                    mod->dataPool[i].strVal = basStringNew(s, (int32_t)strlen(s));
                    free(s);
                    break;
                }

                case BAS_TYPE_BOOLEAN:
                    mod->dataPool[i].intVal = rU8(&r) ? -1 : 0;
                    break;

                default:
                    rI32(&r);
                    break;
            }
        }
    }

    // Procedure table
    if (mod->procCount > 0) {
        mod->procs = (BasProcEntryT *)calloc(mod->procCount, sizeof(BasProcEntryT));

        for (int32_t i = 0; i < mod->procCount; i++) {
            BasProcEntryT *p = &mod->procs[i];
            p->codeAddr   = rI32(&r);
            p->paramCount = rI32(&r);
            p->localCount = rI32(&r);
            p->returnType = rU8(&r);
            p->isFunction = rU8(&r) != 0;

            char *name = rStr(&r);
            snprintf(p->name, BAS_MAX_PROC_NAME, "%s", name);
            free(name);
        }
    }

    // Form variable info
    mod->formVarInfoCount = rI32(&r);

    if (mod->formVarInfoCount > 0) {
        mod->formVarInfo = (BasFormVarInfoT *)calloc(mod->formVarInfoCount, sizeof(BasFormVarInfoT));

        for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
            BasFormVarInfoT *fv = &mod->formVarInfo[i];
            char *fvName = rStr(&r);
            snprintf(fv->formName, BAS_MAX_PROC_NAME, "%s", fvName);
            free(fvName);
            fv->varCount     = rI32(&r);
            fv->initCodeAddr = rI32(&r);
            fv->initCodeLen  = rI32(&r);
        }
    }

    return mod;
}


void basModuleFree(BasModuleT *mod) {
    if (!mod) {
        return;
    }

    free(mod->code);

    if (mod->constants) {
        for (int32_t i = 0; i < mod->constCount; i++) {
            basStringUnref(mod->constants[i]);
        }

        free(mod->constants);
    }

    if (mod->dataPool) {
        for (int32_t i = 0; i < mod->dataCount; i++) {
            basValRelease(&mod->dataPool[i]);
        }

        free(mod->dataPool);
    }

    free(mod->procs);
    free(mod->formVarInfo);
    free(mod->debugVars);

    if (mod->debugUdtDefs) {
        for (int32_t i = 0; i < mod->debugUdtDefCount; i++) {
            free(mod->debugUdtDefs[i].fields);
        }

        free(mod->debugUdtDefs);
    }

    free(mod);
}


uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen) {
    if (!mod || !outLen) {
        return NULL;
    }

    BufT b;
    bufInit(&b);

    // Header
    bufWrite(&b, BAS_MOD_MAGIC, BAS_MOD_MAGIC_LEN);
    bufWriteU16(&b, BAS_MOD_VERSION);
    bufWriteI32(&b, mod->entryPoint);
    bufWriteI32(&b, mod->globalCount);
    bufWriteI32(&b, mod->codeLen);
    bufWriteI32(&b, mod->constCount);
    bufWriteI32(&b, mod->dataCount);
    bufWriteI32(&b, mod->procCount);

    // Code
    bufWrite(&b, mod->code, mod->codeLen);

    // String constant pool
    for (int32_t i = 0; i < mod->constCount; i++) {
        const char *s = mod->constants[i] ? mod->constants[i]->data : "";
        bufWriteStr(&b, s);
    }

    // Data pool
    for (int32_t i = 0; i < mod->dataCount; i++) {
        const BasValueT *v = &mod->dataPool[i];
        bufWriteU8(&b, v->type);

        switch (v->type) {
            case BAS_TYPE_INTEGER:
                bufWriteI32(&b, v->intVal);
                break;

            case BAS_TYPE_LONG:
                bufWriteI32(&b, v->longVal);
                break;

            case BAS_TYPE_SINGLE: {
                float f = (float)v->dblVal;
                bufWrite(&b, &f, 4);
                break;
            }

            case BAS_TYPE_DOUBLE:
                bufWrite(&b, &v->dblVal, 8);
                break;

            case BAS_TYPE_STRING:
                bufWriteStr(&b, v->strVal ? v->strVal->data : "");
                break;

            case BAS_TYPE_BOOLEAN:
                bufWriteU8(&b, v->intVal ? 1 : 0);
                break;

            default:
                bufWriteI32(&b, 0);
                break;
        }
    }

    // Procedure table
    for (int32_t i = 0; i < mod->procCount; i++) {
        const BasProcEntryT *p = &mod->procs[i];
        bufWriteI32(&b, p->codeAddr);
        bufWriteI32(&b, p->paramCount);
        bufWriteI32(&b, p->localCount);
        bufWriteU8(&b, p->returnType);
        bufWriteU8(&b, p->isFunction ? 1 : 0);
        bufWriteStr(&b, p->name);
    }

    // Form variable info (runtime-essential for per-form variable allocation)
    bufWriteI32(&b, mod->formVarInfoCount);

    for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
        const BasFormVarInfoT *fv = &mod->formVarInfo[i];
        bufWriteStr(&b, fv->formName);
        bufWriteI32(&b, fv->varCount);
        bufWriteI32(&b, fv->initCodeAddr);
        bufWriteI32(&b, fv->initCodeLen);
    }

    *outLen = b.len;
    return b.buf;
}


static void bufGrow(BufT *b, int32_t need) {
    while (b->len + need > b->cap) {
        b->cap *= 2;
    }

    b->buf = (uint8_t *)realloc(b->buf, b->cap);
}


static void bufInit(BufT *b) {
    b->cap = 4096;
    b->buf = (uint8_t *)malloc(b->cap);
    b->len = 0;
}


static void bufWrite(BufT *b, const void *data, int32_t len) {
    bufGrow(b, len);
    memcpy(b->buf + b->len, data, len);
    b->len += len;
}


static void bufWriteI32(BufT *b, int32_t v) {
    bufWrite(b, &v, 4);
}


static void bufWriteStr(BufT *b, const char *s) {
    uint16_t len = s ? (uint16_t)strlen(s) : 0;
    bufWriteU16(b, len);

    if (len > 0) {
        bufWrite(b, s, len);
    }
}


static void bufWriteU16(BufT *b, uint16_t v) {
    bufWrite(b, &v, 2);
}


static void bufWriteU8(BufT *b, uint8_t v) {
    bufWrite(b, &v, 1);
}


static double rF64(ReaderT *r) {
    if (!rOk(r, 8)) {
        return 0.0;
    }

    double v;
    memcpy(&v, r->data + r->pos, 8);
    r->pos += 8;
    return v;
}


static int32_t rI32(ReaderT *r) {
    if (!rOk(r, 4)) {
        return 0;
    }

    int32_t v;
    memcpy(&v, r->data + r->pos, 4);
    r->pos += 4;
    return v;
}


static bool rOk(const ReaderT *r, int32_t need) {
    return r->pos + need <= r->len;
}


// Read a length-prefixed string into a malloc'd buffer.
static char *rStr(ReaderT *r) {
    uint16_t len = rU16(r);

    if (len == 0 || !rOk(r, len)) {
        char *s = (char *)malloc(1);
        s[0] = '\0';
        return s;
    }

    char *s = (char *)malloc(len + 1);
    memcpy(s, r->data + r->pos, len);
    s[len] = '\0';
    r->pos += len;
    return s;
}


static uint16_t rU16(ReaderT *r) {
    if (!rOk(r, 2)) {
        return 0;
    }

    uint16_t v;
    memcpy(&v, r->data + r->pos, 2);
    r->pos += 2;
    return v;
}


static uint8_t rU8(ReaderT *r) {
    if (!rOk(r, 1)) {
        return 0;
    }

    return r->data[r->pos++];
}
