#include "Configuration.h"
#include <QApplication>
#include <QDir>
#include <QFile>
#include <QFontDatabase>
#include <QJsonArray>
#include <QJsonObject>

#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
#include <KSyntaxHighlighting/Definition>
#include <KSyntaxHighlighting/Repository>
#include <KSyntaxHighlighting/Theme>
#endif

#include "common/ColorThemeWorker.h"
#include "common/ResourcePaths.h"
#include "common/SyntaxHighlighter.h"

#include <QMetaType>
#include <QVariant>

namespace {
static inline int variantTypeId(const QVariant &v)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    return v.metaType().id();
#else
    return v.userType();
#endif
}
} // namespace

/* Map with names of themes associated with its color palette
 * (Dark or Light), so for dark interface themes will be shown only Dark color
 * themes and for light - only light ones.
 */
const QHash<QString, ColorFlags> Configuration::relevantThemes
    = {{"ayu", DarkFlag},
       {"basic", DarkFlag},
       {"bluy", DarkFlag},
       {"bold", DarkFlag},
       {"bright", DarkFlag},
       {"cga", DarkFlag},
       {"consonance", DarkFlag},
       {"darkda", DarkFlag},
       {"focus", DarkFlag},
       {"gb", DarkFlag},
       {"lima", DarkFlag},
       {"monokai", DarkFlag},
       {"ogray", DarkFlag},
       {"onedark", DarkFlag},
       {"pink", DarkFlag},
       {"rasta", DarkFlag},
       {"sepia", DarkFlag},
       {"smyck", DarkFlag},
       {"solarized", DarkFlag},
       {"twilight", DarkFlag},
       {"zenburn", DarkFlag},
       // light themes
       {"iaito", LightFlag},
       {"dark", LightFlag},
       {"matrix", LightFlag},
       {"rasta", LightFlag},
       {"tango", LightFlag},
       {"white", LightFlag},
       {"white2", LightFlag}};
static const QString DEFAULT_LIGHT_COLOR_THEME = "iaito";
static const QString DEFAULT_DARK_COLOR_THEME = "ayu";

