#include "fastfetch.h"
#include "common/init.h"
#include "common/parsing.h"
#include "common/thread.h"
#include "common/textModifier.h"
#include "detection/displayserver/displayserver.h"
#include "detection/terminaltheme/terminaltheme.h"
#include "logo/logo.h"

#include <stdlib.h>
#include <unistd.h>
#include <locale.h>
#ifdef _WIN32
    #include <windows.h>
    #include "common/windows/unicode.h"
#else
    #include <signal.h>
#endif

FFinstance instance; // Global singleton

static void initState(FFstate* state)
{
    state->logoWidth = 0;
    state->logoHeight = 0;
    state->keysHeight = 0;
    state->terminalLightTheme = false;
    state->titleFqdn = false;

    ffPlatformInit(&state->platform);
    state->dynamicInterval = 0;

    {
        // don't enable bright color if the terminal is in light mode
        FFTerminalThemeResult result;
        if (ffDetectTerminalTheme(&result, true /* forceEnv for performance */) && !result.bg.dark)
            state->terminalLightTheme = true;
    }
}

static void defaultConfig(void)
{
    ffOptionsInitLogo(&instance.config.logo);
    ffOptionsInitGeneral(&instance.config.general);
    ffOptionsInitDisplay(&instance.config.display);
}

void ffInitInstance(void)
{
    #ifdef _WIN32
        // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?source=recommendat>
        setlocale(LC_ALL, ".UTF8");
    #else
        // Never use `setlocale(LC_ALL, "")`
        setlocale(LC_TIME, "");
    #endif

    initState(&instance.state);
    defaultConfig();
}

static volatile bool ffDisableLinewrap = true;
static volatile bool ffHideCursor = true;

static void resetConsole(void)
{
    if(ffDisableLinewrap)
        fputs("\033[?7h", stdout);

    if(ffHideCursor)
        fputs("\033[?25h", stdout);

    #if defined(_WIN32)
        fflush(stdout);
    #endif

    if(instance.state.dynamicInterval > 0)
        fputs("\033[?1049l", stdout); // Disable alternate buffer
}

#ifdef _WIN32
BOOL WINAPI consoleHandler(FF_MAYBE_UNUSED DWORD signal)
{
    resetConsole();
    exit(0);
}
#else
static void exitSignalHandler(FF_MAYBE_UNUSED int signal)
{
    resetConsole();
    exit(0);
}
#endif

void ffStart(void)
{
    ffDisableLinewrap = instance.config.display.disableLinewrap && !instance.config.display.pipe;
    ffHideCursor = instance.config.display.hideCursor && !instance.config.display.pipe;

    #ifdef _WIN32
    SetErrorMode(SEM_FAILCRITICALERRORS);
    if (instance.config.display.noBuffer)
        setvbuf(stdout, NULL, _IONBF, 0);
    else
        setvbuf(stdout, NULL, _IOFBF, 4096);
    SetConsoleCtrlHandler(consoleHandler, TRUE);
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD mode = 0;
    GetConsoleMode(hStdout, &mode);
    SetConsoleMode(hStdout, mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
    SetConsoleOutputCP(CP_UTF8);
    #else
    if (instance.config.display.noBuffer) setvbuf(stdout, NULL, _IONBF, 0);
    struct sigaction action = { .sa_handler = exitSignalHandler };
    sigaction(SIGINT, &action, NULL);
    sigaction(SIGTERM, &action, NULL);
    sigaction(SIGQUIT, &action, NULL);
    sigset_t newmask;
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &newmask, NULL);
    #endif

    //reset everything to default before we start printing
    if(!instance.config.display.pipe)
        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);

    if(ffHideCursor)
        fputs("\033[?25l", stdout);

    if(ffDisableLinewrap)
        fputs("\033[?7l", stdout);

    if(instance.state.dynamicInterval > 0)
    {
        fputs("\033[?1049h\033[H", stdout); // Enable alternate buffer
        fflush(stdout);
    }
}

void ffFinish(void)
{
    resetConsole();
}

static void destroyConfig(void)
{
    ffOptionsDestroyLogo(&instance.config.logo);
    ffOptionsDestroyGeneral(&instance.config.general);
    ffOptionsDestroyDisplay(&instance.config.display);
}

static void destroyState(void)
{
    ffPlatformDestroy(&instance.state.platform);
}

void ffDestroyInstance(void)
{
    destroyConfig();
    destroyState();
}

//Must be in a file compiled with the libfastfetch target, because the FF_HAVE* macros are not defined for the executable targets
void ffListFeatures(void)
{
    fputs(
        #if FF_HAVE_THREADS
            "threads\n"
        #endif
        #if FF_HAVE_VULKAN
            "vulkan\n"
        #endif
        #if FF_HAVE_WAYLAND
            "wayland\n"
        #endif
        #if FF_HAVE_XCB_RANDR
            "xcb-randr\n"
        #endif
        #if FF_HAVE_XRANDR
            "xrandr\n"
        #endif
        #if FF_HAVE_DRM
            "drm\n"
        #endif
        #if FF_HAVE_DRM_AMDGPU
            "drm_amdgpu\n"
        #endif
        #if FF_HAVE_GIO
            "gio\n"
        #endif
        #if FF_HAVE_DCONF
            "dconf\n"
        #endif
        #if FF_HAVE_DBUS
            "dbus\n"
        #endif
        #if FF_HAVE_IMAGEMAGICK7
            "imagemagick7\n"
        #endif
        #if FF_HAVE_IMAGEMAGICK6
            "imagemagick6\n"
        #endif
        #if FF_HAVE_CHAFA
            "chafa\n"
        #endif
        #if FF_HAVE_ZLIB
            "zlib\n"
        #endif
        #if FF_HAVE_SQLITE3
            "sqlite3\n"
        #endif
        #if FF_HAVE_RPM
            "rpm\n"
        #endif
        #if FF_HAVE_EGL
            "egl\n"
        #endif
        #if FF_HAVE_GLX
            "glx\n"
        #endif
        #if FF_HAVE_OPENCL
            "opencl\n"
        #endif
        #if FF_HAVE_FREETYPE
            "freetype\n"
        #endif
        #if FF_HAVE_PCIACCESS
            "libpciaccess\n"
        #endif
        #if FF_HAVE_PULSE
            "libpulse\n"
        #endif
        #if FF_HAVE_DDCUTIL
            "libddcutil\n"
        #endif
        #if FF_HAVE_ELF || __sun || (__FreeBSD__ && !__DragonFly__) || __OpenBSD__ || __NetBSD__
            "libelf\n"
        #endif
        #if FF_HAVE_LIBZFS
            "libzfs\n"
        #endif
        #if FF_HAVE_DIRECTX_HEADERS
            "Directx Headers\n"
        #endif
        #if FF_USE_SYSTEM_YYJSON
            "System yyjson\n"
        #endif
        #if FF_HAVE_LINUX_VIDEODEV2
            "linux/videodev2\n"
        #endif
        #if FF_HAVE_LINUX_WIRELESS
            "linux/wireless\n"
        #endif
        #if FF_HAVE_EMBEDDED_PCIIDS
            "Embedded pciids\n"
        #endif
        #if FF_WIN7_COMPAT
            "Windows 7 Compatibility\n"
        #endif
        ""
    , stdout);
}
