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

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

FF_MAYBE_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;
}
