//  SPDX-License-Identifier: MIT
//
//  EmulationStation Desktop Edition
//  GuiScraperMenu.cpp
//
//  Game media scraper, including settings as well as the scraping start button.
//  Submenu to the GuiMenu main menu.
//  Will call GuiScraperMulti to perform the actual scraping.
//

#include "guis/GuiScraperMenu.h"

#include "components/OptionListComponent.h"
#include "components/SwitchComponent.h"
#include "guis/GuiMsgBox.h"
#include "guis/GuiScraperMulti.h"
#include "guis/GuiSettings.h"
#include "views/ViewController.h"
#include "FileData.h"
#include "SystemData.h"


GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
        : GuiComponent(window), mMenu(window, title)
{
    // Scraper service.
    mScraper = std::make_shared<OptionListComponent<std::string>>
            (mWindow, getHelpStyle(), "SCRAPE FROM", false);
    std::vector<std::string> scrapers = getScraperList();
    // Select either the first entry or the one read from the settings,
    // just in case the scraper from settings has vanished.
    for (auto it = scrapers.cbegin(); it != scrapers.cend(); it++)
        mScraper->add(*it, *it, *it == Settings::getInstance()->getString("Scraper"));
    mMenu.addWithLabel("SCRAPE FROM", mScraper);

    // Search filters, getSearches() will generate a queue of games to scrape
    // based on the outcome of the checks below.
    mFilters = std::make_shared< OptionListComponent<GameFilterFunc>>
            (mWindow, getHelpStyle(), "SCRAPE THESE GAMES", false);
    mFilters->add("ALL GAMES", [](SystemData*, FileData*) -> bool { return true; }, false);
    mFilters->add("FAVORITE GAMES", [](SystemData*, FileData* g) -> bool {
        return g->getFavorite(); }, false);
    mFilters->add("NO METADATA", [](SystemData*, FileData* g) -> bool {
        return g->metadata.get("desc").empty(); }, false);
    mFilters->add("NO GAME IMAGE",
            [](SystemData*, FileData* g) -> bool {
        return g->getImagePath().empty(); }, false);
    mFilters->add("NO GAME VIDEO",
            [](SystemData*, FileData* g) -> bool {
        return g->getVideoPath().empty(); }, false);
    mFilters->add("FOLDERS ONLY",
            [](SystemData*, FileData* g) -> bool {
        return g->getType() == FOLDER; }, false);

    mFilters->selectEntry(Settings::getInstance()->getInt("ScraperFilter"));
    mMenu.addWithLabel("SCRAPE THESE GAMES", mFilters);

    mMenu.addSaveFunc([this] {
        if (mScraper->getSelected() != Settings::getInstance()->getString("Scraper")) {
            Settings::getInstance()->setString("Scraper", mScraper->getSelected());
            mMenu.setNeedsSaving();
        }
        // The filter setting is only retained during the program session i.e. it's not saved
        // to es_settings.cfg.
        if (mFilters->getSelectedId() != Settings::getInstance()->getInt("ScraperFilter"))
            Settings::getInstance()->setInt("ScraperFilter", mFilters->getSelectedId());
    });

    // Add systems (all systems with an existing platform ID are listed).
    mSystems = std::make_shared< OptionListComponent<SystemData*>>
            (mWindow, getHelpStyle(), "SCRAPE THESE SYSTEMS", true);
    for (unsigned int i = 0; i < SystemData::sSystemVector.size(); i++) {
        if (!SystemData::sSystemVector[i]->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) {
            mSystems->add(SystemData::sSystemVector[i]->getFullName(),
                    SystemData::sSystemVector[i],
                    !SystemData::sSystemVector[i]->getPlatformIds().empty());
            SystemData::sSystemVector[i]->getScrapeFlag() ?
                    mSystems->selectEntry(i) : mSystems->unselectEntry(i);
        }
    }
    mMenu.addWithLabel("SCRAPE THESE SYSTEMS", mSystems);

    addEntry("ACCOUNT SETTINGS", 0x777777FF, true, [this] {
        openAccountOptions();
    });
    addEntry("CONTENT SETTINGS", 0x777777FF, true, [this] {
        // If the scraper service has been changed before entering this menu, then save the
        // settings so that the specific options supported by the respective scrapers
        // can be enabled or disabled.
        if (mScraper->getSelected() != Settings::getInstance()->getString("Scraper"))
            mMenu.save();
        openContentOptions();
    });
    addEntry("MIXIMAGE SETTINGS", 0x777777FF, true, [this] {
        openMiximageOptions();
    });
    addEntry("OTHER SETTINGS", 0x777777FF, true, [this] {
        // If the scraper service has been changed before entering this menu, then save the
        // settings so that the specific options supported by the respective scrapers
        // can be enabled or disabled.
        if (mScraper->getSelected() != Settings::getInstance()->getString("Scraper"))
            mMenu.save();
        openOtherOptions();
    });

    addChild(&mMenu);

    mMenu.addButton("START", "start", std::bind(&GuiScraperMenu::pressedStart, this));
    mMenu.addButton("BACK", "back", [&] { delete this; });

    setSize(mMenu.getSize());

    setPosition((Renderer::getScreenWidth() - mSize.x()) / 2,
            Renderer::getScreenHeight() * 0.13f);
}

