#include "os.h"
#include "common/properties.h"
#include "common/parsing.h"
#include "common/io/io.h"
#include "common/processing.h"
#include "util/stringUtils.h"

#include <string.h>
#include <stdlib.h>

#define FF_STR_INDIR(x) #x
#define FF_STR(x) FF_STR_INDIR(x)

static bool parseLsbRelease(const char* fileName, FFOSResult* result)
{
    return ffParsePropFileValues(fileName, 4, (FFpropquery[]) {
        {"DISTRIB_ID =", &result->id},
        {"DISTRIB_DESCRIPTION =", &result->prettyName},
        {"DISTRIB_RELEASE =", &result->version},
        {"DISTRIB_CODENAME =", &result->codename},
    });
}

static bool parseOsRelease(const char* fileName, FFOSResult* result)
{
    return ffParsePropFileValues(fileName, 11, (FFpropquery[]) {
        {"PRETTY_NAME =", &result->prettyName},
        {"NAME =", &result->name},
        {"ID =", &result->id},
        {"ID_LIKE =", &result->idLike},
        {"VARIANT =", &result->variant},
        {"VARIANT_ID =", &result->variantID},
        {"VERSION =", &result->version},
        {"VERSION_ID =", &result->versionID},
        {"VERSION_CODENAME =", &result->codename},
        {"CODENAME =", &result->codename},
        {"BUILD_ID =", &result->buildID},
    });
}

// Common logic for detecting Armbian image version
FF_MAYBE_UNUSED static bool detectArmbianVersion(FFOSResult* result)
{
    // Possible values `PRETTY_NAME` starts with on Armbian:
    // - `Armbian` for official releases
    // - `Armbian_community` for community releases
    // - `Armbian_Security` for images with kali repo added
    // - `Armbian-unofficial` for an unofficial image built from source, e.g. during development and testing
    if (ffStrbufStartsWithS(&result->prettyName, "Armbian"))
        ffStrbufSetS(&result->name, "Armbian");
    else
        return false;
    ffStrbufSet(&result->idLike, &result->id);
    ffStrbufSetS(&result->id, "armbian");
    ffStrbufClear(&result->versionID);
    uint32_t versionStart = ffStrbufFirstIndexC(&result->prettyName, ' ') + 1;
    uint32_t versionEnd = ffStrbufNextIndexC(&result->prettyName, versionStart, ' ');
    ffStrbufSetNS(&result->versionID, versionEnd - versionStart, result->prettyName.chars + versionStart);
    return true;
}

