// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <cstring>
#include <QMessageBox>
#include "citra_qt/configuration/configure_system.h"
#include "citra_qt/uisettings.h"
#include "core/core.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/settings.h"
#include "ui_configure_system.h"

static const std::array<int, 12> days_in_month = {{
    31,
    29,
    31,
    30,
    31,
    30,
    31,
    31,
    30,
    31,
    30,
    31,
}};

static const std::array<const char*, 187> country_names = {
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "Japan"),
    "",
    "",
    "",
    "",
    "",
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "Anguilla"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Antigua and Barbuda"), // 0-9
    QT_TRANSLATE_NOOP("ConfigureSystem", "Argentina"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Aruba"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Bahamas"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Barbados"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Belize"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Bolivia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Brazil"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "British Virgin Islands"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Canada"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Cayman Islands"), // 10-19
    QT_TRANSLATE_NOOP("ConfigureSystem", "Chile"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Colombia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Costa Rica"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Dominica"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Dominican Republic"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Ecuador"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "El Salvador"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "French Guiana"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Grenada"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Guadeloupe"), // 20-29
    QT_TRANSLATE_NOOP("ConfigureSystem", "Guatemala"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Guyana"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Haiti"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Honduras"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Jamaica"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Martinique"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Mexico"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Montserrat"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Netherlands Antilles"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Nicaragua"), // 30-39
    QT_TRANSLATE_NOOP("ConfigureSystem", "Panama"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Paraguay"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Peru"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Saint Kitts and Nevis"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Saint Lucia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Saint Vincent and the Grenadines"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Suriname"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Trinidad and Tobago"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Turks and Caicos Islands"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "United States"), // 40-49
    QT_TRANSLATE_NOOP("ConfigureSystem", "Uruguay"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "US Virgin Islands"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Venezuela"),
    "",
    "",
    "",
    "",
    "",
    "",
    "", // 50-59
    "",
    "",
    "",
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "Albania"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Australia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Austria"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Belgium"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Bosnia and Herzegovina"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Botswana"), // 60-69
    QT_TRANSLATE_NOOP("ConfigureSystem", "Bulgaria"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Croatia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Cyprus"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Czech Republic"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Denmark"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Estonia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Finland"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "France"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Germany"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Greece"), // 70-79
    QT_TRANSLATE_NOOP("ConfigureSystem", "Hungary"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Iceland"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Ireland"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Italy"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Latvia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Lesotho"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Liechtenstein"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Lithuania"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Luxembourg"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Macedonia"), // 80-89
    QT_TRANSLATE_NOOP("ConfigureSystem", "Malta"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Montenegro"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Mozambique"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Namibia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Netherlands"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "New Zealand"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Norway"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Poland"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Portugal"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Romania"), // 90-99
    QT_TRANSLATE_NOOP("ConfigureSystem", "Russia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Serbia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Slovakia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Slovenia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "South Africa"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Spain"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Swaziland"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Sweden"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Switzerland"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Turkey"), // 100-109
    QT_TRANSLATE_NOOP("ConfigureSystem", "United Kingdom"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Zambia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Zimbabwe"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Azerbaijan"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Mauritania"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Mali"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Niger"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Chad"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Sudan"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Eritrea"), // 110-119
    QT_TRANSLATE_NOOP("ConfigureSystem", "Djibouti"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Somalia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Andorra"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Gibraltar"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Guernsey"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Isle of Man"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Jersey"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Monaco"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Taiwan"),
    "", // 120-129
    "",
    "",
    "",
    "",
    "",
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "South Korea"),
    "",
    "",
    "", // 130-139
    "",
    "",
    "",
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "Hong Kong"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Macau"),
    "",
    "",
    "",
    "", // 140-149
    "",
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "Indonesia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Singapore"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Thailand"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Philippines"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Malaysia"),
    "",
    "",
    "", // 150-159
    QT_TRANSLATE_NOOP("ConfigureSystem", "China"),
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "United Arab Emirates"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "India"), // 160-169
    QT_TRANSLATE_NOOP("ConfigureSystem", "Egypt"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Oman"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Qatar"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Kuwait"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Saudi Arabia"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Syria"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Bahrain"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Jordan"),
    "",
    "", // 170-179
    "",
    "",
    "",
    "",
    QT_TRANSLATE_NOOP("ConfigureSystem", "San Marino"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Vatican City"),
    QT_TRANSLATE_NOOP("ConfigureSystem", "Bermuda"), // 180-186
};

// The QSlider doesn't have an easy way to set a custom step amount,
// so we can just convert from the sliders range (0 - 79) to the expected
// settings range (5 - 400) with simple math.
static constexpr int SliderToSettings(int value) {
    return 5 * value + 5;
}

static constexpr int SettingsToSlider(int value) {
    return (value - 5) / 5;
}

ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
    ui->setupUi(this);
    connect(ui->combo_birthmonth,
            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
            &ConfigureSystem::UpdateBirthdayComboBox);
    connect(ui->combo_init_clock,
            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
            &ConfigureSystem::UpdateInitTime);
    connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
            &ConfigureSystem::RefreshConsoleID);
    for (u8 i = 0; i < country_names.size(); i++) {
        if (std::strcmp(country_names.at(i), "") != 0) {
            ui->combo_country->addItem(tr(country_names.at(i)), i);
        }
    }

    connect(ui->slider_clock_speed, &QSlider::valueChanged, [&](int value) {
        ui->clock_display_label->setText(QStringLiteral("%1%").arg(SliderToSettings(value)));
    });

    ConfigureTime();
}

ConfigureSystem::~ConfigureSystem() = default;

void ConfigureSystem::SetConfiguration() {
    enabled = !Core::System::GetInstance().IsPoweredOn();

    ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock));
    QDateTime date_time;
    date_time.setTime_t(Settings::values.init_time);
    ui->edit_init_time->setDateTime(date_time);

    if (!enabled) {
        cfg = Service::CFG::GetModule(Core::System::GetInstance());
        ASSERT_MSG(cfg, "CFG Module missing!");
        ReadSystemSettings();
        ui->group_system_settings->setEnabled(false);
    } else {
        // This tab is enabled only when game is not running (i.e. all service are not initialized).
        cfg = std::make_shared<Service::CFG::Module>();
        ReadSystemSettings();

        ui->label_disable_info->hide();
    }

    ui->slider_clock_speed->setValue(SettingsToSlider(Settings::values.cpu_clock_percentage));
    ui->clock_display_label->setText(
        QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage));

    ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds);
}