GuiScraperMenu::~GuiScraperMenu()
{
    // Save the scrape flags to the system settings so that they are
    // remembered throughout the program session.
    std::vector<SystemData*> sys = mSystems->getSelectedObjects();
    for (auto it = SystemData::sSystemVector.cbegin();
            it != SystemData::sSystemVector.cend(); it++) {
            (*it)->setScrapeFlag(false);
        for (auto it_sys = sys.cbegin(); it_sys != sys.cend(); it_sys++) {
            if ((*it)->getFullName() == (*it_sys)->getFullName())
                (*it)->setScrapeFlag(true);
        }
    }
}

void GuiScraperMenu::openAccountOptions()
{
    auto s = new GuiSettings(mWindow, "ACCOUNT SETTINGS");

    // Whether to use the ScreenScraper account when scraping.
    auto scraper_use_account_screenscraper = std::make_shared<SwitchComponent>(mWindow);
    scraper_use_account_screenscraper->setState(Settings::getInstance()->
            getBool("ScraperUseAccountScreenScraper"));
    s->addWithLabel("USE THIS ACCOUNT FOR SCREENSCRAPER", scraper_use_account_screenscraper);
    s->addSaveFunc([scraper_use_account_screenscraper, s] {
        if (scraper_use_account_screenscraper->getState() !=
                Settings::getInstance()->getBool("ScraperUseAccountScreenScraper")) {
            Settings::getInstance()->setBool("ScraperUseAccountScreenScraper",
                    scraper_use_account_screenscraper->getState());
            s->setNeedsSaving();
        }
    });

    // ScreenScraper username.
    auto scraper_username_screenscraper = std::make_shared<TextComponent>(mWindow, "",
            Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT);
    s->addEditableTextComponent("SCREENSCRAPER USERNAME", scraper_username_screenscraper,
            Settings::getInstance()->getString("ScraperUsernameScreenScraper"));
    s->addSaveFunc([scraper_username_screenscraper, s] {
        if (scraper_username_screenscraper->getValue() !=
                Settings::getInstance()->getString("ScraperUsernameScreenScraper")) {
            Settings::getInstance()->setString("ScraperUsernameScreenScraper",
                    scraper_username_screenscraper->getValue());
            s->setNeedsSaving();
        }
    });

    // ScreenScraper password.
    auto scraper_password_screenscraper = std::make_shared<TextComponent>(mWindow, "",
            Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT);
    std::string passwordMasked;
    if (Settings::getInstance()->getString("ScraperPasswordScreenScraper") != "") {
        passwordMasked = "********";
        scraper_password_screenscraper->setHiddenValue(
                Settings::getInstance()->getString("ScraperPasswordScreenScraper"));
    }
    s->addEditableTextComponent("SCREENSCRAPER PASSWORD",
            scraper_password_screenscraper, passwordMasked, "", true);
    s->addSaveFunc([scraper_password_screenscraper, s] {
        if (scraper_password_screenscraper->getHiddenValue() !=
                Settings::getInstance()->getString("ScraperPasswordScreenScraper")) {
            Settings::getInstance()->setString("ScraperPasswordScreenScraper",
                    scraper_password_screenscraper->getHiddenValue());
            s->setNeedsSaving();
        }
    });

    mWindow->pushGui(s);
}

