/****************************************************************************
**
**  This file is part of GAP, a system for computational discrete algebra.
**
**  Copyright of GAP belongs to its developers, whose names are too numerous
**  to list here. Please refer to the COPYRIGHT file for details.
**
**  SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "common.h"
#include "sysfiles.h"
#include "sysroots.h"
#include "sysstr.h"
#include "system.h"

#ifdef HPCGAP
#include "hpc/thread.h"
#endif

#include "config.h"

#include <stdlib.h>     // for getenv, realpath, ...
#include <string.h>     // for snprintf
#include <unistd.h>     // for access, readlink, ...

#if defined(__APPLE__) && defined(__MACH__)
// Workaround: TRUE / FALSE are also defined by the macOS Mach-O headers
#define ENUM_DYLD_BOOL
#include <mach-o/dyld.h>
#endif

extern int realmain(int argc, const char * argv[]);

/****************************************************************************
**
*F * * * * * * * * * * finding location of executable * * * * * * * * * * * *
*/

#ifdef SYS_DEFAULT_PATHS

static void SetupInitialGapRoot(const char * argv0)
{
    gap_strlcpy(SyDefaultRootPath, SYS_DEFAULT_PATHS, sizeof(SyDefaultRootPath));
}

#else

/****************************************************************************
**
** The function 'find_yourself' is based on code (C) 2015 Mark Whitis, under
** the MIT License : https://stackoverflow.com/a/34271901/928031
*/
static void
find_yourself(const char * argv0, char * result, size_t resultsize)
{
    GAP_ASSERT(resultsize >= GAP_PATH_MAX);

    char tmpbuf[GAP_PATH_MAX];

    // absolute path, like '/usr/bin/gap'
    if (argv0[0] == '/') {
        if (realpath(argv0, result) && !access(result, F_OK)) {
            return;    // success
        }
    }
    // relative path, like 'bin/gap.sh'
    else if (strchr(argv0, '/')) {
        if (!getcwd(tmpbuf, sizeof(tmpbuf)))
            return;
        gap_strlcat(tmpbuf, "/", sizeof(tmpbuf));
        gap_strlcat(tmpbuf, argv0, sizeof(tmpbuf));
        if (realpath(tmpbuf, result) && !access(result, F_OK)) {
            return;    // success
        }
    }
    // executable name, like 'gap'
    else {
        char pathenv[GAP_PATH_MAX], *saveptr, *pathitem;
        gap_strlcpy(pathenv, getenv("PATH"), sizeof(pathenv));
        pathitem = strtok_r(pathenv, ":", &saveptr);
        for (; pathitem; pathitem = strtok_r(NULL, ":", &saveptr)) {
            gap_strlcpy(tmpbuf, pathitem, sizeof(tmpbuf));
            gap_strlcat(tmpbuf, "/", sizeof(tmpbuf));
            gap_strlcat(tmpbuf, argv0, sizeof(tmpbuf));
            if (realpath(tmpbuf, result) && !access(result, F_OK)) {
                return;    // success
            }
        }
    }

    *result = 0;    // reset buffer after error
}

static void SetupGAPLocation(const char * argv0, char * GAPExecLocation)
{
    // In the code below, we keep resetting locBuf, as some of the methods we
    // try do not promise to leave the buffer empty on a failed return.
    char locBuf[GAP_PATH_MAX] = "";
    Int4 length = 0;

#if defined(__APPLE__) && defined(__MACH__)
    uint32_t len = sizeof(locBuf);
    if (_NSGetExecutablePath(locBuf, &len) != 0) {
        *locBuf = 0;    // reset buffer after error
    }
#endif

    // try Linux procfs
    if (!*locBuf) {
        ssize_t ret = readlink("/proc/self/exe", locBuf, sizeof(locBuf));
        if (ret < 0)
            *locBuf = 0;    // reset buffer after error
    }

    // try FreeBSD / DragonFly BSD procfs
    if (!*locBuf) {
        ssize_t ret = readlink("/proc/curproc/file", locBuf, sizeof(locBuf));
        if (ret < 0)
            *locBuf = 0;    // reset buffer after error
    }

    // try NetBSD procfs
    if (!*locBuf) {
        ssize_t ret = readlink("/proc/curproc/exe", locBuf, sizeof(locBuf));
        if (ret < 0)
            *locBuf = 0;    // reset buffer after error
    }

    // if we are still failing, go and search the path
    if (!*locBuf) {
        find_yourself(argv0, locBuf, GAP_PATH_MAX);
    }

    // resolve symlinks (if present)
    if (!realpath(locBuf, GAPExecLocation))
        *GAPExecLocation = 0;    // reset buffer after error

    // now strip the executable name off
    length = strlen(GAPExecLocation);
    while (length > 0 && GAPExecLocation[length] != '/') {
        GAPExecLocation[length] = 0;
        length--;
    }
}

/****************************************************************************
**
*F  SySetInitialGapRootPaths( <string> )  . . . . .  set the root directories
**
**  Set up GAP's initial root paths, based on the location of the
**  GAP executable.
*/
static void SySetInitialGapRootPaths(const char * GAPExecLocation)
{
    if (GAPExecLocation[0] != 0) {
        // GAPExecLocation might be a subdirectory of GAP root,
        // so we will go and search for the true GAP root.
        // We try stepping back up to two levels.
        char pathbuf[GAP_PATH_MAX];
        char initgbuf[GAP_PATH_MAX];
        strxcpy(pathbuf, GAPExecLocation, sizeof(pathbuf));
        for (Int i = 0; i < 3; ++i) {
            strxcpy(initgbuf, pathbuf, sizeof(initgbuf));
            strxcat(initgbuf, "lib/init.g", sizeof(initgbuf));

            if (SyIsReadableFile(initgbuf) == 0) {
                gap_strlcpy(SyDefaultRootPath, pathbuf, sizeof(SyDefaultRootPath));
                // escape from loop
                return;
            }
            // try up a directory level
            strxcat(pathbuf, "../", sizeof(pathbuf));
        }
    }
}

static void SetupInitialGapRoot(const char * argv0)
{
    char GAPExecLocation[GAP_PATH_MAX] = "";
    SetupGAPLocation(argv0, GAPExecLocation);
    SySetInitialGapRootPaths(GAPExecLocation);
}
#endif

int main(int argc, const char * argv[])
{
    InstallBacktraceHandlers();
    SetupInitialGapRoot(argv[0]);

#ifdef HPCGAP
    RunThreadedMain(realmain, argc, argv);
    return 0;
#else
    return realmain(argc, argv);
#endif
}
