// loaderMain.c -- DVX bootstrap loader entry point
//
// Loads all DXE modules from two directories:
//   libs/     *.lib  -- core libraries (libtasks, libdvx, dvxshell)
//   widgets/  *.wgt  -- widget type plugins (box, button, listview, etc.)
//
// Each module may have a .dep file (same base name, .dep extension)
// listing base names of modules that must be loaded before it.
// The loader resolves the dependency graph and loads in topological
// order.  After loading, any module that exports wgtRegister() has
// it called.  Finally, the loader finds and calls shellMain().

#include "dvxPlatform.h"

#include <ctype.h>
#include <dirent.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>

// Route stb_ds allocations through the tracking wrappers so that
// arrput/arrfree in DXE code is tracked per-app.
extern void *dvxRealloc(void *ptr, size_t size);
extern void  dvxFree(void *ptr);
#define STBDS_REALLOC(c, p, s) dvxRealloc((p), (s))
#define STBDS_FREE(c, p)       dvxFree(p)
#define STB_DS_IMPLEMENTATION
#include "stb_ds_wrap.h"

// ============================================================
// Constants
// ============================================================

#define LIBS_DIR    "LIBS"
#define WIDGET_DIR  "WIDGETS"
#define LOG_PATH    "dvx.log"

// ============================================================
// Splash screen (delegates to platformSplash* in dvxPlatformDos.c)
// ============================================================

// Palette indices for progress bar (indices into SPLASH.RAW palette)
#define SPLASH_BAR_BG   50   // dark gray (RGB 68,68,68)
#define SPLASH_BAR_FG   135  // light gray (RGB 168,168,168)
#define SPLASH_BAR_OUT  45   // darker gray (RGB 60,60,60)

static int32_t sSplashActive    = 0;
static int32_t sSplashTotal     = 0;
static int32_t sSplashLoaded    = 0;

// Progress bar geometry
#define PBAR_X      10
#define PBAR_Y      188
#define PBAR_W      300
#define PBAR_H      6


static void splashDrawScreen(void) {
    if (platformSplashLoadRaw("CONFIG/SPLASH.RAW")) {
        platformSplashFillRect(PBAR_X - 1, PBAR_Y - 1, PBAR_W + 2, PBAR_H + 2, SPLASH_BAR_OUT);
        platformSplashFillRect(PBAR_X, PBAR_Y, PBAR_W, PBAR_H, SPLASH_BAR_BG);
    }
}


static void splashUpdateProgress(void) {
    if (!sSplashActive || sSplashTotal <= 0) {
        return;
    }

    int32_t fillW = (PBAR_W * sSplashLoaded) / sSplashTotal;

    if (fillW > PBAR_W) {
        fillW = PBAR_W;
    }

    platformSplashFillRect(PBAR_X, PBAR_Y, fillW, PBAR_H, SPLASH_BAR_FG);
}

// ============================================================
// dvxLog -- append a line to dvx.log
// ============================================================
//
// Global logging function exported to all DXE modules.
// Opens/closes the file per-write so it's never held open.

void dvxLog(const char *fmt, ...) {
    FILE *f = fopen(LOG_PATH, "a");

    if (!f) {
        return;
    }

    va_list ap;
    va_start(ap, fmt);
    vfprintf(f, fmt, ap);
    va_end(ap);
    fprintf(f, "\n");
    fclose(f);
}

// ============================================================
// Module entry for dependency resolution
// ============================================================

typedef struct {
    char   path[DVX_MAX_PATH];
    char   baseName[16];
    char **deps;
    bool   loaded;
    void  *handle;
} ModuleT;

// ============================================================
// Prototypes
// ============================================================

static bool     allDepsLoaded(const ModuleT *mod, const ModuleT *mods);
static void     extractBaseName(const char *path, const char *ext, char *out, int32_t outSize);
static void    *findSymbol(void **handles, const char *symbol);
static void     freeMods(ModuleT *mods);
static void   **loadAllModules(void);
static void     loadInOrder(ModuleT *mods);
static void     logAndReadDeps(ModuleT *mods);
static void     readDeps(ModuleT *mod);
static void     scanDir(const char *dirPath, const char *ext, ModuleT **mods);