const QHash<QString, QHash<ColorFlags, QColor>> Configuration::iaitoOptionColors
    = {{"gui.cflow", {{DarkFlag, QColor(0xff, 0xff, 0xff)}, {LightFlag, QColor(0x00, 0x00, 0x00)}}},
       {"gui.dataoffset",
        {{DarkFlag, QColor(0xff, 0xff, 0xff)}, {LightFlag, QColor(0x00, 0x00, 0x00)}}},
       {"gui.imports",
        {{DarkFlag, QColor(0x32, 0x8c, 0xff)}, {LightFlag, QColor(0x32, 0x8c, 0xff)}}},
       {"gui.item_invalid",
        {{DarkFlag, QColor(0x9b, 0x9b, 0x9b)}, {LightFlag, QColor(0x9b, 0x9b, 0x9b)}}},
       {"gui.main", {{DarkFlag, QColor(0x00, 0x80, 0x00)}, {LightFlag, QColor(0x00, 0x80, 0x00)}}},
       {"gui.item_unsafe",
        {{DarkFlag, QColor(0xff, 0x81, 0x7b)}, {LightFlag, QColor(0xff, 0x81, 0x7b)}}},
       {"gui.item_thread_unsafe",
        {{DarkFlag, QColor(0x7b, 0x81, 0xff)}, {LightFlag, QColor(0x7b, 0x81, 0xff)}}},
       {"gui.navbar.seek",
        {{DarkFlag, QColor(0xe9, 0x56, 0x56)}, {LightFlag, QColor(0xff, 0x00, 0x00)}}},
       {"gui.navbar.pc",
        {{DarkFlag, QColor(0x42, 0xee, 0xf4)}, {LightFlag, QColor(0x42, 0xee, 0xf4)}}},
       {"gui.navbar.code",
        {{DarkFlag, QColor(0x82, 0xa8, 0x6f)}, {LightFlag, QColor(0x68, 0xc5, 0x45)}}},
       {"gui.navbar.str",
        {{DarkFlag, QColor(0x6f, 0x86, 0xd8)}, {LightFlag, QColor(0x45, 0x68, 0xe5)}}},
       {"gui.navbar.sym",
        {{DarkFlag, QColor(0xdd, 0xa3, 0x68)}, {LightFlag, QColor(0xe5, 0x96, 0x45)}}},
       {"gui.navbar.empty",
        {{DarkFlag, QColor(0x64, 0x64, 0x64)}, {LightFlag, QColor(0xdc, 0xec, 0xf5)}}},
       {"gui.breakpoint_background",
        {{DarkFlag, QColor(0x8c, 0x4c, 0x4c)}, {LightFlag, QColor(0xe9, 0x8f, 0x8f)}}},
       {"gui.overview.node",
        {{DarkFlag, QColor(0x64, 0x64, 0x64)}, {LightFlag, QColor(0xf5, 0xfa, 0xff)}}},
       {"gui.tooltip.background",
        {{DarkFlag, QColor(0x2a, 0x2c, 0x2e)}, {LightFlag, QColor(0xfa, 0xfc, 0xfe)}}},
       {"gui.tooltip.foreground",
        {{DarkFlag, QColor(0xfa, 0xfc, 0xfe)}, {LightFlag, QColor(0x2a, 0x2c, 0x2e)}}},
       {"gui.border", {{DarkFlag, QColor(0x64, 0x64, 0x64)}, {LightFlag, QColor(0x91, 0xc8, 0xfa)}}},
       {"gui.background",
        {{DarkFlag, QColor(0x25, 0x28, 0x2b)}, {LightFlag, QColor(0xff, 0xff, 0xff)}}},
       {"gui.alt_background",
        {{DarkFlag, QColor(0x1c, 0x1f, 0x24)}, {LightFlag, QColor(0xf5, 0xfa, 0xff)}}},
       {"gui.disass_selected",
        {{DarkFlag, QColor(0x1f, 0x22, 0x28)}, {LightFlag, QColor(0xff, 0xff, 0xff)}}},
       {"lineHighlight",
        {{DarkFlag, QColor(0x15, 0x1d, 0x1d, 0x96)}, {LightFlag, QColor(0xd2, 0xd2, 0xff, 0x96)}}},
       {"wordHighlight",
        {{DarkFlag, QColor(0x34, 0x3a, 0x47, 0xff)}, {LightFlag, QColor(0xb3, 0x77, 0xd6, 0x3c)}}},
       {"highlightPC",
        {{DarkFlag, QColor(0x57, 0x1a, 0x07)}, {LightFlag, QColor(0xd6, 0xff, 0xd2)}}},
       {"gui.overview.fill",
        {{DarkFlag, QColor(0xff, 0xff, 0xff, 0x28)}, {LightFlag, QColor(0xaf, 0xd9, 0xea, 0x41)}}},
       {"gui.overview.border",
        {{DarkFlag, QColor(0x63, 0xda, 0xe8, 0x32)}, {LightFlag, QColor(0x63, 0xda, 0xe8, 0x32)}}},
       {"gui.navbar.err",
        {{DarkFlag, QColor(0x03, 0xaa, 0xf5)}, {LightFlag, QColor(0x03, 0xaa, 0xf5)}}}};

Configuration *Configuration::mPtr = nullptr;

/**
 * @brief All asm.* options saved as settings. Values are the default values.
 */