FF_MAYBE_UNUSED static void getUbuntuFlavour(FFOSResult* result)
{
    const char* xdgConfigDirs = getenv("XDG_CONFIG_DIRS");
    if(!ffStrSet(xdgConfigDirs))
        return;

    if (detectArmbianVersion(result))
        return;
    else if(ffStrbufStartsWithS(&result->prettyName, "Linux Lite "))
    {
        ffStrbufSetS(&result->name, "Linux Lite");
        ffStrbufSetS(&result->id, "linuxlite");
        ffStrbufSetS(&result->idLike, "ubuntu");
        ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen("Linux Lite "));
        return;
    }
    else if(ffStrbufStartsWithS(&result->prettyName, "Rhino Linux "))
    {
        ffStrbufSetS(&result->name, "Rhino Linux");
        ffStrbufSetS(&result->id, "rhinolinux");
        ffStrbufSetS(&result->idLike, "ubuntu");
        ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen("Rhino Linux "));
        return;
    }
    else if(ffStrbufStartsWithS(&result->prettyName, "VanillaOS "))
    {
        ffStrbufSetS(&result->id, "vanilla");
        ffStrbufSetS(&result->idLike, "ubuntu");
    }

    if(ffStrContains(xdgConfigDirs, "kde") || ffStrContains(xdgConfigDirs, "plasma") || ffStrContains(xdgConfigDirs, "kubuntu"))
    {
        ffStrbufSetS(&result->name, "Kubuntu");
        ffStrbufSetS(&result->prettyName, "Kubuntu");
        ffStrbufSetS(&result->id, "kubuntu");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "xfce") || ffStrContains(xdgConfigDirs, "xubuntu"))
    {
        ffStrbufSetS(&result->name, "Xubuntu");
        ffStrbufSetS(&result->prettyName, "Xubuntu");
        ffStrbufSetS(&result->id, "xubuntu");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "lxqt") || ffStrContains(xdgConfigDirs, "lubuntu"))
    {
        ffStrbufSetS(&result->name, "Lubuntu");
        ffStrbufSetS(&result->prettyName, "Lubuntu");
        ffStrbufSetS(&result->id, "lubuntu");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "budgie"))
    {
        ffStrbufSetS(&result->name, "Ubuntu Budgie");
        ffStrbufSetS(&result->prettyName, "Ubuntu Budgie");
        ffStrbufSetS(&result->id, "ubuntu-budgie");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "cinnamon"))
    {
        ffStrbufSetS(&result->name, "Ubuntu Cinnamon");
        ffStrbufSetS(&result->prettyName, "Ubuntu Cinnamon");
        ffStrbufSetS(&result->id, "ubuntu-cinnamon");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "mate"))
    {
        ffStrbufSetS(&result->name, "Ubuntu MATE");
        ffStrbufSetS(&result->prettyName, "Ubuntu MATE");
        ffStrbufSetS(&result->id, "ubuntu-mate");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "studio"))
    {
        ffStrbufSetS(&result->name, "Ubuntu Studio");
        ffStrbufSetS(&result->prettyName, "Ubuntu Studio");
        ffStrbufSetS(&result->id, "ubuntu-studio");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "sway"))
    {
        ffStrbufSetS(&result->name, "Ubuntu Sway");
        ffStrbufSetS(&result->prettyName, "Ubuntu Sway");
        ffStrbufSetS(&result->id, "ubuntu-sway");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "touch"))
    {
        ffStrbufSetS(&result->name, "Ubuntu Touch");
        ffStrbufSetS(&result->prettyName, "Ubuntu Touch");
        ffStrbufSetS(&result->id, "ubuntu-touch");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }

    if(ffStrContains(xdgConfigDirs, "lliurex"))
    {
        ffStrbufSetS(&result->name, "LliureX");
        ffStrbufSetS(&result->prettyName, "LliureX");
        ffStrbufSetS(&result->id, "lliurex");
        ffStrbufSetS(&result->idLike, "ubuntu");
        return;
    }
}

FF_MAYBE_UNUSED static void getDebianVersion(FFOSResult* result)
{
    FF_STRBUF_AUTO_DESTROY debianVersion = ffStrbufCreate();
    ffAppendFileBuffer("/etc/debian_version", &debianVersion);
    ffStrbufTrimRightSpace(&debianVersion);
    if (!debianVersion.length) return;
    ffStrbufSet(&result->version, &debianVersion);
    ffStrbufSet(&result->versionID, &debianVersion);
}