void ConfigureSystem::ReadSystemSettings() {
    // set username
    username = cfg->GetUsername();
    // TODO(wwylele): Use this when we move to Qt 5.5
    // ui->edit_username->setText(QString::fromStdU16String(username));
    ui->edit_username->setText(
        QString::fromUtf16(reinterpret_cast<const ushort*>(username.data())));

    // set birthday
    std::tie(birthmonth, birthday) = cfg->GetBirthday();
    ui->combo_birthmonth->setCurrentIndex(birthmonth - 1);
    UpdateBirthdayComboBox(
        birthmonth -
        1); // explicitly update it because the signal from setCurrentIndex is not reliable
    ui->combo_birthday->setCurrentIndex(birthday - 1);

    // set system language
    language_index = cfg->GetSystemLanguage();
    ui->combo_language->setCurrentIndex(language_index);

    // set sound output mode
    sound_index = cfg->GetSoundOutputMode();
    ui->combo_sound->setCurrentIndex(sound_index);

    // set the country code
    country_code = cfg->GetCountryCode();
    ui->combo_country->setCurrentIndex(ui->combo_country->findData(country_code));

    // set the console id
    u64 console_id = cfg->GetConsoleUniqueId();
    ui->label_console_id->setText(
        tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));

    // set play coin
    play_coin = Service::PTM::Module::GetPlayCoins();
    ui->spinBox_play_coins->setValue(play_coin);
}