static const QHash<QString, QVariant> asmOptions
    = {{"asm.esil", false},
       {"asm.pseudo", false},
       {"asm.xrefs", false},
       {"asm.indent", false},
       {"asm.describe", false},
       {"asm.slow", true},
       {"asm.lines", true},
       {"asm.lines.fcn", true},
       {"asm.flags.offset", false},
       {"asm.emu", false},
       {"emu.str", true},
       {"asm.cmt.right", true},
       {"asm.cmt.col", 60},
       {"asm.var.summary", 4},
       {"asm.bytes", false},
       {"asm.size", false},
       {"asm.bytes.space", false},
       {"asm.lbytes", true},
       {"asm.nbytes", 10},
       {"asm.syntax", "intel"},
       {"asm.ucase", false},
       {"asm.lines.bb", false},
       {"asm.capitalize", false},
       {"asm.sub.var", true},
       {"asm.sub.varonly", true},
       {"asm.tabs", 8},
       {"asm.tabs.off", 5},
       {"asm.marks", false},
       {"asm.refptr", false},
       {"asm.flags.real", true},
       {"esil.breakoninvalid", false},
#if R2_VERSION_NUMBER >= 50909
       {"graph.addr", true},
       {"asm.addr", true},
       {"asm.addr.relto", ""},
       {"asm.flags.addr", false}
#else
       {"graph.offset", true},
       {"asm.offset", true},
       {"asm.offset.relative", false},
       {"asm.offset.flags", false}
#endif
};

Configuration::Configuration()
    : QObject()
    , nativePalette(qApp->palette())
{
    mPtr = this;
    if (!s.isWritable()) {
        QMessageBox::critical(
            nullptr,
            tr("Critical!"),
            tr("!!! Settings are not writable! Make sure you "
               "have a write access to \"%1\"")
                .arg(s.fileName()));
    }
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
    kSyntaxHighlightingRepository = nullptr;
#endif
}

Configuration *Configuration::instance()
{
    if (!mPtr)
        mPtr = new Configuration();
    return mPtr;
}

void Configuration::loadInitial()
{
    setInterfaceTheme(getInterfaceTheme());
    setColorTheme(getColorTheme());
    applySavedAsmOptions();

#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
    kSyntaxHighlightingRepository = new KSyntaxHighlighting::Repository();
#endif
}

QString Configuration::getDirProjects()
{
    auto projectsDir = s.value("dir.projects").toString();
    if (projectsDir.isEmpty()) {
        projectsDir = Core()->getConfig("dir.projects");
        setDirProjects(projectsDir);
    }

    return QDir::toNativeSeparators(projectsDir);
}

void Configuration::setDirProjects(const QString &dir)
{
    s.setValue("dir.projects", QDir::toNativeSeparators(dir));
}

QString Configuration::getRecentFolder()
{
    QString recentFolder = s.value("dir.recentFolder", QDir::homePath()).toString();

    return QDir::toNativeSeparators(recentFolder);
}

void Configuration::setRecentFolder(const QString &dir)
{
    s.setValue("dir.recentFolder", QDir::toNativeSeparators(dir));
}

/**
 * @brief Configuration::setFilesTabLastClicked
 * Set the new file dialog last clicked tab
 * @param lastClicked
 */
void Configuration::setNewFileLastClicked(int lastClicked)
{
    s.setValue("newFileLastClicked", lastClicked);
}

int Configuration::getNewFileLastClicked()
{
    return s.value("newFileLastClicked").toInt();
}

void Configuration::resetAll()
{
    // Don't reset all r2 vars, that currently breaks a bunch of stuff.
    // settingsFile.remove()+loadInitials() should reset all settings
    // configurable using Iaito GUI.
    // Core()->cmdRaw("e-");

    Core()->setSettings();
    // Delete the file so no extra configuration is in it.
    QFile settingsFile(s.fileName());
    settingsFile.remove();
    s.clear();

    loadInitial();
    emit fontsUpdated();
}

bool Configuration::getAutoUpdateEnabled() const
{
    return s.value("autoUpdateEnabled", false).toBool();
}

void Configuration::setAutoUpdateEnabled(bool au)
{
    s.setValue("autoUpdateEnabled", au);
}

/**
 * @brief get the current Locale set in Iaito's user configuration
 * @return a QLocale object describes user's current locale
 */
QLocale Configuration::getCurrLocale() const
{
    return s.value("locale", QLocale().system()).toLocale();
}

/**
 * @brief sets Iaito's locale
 * @param l - a QLocale object describes the locate to configure
 */
void Configuration::setLocale(const QLocale &l)
{
    s.setValue("locale", l);
}

/**
 * @brief set Iaito's interface language by a given locale name
 * @param language - a string represents the name of a locale language
 * @return true on success
 */
