// basstub.c -- DVX BASIC standalone application stub
//
// Minimal entry point for compiled BASIC applications. Reads the
// MODULE resource (serialized bytecode), FORMn resources (.frm text),
// and optional DEBUG resource from its own DXE file. Creates a VM
// and form runtime from basrt.lib and runs the program.
//
// This file is compiled into basstub.app, which is embedded as a
// resource in dvxbasic.app. "Make Executable" extracts it, renames
// it to the project name, and attaches the compiled resources.

#include "dvxApp.h"
#include "dvxDlg.h"
#include "dvxRes.h"
#include "dvxWgt.h"
#include "shellApp.h"
#include "../basRes.h"
#include "../runtime/vm.h"
#include "../runtime/serialize.h"
#include "../formrt/formrt.h"

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

// ============================================================
// App descriptor
// ============================================================

#define STUB_STACK_SIZE 65536

AppDescriptorT appDescriptor = {
    .name          = "BASIC App",
    .hasMainLoop   = true,
    .multiInstance = false,
    .stackSize     = STUB_STACK_SIZE,
    .priority      = TS_PRIORITY_NORMAL
};


// ============================================================
// Callbacks
// ============================================================

static AppContextT *sAc = NULL;


// Function prototypes (alphabetical; main/appMain last)
static bool stubDoEvents(void *ctx);
static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize);
static void stubPrint(void *ctx, const char *text, bool newline);
int32_t     appMain(DxeAppContextT *ctx);
static bool stubDoEvents(void *ctx) {
    (void)ctx;

    if (!sAc) {
        return false;
    }

    return dvxUpdate(sAc);
}


static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize) {
    (void)ctx;
    buf[0] = '\0';

    if (!sAc) {
        return false;
    }

    return dvxInputBox(sAc, "Input", prompt, "", buf, bufSize);
}


static void stubPrint(void *ctx, const char *text, bool newline) {
    (void)ctx;
    (void)text;
    (void)newline;
}
int32_t appMain(DxeAppContextT *ctx) {
    sAc = ctx->shellCtx;

    // Open our own resources
    DvxResHandleT *res = dvxResOpen(ctx->appPath);

    if (!res) {
        dvxMessageBox(sAc, "Error", "No compiled module found in this application.", 0);
        return 1;
    }

    // Read app name and update the shell's app record
    uint32_t nameSize = 0;
    char *appName = (char *)dvxResRead(res, BAS_RES_NAME, &nameSize);

    if (appName) {
        ShellAppT *app = shellGetApp(ctx->appId);

        if (app) {
            snprintf(app->name, SHELL_APP_NAME_MAX, "%s", appName);
        }

        free(appName);
    }

    // Set help file path if present
    uint32_t helpNameSize = 0;
    char *helpName = (char *)dvxResRead(res, BAS_RES_HELPFILE, &helpNameSize);

    if (helpName) {
        snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s" DVX_PATH_SEP "%s", ctx->appDir, helpName);
        free(helpName);
    }

    // Load MODULE resource
    uint32_t modSize = 0;
    uint8_t *modData = (uint8_t *)dvxResRead(res, BAS_RES_MODULE, &modSize);

    if (!modData) {
        dvxMessageBox(sAc, "Error", "MODULE resource not found.", 0);
        dvxResClose(res);
        return 1;
    }

    BasModuleT *mod = basModuleDeserialize(modData, (int32_t)modSize);
    free(modData);

    if (!mod) {
        dvxMessageBox(sAc, "Error", "Failed to deserialize module.", 0);
        dvxResClose(res);
        return 1;
    }

    // Load optional DEBUG resource
    uint32_t dbgSize = 0;
    uint8_t *dbgData = (uint8_t *)dvxResRead(res, BAS_RES_DEBUG, &dbgSize);

    if (dbgData) {
        basDebugDeserialize(mod, dbgData, (int32_t)dbgSize);
        free(dbgData);
    }

    // Create VM
    BasVmT *vm = basVmCreate();
    basVmLoadModule(vm, mod);
    basVmSetPrintCallback(vm, stubPrint, NULL);
    basVmSetInputCallback(vm, stubInput, NULL);
    basVmSetDoEventsCallback(vm, stubDoEvents, NULL);

    // Set app paths
    snprintf(vm->appPath, DVX_MAX_PATH, "%s", ctx->appDir);
    snprintf(vm->appConfig, DVX_MAX_PATH, "%s", ctx->appDir);
    snprintf(vm->appData, DVX_MAX_PATH, "%s", ctx->appDir);

    // Set extern call callbacks (required for DECLARE LIBRARY functions)
    BasExternCallbacksT extCb;
    memset(&extCb, 0, sizeof(extCb));
    extCb.resolveExtern = basExternResolve;
    extCb.callExtern    = basExternCall;
    basVmSetExternCallbacks(vm, &extCb);

    // Create form runtime
    BasFormRtT *rt = basFormRtCreate(sAc, vm, mod);

    // Register .frm source text for lazy loading
    for (int32_t i = 0; i < 256; i++) {
        char resName[16];
        snprintf(resName, sizeof(resName), "FORM%ld", (long)i);

        uint32_t frmSize = 0;
        char *frmText = (char *)dvxResRead(res, resName, &frmSize);

        if (!frmText) {
            break;
        }

        // Extract form name from "Begin Form <name>" line
        char frmName[BAS_MAX_CTRL_NAME] = "";
        const char *p = frmText;

        while (*p) {
            p = dvxSkipWs(p);

            if (strncasecmp(p, "Begin Form ", 11) == 0) {
                p = dvxSkipWs(p + 11);

                int32_t ni = 0;

                while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && ni < BAS_MAX_CTRL_NAME - 1) {
                    frmName[ni++] = *p++;
                }

                frmName[ni] = '\0';
                break;
            }

            while (*p && *p != '\n') { p++; }

            if (*p == '\n') { p++; }
        }

        if (frmName[0]) {
            basFormRtRegisterFrm(rt, frmName, frmText, (int32_t)frmSize);
        }

        free(frmText);
    }

    dvxResClose(res);

    // Load all cached forms and show the startup form
    basFormRtLoadAllForms(rt, NULL);

    // Run bytecode + VB-style event loop
    basFormRtRunSimple(rt);

    // Cleanup
    basFormRtDestroy(rt);
    basVmDestroy(vm);
    basModuleFree(mod);

    return 0;
}