void GuiScraperMenu::openContentOptions()
{
    auto s = new GuiSettings(mWindow, "CONTENT SETTINGS");

    // Scrape game names.
    auto scrape_game_names = std::make_shared<SwitchComponent>(mWindow);
    scrape_game_names->setState(Settings::getInstance()->getBool("ScrapeGameNames"));
    s->addWithLabel("SCRAPE GAME NAMES", scrape_game_names);
    s->addSaveFunc([scrape_game_names, s] {
        if (scrape_game_names->getState() != Settings::getInstance()->getBool("ScrapeGameNames")) {
            Settings::getInstance()->setBool("ScrapeGameNames", scrape_game_names->getState());
            s->setNeedsSaving();
        }
    });

    // Scrape ratings.
    auto scrape_ratings = std::make_shared<SwitchComponent>(mWindow);
    scrape_ratings->setState(Settings::getInstance()->getBool("ScrapeRatings"));
    s->addWithLabel("SCRAPE RATINGS", scrape_ratings);
    s->addSaveFunc([scrape_ratings, s] {
        if (scrape_ratings->getState() != Settings::getInstance()->getBool("ScrapeRatings")) {
            Settings::getInstance()->setBool("ScrapeRatings", scrape_ratings->getState());
            s->setNeedsSaving();
        }
    });

    // Ratings are not supported by TheGamesDB, so disable the option if this scraper is selected.
    if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
        scrape_ratings->setEnabled(false);
        scrape_ratings->setOpacity(DISABLED_OPACITY);
        scrape_ratings->getParent()->getChild(scrape_ratings->
                getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
    }

    // Scrape other metadata.
    auto scrape_metadata = std::make_shared<SwitchComponent>(mWindow);
    scrape_metadata->setState(Settings::getInstance()->getBool("ScrapeMetadata"));
    s->addWithLabel("SCRAPE OTHER METADATA", scrape_metadata);
    s->addSaveFunc([scrape_metadata, s] {
        if (scrape_metadata->getState() != Settings::getInstance()->getBool("ScrapeMetadata")) {
            Settings::getInstance()->setBool("ScrapeMetadata", scrape_metadata->getState());
            s->setNeedsSaving();
        }
    });

    // Scrape videos.
    auto scrape_videos = std::make_shared<SwitchComponent>(mWindow);
    scrape_videos->setState(Settings::getInstance()->getBool("ScrapeVideos"));
    s->addWithLabel("SCRAPE VIDEOS", scrape_videos);
    s->addSaveFunc([scrape_videos, s] {
        if (scrape_videos->getState() != Settings::getInstance()->getBool("ScrapeVideos")) {
            Settings::getInstance()->setBool("ScrapeVideos", scrape_videos->getState());
            s->setNeedsSaving();
        }
    });

    // Videos are not supported by TheGamesDB, so disable the option if this scraper is selected.
    if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
        scrape_videos->setEnabled(false);
        scrape_videos->setOpacity(DISABLED_OPACITY);
        scrape_videos->getParent()->getChild(scrape_videos->
                getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
    }

    // Scrape screenshots images.
    auto scrape_screenshots = std::make_shared<SwitchComponent>(mWindow);
    scrape_screenshots->setState(Settings::getInstance()->getBool("ScrapeScreenshots"));
    s->addWithLabel("SCRAPE SCREENSHOT IMAGES", scrape_screenshots);
    s->addSaveFunc([scrape_screenshots, s] {
        if (scrape_screenshots->getState() !=
                Settings::getInstance()->getBool("ScrapeScreenshots")) {
            Settings::getInstance()->setBool("ScrapeScreenshots", scrape_screenshots->getState());
            s->setNeedsSaving();
        }
    });

    // Scrape cover images.
    auto scrape_covers = std::make_shared<SwitchComponent>(mWindow);
    scrape_covers->setState(Settings::getInstance()->getBool("ScrapeCovers"));
    s->addWithLabel("SCRAPE BOX COVER IMAGES", scrape_covers);
    s->addSaveFunc([scrape_covers, s] {
        if (scrape_covers->getState() != Settings::getInstance()->getBool("ScrapeCovers")) {
            Settings::getInstance()->setBool("ScrapeCovers", scrape_covers->getState());
            s->setNeedsSaving();
        }
    });

    // Scrape marquee images.
    auto scrape_marquees = std::make_shared<SwitchComponent>(mWindow);
    scrape_marquees->setState(Settings::getInstance()->getBool("ScrapeMarquees"));
    s->addWithLabel("SCRAPE MARQUEE (WHEEL) IMAGES", scrape_marquees);
    s->addSaveFunc([scrape_marquees, s] {
        if (scrape_marquees->getState() != Settings::getInstance()->getBool("ScrapeMarquees")) {
            Settings::getInstance()->setBool("ScrapeMarquees", scrape_marquees->getState());
            s->setNeedsSaving();
        }
    });

    // Scrape 3D box images.
    auto scrape_3dboxes = std::make_shared<SwitchComponent>(mWindow);
    scrape_3dboxes->setState(Settings::getInstance()->getBool("Scrape3DBoxes"));
    s->addWithLabel("SCRAPE 3D BOX IMAGES", scrape_3dboxes);
    s->addSaveFunc([scrape_3dboxes, s] {
        if (scrape_3dboxes->getState() != Settings::getInstance()->getBool("Scrape3DBoxes")) {
            Settings::getInstance()->setBool("Scrape3DBoxes", scrape_3dboxes->getState());
            s->setNeedsSaving();
        }
    });

    // 3D box images are not supported by TheGamesDB, so disable the option if this scraper
    // is selected.
    if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
        scrape_3dboxes->setEnabled(false);
        scrape_3dboxes->setOpacity(DISABLED_OPACITY);
        scrape_3dboxes->getParent()->getChild(scrape_3dboxes->
                getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
    }

    mWindow->pushGui(s);
}