FF_MAYBE_UNUSED static bool detectDebianDerived(FFOSResult* result)
{
    if (detectArmbianVersion(result))
        return true;
    else if (ffStrbufStartsWithS(&result->name, "Loc-OS"))
    {
        ffStrbufSetS(&result->id, "locos");
        ffStrbufSetS(&result->idLike, "debian");
        return true;
    }
    else if (ffStrbufEqualS(&result->name, "Parrot Security"))
    {
        // https://github.com/ParrotSec/base-files/blob/c06f6d42ddf8d79564882306576576eddab7d907/etc/os-release
        ffStrbufSetS(&result->id, "parrot");
        ffStrbufSetS(&result->idLike, "debian");
        return true;
    }
    else if (ffStrbufStartsWithS(&result->name, "Lilidog GNU/Linux"))
    {
        // https://github.com/fastfetch-cli/fastfetch/issues/1373
        ffStrbufSetS(&result->id, "lilidog");
        ffStrbufSetS(&result->idLike, "debian");
        return true;
    }
    else if (access("/usr/bin/pveversion", X_OK) == 0)
    {
        ffStrbufSetS(&result->id, "pve");
        ffStrbufSetS(&result->idLike, "debian");
        ffStrbufSetS(&result->name, "Proxmox VE");
        ffStrbufClear(&result->versionID);
        if (ffProcessAppendStdOut(&result->versionID, (char* const[]) {
            "/usr/bin/dpkg-query",
            "--showformat=${version}",
            "--show",
            "pve-manager",
            NULL,
        }) == NULL) // 8.2.2
            ffStrbufTrimRightSpace(&result->versionID);
        ffStrbufSetF(&result->prettyName, "Proxmox VE %s", result->versionID.chars);
        return true;
    }
    else
    {
        // Hack for MX Linux. See #847
        FF_STRBUF_AUTO_DESTROY lsbRelease = ffStrbufCreate();
        if (ffAppendFileBuffer("/etc/lsb-release", &lsbRelease) && ffStrbufContainS(&lsbRelease, "DISTRIB_ID=MX"))
        {
            ffStrbufSetS(&result->id, "mx");
            ffStrbufSetS(&result->idLike, "debian");
            ffStrbufSetS(&result->name, "MX");

            ffStrbufClear(&result->version);
            ffParsePropLines(lsbRelease.chars, "DISTRIB_RELEASE=", &result->version);
            ffStrbufSet(&result->versionID, &result->version);

            ffStrbufClear(&result->codename);
            ffParsePropLines(lsbRelease.chars, "DISTRIB_CODENAME=", &result->codename);

            ffStrbufClear(&result->prettyName);
            ffParsePropLines(lsbRelease.chars, "DISTRIB_DESCRIPTION=", &result->prettyName);
            return true;
        }
    }
    return false;
}

static void detectOS(FFOSResult* os)
{
    #ifdef FF_CUSTOM_OS_RELEASE_PATH
    parseOsRelease(FF_STR(FF_CUSTOM_OS_RELEASE_PATH), os);
        #ifdef FF_CUSTOM_LSB_RELEASE_PATH
        parseLsbRelease(FF_STR(FF_CUSTOM_LSB_RELEASE_PATH), os);
        #endif
    return;
    #endif

    if(parseOsRelease(FASTFETCH_TARGET_DIR_ROOT "/bedrock" FASTFETCH_TARGET_DIR_ETC "/bedrock-release", os))
    {
        if(os->id.length == 0)
            ffStrbufAppendS(&os->id, "bedrock");

        if(os->name.length == 0)
            ffStrbufAppendS(&os->name, "Bedrock");

        if(os->prettyName.length == 0)
            ffStrbufAppendS(&os->prettyName, "Bedrock Linux");

        parseOsRelease("/bedrock" FASTFETCH_TARGET_DIR_ETC "/os-release", os);
        return;
    }

    // Refer: https://gist.github.com/natefoo/814c5bf936922dad97ff

    parseOsRelease(FASTFETCH_TARGET_DIR_ETC "/os-release", os);
    if (os->id.length == 0 || os->version.length == 0 || os->prettyName.length == 0 || os->codename.length == 0)
        parseLsbRelease(FASTFETCH_TARGET_DIR_ETC "/lsb-release", os);
    if (os->id.length == 0 || os->name.length > 0 || os->prettyName.length > 0)
        parseOsRelease(FASTFETCH_TARGET_DIR_USR "/lib/os-release", os);
}

void ffDetectOSImpl(FFOSResult* os)
{
    detectOS(os);

    #ifdef __linux__
    if(ffStrbufIgnCaseEqualS(&os->id, "ubuntu"))
        getUbuntuFlavour(os);
    else if(ffStrbufIgnCaseEqualS(&os->id, "debian"))
    {
        if (!detectDebianDerived(os))
            getDebianVersion(os);
    }
    else if(ffStrbufEqualS(&os->id, "linuxmint"))
    {
        if (ffStrbufEqualS(&os->name, "LMDE"))
        {
            ffStrbufSetS(&os->id, "lmde");
            ffStrbufSetS(&os->idLike, "linuxmint");
        }
    }
    #endif
}
