#include "initsystem.h"
#include "common/processing.h"
#include "common/binary.h"
#include "common/strutil.h"

#include <libgen.h>
#include <unistd.h>

FF_A_UNUSED static bool extractSystemdVersion(const char* str, uint32_t len, void* userdata) {
    if (!ffStrStartsWith(str, "systemd ")) {
        return true;
    }
    const char* pstart = str + strlen("systemd ");
    const char* pend = memmem(pstart, len - strlen("systemd "), " running in ", strlen(" running in "));
    if (!pend) {
        return true;
    }
    ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (pend - pstart), pstart);
    return false;
}

const char* ffDetectInitSystem(FFInitSystemResult* result) {
    const char* error = ffProcessGetBasicInfoLinux((int) result->pid, &result->name, NULL, NULL);
    if (error) {
#ifdef __ANDROID__
        if (access("/system/bin/init", F_OK) == 0) {
            ffStrbufSetStatic(&result->exe, "/system/bin/init");
            ffStrbufSetStatic(&result->name, "init");
            return NULL;
        }
#endif
        return error;
    }

    const char* _;
    // In linux /proc/1/exe is not readable
    ffProcessGetInfoLinux((int) result->pid, &result->name, &result->exe, &_, NULL);
    if (result->exe.chars[0] == '/') {
        // In some old system, /sbin/init is a symlink
        char buf[PATH_MAX];
        if (realpath(result->exe.chars, buf)) {
            ffStrbufSetS(&result->exe, buf);
            ffStrbufSetS(&result->name, basename(result->exe.chars));
        }
    }

    if (instance.config.general.detectVersion) {
#if (defined(__linux__) && !defined(__ANDROID__)) || defined(__GNU__)
        if (ffStrbufEqualS(&result->name, "systemd")) {
            ffBinaryExtractStrings(result->exe.chars, extractSystemdVersion, &result->version, (uint32_t) strlen("systemd 0.0 running in x"));
            if (result->version.length == 0) {
                if (ffProcessAppendStdOut(&result->version, (char* const[]) {
                                                                ffStrbufEndsWithS(&result->exe, "/systemd") ? result->exe.chars : "systemctl", // use exe path in case users have another systemd installed
                                                                "--version",
                                                                NULL,
                                                            }) == NULL &&
                    result->version.length) {
                    uint32_t iStart = ffStrbufFirstIndexC(&result->version, '(');
                    if (iStart < result->version.length) {
                        uint32_t iEnd = ffStrbufNextIndexC(&result->version, iStart + 1, ')');
                        ffStrbufSubstrBefore(&result->version, iEnd);
                        ffStrbufSubstrAfter(&result->version, iStart);
                    }
                }
            }
        } else if (ffStrbufEqualS(&result->name, "dinit")) {
            if (ffProcessAppendStdOut(&result->version, (char* const[]) {
                                                            ffStrbufEndsWithS(&result->exe, "/dinit") ? result->exe.chars : "dinit",
                                                            "--version",
                                                            NULL,
                                                        }) == NULL &&
                result->version.length) {
                // Dinit version 0.18.0.
                ffStrbufSubstrBeforeFirstC(&result->version, '\n');
                ffStrbufTrimRight(&result->version, '.');
                ffStrbufSubstrAfterLastC(&result->version, ' ');
            }
        } else if (ffStrbufEqualS(&result->name, "shepherd")) {
            if (ffProcessAppendStdOut(&result->version, (char* const[]) {
                                                            ffStrbufEndsWithS(&result->exe, "/shepherd") ? result->exe.chars : "shepherd",
                                                            "--version",
                                                            NULL,
                                                        }) == NULL &&
                result->version.length) {
                // shepherd (GNU Shepherd) 1.0.6
                // The first line in the output might not contain the version
                if (!ffStrbufStartsWithS(&result->version, "shepherd")) {
                    ffStrbufSubstrAfterFirstC(&result->version, '\n');
                }

                ffStrbufSubstrBeforeFirstC(&result->version, '\n');
                ffStrbufSubstrAfterLastC(&result->version, ' ');
            }
        }
#elif __APPLE__
        if (ffStrbufEqualS(&result->name, "launchd")) {
            if (ffProcessAppendStdOut(&result->version, (char* const[]) {
                                                            "/bin/launchctl",
                                                            "version",
                                                            NULL,
                                                        }) == NULL &&
                result->version.length) {
                uint32_t iStart = ffStrbufFirstIndexS(&result->version, "Version ");
                if (iStart < result->version.length) {
                    iStart += (uint32_t) strlen("Version");
                    uint32_t iEnd = ffStrbufNextIndexC(&result->version, iStart + 1, ':');
                    ffStrbufSubstrBefore(&result->version, iEnd);
                    ffStrbufSubstrAfter(&result->version, iStart);
                }
            }
        }
#endif
    }

    return NULL;
}