void ConfigureSystem::ApplyConfiguration() {
    if (enabled) {
        bool modified = false;

        // apply username
        // TODO(wwylele): Use this when we move to Qt 5.5
        // std::u16string new_username = ui->edit_username->text().toStdU16String();
        std::u16string new_username(
            reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
        if (new_username != username) {
            cfg->SetUsername(new_username);
            modified = true;
        }

        // apply birthday
        int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
        int new_birthday = ui->combo_birthday->currentIndex() + 1;
        if (birthmonth != new_birthmonth || birthday != new_birthday) {
            cfg->SetBirthday(new_birthmonth, new_birthday);
            modified = true;
        }

        // apply language
        int new_language = ui->combo_language->currentIndex();
        if (language_index != new_language) {
            cfg->SetSystemLanguage(static_cast<Service::CFG::SystemLanguage>(new_language));
            modified = true;
        }

        // apply sound
        int new_sound = ui->combo_sound->currentIndex();
        if (sound_index != new_sound) {
            cfg->SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(new_sound));
            modified = true;
        }

        // apply country
        u8 new_country = static_cast<u8>(ui->combo_country->currentData().toInt());
        if (country_code != new_country) {
            cfg->SetCountryCode(new_country);
            modified = true;
        }

        // apply play coin
        u16 new_play_coin = static_cast<u16>(ui->spinBox_play_coins->value());
        if (play_coin != new_play_coin) {
            Service::PTM::Module::SetPlayCoins(new_play_coin);
        }

        // update the config savegame if any item is modified.
        if (modified) {
            cfg->UpdateConfigNANDSavegame();
        }

        Settings::values.init_clock =
            static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
        Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();

        Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked();
    }

    Settings::values.cpu_clock_percentage = SliderToSettings(ui->slider_clock_speed->value());
    Settings::Apply();
}

void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
    if (birthmonth_index < 0 || birthmonth_index >= 12)
        return;

    // store current day selection
    int birthday_index = ui->combo_birthday->currentIndex();

    // get number of days in the new selected month
    int days = days_in_month[birthmonth_index];

    // if the selected day is out of range,
    // reset it to 1st
    if (birthday_index < 0 || birthday_index >= days)
        birthday_index = 0;

    // update the day combo box
    ui->combo_birthday->clear();
    for (int i = 1; i <= days; ++i) {
        ui->combo_birthday->addItem(QString::number(i));
    }

    // restore the day selection
    ui->combo_birthday->setCurrentIndex(birthday_index);
}

void ConfigureSystem::ConfigureTime() {
    ui->edit_init_time->setCalendarPopup(true);
    QDateTime dt;
    dt.fromString(QStringLiteral("2000-01-01 00:00:01"), QStringLiteral("yyyy-MM-dd hh:mm:ss"));
    ui->edit_init_time->setMinimumDateTime(dt);

    SetConfiguration();

    UpdateInitTime(ui->combo_init_clock->currentIndex());
}

void ConfigureSystem::UpdateInitTime(int init_clock) {
    const bool is_fixed_time =
        static_cast<Settings::InitClock>(init_clock) == Settings::InitClock::FixedTime;
    ui->label_init_time->setVisible(is_fixed_time);
    ui->edit_init_time->setVisible(is_fixed_time);
}

void ConfigureSystem::RefreshConsoleID() {
    QMessageBox::StandardButton reply;
    QString warning_text = tr("This will replace your current virtual 3DS with a new one. "
                              "Your current virtual 3DS will not be recoverable. "
                              "This might have unexpected effects in games. This might fail, "
                              "if you use an outdated config savegame. Continue?");
    reply = QMessageBox::critical(this, tr("Warning"), warning_text,
                                  QMessageBox::No | QMessageBox::Yes);
    if (reply == QMessageBox::No) {
        return;
    }

    u32 random_number;
    u64 console_id;
    cfg->GenerateConsoleUniqueId(random_number, console_id);
    cfg->SetConsoleUniqueId(random_number, console_id);
    cfg->UpdateConfigNANDSavegame();
    ui->label_console_id->setText(
        tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}

void ConfigureSystem::RetranslateUI() {
    ui->retranslateUi(this);
}