bool Configuration::setLocaleByName(const QString &language)
{
    const auto &allLocales
        = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);

    for (auto &it : allLocales) {
        if (QString::compare(it.nativeLanguageName(), language, Qt::CaseInsensitive) == 0) {
            setLocale(it);
            return true;
        }
    }
    return false;
}

bool Configuration::windowColorIsDark()
{
    ColorFlags currentThemeColorFlags = getCurrentTheme()->flag;
    if (currentThemeColorFlags == ColorFlags::LightFlag) {
        return false;
    } else if (currentThemeColorFlags == ColorFlags::DarkFlag) {
        return true;
    }
    return nativeWindowIsDark();
}

bool Configuration::nativeWindowIsDark()
{
    const QPalette &palette = qApp->palette();
    auto windowColor = palette.color(QPalette::Window).toRgb();
    return (windowColor.red() + windowColor.green() + windowColor.blue()) < 382;
}

void Configuration::loadNativeStylesheet()
{
    /* Load Qt Theme */
    QFile f(":native/native.qss");
    if (!f.exists()) {
        qWarning() << "Can't find Native theme stylesheet.";
    } else {
        f.open(QFile::ReadOnly | QFile::Text);
        QTextStream ts(&f);
        QString stylesheet = ts.readAll();
        qApp->setStyleSheet(stylesheet);
    }

    qApp->setPalette(nativePalette);
    /* Some widgets does not change its palette when QApplication changes one
     * so this loop force all widgets do this, but all widgets take palette from
     * QApplication::palette() when they are created so line above is necessary
     * too.
     */
    for (auto widget : qApp->allWidgets()) {
        widget->setPalette(nativePalette);
    }
}

/**
 * @brief Loads the Light theme of Iaito and modify special theme colors
 */
void Configuration::loadLightStylesheet()
{
    /* Load Qt Theme */
    QFile f(":lightstyle/light.qss");
    if (!f.exists()) {
        qWarning() << "Can't find Light theme stylesheet.";
    } else {
        f.open(QFile::ReadOnly | QFile::Text);
        QTextStream ts(&f);
        QString stylesheet = ts.readAll();

        QPalette p = qApp->palette();
        p.setColor(QPalette::Text, Qt::black);
        qApp->setPalette(p);

        qApp->setStyleSheet(stylesheet);
    }
}

void Configuration::loadDarkStylesheet()
{
    /* Load Qt Theme */
    QFile f(":qdarkstyle/style.qss");
    if (!f.exists()) {
        qWarning() << "Can't find Dark theme stylesheet.";
    } else {
        f.open(QFile::ReadOnly | QFile::Text);
        QTextStream ts(&f);
        QString stylesheet = ts.readAll();
#ifdef Q_OS_MACOS
        // see
        // https://github.com/ColinDuquesnoy/QDarkStyleSheet/issues/22#issuecomment-96179529
        stylesheet += "QDockWidget::title"
                      "{"
                      "    background-color: #31363b;"
                      "    text-align: center;"
                      "    height: 12px;"
                      "}";
#endif
        QPalette p = qApp->palette();
        p.setColor(QPalette::Text, Qt::white);
        qApp->setPalette(p);
        qApp->setStyleSheet(stylesheet);
    }
}

void Configuration::loadMidnightStylesheet()
{
    /* Load Qt Theme */
    QFile f(":midnight/style.css");
    if (!f.exists()) {
        qWarning() << "Can't find Midnight theme stylesheet.";
    } else {
        f.open(QFile::ReadOnly | QFile::Text);
        QTextStream ts(&f);
        QString stylesheet = ts.readAll();

        QPalette p = qApp->palette();
        p.setColor(QPalette::Text, Qt::white);
        qApp->setPalette(p);

        qApp->setStyleSheet(stylesheet);
    }
}

const QFont Configuration::getBaseFont() const
{
    QFont font = s.value("font", QFont("Inconsolata", 11)).value<QFont>();
    return font;
}

const QFont Configuration::getFont() const
{
    QFont font = getBaseFont();
    font.setPointSizeF(font.pointSizeF() * getZoomFactor());
    return font;
}