// ============================================================
// extractBaseName -- strip directory and extension, lowercase
// ============================================================

static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize) {
    // Find last directory separator
    const char *start = path;
    const char *p     = path;

    while (*p) {
        if (*p == '/' || *p == '\\') {
            start = p + 1;
        }

        p++;
    }

    // Copy up to the extension
    int32_t extLen = strlen(ext);
    int32_t len    = strlen(start);

    if (len > extLen && strcasecmp(start + len - extLen, ext) == 0) {
        len -= extLen;
    }

    if (len >= outSize) {
        len = outSize - 1;
    }

    for (int32_t i = 0; i < len; i++) {
        out[i] = tolower((unsigned char)start[i]);
    }

    out[len] = '\0';
}


// ============================================================
// readDeps -- parse .dep file for a module
// ============================================================
//
// The .dep file has the same path as the module but with a .dep
// extension.  Each line is a dependency base name.  Empty lines
// and lines starting with # are ignored.

static void readDeps(ModuleT *mod) {
    // Build dep file path: replace extension with .dep
    char depPath[DVX_MAX_PATH];
    strncpy(depPath, mod->path, sizeof(depPath) - 1);
    depPath[sizeof(depPath) - 1] = '\0';

    char *dot = strrchr(depPath, '.');

    if (!dot) {
        return;
    }

    strcpy(dot, ".dep");

    FILE *f = fopen(depPath, "r");

    if (!f) {
        return;
    }

    char line[64];

    while (fgets(line, sizeof(line), f)) {
        // Strip \r and \n
        int32_t len = strlen(line);

        while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
            line[--len] = '\0';
        }

        // Skip empty lines and comments
        if (len == 0 || line[0] == '#') {
            continue;
        }

        // Lowercase the dep name for case-insensitive matching
        for (int32_t i = 0; i < len; i++) {
            line[i] = tolower((unsigned char)line[i]);
        }

        arrput(mod->deps, strdup(line));
    }

    fclose(f);
}


// ============================================================
// scanDir -- recursively find modules with a given extension
// ============================================================

static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) {
    DIR *dir = opendir(dirPath);

    if (!dir) {
        return;
    }

    struct dirent *ent;

    while ((ent = readdir(dir)) != NULL) {
        const char *name = ent->d_name;

        // Skip . and ..
        if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) {
            continue;
        }

        char path[DVX_MAX_PATH];
        snprintf(path, sizeof(path), "%s/%s", dirPath, name);

        // Check for matching extension
        int32_t nameLen = strlen(name);
        int32_t extLen  = strlen(ext);

        if (nameLen > extLen && strcasecmp(name + nameLen - extLen, ext) == 0) {
            ModuleT mod;
            memset(&mod, 0, sizeof(mod));
            snprintf(mod.path, sizeof(mod.path), "%s", path);
            extractBaseName(path, ext, mod.baseName, sizeof(mod.baseName));
            arrput(*mods, mod);
            continue;
        }

        // Recurse into subdirectories
        struct stat st;

        if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
            scanDir(path, ext, mods);
        }
    }

    closedir(dir);
}


// ============================================================
// allDepsLoaded -- check if a module's dependencies are satisfied
// ============================================================
//
// A dep is satisfied if either:
//   1. No module with that base name exists (external, assumed OK)
//   2. The module with that base name is already loaded

static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods) {
    for (int32_t d = 0; d < arrlen(mod->deps); d++) {
        for (int32_t j = 0; j < arrlen(mods); j++) {
            if (strcasecmp(mods[j].baseName, mod->deps[d]) == 0) {
                if (!mods[j].loaded) {
                    return false;
                }

                break;
            }
        }
    }

    return true;
}