void GuiScraperMenu::openMiximageOptions()
{
    auto s = new GuiSettings(mWindow, "MIXIMAGE SETTINGS");

    // Miximage resolution.
    auto miximage_resolution = std::make_shared<OptionListComponent<std::string>>
            (mWindow, getHelpStyle(), "MIXIMAGE RESOLUTION", false);
    std::string selectedResolution = Settings::getInstance()->getString("MiximageResolution");
    miximage_resolution->add("1280x960",  "1280x960",  selectedResolution == "1280x960");
    miximage_resolution->add("1920x1440", "1920x1440",  selectedResolution == "1920x1440");
    miximage_resolution->add("640x480",   "640x480",  selectedResolution == "640x480");
    // If there are no objects returned, then there must be a manually modified entry in the
    // configuration file. Simply set the resolution to "1280x960" in this case.
    if (miximage_resolution->getSelectedObjects().size() == 0)
        miximage_resolution->selectEntry(0);
    s->addWithLabel("MIXIMAGE RESOLUTION", miximage_resolution);
    s->addSaveFunc([miximage_resolution, s] {
        if (miximage_resolution->getSelected() !=
                Settings::getInstance()->getString("MiximageResolution")) {
            Settings::getInstance()->
                    setString("MiximageResolution", miximage_resolution->getSelected());
            s->setNeedsSaving();
        }
    });

    // Screenshot scaling method.
    auto miximage_scaling = std::make_shared<OptionListComponent<std::string>>
            (mWindow, getHelpStyle(), "SCREENSHOT SCALING", false);
    std::string selectedScaling = Settings::getInstance()->getString("MiximageScreenshotScaling");
    miximage_scaling->add("sharp",  "sharp",  selectedScaling == "sharp");
    miximage_scaling->add("smooth", "smooth",  selectedScaling == "smooth");
    // If there are no objects returned, then there must be a manually modified entry in the
    // configuration file. Simply set the scaling method to "sharp" in this case.
    if (miximage_scaling->getSelectedObjects().size() == 0)
        miximage_scaling->selectEntry(0);
    s->addWithLabel("SCREENSHOT SCALING METHOD", miximage_scaling);
    s->addSaveFunc([miximage_scaling, s] {
        if (miximage_scaling->getSelected() !=
                Settings::getInstance()->getString("MiximageScreenshotScaling")) {
            Settings::getInstance()->
                    setString("MiximageScreenshotScaling", miximage_scaling->getSelected());
            s->setNeedsSaving();
        }
    });

    // Whether to generate miximages when scraping.
    auto miximage_generate = std::make_shared<SwitchComponent>(mWindow);
    miximage_generate->setState(Settings::getInstance()->getBool("MiximageGenerate"));
    s->addWithLabel("GENERATE MIXIMAGES WHEN SCRAPING", miximage_generate);
    s->addSaveFunc([miximage_generate, s] {
        if (miximage_generate->getState() !=
                Settings::getInstance()->getBool("MiximageGenerate")) {
            Settings::getInstance()->setBool("MiximageGenerate", miximage_generate->getState());
            s->setNeedsSaving();
        }
    });

    // Whether to overwrite miximages (both for the scraper and offline generator).
    auto miximage_overwrite = std::make_shared<SwitchComponent>(mWindow);
    miximage_overwrite->setState(Settings::getInstance()->getBool("MiximageOverwrite"));
    s->addWithLabel("OVERWRITE MIXIMAGES (SCRAPER/OFFLINE GENERATOR)", miximage_overwrite);
    s->addSaveFunc([miximage_overwrite, s] {
        if (miximage_overwrite->getState() !=
                Settings::getInstance()->getBool("MiximageOverwrite")) {
            Settings::getInstance()->setBool("MiximageOverwrite", miximage_overwrite->getState());
            s->setNeedsSaving();
        }
    });

    // Whether to remove letterboxes from the screenshots.
    auto remove_letterboxes = std::make_shared<SwitchComponent>(mWindow);
    remove_letterboxes->setState(Settings::getInstance()->getBool("MiximageRemoveLetterboxes"));
    s->addWithLabel("REMOVE LETTERBOXES FROM SCREENSHOTS", remove_letterboxes);
    s->addSaveFunc([remove_letterboxes, s] {
        if (remove_letterboxes->getState() !=
                Settings::getInstance()->getBool("MiximageRemoveLetterboxes")) {
            Settings::getInstance()->setBool("MiximageRemoveLetterboxes",
                    remove_letterboxes->getState());
            s->setNeedsSaving();
        }
    });

    // Whether to remove pillarboxes from the screenshots.
    auto remove_pillarboxes = std::make_shared<SwitchComponent>(mWindow);
    remove_pillarboxes->setState(Settings::getInstance()->getBool("MiximageRemovePillarboxes"));
    s->addWithLabel("REMOVE PILLARBOXES FROM SCREENSHOTS", remove_pillarboxes);
    s->addSaveFunc([remove_pillarboxes, s] {
        if (remove_pillarboxes->getState() !=
                Settings::getInstance()->getBool("MiximageRemovePillarboxes")) {
            Settings::getInstance()->setBool("MiximageRemovePillarboxes",
                    remove_pillarboxes->getState());
            s->setNeedsSaving();
        }
    });

    // Whether to include marquee images.
    auto miximage_marquee = std::make_shared<SwitchComponent>(mWindow);
    miximage_marquee->setState(Settings::getInstance()->getBool("MiximageIncludeMarquee"));
    s->addWithLabel("INCLUDE MARQUEE IMAGE", miximage_marquee);
    s->addSaveFunc([miximage_marquee, s] {
        if (miximage_marquee->getState() !=
                Settings::getInstance()->getBool("MiximageIncludeMarquee")) {
            Settings::getInstance()->
                    setBool("MiximageIncludeMarquee", miximage_marquee->getState());
            s->setNeedsSaving();
        }
    });

    // Whether to include box images.
    auto miximage_box = std::make_shared<SwitchComponent>(mWindow);
    miximage_box->setState(Settings::getInstance()->getBool("MiximageIncludeBox"));
    s->addWithLabel("INCLUDE BOX IMAGE", miximage_box);
    s->addSaveFunc([miximage_box, s] {
        if (miximage_box->getState() !=
                Settings::getInstance()->getBool("MiximageIncludeBox")) {
            Settings::getInstance()->
                    setBool("MiximageIncludeBox", miximage_box->getState());
            s->setNeedsSaving();
        }
    });

    // Whether to use cover image if there is no 3D box image.
    auto miximage_cover_fallback = std::make_shared<SwitchComponent>(mWindow);
    miximage_cover_fallback->setState(Settings::getInstance()->getBool("MiximageCoverFallback"));
    s->addWithLabel("USE COVER IMAGE IF 3D BOX IS MISSING", miximage_cover_fallback);
    s->addSaveFunc([miximage_cover_fallback, s] {
        if (miximage_cover_fallback->getState() !=
                Settings::getInstance()->getBool("MiximageCoverFallback")) {
            Settings::getInstance()->
                    setBool("MiximageCoverFallback", miximage_cover_fallback->getState());
            s->setNeedsSaving();
        }
    });

    mWindow->pushGui(s);
}