void Configuration::setFont(const QFont &font)
{
    s.setValue("font", font);
    emit fontsUpdated();
}

void Configuration::refreshFont()
{
    emit fontsUpdated();
}

qreal Configuration::getZoomFactor() const
{
    qreal fontZoom = s.value("zoomFactor", 1.0).value<qreal>();
    return qMax(fontZoom, 0.1);
}

void Configuration::setZoomFactor(qreal zoom)
{
    s.setValue("zoomFactor", qMax(zoom, 0.1));
    emit fontsUpdated();
}

QString Configuration::getLastThemeOf(const IaitoInterfaceTheme &currInterfaceTheme) const
{
    return s.value("lastThemeOf." + currInterfaceTheme.name, Config()->getColorTheme()).toString();
}

void Configuration::setInterfaceTheme(int theme)
{
    if (theme >= iaitoInterfaceThemesList().size() || theme < 0) {
        theme = 0;
    }
    s.setValue("ColorPalette", theme);

    IaitoInterfaceTheme interfaceTheme = iaitoInterfaceThemesList()[theme];

    if (interfaceTheme.name == "Native") {
        loadNativeStylesheet();
    } else if (interfaceTheme.name == "Dark") {
        loadDarkStylesheet();
    } else if (interfaceTheme.name == "Midnight") {
        loadMidnightStylesheet();
    } else if (interfaceTheme.name == "Light") {
        loadLightStylesheet();
    } else {
        loadNativeStylesheet();
    }

    for (auto it = iaitoOptionColors.cbegin(); it != iaitoOptionColors.cend(); it++) {
        setColor(it.key(), it.value()[interfaceTheme.flag]);
    }

    adjustColorThemeDarkness();

    emit interfaceThemeChanged();
    emit colorsUpdated();
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
    emit kSyntaxHighlightingThemeChanged();
#endif
}

const IaitoInterfaceTheme *Configuration::getCurrentTheme()
{
    int i = getInterfaceTheme();
    if (i < 0 || i >= iaitoInterfaceThemesList().size()) {
        i = 0;
        setInterfaceTheme(i);
    }
    return &iaitoInterfaceThemesList()[i];
}

#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
KSyntaxHighlighting::Repository *Configuration::getKSyntaxHighlightingRepository()
{
    return kSyntaxHighlightingRepository;
}

KSyntaxHighlighting::Theme Configuration::getKSyntaxHighlightingTheme()
{
    auto repo = getKSyntaxHighlightingRepository();
    if (!repo) {
        return KSyntaxHighlighting::Theme();
    }
    return repo->defaultTheme(
        getCurrentTheme()->flag & DarkFlag
            ? KSyntaxHighlighting::Repository::DefaultTheme::DarkTheme
            : KSyntaxHighlighting::Repository::DefaultTheme::LightTheme);
}
#endif

QSyntaxHighlighter *Configuration::createSyntaxHighlighter(QTextDocument *document)
{
#ifdef IAITO_ENABLE_KSYNTAXHIGHLIGHTING
    auto syntaxHighlighter = new SyntaxHighlighter(document);
    auto repo = getKSyntaxHighlightingRepository();
    if (repo) {
        syntaxHighlighter->setDefinition(repo->definitionForName("C"));
    }
    return syntaxHighlighter;
#else
    return new FallbackSyntaxHighlighter(document);
#endif
}

QString Configuration::getLogoFile()
{
    return windowColorIsDark() ? QStringLiteral(":/img/iaito-o-light.svg")
                               : QStringLiteral(":/img/iaito-o.svg");
}

/**
 * @brief Configuration::setColor sets the local Iaito configuration color
 * @param name Color Name
 * @param color The color you want to set
 */
void Configuration::setColor(const QString &name, const QColor &color)
{
    s.setValue("colors." + name, color);
}

void Configuration::setLastThemeOf(
    const IaitoInterfaceTheme &currInterfaceTheme, const QString &theme)
{
    s.setValue("lastThemeOf." + currInterfaceTheme.name, theme);
}