// ============================================================
// loadInOrder -- topological sort and load
// ============================================================
//
// Repeatedly scans the module list for entries whose dependencies
// are all satisfied, loads them, and marks them done.  Stops when
// all modules are loaded or no progress can be made (circular dep).

static void loadInOrder(ModuleT *mods) {
    typedef void (*RegFnT)(void);
    int32_t total  = arrlen(mods);
    int32_t loaded = 0;
    bool    progress;

    do {
        progress = false;

        for (int32_t i = 0; i < total; i++) {
            if (mods[i].loaded) {
                continue;
            }

            if (!allDepsLoaded(&mods[i], mods)) {
                continue;
            }

            dvxLog("Loading: %s", mods[i].path);
            mods[i].handle = dlopen(mods[i].path, RTLD_GLOBAL);

            if (!mods[i].handle) {
                const char *err = dlerror();
                dvxLog("  FAILED: %s", err ? err : "(unknown)");
                platformSplashShutdown();
                sSplashActive = 0;
                fprintf(stderr, "FATAL: Failed to load %s\n  %s\n", mods[i].path, err ? err : "(unknown error)");
                exit(1);
            }

            RegFnT regFn = (RegFnT)dlsym(mods[i].handle, "_wgtRegister");

            if (regFn) {
                regFn();

                // Record the .wgt path for any newly registered ifaces
                typedef int32_t (*IfaceCountFnT)(void);
                typedef const void *(*IfaceAtFnT)(int32_t, const char **);
                typedef const char *(*IfaceGetPathFnT)(const char *);
                typedef void (*IfaceSetPathFnT)(const char *, const char *);

                IfaceCountFnT   countFn   = (IfaceCountFnT)dlsym(NULL, "_wgtIfaceCount");
                IfaceAtFnT      atFn      = (IfaceAtFnT)dlsym(NULL, "_wgtIfaceAt");
                IfaceGetPathFnT getPathFn = (IfaceGetPathFnT)dlsym(NULL, "_wgtIfaceGetPath");
                IfaceSetPathFnT setPathFn = (IfaceSetPathFnT)dlsym(NULL, "_wgtIfaceSetPath");

                if (countFn && atFn && getPathFn && setPathFn) {
                    int32_t ic = countFn();

                    for (int32_t k = 0; k < ic; k++) {
                        const char *ifaceName = NULL;
                        atFn(k, &ifaceName);

                        if (ifaceName && !getPathFn(ifaceName)) {
                            setPathFn(ifaceName, mods[i].path);
                        }
                    }
                }
            } else if (strstr(mods[i].path, ".wgt") || strstr(mods[i].path, ".WGT")) {
                dvxLog("  No _wgtRegister in %s", mods[i].baseName);
            }

            mods[i].loaded = true;
            loaded++;
            progress = true;
            sSplashLoaded++;
            splashUpdateProgress();
        }
    } while (progress && loaded < total);

    if (loaded < total) {
        fprintf(stderr, "Module loader: %d of %d modules could not be loaded (circular deps or missing deps)\n", (int)(total - loaded), (int)total);

        for (int32_t i = 0; i < total; i++) {
            if (!mods[i].loaded) {
                fprintf(stderr, "  %s\n", mods[i].path);
            }
        }
    }
}


// ============================================================
// loadAllModules -- scan libs/ and widgets/, load in dep order
// ============================================================
//
// Returns a stb_ds dynamic array of dlopen handles.

static void logAndReadDeps(ModuleT *mods) {
    dvxLog("Discovered %d modules:", arrlen(mods));

    for (int32_t i = 0; i < arrlen(mods); i++) {
        dvxLog("  [%d] %s (base: %s)", i, mods[i].path, mods[i].baseName);
    }

    for (int32_t i = 0; i < arrlen(mods); i++) {
        readDeps(&mods[i]);

        if (arrlen(mods[i].deps) > 0) {
            char line[512];
            int32_t pos = snprintf(line, sizeof(line), "  %s deps:", mods[i].baseName);

            for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
                pos += snprintf(line + pos, sizeof(line) - pos, " %s", mods[i].deps[d]);
            }

            dvxLog("%s", line);
        }
    }
}


