// SPDX-License-Identifier: MIT
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <unistd.h>

#include "debug.h"
#include "box64context.h"
#include "fileutils.h"

#ifndef MAX_PATH
#define MAX_PATH 4096
#endif

static void create_lib_symlink(const char* lib)
{
    char file[MAX_PATH] = {0};
    char folder[MAX_PATH] = {0};
    char tmp[MAX_PATH] = {0};
    char* p = strrchr(lib, '/');
    if(!p) return;  // no folder?
    strcpy(file, p+1);
    p = strrchr(file, '.');
    if(!p) return; //no '.' in name?
    *p = '\0';
    strcpy(folder, lib);
    *(strrchr(folder, '/')) = '\0';
    snprintf(tmp, sizeof(tmp), "%s/%s", folder, file);
    if(FileExist(tmp, IS_FILE)) return; // already there
    strcpy(file, strrchr(lib, '/')+1);
    printf_log(LOG_DEBUG, "Creating symlinks %s -> %s\n", tmp, file);
    int ret = symlink(file, tmp);
    (void)ret;
}

static void create_libs_symlink(const char* folder)
{
    glob_t g = {0};
    char tmp[MAX_PATH] = {0};
    // start with lib*.so.X.X.XXX
    snprintf(tmp, sizeof(tmp), "%s/lib*.so.*.*.*", folder);
    if(!glob(tmp, 0, NULL, &g)) {
        printf_log(LOG_DEBUG, "Creating symlinks for %s\n", folder);
        for(ulong_t i=0; i<g.gl_pathc; ++i) {
            if(FileIsX64X86ELF(g.gl_pathv[i])) {
                create_lib_symlink(g.gl_pathv[i]);
            }
        }
        globfree(&g);
    }
    // then do lib*.so.X.X
    snprintf(tmp, sizeof(tmp), "%s/lib*.so.*.*", folder);
    if(!glob(tmp, 0, NULL, &g)) {
        for(ulong_t i=0; i<g.gl_pathc; ++i) {
            if(FileIsX64X86ELF(g.gl_pathv[i])) {
                create_lib_symlink(g.gl_pathv[i]);
            }
        }
        globfree(&g);
    }
}