void GuiScraperMenu::openOtherOptions()
{
    auto s = new GuiSettings(mWindow, "OTHER SETTINGS");

    // Scraper region.
    auto scraper_region = std::make_shared<OptionListComponent<std::string>>
            (mWindow, getHelpStyle(), "REGION", false);
    std::string selectedScraperRegion = Settings::getInstance()->getString("ScraperRegion");
    scraper_region->add("Europe", "eu",  selectedScraperRegion == "eu");
    scraper_region->add("Japan",  "jp",  selectedScraperRegion == "jp");
    scraper_region->add("USA",    "us",  selectedScraperRegion == "us");
    scraper_region->add("World",  "wor", selectedScraperRegion == "wor");
    // If there are no objects returned, then there must be a manually modified entry in the
    // configuration file. Simply set the region to "Europe" in this case.
    if (scraper_region->getSelectedObjects().size() == 0)
        scraper_region->selectEntry(0);
    s->addWithLabel("REGION", scraper_region);
    s->addSaveFunc([scraper_region, s] {
        if (scraper_region->getSelected() != Settings::getInstance()->getString("ScraperRegion")) {
            Settings::getInstance()->setString("ScraperRegion", scraper_region->getSelected());
            s->setNeedsSaving();
        }
    });

    // Regions are not supported by TheGamesDB, so disable the option if this scraper is selected.
    if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
        scraper_region->setEnabled(false);
        scraper_region->setOpacity(DISABLED_OPACITY);
        scraper_region->getParent()->getChild(scraper_region->
                getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
    }

    // Scraper language.
    auto scraper_language = std::make_shared<OptionListComponent<std::string>>
            (mWindow, getHelpStyle(), "PREFERRED LANGUAGE", false);
    std::string selectedScraperLanguage = Settings::getInstance()->getString("ScraperLanguage");
    scraper_language->add("English",    "en", selectedScraperLanguage == "en");
    scraper_language->add("Español",    "es", selectedScraperLanguage == "es");
    scraper_language->add("Português",  "pt", selectedScraperLanguage == "pt");
    scraper_language->add("Français",   "fr", selectedScraperLanguage == "fr");
    scraper_language->add("Deutsch",    "de", selectedScraperLanguage == "de");
    scraper_language->add("Italiano",   "it", selectedScraperLanguage == "it");
    scraper_language->add("Nederlands", "nl", selectedScraperLanguage == "nl");
    scraper_language->add("日本語",      "ja", selectedScraperLanguage == "ja");
    scraper_language->add("简体中文",    "zh", selectedScraperLanguage == "zh");
    scraper_language->add("한국어",      "ko", selectedScraperLanguage == "ko");
    scraper_language->add("Русский",    "ru", selectedScraperLanguage == "ru");
    scraper_language->add("Dansk",      "da", selectedScraperLanguage == "da");
    scraper_language->add("Suomi",      "fi", selectedScraperLanguage == "fi");
    scraper_language->add("Svenska",    "sv", selectedScraperLanguage == "sv");
    scraper_language->add("Magyar",     "hu", selectedScraperLanguage == "hu");
    scraper_language->add("Norsk",      "no", selectedScraperLanguage == "no");
    scraper_language->add("Polski",     "pl", selectedScraperLanguage == "pl");
    scraper_language->add("Čeština",    "cz", selectedScraperLanguage == "cz");
    scraper_language->add("Slovenčina", "sk", selectedScraperLanguage == "sk");
    scraper_language->add("Türkçe",     "tr", selectedScraperLanguage == "tr");
    // If there are no objects returned, then there must be a manually modified entry in the
    // configuration file. Simply set the language to "English" in this case.
    if (scraper_language->getSelectedObjects().size() == 0)
        scraper_language->selectEntry(0);
    s->addWithLabel("PREFERRED LANGUAGE", scraper_language);
    s->addSaveFunc([scraper_language, s] {
        if (scraper_language->getSelected() !=
                Settings::getInstance()->getString("ScraperLanguage")) {
            Settings::getInstance()->setString("ScraperLanguage", scraper_language->getSelected());
            s->setNeedsSaving();
        }
    });

    // Languages are not supported by TheGamesDB, so disable the option if this scraper is selected.
    if (Settings::getInstance()->getString("Scraper") == "thegamesdb") {
        scraper_language->setEnabled(false);
        scraper_language->setOpacity(DISABLED_OPACITY);
        scraper_language->getParent()->getChild(scraper_language->
                getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
    }

    // Overwrite files and data.
    auto scraper_overwrite_data = std::make_shared<SwitchComponent>(mWindow);
    scraper_overwrite_data->setState(Settings::getInstance()->getBool("ScraperOverwriteData"));
    s->addWithLabel("OVERWRITE FILES AND DATA", scraper_overwrite_data);
    s->addSaveFunc([scraper_overwrite_data, s] {
        if (scraper_overwrite_data->getState() !=
                Settings::getInstance()->getBool("ScraperOverwriteData")) {
            Settings::getInstance()->setBool("ScraperOverwriteData",
                    scraper_overwrite_data->getState());
            s->setNeedsSaving();
        }
    });

    // Halt scraping on invalid media files.
    auto scraper_halt_on_invalid_media = std::make_shared<SwitchComponent>(mWindow);
    scraper_halt_on_invalid_media->
            setState(Settings::getInstance()->getBool("ScraperHaltOnInvalidMedia"));
    s->addWithLabel("HALT ON INVALID MEDIA FILES", scraper_halt_on_invalid_media);
    s->addSaveFunc([scraper_halt_on_invalid_media, s] {
        if (scraper_halt_on_invalid_media->getState() !=
                Settings::getInstance()->getBool("ScraperHaltOnInvalidMedia")) {
            Settings::getInstance()->setBool("ScraperHaltOnInvalidMedia",
                    scraper_halt_on_invalid_media->getState());
            s->setNeedsSaving();
        }
    });

    // Search using metadata names.
    auto scraper_search_metadata_name = std::make_shared<SwitchComponent>(mWindow);
    scraper_search_metadata_name->
            setState(Settings::getInstance()->getBool("ScraperSearchMetadataName"));
    s->addWithLabel("SEARCH USING METADATA NAMES", scraper_search_metadata_name);
    s->addSaveFunc([scraper_search_metadata_name, s] {
        if (scraper_search_metadata_name->getState() !=
                Settings::getInstance()->getBool("ScraperSearchMetadataName")) {
            Settings::getInstance()->setBool("ScraperSearchMetadataName",
                    scraper_search_metadata_name->getState());
            s->setNeedsSaving();
        }
    });

    // Interactive scraping.
    auto scraper_interactive = std::make_shared<SwitchComponent>(mWindow);
    scraper_interactive->setState(Settings::getInstance()->getBool("ScraperInteractive"));
    s->addWithLabel("INTERACTIVE MODE", scraper_interactive);
    s->addSaveFunc([scraper_interactive, s] {
        if (scraper_interactive->getState() !=
                Settings::getInstance()->getBool("ScraperInteractive")) {
            Settings::getInstance()->setBool("ScraperInteractive", scraper_interactive->getState());
            s->setNeedsSaving();
        }
    });

    // Semi-automatic scraping.
    auto scraper_semiautomatic = std::make_shared<SwitchComponent>(mWindow);
    scraper_semiautomatic->setState(Settings::getInstance()->getBool("ScraperSemiautomatic"));
    s->addWithLabel("AUTO-ACCEPT SINGLE GAME MATCHES", scraper_semiautomatic);
    s->addSaveFunc([scraper_semiautomatic ,s] {
         if (scraper_semiautomatic->getState() !=
                Settings::getInstance()->getBool("ScraperSemiautomatic")) {
            Settings::getInstance()->setBool("ScraperSemiautomatic",
                    scraper_semiautomatic->getState());
            s->setNeedsSaving();
        }
    });

    // If interactive mode is set to off, then disable this option.
    if (!Settings::getInstance()->getBool("ScraperInteractive")) {
        scraper_semiautomatic->setEnabled(false);
        scraper_semiautomatic->setOpacity(DISABLED_OPACITY);
        scraper_semiautomatic->getParent()->getChild(scraper_semiautomatic->
                getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
    }


    // Respect the per-file multi-scraper exclusion flag.
    auto scraper_respect_exclusions = std::make_shared<SwitchComponent>(mWindow);
    scraper_respect_exclusions->setState(
            Settings::getInstance()->getBool("ScraperRespectExclusions"));
    s->addWithLabel("RESPECT PER-FILE SCRAPER EXCLUSIONS", scraper_respect_exclusions);
    s->addSaveFunc([scraper_respect_exclusions, s] {
        if (scraper_respect_exclusions->getState() !=
                Settings::getInstance()->getBool("ScraperRespectExclusions")) {
            Settings::getInstance()->setBool("ScraperRespectExclusions",
                    scraper_respect_exclusions->getState());
            s->setNeedsSaving();
        }
    });

    // Exclude files recursively for excluded folders.
    auto scraper_exclude_recursively = std::make_shared<SwitchComponent>(mWindow);
    scraper_exclude_recursively->setState(
            Settings::getInstance()->getBool("ScraperExcludeRecursively"));
    s->addWithLabel("EXCLUDE FOLDERS RECURSIVELY", scraper_exclude_recursively);
    s->addSaveFunc([scraper_exclude_recursively, s] {
        if (scraper_exclude_recursively->getState() !=
                Settings::getInstance()->getBool("ScraperExcludeRecursively")) {
            Settings::getInstance()->setBool("ScraperExcludeRecursively",
                    scraper_exclude_recursively->getState());
            s->setNeedsSaving();
        }
    });

    // If respecting excluded files is set to off, then disable this option.
    if (!Settings::getInstance()->getBool("ScraperRespectExclusions")) {
        scraper_exclude_recursively->setEnabled(false);
        scraper_exclude_recursively->setOpacity(DISABLED_OPACITY);
        scraper_exclude_recursively->getParent()->getChild(scraper_exclude_recursively->
                getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
    }

    // Include actual folders when scraping.
    auto scraper_include_folders = std::make_shared<SwitchComponent>(mWindow);
    scraper_include_folders->setState(
            Settings::getInstance()->getBool("ScraperIncludeFolders"));
    s->addWithLabel("SCRAPE ACTUAL FOLDERS", scraper_include_folders);
    s->addSaveFunc([scraper_include_folders, s] {
        if (scraper_include_folders->getState() !=
                Settings::getInstance()->getBool("ScraperIncludeFolders")) {
            Settings::getInstance()->setBool("ScraperIncludeFolders",
                    scraper_include_folders->getState());
            s->setNeedsSaving();
        }
    });

    // Retry search on peer verification errors (TLS/certificate issues).
    auto retry_peer_verification = std::make_shared<SwitchComponent>(mWindow);
    retry_peer_verification->setState(
            Settings::getInstance()->getBool("ScraperRetryPeerVerification"));
    s->addWithLabel("AUTO-RETRY ON PEER VERIFICATION ERRORS", retry_peer_verification);
    s->addSaveFunc([retry_peer_verification, s] {
        if (retry_peer_verification->getState() !=
                Settings::getInstance()->getBool("ScraperRetryPeerVerification")) {
            Settings::getInstance()->setBool("ScraperRetryPeerVerification",
                    retry_peer_verification->getState());
            s->setNeedsSaving();
        }
    });

    // Switch callbacks.
    auto interactiveToggleFunc = [scraper_semiautomatic]() {
        if (scraper_semiautomatic->getEnabled()) {
            scraper_semiautomatic->setEnabled(false);
            scraper_semiautomatic->setOpacity(DISABLED_OPACITY);
            scraper_semiautomatic->getParent()->getChild(scraper_semiautomatic->
                    getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
        }
        else {
            scraper_semiautomatic->setEnabled(true);
            scraper_semiautomatic->setOpacity(255);
            scraper_semiautomatic->getParent()->getChild(scraper_semiautomatic->
                    getChildIndex() - 1)->setOpacity(255);
        }
    };

    auto excludeRecursivelyToggleFunc = [scraper_exclude_recursively]() {
        if (scraper_exclude_recursively->getEnabled()) {
            scraper_exclude_recursively->setEnabled(false);
            scraper_exclude_recursively->setOpacity(DISABLED_OPACITY);
            scraper_exclude_recursively->getParent()->getChild(scraper_exclude_recursively->
                    getChildIndex() - 1)->setOpacity(DISABLED_OPACITY);
        }
        else {
            scraper_exclude_recursively->setEnabled(true);
            scraper_exclude_recursively->setOpacity(255);
            scraper_exclude_recursively->getParent()->getChild(scraper_exclude_recursively->
                    getChildIndex() - 1)->setOpacity(255);
        }
    };

    scraper_interactive->setCallback(interactiveToggleFunc);
    scraper_respect_exclusions->setCallback(excludeRecursivelyToggleFunc);

    mWindow->pushGui(s);
}

void GuiScraperMenu::pressedStart()
{
    // If the scraper service has been changed, then save the settings as otherwise the
    // wrong scraper would be used.
    if (mScraper->getSelected() != Settings::getInstance()->getString("Scraper"))
        mMenu.save();

    std::vector<SystemData*> sys = mSystems->getSelectedObjects();
    for (auto it = sys.cbegin(); it != sys.cend(); it++) {
        if ((*it)->getPlatformIds().empty()) {
            std::string warningString;
            if (sys.size() == 1) {
                warningString = "The selected system does not have a\n"
                        "platform set, results may be inaccurate\n"
                        "Continue anyway?";
            }
            else {
                warningString = "At least one of your selected\n"
                        "systems does not have a platform\n"
                        "set, results may be inaccurate\n"
                        "Continue anyway?";
            }
            mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
                    Utils::String::toUpper(warningString),
                    "YES", std::bind(&GuiScraperMenu::start, this),
                    "NO", nullptr));
            return;
        }
    }
    start();
}

void GuiScraperMenu::start()
{
    if (mSystems->getSelectedObjects().empty()) {
        mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
            "PLEASE SELECT AT LEAST ONE SYSTEM TO SCRAPE"));
        return;
    }

    bool contentToScrape = false;
    std::string scraperService = Settings::getInstance()->getString("Scraper");

    // Check if there is actually any type of content selected for scraping.
    do {
        if (Settings::getInstance()->getBool("ScrapeGameNames")) {
            contentToScrape = true;
            break;
        }
        if (scraperService == "screenscraper" &&
                Settings::getInstance()->getBool("ScrapeRatings")) {
            contentToScrape = true;
            break;
        }
        if (Settings::getInstance()->getBool("ScrapeMetadata")) {
            contentToScrape = true;
            break;
        }
        if (scraperService == "screenscraper" &&
                Settings::getInstance()->getBool("ScrapeVideos")) {
            contentToScrape = true;
            break;
        }
        if (Settings::getInstance()->getBool("ScrapeScreenshots")) {
            contentToScrape = true;
            break;
        }
        if (Settings::getInstance()->getBool("ScrapeCovers")) {
            contentToScrape = true;
            break;
        }
        if (Settings::getInstance()->getBool("ScrapeMarquees")) {
            contentToScrape = true;
            break;
        }
        if (scraperService == "screenscraper" &&
                Settings::getInstance()->getBool("Scrape3DBoxes")) {
            contentToScrape = true;
            break;
        }
    } while (0);

    if (!contentToScrape) {
        mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
            "PLEASE SELECT AT LEAST ONE CONTENT TYPE TO SCRAPE"));
        return;
    }

    std::queue<ScraperSearchParams> searches =
            getSearches(mSystems->getSelectedObjects(), mFilters->getSelected());

    if (searches.empty()) {
        mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
            "ALL GAMES WERE FILTERED, NOTHING TO SCRAPE"));
    }
    else {
        GuiScraperMulti* gsm = new GuiScraperMulti(mWindow, searches,
                Settings::getInstance()->getBool("ScraperInteractive"));
        mWindow->pushGui(gsm);
        mMenu.setCursorToList();
        mMenu.setCursorToFirstListEntry();
    }
}