const QColor Configuration::getColor(const QString &name) const
{
    if (s.contains("colors." + name)) {
        return s.value("colors." + name).value<QColor>();
    } else {
        return s.value("colors.other").value<QColor>();
    }
}

void Configuration::setColorTheme(const QString &theme)
{
    if (theme == "default") {
        Core()->cmdRaw("ecd");
        s.setValue("theme", "default");
    } else {
        Core()->cmdRaw(QStringLiteral("eco %1").arg(theme));
        s.setValue("theme", theme);
    }

    QJsonObject colorTheme = ThemeWorker().getTheme(theme).object();
    for (auto it = colorTheme.constBegin(); it != colorTheme.constEnd(); it++) {
        QJsonArray rgb = it.value().toArray();
        if (rgb.size() != 4) {
            continue;
        }
        setColor(it.key(), QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt(), rgb[3].toInt()));
    }

    emit colorsUpdated();
}

void Configuration::adjustColorThemeDarkness()
{
    bool windowIsDark = windowColorIsDark();
    int windowDarkness = windowIsDark ? DarkFlag : LightFlag;
    int currentColorThemeDarkness = colorThemeDarkness(getColorTheme());

    if ((currentColorThemeDarkness & windowDarkness) == 0) {
        setColorTheme(windowIsDark ? DEFAULT_DARK_COLOR_THEME : DEFAULT_LIGHT_COLOR_THEME);
    }
}

int Configuration::colorThemeDarkness(const QString &colorTheme) const
{
    auto flags = relevantThemes.find(colorTheme);
    if (flags != relevantThemes.end()) {
        return static_cast<int>(*flags);
    }
    return DarkFlag | LightFlag;
}

void Configuration::resetToDefaultAsmOptions()
{
    for (auto it = asmOptions.cbegin(); it != asmOptions.cend(); it++) {
        setConfig(it.key(), it.value());
    }
}

void Configuration::applySavedAsmOptions()
{
    for (auto it = asmOptions.cbegin(); it != asmOptions.cend(); it++) {
        Core()->setConfig(it.key(), s.value(it.key(), it.value()));
    }
}

const QList<IaitoInterfaceTheme> &Configuration::iaitoInterfaceThemesList()
{
    static const QList<IaitoInterfaceTheme> list
        = {{"Native", Configuration::nativeWindowIsDark() ? DarkFlag : LightFlag},
           {"Dark", DarkFlag},
           {"Midnight", DarkFlag},
           {"Light", LightFlag}};
    return list;
}

QVariant Configuration::getConfigVar(const QString &key)
{
    QHash<QString, QVariant>::const_iterator it = asmOptions.find(key);
    if (it != asmOptions.end()) {
        switch (variantTypeId(it.value())) {
        case QMetaType::Bool:
            return Core()->getConfigb(key);
        case QMetaType::Int:
            return Core()->getConfigi(key);
        default:
            return Core()->getConfig(key);
        }
    }
    return QVariant();
}

bool Configuration::getConfigBool(const QString &key)
{
    return getConfigVar(key).toBool();
}

int Configuration::getConfigInt(const QString &key)
{
    return getConfigVar(key).toInt();
}

QString Configuration::getConfigString(const QString &key)
{
    return getConfigVar(key).toString();
}

/**
 * @brief Configuration::setConfig
 * Set radare2 configuration value (e.g. "asm.lines")
 * @param key
 * @param value
 */
void Configuration::setConfig(const QString &key, const QVariant &value)
{
    if (asmOptions.contains(key)) {
        s.setValue(key, value);
    }

    Core()->setConfig(key, value);
}

/**
 * @brief this function will gather and return available translation for Iaito
 * @return a list of all available translations
 */