static void freeMods(ModuleT *mods) {
    for (int32_t i = 0; i < arrlen(mods); i++) {
        for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
            free(mods[i].deps[d]);
        }

        arrfree(mods[i].deps);
    }

    arrfree(mods);
}


static void **loadAllModules(void) {
    void **handles = NULL;

    // Scan both directories first to get total module count for progress bar
    ModuleT *libs = NULL;
    scanDir(LIBS_DIR, ".lib", &libs);
    logAndReadDeps(libs);

    ModuleT *widgets = NULL;
    scanDir(WIDGET_DIR, ".wgt", &widgets);
    logAndReadDeps(widgets);

    sSplashTotal = (int32_t)arrlen(libs) + (int32_t)arrlen(widgets);

    // Phase 1: load libraries in dependency order
    loadInOrder(libs);

    for (int32_t i = 0; i < arrlen(libs); i++) {
        if (libs[i].handle) {
            arrput(handles, libs[i].handle);
        }
    }

    freeMods(libs);

    // Phase 2: load widgets in dependency order (all libs already loaded)
    loadInOrder(widgets);

    for (int32_t i = 0; i < arrlen(widgets); i++) {
        if (widgets[i].handle) {
            arrput(handles, widgets[i].handle);
        }
    }

    freeMods(widgets);

    return handles;
}


// ============================================================
// findSymbol -- search loaded handles for a symbol
// ============================================================

static void *findSymbol(void **handles, const char *symbol) {
    for (int32_t i = 0; i < arrlen(handles); i++) {
        void *sym = dlsym(handles[i], symbol);

        if (sym) {
            return sym;
        }
    }

    return NULL;
}


// ============================================================
// main
// ============================================================

int main(int argc, char *argv[]) {
    // Change to the directory containing the executable so relative
    // paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly.
    char exeDir[DVX_MAX_PATH];
    strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
    exeDir[sizeof(exeDir) - 1] = '\0';

    char *sep = platformPathDirEnd(exeDir);

    if (sep) {
        *sep = '\0';
        platformChdir(exeDir);
    }

    // Truncate log, then use append-per-write
    FILE *logInit = fopen(LOG_PATH, "w");

    if (logInit) {
        fclose(logInit);
    }

    // Suppress Ctrl+C before anything else
    platformInit();

    // Switch to VGA mode 13h and show graphical splash
    platformSplashInit();
    sSplashActive = 1;
    splashDrawScreen();

    dvxLog("DVX Loader starting...");

    // Register platform + libc/libm/runtime symbols for DXE resolution
    platformRegisterDxeExports();
    dvxLog("Platform exports registered.");

    // Load all modules from libs/ and widgets/ in dependency order.
    // Each module may have a .dep file specifying load-before deps.
    // Widget modules that export wgtRegister() get it called.
    void **handles = loadAllModules();

    if (!handles || arrlen(handles) == 0) {
        fprintf(stderr, "No modules loaded from %s/ or %s/\n", LIBS_DIR, WIDGET_DIR);
        arrfree(handles);
        return 1;
    }

    // Find and call shellMain from whichever module exports it
    typedef int (*ShellMainFnT)(int, char **);
    ShellMainFnT shellMain = (ShellMainFnT)findSymbol(handles, "_shellMain");

    if (!shellMain) {
        dvxLog("ERROR: No module exports shellMain");

        for (int32_t i = arrlen(handles) - 1; i >= 0; i--) {
            dlclose(handles[i]);
        }

        arrfree(handles);
        return 1;
    }

    int result = shellMain(argc, argv);

    // Clean up in reverse load order
    for (int32_t i = arrlen(handles) - 1; i >= 0; i--) {
        dlclose(handles[i]);
    }

    arrfree(handles);
    return result;
}
