#include "fastfetch.h"
#include "common/printing.h"
#include "common/textModifier.h"
#include "logo/logo.h"

void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType) {
    ffLogoPrintLine();

    // This is used by --set-keyless, in this case we want neither the module name nor the separator
    if (moduleName == nullptr) {
        return;
    }

    // This is used as a magic value for hiding keys
    if (!(moduleArgs && ffStrbufEqualS(&moduleArgs->key, " ")) && instance.config.display.keyType != FF_MODULE_KEY_TYPE_NONE) {
        ffPrintCharTimes(' ', instance.config.display.keyPaddingLeft);

        if (!instance.config.display.pipe) {
            fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);
            if (instance.config.display.brightColor) {
                fputs(FASTFETCH_TEXT_MODIFIER_BOLT, stdout);
            }

            if (moduleArgs && !(printType & FF_PRINT_TYPE_NO_CUSTOM_KEY_COLOR) && moduleArgs->keyColor.length > 0) {
                ffPrintColor(&moduleArgs->keyColor);
            } else {
                ffPrintColor(&instance.config.display.colorKeys);
            }
        }

        if (instance.config.display.keyType & FF_MODULE_KEY_TYPE_ICON && moduleArgs && moduleArgs->keyIcon.length > 0) {
            ffStrbufWriteTo(&moduleArgs->keyIcon, stdout);
        }

        if (instance.config.display.keyType & FF_MODULE_KEY_TYPE_STRING) {
            ffPrintCharTimes(' ', instance.config.display.keyType >> FF_MODULE_KEY_TYPE_SPACE_SHIFT);

            // nullptr check is required for modules with custom keys, e.g. disk with the folder path
            if ((printType & FF_PRINT_TYPE_NO_CUSTOM_KEY) || !moduleArgs || moduleArgs->key.length == 0) {
                fputs(moduleName, stdout);

                if (moduleIndex > 0) {
                    printf(" %hhu", moduleIndex);
                }
            } else {
                FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
                FF_PARSE_FORMAT_STRING_CHECKED(&key, &moduleArgs->key, ((FFformatarg[]) {
                                                                           FF_ARG(moduleIndex, "index"),
                                                                           FF_ARG(moduleArgs->keyIcon, "icon"),
                                                                           FF_ARG(moduleName, "module-name"),
                                                                       }));
                ffStrbufWriteTo(&key, stdout);
            }
        }

        if (!instance.config.display.pipe) {
            fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);
            ffPrintColor(&instance.config.display.colorSeparator);
        }

        ffStrbufWriteTo(&instance.config.display.keyValueSeparator, stdout);

        if (!instance.config.display.pipe && instance.config.display.colorSeparator.length) {
            fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);
        }

        if (!(printType & FF_PRINT_TYPE_NO_CUSTOM_KEY_WIDTH)) {
            uint32_t keyWidth = moduleArgs && moduleArgs->keyWidth > 0 ? moduleArgs->keyWidth : instance.config.display.keyWidth;
            if (keyWidth > 0) {
                printf("\e[%uG", (unsigned) (keyWidth + instance.state.logoWidth));
            }
        }
    }

    if (!instance.config.display.pipe) {
        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);
        if (moduleArgs && moduleArgs->outputColor.length) {
            ffPrintColor(&moduleArgs->outputColor);
        } else if (instance.config.display.colorOutput.length) {
            ffPrintColor(&instance.config.display.colorOutput);
        }
    }
}

bool ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments) {
    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
    bool success;
    if (__builtin_expect(moduleArgs != nullptr, 1)) {
        success = ffParseFormatString(&buffer, &moduleArgs->outputFormat, numArgs, arguments);
    } else {
        ffStrbufSetStatic(&buffer, "undefined format");
        success = false;
    }

    if (success) {
        ffPrintLogoAndKey(moduleName, moduleIndex, moduleArgs, printType);
        ffStrbufPutTo(&buffer, stdout);
    } else {
        ffPrintError(moduleName, moduleIndex, moduleArgs, printType, "%s", buffer.chars);
    }

    return success;
}

void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...) {
    if (!instance.config.display.showErrors) {
        return;
    }

    ffPrintLogoAndKey(moduleName, moduleIndex, moduleArgs, printType);

    if (!instance.config.display.pipe) {
        fputs(FASTFETCH_TEXT_MODIFIER_ERROR, stdout);
    }

    va_list arguments;
    va_start(arguments, message);
    vprintf(message, arguments);
    va_end(arguments);

    if (!instance.config.display.pipe) {
        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);
    }

    putchar('\n');
}

void ffPrintColor(const FFstrbuf* colorValue) {
    // If the color is not set, this would reset in \033[m, which resets everything.
    // So we only print it, if the main color is at least one char.
    if (colorValue->length == 0) {
        return;
    }

    printf("\e[%sm", colorValue->chars);
}

void ffPrintCharTimes(char c, uint32_t times) {
    if (times == 0) {
        return;
    }

    if (times == 1) {
        putchar(c);
        return;
    }

    char str[32];
    memset(str, c, sizeof(str)); // 2 instructions when compiling with AVX2 enabled
    for (uint32_t i = sizeof(str); i <= times; i += (uint32_t) sizeof(str)) {
        fwrite(str, 1, sizeof(str), stdout);
    }
    uint32_t remaining = times % sizeof(str);
    if (remaining > 0) {
        fwrite(str, 1, remaining, stdout);
    }
}
