#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

char* resolve_abs_path(char *arg) {
    char *abs_path = realpath(arg, NULL);
    if (abs_path)
        return abs_path;
    return arg;
}

void args_update_relpaths_to_abs(int argc, char **argv) {
    for (int i = 0; i < argc; i++)
        if (argv[i][0] != '/' && !access(argv[i], F_OK))
            argv[i] = resolve_abs_path(argv[i]);
}

int file_grep(const char *filename, const char *search_str) {

    if (access(filename, F_OK))
        return 0;

    FILE *file = fopen(filename, "r");
    if (!file) {
        perror("fopen");
        return 0;
    }

    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    int found = 0;

    while ((read = getline(&line, &len, file)) != -1) {
        if (strstr(line, search_str)) {
            found = 1;
            break;
        }
    }

    free(line);
    fclose(file);

    return found;
}

void set_env_with_mydir(const char *env_var, const char *relative_path, const char *mydir) {
    char path[PATH_MAX];
    snprintf(path, sizeof(path), "%s/%s", mydir, relative_path);
    setenv(env_var, path, 1);
}


void set_ca_file(const char *mydir) {
    const char *system_ca_files[] = {
        "/etc/ssl/certs/ca-certificates.crt",
        "/etc/ssl/cert.pem",
        "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
    };

    for (int i = 0; i < sizeof(system_ca_files) / sizeof(system_ca_files[0]); i++) {
        if (file_grep(system_ca_files[i], "BEGIN CERTIFICATE")) {
            setenv("WEBKIT_TLS_CAFILE_PEM", system_ca_files[i], 1);
            return;
        }
    }

    // if we can't find a valid system cert use the one we bundle.
    set_env_with_mydir("WEBKIT_TLS_CAFILE_PEM", "sys/share/certs/bundle-ca-certificates.pem", mydir);
}

int exec_and_wait(const char *program, char *const argv[]) {
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        return -1;
    }
    if (pid == 0) { //child
        execvp(program, argv);
        perror("execv");
        exit(EXIT_FAILURE);
    } else {  //parent
        int status;
        pid_t wpid = waitpid(pid, &status, 0);
        if (wpid == -1) {
            perror("waitpid");
            return -1;
        }
        if (WIFEXITED(status))
            return WEXITSTATUS(status);
        return -1;
    }
}

int maybe_update_gdx_pixbuf_cache(const char *mydir) {
    char gdk_pixbuf_module_file[PATH_MAX];
    char gdk_pixbuf_module_dir[PATH_MAX];
    snprintf(gdk_pixbuf_module_file, sizeof(gdk_pixbuf_module_file), "%s/sys/lib/gdk-pixbuf/loaders.cache", mydir);
    snprintf(gdk_pixbuf_module_dir, sizeof(gdk_pixbuf_module_dir), "%s/sys/lib/gdk-pixbuf/loaders", mydir);

    if (file_grep(gdk_pixbuf_module_file, gdk_pixbuf_module_dir))
        return 0;

    char *gdk_pixbuf_args[] = { "gdk-pixbuf-query-loaders", "--update-cache", NULL };
    return exec_and_wait("bin/gdk-pixbuf-query-loaders", gdk_pixbuf_args);
}

int main(int argc, char *argv[]) {
    // options
    int should_set_ca_file = 1;
    int should_xkb_bundle  = 1;
    int should_update_gdx_pixbuf_cache  = 1;


    // Get the directory of the executable
    char mydir[PATH_MAX];
    ssize_t len = readlink("/proc/self/exe", mydir, sizeof(mydir) - 1);
    if (len == -1) {
        perror("readlink");
        exit(EXIT_FAILURE);
    }
    mydir[len] = '\0';
    strcpy(mydir, dirname(mydir));

    // Update args with full paths
    args_update_relpaths_to_abs(argc, argv);

    // Change to the directory where the script resides
    if (chdir(mydir) == -1) {
        perror("chdir");
        exit(EXIT_FAILURE);
    }

    if (should_set_ca_file)
        set_ca_file(mydir);

    // export required environment variables
    set_env_with_mydir("LD_PRELOAD", "lib/dlopenwrap.so", mydir);
    set_env_with_mydir("GIO_MODULE_DIR", "sys/lib/gio", mydir);
    set_env_with_mydir("GST_PLUGIN_SYSTEM_PATH_1_0", "sys/lib/gst", mydir);
    set_env_with_mydir("GST_REGISTRY_1_0", "sys/lib/gst/gstreamer-1.0.registry", mydir);
    set_env_with_mydir("GST_PLUGIN_SCANNER", "bin/gst-plugin-scanner", mydir);
    set_env_with_mydir("SPA_PLUGIN_DIR", "sys/lib/pipewire/spa", mydir);
    set_env_with_mydir("PIPEWIRE_MODULE_DIR", "sys/lib/pipewire/modules", mydir);
    set_env_with_mydir("LIBGL_DRIVERS_PATH", "sys/lib/dri", mydir);
    set_env_with_mydir("__EGL_VENDOR_LIBRARY_DIRS", "sys/lib/glvnd/egl_vendor.d", mydir);
    set_env_with_mydir("XDG_DATA_HOME", "sys/share", mydir);
    set_env_with_mydir("XDG_DATA_DIRS", "sys/share", mydir);
    set_env_with_mydir("FONTCONFIG_PATH", "sys/share/fonts/config", mydir);
    set_env_with_mydir("GTK_PATH", "sys/lib/gtk", mydir);
    set_env_with_mydir("WEBKIT_EXEC_PATH", "bin", mydir);
    set_env_with_mydir("WEBKIT_INJECTED_BUNDLE_PATH", "lib", mydir);
    setenv("WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS", "1", 1);
    set_env_with_mydir("PATH", "bin", mydir);
    set_env_with_mydir("GDK_PIXBUF_MODULEDIR", "sys/lib/gdk-pixbuf/loaders", mydir);
    set_env_with_mydir("GDK_PIXBUF_MODULE_FILE", "sys/lib/gdk-pixbuf/loaders.cache", mydir);


    if (should_xkb_bundle && !access("/usr/share/X11/xkb", F_OK))
        set_env_with_mydir("XKB_CONFIG_ROOT", "sys/share/xkb", mydir);

    char ldpath[PATH_MAX];
    snprintf(ldpath, sizeof(ldpath), "%s/lib:%s/sys/lib", mydir, mydir);
    setenv("LD_LIBRARY_PATH", ldpath, 1);

    if (should_update_gdx_pixbuf_cache) {
        int retcode = maybe_update_gdx_pixbuf_cache(mydir);
        if (retcode)
            fprintf(stderr, "gdk-pixbuf cache update command returned non-zero: %d\n", retcode);
    }

    char realProgram[] = "bin/MiniBrowser";
    execv(realProgram, argv);

    // execv failed
    perror("execv");
    exit(EXIT_FAILURE);
}