std::queue<ScraperSearchParams> GuiScraperMenu::getSearches(
        std::vector<SystemData*> systems, GameFilterFunc selector)
{
    std::queue<ScraperSearchParams> queue;
    for (auto sys = systems.cbegin(); sys != systems.cend(); sys++) {
        std::vector<FileData*> games = (*sys)->getRootFolder()->getScrapeFilesRecursive(
                Settings::getInstance()->getBool("ScraperIncludeFolders"),
                Settings::getInstance()->getBool("ScraperExcludeRecursively"),
                Settings::getInstance()->getBool("ScraperRespectExclusions"));
        for (auto game = games.cbegin(); game != games.cend(); game++) {
            if (selector((*sys), (*game))) {
                ScraperSearchParams search;
                search.game = *game;
                search.system = *sys;

                queue.push(search);
            }
        }
    }
    return queue;
}

void GuiScraperMenu::addEntry(const std::string& name, unsigned int color,
        bool add_arrow, const std::function<void()>& func)
{
    std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);

    // Populate the list.
    ComponentListRow row;
    row.addElement(std::make_shared<TextComponent>(mWindow, name, font, color), true);

    if (add_arrow) {
        std::shared_ptr<ImageComponent> bracket = makeArrow(mWindow);
        row.addElement(bracket, false);
    }

    row.makeAcceptInputHandler(func);
    mMenu.addRow(row);
}

bool GuiScraperMenu::input(InputConfig* config, Input input)
{
    if (GuiComponent::input(config, input))
        return true;

    if (config->isMappedTo("y", input) && input.value != 0)
        pressedStart();

    if (config->isMappedTo("b", input) && input.value != 0) {
        delete this;
        return true;
    }

    return false;
}

std::vector<HelpPrompt> GuiScraperMenu::getHelpPrompts()
{
    std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts();
    prompts.push_back(HelpPrompt("b", "back"));
    prompts.push_back(HelpPrompt("y", "start"));
    return prompts;
}

HelpStyle GuiScraperMenu::getHelpStyle()
{
    HelpStyle style = HelpStyle();
    style.applyTheme(ViewController::get()->getState().getSystem()->getTheme(), "system");
    return style;
}