QStringList Configuration::getAvailableTranslations()
{
    const auto &trDirs = Iaito::getTranslationsDirectories();
    QSet<QString> fileNamesSet;
    for (const auto &trDir : trDirs) {
        QDir dir(trDir);
        if (!dir.exists()) {
            continue;
        }
        const QStringList &currTrFileNames
            = dir.entryList(QStringList("iaito_*.qm"), QDir::Files, QDir::Name);
        for (const auto &trFile : currTrFileNames) {
            fileNamesSet << trFile;
        }
    }

    QStringList fileNames = fileNamesSet.values();
    std::sort(fileNames.begin(), fileNames.end());
    QStringList languages;
    QString currLanguageName;
    auto allLocales
        = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);

    for (auto i : fileNames) {
        QString localeName = i.mid(sizeof("iaito_") - 1, 2);
        for (auto j : allLocales) {
            if (j.name().startsWith(localeName)) {
                currLanguageName = j.nativeLanguageName();
                if (currLanguageName == nullptr) {
                    break;
                }
                currLanguageName = currLanguageName.at(0).toUpper()
                                   + currLanguageName.right(currLanguageName.length() - 1);
                languages << currLanguageName;
                break;
            }
        }
    }
    return languages << QLatin1String("English");
}

/**
 * @brief check if this is the first time Iaito's is executed on this computer
 * @return true if this is first execution; otherwise returns false.
 */
bool Configuration::isFirstExecution()
{
    // check if a variable named firstExecution existed in the configuration
    if (s.contains("firstExecution")) {
        return false;
    } else {
        s.setValue("firstExecution", false);
        return true;
    }
}

QString Configuration::getSelectedDecompiler()
{
    return s.value("selectedDecompiler").toString();
}

void Configuration::setSelectedDecompiler(const QString &id)
{
    s.setValue("selectedDecompiler", id);
}

bool Configuration::getDecompilerAutoRefreshEnabled()
{
    return s.value("decompilerAutoRefresh", true).toBool();
}

void Configuration::setDecompilerAutoRefreshEnabled(bool enabled)
{
    s.setValue("decompilerAutoRefresh", enabled);
}

bool Configuration::getDecompilerRunInBackground()
{
    // Disabled by default: run synchronously (safer)
    return s.value("decompilerRunInBackground", false).toBool();
}

void Configuration::setDecompilerRunInBackground(bool enabled)
{
    s.setValue("decompilerRunInBackground", enabled);
}

void Configuration::enableDecompilerAnnotationHighlighter(bool useDecompilerHighlighter)
{
    s.setValue("decompilerAnnotationHighlighter", !useDecompilerHighlighter);
    emit colorsUpdated();
}

bool Configuration::isDecompilerAnnotationHighlighterEnabled()
{
    return s.value("decompilerAnnotationHighlighter", true).value<bool>();
}

bool Configuration::getBitmapTransparentState()
{
    return s.value("bitmapGraphExportTransparency", false).value<bool>();
}

double Configuration::getBitmapExportScaleFactor()
{
    return s.value("bitmapGraphExportScale", 1.0).value<double>();
}

void Configuration::setBitmapTransparentState(bool inputValueGraph)
{
    s.setValue("bitmapGraphExportTransparency", inputValueGraph);
}

void Configuration::setBitmapExportScaleFactor(double inputValueGraph)
{
    s.setValue("bitmapGraphExportScale", inputValueGraph);
}

void Configuration::setGraphSpacing(QPoint blockSpacing, QPoint edgeSpacing)
{
    s.setValue("graph.blockSpacing", blockSpacing);
    s.setValue("graph.edgeSpacing", edgeSpacing);
}

QPoint Configuration::getGraphBlockSpacing()
{
    return s.value("graph.blockSpacing", QPoint(20, 40)).value<QPoint>();
}

QPoint Configuration::getGraphEdgeSpacing()
{
    return s.value("graph.edgeSpacing", QPoint(10, 10)).value<QPoint>();
}

void Configuration::setOutputRedirectionEnabled(bool enabled)
{
    this->outputRedirectEnabled = enabled;
}

bool Configuration::getOutputRedirectionEnabled() const
{
    return outputRedirectEnabled;
}

bool Configuration::getGraphBlockEntryOffset()
{
    return s.value("graphBlockEntryOffset", true).value<bool>();
}

void Configuration::setGraphBlockEntryOffset(bool enabled)
{
    s.setValue("graphBlockEntryOffset", enabled);
}