void pressure_vessel(int argc, const char** argv, int nextarg, const char* prog)
{
    // skip all the parameter, but parse some of them
    const char* runtime = getenv("PRESSURE_VESSEL_RUNTIME");
    // Example:
    //      pressure-vessel-wrap --env-if-host=... --variable-dir=... --share-pid --launcher \
    //                           --pass-fd <fd> --filesystem <dir>... -- --info-fd <fd> \
    //                           --bus-name=com.github.Matoking.protontricks.App<APPID>_<SESSION>
    // search for the "--" separator first, and handle --launcher and --pass-fd parameters
    int sep = nextarg;
    int launcher = 0;
    int pass_fd = -1;
    while (sep < argc && strcmp(argv[sep], "--")) {
        if (!strcmp(argv[sep], "--launcher")) {
            launcher = 1; // launcher mode: the parameters after "--" are for steam-runtime-launcher-service
        } else if (!strcmp(argv[sep], "--pass-fd") && sep + 1 < argc) {
            pass_fd = atoi(argv[sep + 1]);
            if (pass_fd > 2) {
                // pass_fd must be passed through into the container (launcher-service)!
                // so clear the close-on-exec flag
                fcntl(pass_fd, F_SETFD, fcntl(pass_fd, F_GETFD) & ~FD_CLOEXEC);
            }
        }
        ++sep;
    }
    // look for the comand
    const char* cmd = NULL;
    if (sep < argc) {
        if (sep + 1 < argc)
            cmd = argv[sep + 1];
    } else {
        int i = 0;
        cmd = argv[nextarg];
        while (cmd && cmd[0] == '-' && cmd[1] == '-')
            cmd = argv[nextarg + (++i)];
    }
    int is_usr = (cmd && strlen(cmd)>5 && strstr(cmd, "/usr/")==cmd)?1:0;
    if (nextarg < argc && argv[nextarg][0] == '-' && argv[nextarg][1] == '-')
        while (nextarg < sep && argv[nextarg][0] == '-' && argv[nextarg][1] == '-') {
            if (strstr(argv[nextarg], "--env-if-host=") == argv[nextarg]) {
                if (launcher) {
                    // in launcher mode, all the env-if-host variables are given to the launcher-service
                    char tmp[strlen(argv[nextarg]) + 1];
                    strcpy(tmp, argv[nextarg] + strlen("--env-if-host="));
                    char* p = strchr(tmp, '=');
                    if (p) {
                        *p = '\0';
                        ++p;
                        setenv(tmp, p, 1);
                        printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", tmp, p);
                    }
                } else if (strstr(argv[nextarg], "--env-if-host=PRESSURE_VESSEL_APP_LD_LIBRARY_PATH=") == argv[nextarg]) {
                    if (is_usr) {
                        // transform RESSURE_VESSEL_APP_LD_LIBRARY_PATH to BOX86_ / BOX64_ LD_LIBRARY_PATH
                        char tmp[strlen(argv[nextarg]) + (runtime ? (strlen(runtime) + 1) : 0)];
                        strcpy(tmp, "");
                        strcat(tmp, argv[nextarg] + strlen("--env-if-host=PRESSURE_VESSEL_APP_"));
                        char* p = strchr(tmp, '=');
                        *p = '\0';
                        ++p;
                        setenv(tmp, p, 1);
                        printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", tmp, p);
                    }
                } else if (strstr(argv[nextarg], "--env-if-host=STEAM_RUNTIME_LIBRARY_PATH=") == argv[nextarg]) {
                    if (is_usr) {
                        // transform RESSURE_VESSEL_APP_LD_LIBRARY_PATH to BOX86_ / BOX64_ LD_LIBRARY_PATH
                        char tmp[strlen(argv[nextarg]) + 150];
                        strcpy(tmp, "BOX86_LD_LIBRARY_PATH=/lib/box86:/usr/lib/box86:/lib/i386-linux-gnu:/usr/lib/i386-linux-gnu:/usr/lib/box86-i386-linux-gnu:/usr/lib/box64-i386-linux-gnu:/usr/lib/i686-pc-linux-gnu:/usr/lib32:");
                        strcat(tmp, argv[nextarg] + strlen("--env-if-host=STEAM_RUNTIME_LIBRARY_PATH="));
                        char* p = strchr(tmp, '=');
                        *p = '\0';
                        ++p;
                        setenv(tmp, p, 1);
                        printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", tmp, p);
                        strcpy(tmp, "BOX64_LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib/box64-x86_64-linux-gnu:/usr/lib/box64-i386-linux-gnu:");
                        strcat(tmp, argv[nextarg] + strlen("--env-if-host=STEAM_RUNTIME_LIBRARY_PATH="));
                        p = strchr(tmp, '=');
                        *p = '\0';
                        ++p;
                        setenv(tmp, p, 1);
                        printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", tmp, p);
                    }
                }
            } else if (strstr(argv[nextarg], "--ld-preloads=") == argv[nextarg]) {
                // transform to BOX86_ / BOX64_ LD_PRELOAD
                char tmp[strlen(argv[nextarg]) + 4];
                strcpy(tmp, "BOX64_LD_PRELOAD=");
                strcat(tmp, strchr(argv[nextarg], '=') + 1);
                char* p = strchr(tmp, '=');
                *p = '\0';
                ++p;
                setenv(tmp, p, 1);
                printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", tmp, p);
                tmp[3] = '8';
                tmp[4] = '6';
                setenv(tmp, p, 1);
                printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", tmp, p);
            } else if (!strcmp(argv[nextarg], "--pass-fd") || !strcmp(argv[nextarg], "--filesystem")) {
                // those parameters have a value, skip it too
                printf_log(LOG_DEBUG, "Ignored parameter: \"%s\" and its value\n", argv[nextarg]);
                ++nextarg;
            } else {
                printf_log(LOG_DEBUG, "Ignored parameter: \"%s\"\n", argv[nextarg]);
            }
            ++nextarg;
        }
    if (nextarg < argc && !strcmp(argv[nextarg], "--"))
        ++nextarg; // skip the "--" separator
    if (nextarg < argc && argv[nextarg] && !strcmp(argv[nextarg], "steamwebhelper")) {
        // just launch it...
        runtime = NULL;
    }
    if(runtime) {
        char sniper[MAX_PATH] = {0};
        // build sniper path
        strcpy(sniper, prog);
        char* p = strrchr(sniper, '/');
        if(p) {
            *p = '\0';
            if(p) {
                setenv("BOX64_PRESSURE_ENV_PATH", sniper, 1);
                // add the folder in the PATH if not already there
                char* path = getenv("PATH");
                if(!strstr(path, sniper)) {
                    char buf[MAX_PATH] = {0};
                    snprintf(buf, sizeof(buf)-1, "%s:%s", path, sniper);
                    setenv("PATH", buf, 1);
                }
            }
            strcat(sniper, "/../../");
            strcat(sniper, runtime);
        } else {
            printf_log(LOG_INFO, "Warning, could not guess sniper runtime path\n");
            strcpy(sniper, runtime);    // it's wrong...
        }
        printf_log(LOG_DEBUG, "pressure-vessel sniper env: %s\n", sniper);
        // TODO: read metadata from sniper folder and analyse [Environment] section
        strcat(sniper, "/files");  // this is the sniper root
        // do LD_LIBRARY_PATH
        {
            char tmp[MAX_PATH] = {0};
            // prepare folders, using a fake ldconfig: sp just create the symlinks
            snprintf(tmp, sizeof(tmp), "%s/lib/x86_64-linux-gnu", sniper);
            create_libs_symlink(tmp);
            snprintf(tmp, sizeof(tmp), "%s/lib/i386-linux-gnu", sniper);
            create_libs_symlink(tmp);
            snprintf(tmp, sizeof(tmp), "%s/lib", sniper);
            create_libs_symlink(tmp);
            snprintf(tmp, sizeof(tmp), "%s/lib64", sniper);
            create_libs_symlink(tmp);
            snprintf(tmp, sizeof(tmp), "%s/lib32", sniper);
            create_libs_symlink(tmp);
            // setup LD_LIBRARY_PATH
            const char* ld = getenv("LD_LIBRARY_PATH");
            char tmp2[4096];
            tmp[0] = '\0';
            #define GO(A, B) \
            snprintf(tmp2, sizeof(tmp2), A, B);     \
            if(FileExist(tmp2, 0)) {                \
                strncat(tmp, tmp2, sizeof(tmp)-1);  \
                strncat(tmp, ":", sizeof(tmp)-1);   \
            }
            // runtime libs are put first, as the games are built against them
            GO("%s/lib/x86_64-linux-gnu", sniper)
            GO("%s/lib/i386-linux-gnu", sniper)
            GO("%s/lib/x86_64-linux-gnu/openblas", sniper)
            GO("%s/lib/i386-linux-gnu/openblas", sniper)
            GO("%s/lib/x86_64-linux-gnu/openblas-pthread", sniper)
            GO("%s/lib/i386-linux-gnu/openblas-pthread", sniper)
            GO("%s/lib", sniper)
            GO("%s/lib64", sniper)
            GO("%s/lib32", sniper)
            GO("/usr/lib/%s-x86_64-linux-gnu", "box64")
            GO("/usr/lib/%s-i386-linux-gnu/", "box64")
            #undef GO
            if(ld) strncat(tmp, ld, sizeof(tmp)-1);
            setenv("LD_LIBRARY_PATH", tmp, 1);
            printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", "LD_LIBRARY_PATH", tmp);
        }
        // do XDG_DATA_DIRS
        {
            char tmp[MAX_PATH*4] = {0};
            const char* xdg = getenv("XDG_DATA_DIRS");
            snprintf(tmp, sizeof(tmp), "%s/share:%s", sniper, xdg?xdg:"");
            setenv("XDG_DATA_DIRS", tmp, 1);
            printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", "XDG_DATA_DIRS", tmp);
        }
        // disabled GI_TYPELIB_PATH for now
        if(0)
        {
            char tmp[MAX_PATH*4] = {0};
            snprintf(tmp, sizeof(tmp), "%s/lib/x86_64-linux-gnu/girepository-1.0:%s/lib/i386-linux-gnu/girepository-1.0:%s/lib/girepository-1.0", sniper, sniper, sniper);
            setenv("GI_TYPELIB_PATH", tmp, 1);
            printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", "GI_TYPELIB_PATH", tmp);
        }
        // disabled GST_PLUGIN_SYSTEM_PATH
        if(0)
        {
            char tmp[MAX_PATH*4] = {0};
            snprintf(tmp, sizeof(tmp), "%s/lib/x86_64-linux-gnu/gstreamer-1.0:%s/lib/i386-linux-gnu/gstreamer-1.0:%s/lib/gstreamer-1.0", sniper, sniper, sniper);
            setenv("GST_PLUGIN_SYSTEM_PATH", tmp, 1);
            printf_log(LOG_DEBUG, "setenv(%s, %s, 1)\n", "GST_PLUGIN_SYSTEM_PATH", tmp);
        }
        // should disable native gtk on box86 for now, until better wrapping of gstreamer is done on box86 too
        // setenv("BOX86_NOGTK", "1", 1);
        setenv("BOX64_PRESSURE_VESSEL_FILES", sniper, 1);
    }
    if (launcher) {
        // in launcher mode, the command to run is the steam-runtime-launcher-service, with the parameters after "--"
        if (nextarg >= argc) {
            printf_log(LOG_INFO, "Warning, no parameters after \"--\" for the launcher mode\n");
        } else {
            char service[MAX_PATH] = { 0 };
            const char* p = strrchr(prog, '/');
            if (p) {
                int len = (int)(p - prog);
                snprintf(service, sizeof(service), "%.*s/steam-runtime-launcher-service", len, prog);
                if (!FileExist(service, IS_FILE)) {
                    // try the arch-specific one from libexec
                    snprintf(service, sizeof(service), "%.*s/../libexec/steam-runtime-tools-0/x86_64-linux-gnu-srt-launcher-service", len, prog);
                }
                if (FileExist(service, IS_FILE)) {
                    // insert the launcher-service as the new command, keeping all the parameters after "--"
                    int n = argc - nextarg;
                    const char** argv2 = (const char**)box_calloc(n + 1, sizeof(char*));
                    argv2[0] = box_strdup(service);
                    for (int i = 0; i < n; ++i)
                        argv2[i + 1] = argv[nextarg + i];
                    argv = argv2;
                    nextarg = 0;
                    argc = n + 1;
                    printf_log(LOG_DEBUG, "Running steam-runtime-launcher-service instead of the command\n");
                } else {
                    printf_log(LOG_INFO, "Warning, steam-runtime-launcher-service not found\n");
                }
            }
        }
    }
    printf_log(LOG_DEBUG, "Ready to launch \"%s\", nextarg=%d, argc=%d\n", argv[nextarg], nextarg, argc);
    prog = argv[nextarg];
    int x86 = my_context->box86path?FileIsX86ELF(argv[nextarg]):0;
    int x64 = my_context->box64path?FileIsX64ELF(argv[nextarg]):0;
    int sh = my_context->bashpath?FileIsShell(argv[nextarg]):0;
    int python = my_context->pythonpath?FileIsPython(argv[nextarg]):0;
    // create the new argv array
    const char** newargv = (const char**)box_calloc((argc-nextarg)+1+((x86 || x64 || python)?1:(sh?2:0)), sizeof(char*));
    if(x86 || x64 || sh || python) {
        newargv[0] = x86?my_context->box86path:(python?my_context->pythonpath:my_context->box64path);
        printf_log(LOG_DEBUG, "argv[%d]=\"%s\"\n", 0, newargv[0]);
        if(sh) {
            newargv[1] = my_context->bashpath;
            printf_log(LOG_DEBUG, "argv[%d]=\"%s\"\n", 1, newargv[1]);
        }
        for(int i=nextarg; i<argc; ++i) {
            printf_log(LOG_DEBUG, "argv[%d]=\"%s\"\n", (sh ? 2 : 1) + i - nextarg, argv[i]);
            newargv[(sh?2:1)+i-nextarg] = argv[i];
        }
    } else {
        for(int i=nextarg; i<argc; ++i) {
            printf_log(LOG_DEBUG, "argv[%d]=\"%s\"\n", i - nextarg, argv[i]);
            newargv[i-nextarg] = argv[i];
        }
    }

    //setenv("BOX64_PREFER_EMULATED", "1", 1);
    //setenv("BOX86_PREFER_EMULATED", "1", 1);
    //setenv("BOX64_TRACE_FILE", "/tmp/trace64-%pid.txt", 1);
    //setenv("BOX86_TRACE_FILE", "/tmp/trace86-%pid.txt", 1);
    //setenv("BOX86_LOG", "1", 1);
    //setenv("BOX64_LOG", "1", 1);
    //setenv("BOX86_SHOWSEGV", "1", 1);
    //setenv("BOX64_DLSYM_ERROR", "1", 1);
    //setenv("BOX64_SHOWSEGV", "1", 1);
    //setenv("BOX64_SHOWBT", "1", 1);
    //setenv("BOX64_DYNAREC_LOG", "1", 1);

    printf_log(LOG_DEBUG, "Run %s %s and wait\n", x86?"i386":(x64?"x86_64":""), argv[nextarg]);
    pid_t v = vfork();
    if(v==-1) {
        printf_log(LOG_NONE, "vfork failed!\n");
        FreeBox64Context(&my_context);
        return;
    }
    if(!v) {
        // child process, exec the program...
        int ret = execvp(newargv[0], (char * const*)newargv);
        printf_log(LOG_DEBUG, "Error launching program: %d (%s)\n", ret, strerror(errno));
        exit(0);
    } else {
        // parent process, wait the end of child
        if (pass_fd > 2) close(pass_fd);
        FreeBox64Context(&my_context);
        int wstatus;
        wait(&wstatus);
        //waitpid(v, &wstatus, 0);
        exit(WIFEXITED(wstatus) ? WEXITSTATUS(wstatus) : 0);
    }
}
