// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0

#include "controllersettingswindow.h"
#include "controllerbindingwidgets.h"
#include "controllerglobalsettingswidget.h"
#include "hotkeysettingswidget.h"
#include "mainwindow.h"
#include "qthost.h"

#include "core/controller.h"
#include "core/host.h"

#include "util/ini_settings_interface.h"
#include "util/input_manager.h"

#include "common/assert.h"
#include "common/file_system.h"

#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QTextEdit>
#include <array>

#include "moc_controllersettingswindow.cpp"

ControllerSettingsWindow::ControllerSettingsWindow(INISettingsInterface* game_sif /* = nullptr */,
                                                   bool edit_profiles /* = false */, QWidget* parent /* = nullptr */)
  : QWidget(parent), m_editing_settings_interface(game_sif), m_editing_input_profiles(edit_profiles)
{
  m_ui.setupUi(this);
  m_ui.buttonBox->button(QDialogButtonBox::Close)->setDefault(true);

  setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

  connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this,
          &ControllerSettingsWindow::onCategoryCurrentRowChanged);
  connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsWindow::close);

  if (!game_sif && !edit_profiles)
  {
    // editing global settings
    m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel);
    delete m_ui.editProfileLabel;
    m_ui.editProfileLabel = nullptr;
    m_ui.editProfileLayout->removeWidget(m_ui.currentProfile);
    delete m_ui.currentProfile;
    m_ui.currentProfile = nullptr;
    m_ui.editProfileLayout->removeWidget(m_ui.newProfile);
    delete m_ui.newProfile;
    m_ui.newProfile = nullptr;
    m_ui.editProfileLayout->removeWidget(m_ui.applyProfile);
    delete m_ui.applyProfile;
    m_ui.applyProfile = nullptr;
    m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile);
    delete m_ui.deleteProfile;
    m_ui.deleteProfile = nullptr;
    m_ui.editProfileLayout->removeWidget(m_ui.copyGlobalSettings);
    delete m_ui.copyGlobalSettings;
    m_ui.copyGlobalSettings = nullptr;

    if (QPushButton* button = m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults))
      connect(button, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
  }
  else
  {
    if (QPushButton* button = m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults))
      m_ui.buttonBox->removeButton(button);

    connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this,
            &ControllerSettingsWindow::onCopyGlobalSettingsClicked);

    if (edit_profiles)
    {
      setWindowTitle(tr("DuckStation Controller Presets"));
      refreshProfileList();

      connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this,
              &ControllerSettingsWindow::onCurrentProfileChanged);
      connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked);
      connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked);
      connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
    }
    else
    {
      // editing game settings
      m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel);
      delete m_ui.editProfileLabel;
      m_ui.editProfileLabel = nullptr;
      m_ui.editProfileLayout->removeWidget(m_ui.currentProfile);
      delete m_ui.currentProfile;
      m_ui.currentProfile = nullptr;
      m_ui.editProfileLayout->removeWidget(m_ui.newProfile);
      delete m_ui.newProfile;
      m_ui.newProfile = nullptr;
      m_ui.editProfileLayout->removeWidget(m_ui.applyProfile);
      delete m_ui.applyProfile;
      m_ui.applyProfile = nullptr;
      m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile);
      delete m_ui.deleteProfile;
      m_ui.deleteProfile = nullptr;
    }
  }

  createWidgets();
}

ControllerSettingsWindow::~ControllerSettingsWindow() = default;

void ControllerSettingsWindow::editControllerSettingsForGame(QWidget* parent, INISettingsInterface* sif)
{
  ControllerSettingsWindow* dlg = new ControllerSettingsWindow(sif, false, parent);
  dlg->setWindowFlag(Qt::Window);
  dlg->setAttribute(Qt::WA_DeleteOnClose);
  dlg->setWindowModality(Qt::WindowModality::WindowModal);
  dlg->setWindowTitle(parent->windowTitle());
  dlg->setWindowIcon(parent->windowIcon());
  dlg->show();
}

int ControllerSettingsWindow::getHotkeyCategoryIndex() const
{
  const std::array<bool, 2> mtap_enabled = getEnabledMultitaps();
  return 1 + (mtap_enabled[0] ? 4 : 1) + (mtap_enabled[1] ? 4 : 1);
}

ControllerSettingsWindow::Category ControllerSettingsWindow::getCurrentCategory() const
{
  const int index = m_ui.settingsCategory->currentRow();
  if (index == 0)
    return Category::GlobalSettings;
  else if (index >= getHotkeyCategoryIndex())
    return Category::HotkeySettings;
  else
    return Category::FirstControllerSettings;
}

void ControllerSettingsWindow::setCategory(Category category)
{
  switch (category)
  {
    case Category::GlobalSettings:
      m_ui.settingsCategory->setCurrentRow(0);
      break;

    case Category::FirstControllerSettings:
      m_ui.settingsCategory->setCurrentRow(1);
      break;

    case Category::HotkeySettings:
      m_ui.settingsCategory->setCurrentRow(getHotkeyCategoryIndex());
      break;

    default:
      break;
  }
}

void ControllerSettingsWindow::onCategoryCurrentRowChanged(int row)
{
  m_ui.settingsContainer->setCurrentIndex(row);
}

void ControllerSettingsWindow::onCurrentProfileChanged(int index)
{
  switchProfile(m_ui.currentProfile->itemText(index).toStdString());
}

void ControllerSettingsWindow::onNewProfileClicked()
{
  const std::string profile_name =
    QInputDialog::getText(this, tr("Create Controller Preset"), tr("Enter the name for the new controller preset:"))
      .toStdString();
  if (profile_name.empty())
    return;

  std::string profile_path = System::GetInputProfilePath(profile_name);
  if (FileSystem::FileExists(profile_path.c_str()))
  {
    QMessageBox::critical(this, tr("Error"),
                          tr("A preset with the name '%1' already exists.").arg(QString::fromStdString(profile_name)));
    return;
  }

  const int res = QMessageBox::question(this, tr("Create Controller Preset"),
                                        tr("Do you want to copy all bindings from the currently-selected preset to "
                                           "the new preset? Selecting No will create a completely empty preset."),
                                        QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  if (res == QMessageBox::Cancel)
    return;

  INISettingsInterface temp_si(std::move(profile_path));
  if (res == QMessageBox::Yes)
  {
    // copy from global or the current profile
    if (!m_editing_settings_interface)
    {
      const int hkres = QMessageBox::question(
        this, tr("Create Controller Preset"),
        tr("Do you want to copy the current hotkey bindings from global settings to the new controller preset?"),
        QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
      if (hkres == QMessageBox::Cancel)
        return;

      const bool copy_hotkey_bindings = (hkres == QMessageBox::Yes);
      if (copy_hotkey_bindings)
        temp_si.SetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", true);

      // from global
      auto lock = Host::GetSettingsLock();
      InputManager::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), true, true, true,
                                      copy_hotkey_bindings);
    }
    else
    {
      // from profile
      const bool copy_hotkey_bindings =
        m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
      temp_si.SetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", copy_hotkey_bindings);
      InputManager::CopyConfiguration(&temp_si, *m_editing_settings_interface, true, true, true, copy_hotkey_bindings);
    }
  }
  else
  {
    // still need to copy the source config
    if (!m_editing_settings_interface)
      InputManager::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), false, true, false, false);
    else
      InputManager::CopyConfiguration(&temp_si, *m_editing_settings_interface, false, true, false, false);
  }

  if (!temp_si.Save())
  {
    QMessageBox::critical(this, tr("Error"),
                          tr("Failed to save the new preset to '%1'.").arg(QString::fromStdString(temp_si.GetPath())));
    return;
  }

  refreshProfileList();
  switchProfile(profile_name);
}

void ControllerSettingsWindow::onApplyProfileClicked()
{
  if (QMessageBox::question(this, tr("Load Controller Preset"),
                            tr("Are you sure you want to apply the controller preset named '%1'?\n\n"
                               "All current global bindings will be removed, and the preset bindings loaded.\n\n"
                               "You cannot undo this action.")
                              .arg(m_profile_name)) != QMessageBox::Yes)
  {
    return;
  }

  {
    const bool copy_hotkey_bindings =
      m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
    auto lock = Host::GetSettingsLock();
    InputManager::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_editing_settings_interface, true, true,
                                    true, copy_hotkey_bindings);
    QtHost::QueueSettingsSave();
  }
  g_emu_thread->applySettings();

  // Recreate global widget on profile apply
  g_main_window->getControllerSettingsWindow()->createWidgets();
}

void ControllerSettingsWindow::onDeleteProfileClicked()
{
  if (QMessageBox::question(this, tr("Delete Controller Preset"),
                            tr("Are you sure you want to delete the controller preset named '%1'?\n\n"
                               "You cannot undo this action.")
                              .arg(m_profile_name)) != QMessageBox::Yes)
  {
    return;
  }

  std::string profile_path(System::GetInputProfilePath(m_profile_name.toStdString()));
  if (!FileSystem::DeleteFile(profile_path.c_str()))
  {
    QMessageBox::critical(this, tr("Error"), tr("Failed to delete '%1'.").arg(QString::fromStdString(profile_path)));
    return;
  }

  // switch back to global
  refreshProfileList();
  switchProfile({});
}

void ControllerSettingsWindow::onRestoreDefaultsClicked()
{
  if (QMessageBox::question(this, tr("Restore Defaults"),
                            tr("Are you sure you want to restore the default controller configuration?\n\n"
                               "All bindings and configuration will be lost. You cannot undo this action.")) !=
      QMessageBox::Yes)
  {
    return;
  }

  // actually restore it
  g_emu_thread->setDefaultSettings(false, true);

  // reload all settings
  createWidgets();
}

void ControllerSettingsWindow::onCopyGlobalSettingsClicked()
{
  DebugAssert(!isEditingGlobalSettings());

  {
    const auto lock = Host::GetSettingsLock();
    InputManager::CopyConfiguration(m_editing_settings_interface, *Host::Internal::GetBaseSettingsLayer(), true, true,
                                    true, false);
  }

  m_editing_settings_interface->Save();
  g_emu_thread->reloadGameSettings();
  createWidgets();

  QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"),
                           isEditingGameSettings() ? tr("Per-game controller configuration reset to global settings.") :
                                                     tr("Controller preset reset to global settings."));
}

bool ControllerSettingsWindow::getBoolValue(const char* section, const char* key, bool default_value) const
{
  if (m_editing_settings_interface)
    return m_editing_settings_interface->GetBoolValue(section, key, default_value);
  else
    return Host::GetBaseBoolSettingValue(section, key, default_value);
}

s32 ControllerSettingsWindow::getIntValue(const char* section, const char* key, s32 default_value) const
{
  if (m_editing_settings_interface)
    return m_editing_settings_interface->GetIntValue(section, key, default_value);
  else
    return Host::GetBaseIntSettingValue(section, key, default_value);
}

std::string ControllerSettingsWindow::getStringValue(const char* section, const char* key,
                                                     const char* default_value) const
{
  std::string value;
  if (m_editing_settings_interface)
    value = m_editing_settings_interface->GetStringValue(section, key, default_value);
  else
    value = Host::GetBaseStringSettingValue(section, key, default_value);
  return value;
}

void ControllerSettingsWindow::setBoolValue(const char* section, const char* key, bool value)
{
  if (m_editing_settings_interface)
  {
    m_editing_settings_interface->SetBoolValue(section, key, value);
    saveAndReloadGameSettings();
  }
  else
  {
    Host::SetBaseBoolSettingValue(section, key, value);
    Host::CommitBaseSettingChanges();
    g_emu_thread->applySettings();
  }
}

void ControllerSettingsWindow::setIntValue(const char* section, const char* key, s32 value)
{
  if (m_editing_settings_interface)
  {
    m_editing_settings_interface->SetIntValue(section, key, value);
    saveAndReloadGameSettings();
  }
  else
  {
    Host::SetBaseIntSettingValue(section, key, value);
    Host::CommitBaseSettingChanges();
    g_emu_thread->applySettings();
  }
}

void ControllerSettingsWindow::setStringValue(const char* section, const char* key, const char* value)
{
  if (m_editing_settings_interface)
  {
    m_editing_settings_interface->SetStringValue(section, key, value);
    saveAndReloadGameSettings();
  }
  else
  {
    Host::SetBaseStringSettingValue(section, key, value);
    Host::CommitBaseSettingChanges();
    g_emu_thread->applySettings();
  }
}

void ControllerSettingsWindow::saveAndReloadGameSettings()
{
  DebugAssert(m_editing_settings_interface);
  QtHost::SaveGameSettings(m_editing_settings_interface, false);
  g_emu_thread->reloadGameSettings(false);
}

void ControllerSettingsWindow::clearSettingValue(const char* section, const char* key)
{
  if (m_editing_settings_interface)
  {
    m_editing_settings_interface->DeleteValue(section, key);
    m_editing_settings_interface->Save();
    g_emu_thread->reloadGameSettings();
  }
  else
  {
    Host::DeleteBaseSettingValue(section, key);
    Host::CommitBaseSettingChanges();
    g_emu_thread->applySettings();
  }
}

void ControllerSettingsWindow::createWidgets()
{
  QSignalBlocker sb(m_ui.settingsContainer);
  QSignalBlocker sb2(m_ui.settingsCategory);

  while (m_ui.settingsContainer->count() > 0)
  {
    QWidget* widget = m_ui.settingsContainer->widget(m_ui.settingsContainer->count() - 1);
    m_ui.settingsContainer->removeWidget(widget);
    widget->deleteLater();
  }

  m_ui.settingsCategory->clear();

  m_global_settings = nullptr;
  m_hotkey_settings = nullptr;

  {
    // global settings
    QListWidgetItem* item = new QListWidgetItem();
    item->setText(tr("Global Settings"));
    item->setIcon(QIcon::fromTheme(QStringLiteral("settings-3-line")));
    m_ui.settingsCategory->addItem(item);
    m_ui.settingsCategory->setCurrentRow(0);
    m_global_settings = new ControllerGlobalSettingsWidget(m_ui.settingsContainer, this);
    m_ui.settingsContainer->addWidget(m_global_settings);
    connect(m_global_settings, &ControllerGlobalSettingsWidget::bindingSetupChanged, this,
            &ControllerSettingsWindow::createWidgets);
  }

  // load mtap settings
  const std::array<bool, 2> mtap_enabled = getEnabledMultitaps();
  for (u32 global_slot : Controller::PortDisplayOrder)
  {
    const bool is_mtap_port = Controller::PadIsMultitapSlot(global_slot);
    const auto [port, slot] = Controller::ConvertPadToPortAndSlot(global_slot);
    if (is_mtap_port && !mtap_enabled[port])
      continue;

    m_port_bindings[global_slot] = new ControllerBindingWidget(m_ui.settingsContainer, this, global_slot);
    m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]);

    const QString display_name(
      QtUtils::StringViewToQString(m_port_bindings[global_slot]->getControllerInfo()->GetDisplayName()));

    QListWidgetItem* item = new QListWidgetItem();
    item->setText(tr("Controller Port %1\n%2")
                    .arg(Controller::GetPortDisplayName(port, slot, mtap_enabled[port]))
                    .arg(display_name));
    item->setIcon(m_port_bindings[global_slot]->getIcon());
    item->setData(Qt::UserRole, QVariant(global_slot));
    m_ui.settingsCategory->addItem(item);
  }

  // only add hotkeys if we're editing global settings
  if (!m_editing_settings_interface ||
      m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false))
  {
    QListWidgetItem* item = new QListWidgetItem();
    item->setText(tr("Hotkeys"));
    item->setIcon(QIcon::fromTheme(QStringLiteral("keyboard-line")));
    m_ui.settingsCategory->addItem(item);
    m_hotkey_settings = new HotkeySettingsWidget(m_ui.settingsContainer, this);
    m_ui.settingsContainer->addWidget(m_hotkey_settings);
  }

  if (isEditingProfile())
  {
    const bool enable_buttons = static_cast<bool>(m_profile_settings_interface);
    m_ui.applyProfile->setEnabled(enable_buttons);
    m_ui.deleteProfile->setEnabled(enable_buttons);
    m_ui.copyGlobalSettings->setEnabled(enable_buttons);
  }
}

void ControllerSettingsWindow::closeEvent(QCloseEvent* event)
{
  QWidget::closeEvent(event);
  emit windowClosed();
}

void ControllerSettingsWindow::updateListDescription(u32 global_slot, ControllerBindingWidget* widget)
{
  for (int i = 0; i < m_ui.settingsCategory->count(); i++)
  {
    QListWidgetItem* item = m_ui.settingsCategory->item(i);
    const QVariant item_data(item->data(Qt::UserRole));
    bool is_ok;
    if (item_data.toUInt(&is_ok) == global_slot && is_ok)
    {
      const std::array<bool, 2> mtap_enabled = getEnabledMultitaps();
      const auto [port, slot] = Controller::ConvertPadToPortAndSlot(global_slot);

      const QString display_name = QtUtils::StringViewToQString(widget->getControllerInfo()->GetDisplayName());

      item->setText(tr("Controller Port %1\n%2")
                      .arg(Controller::GetPortDisplayName(port, slot, mtap_enabled[port]))
                      .arg(display_name));
      item->setIcon(widget->getIcon());
      break;
    }
  }
}

std::array<bool, 2> ControllerSettingsWindow::getEnabledMultitaps() const
{
  const MultitapMode mtap_mode =
    Settings::ParseMultitapModeName(
      getStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE))
        .c_str())
      .value_or(Settings::DEFAULT_MULTITAP_MODE);
  return {{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
           (mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
}

void ControllerSettingsWindow::refreshProfileList()
{
  const std::vector<std::string> names(InputManager::GetInputProfileNames());

  QSignalBlocker sb(m_ui.currentProfile);
  m_ui.currentProfile->clear();

  bool current_profile_found = false;
  for (const std::string& name : names)
  {
    const QString qname(QString::fromStdString(name));
    m_ui.currentProfile->addItem(qname);
    if (qname == m_profile_name)
    {
      m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->count() - 1);
      current_profile_found = true;
    }
  }

  if (!current_profile_found)
    switchProfile(names.empty() ? std::string_view() : std::string_view(names.front()));
}

void ControllerSettingsWindow::switchProfile(const std::string_view name)
{
  const QString name_qstr = QtUtils::StringViewToQString(name);
  {
    QSignalBlocker sb(m_ui.currentProfile);
    m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name_qstr));
  }
  m_profile_name = name_qstr;
  m_profile_settings_interface.reset();
  m_editing_settings_interface = nullptr;

  // disable UI if there is no selection
  const bool disable_ui = name.empty();
  m_ui.settingsCategory->setDisabled(disable_ui);
  m_ui.settingsContainer->setDisabled(disable_ui);

  if (name_qstr.isEmpty())
  {
    createWidgets();
    return;
  }

  std::string path = System::GetInputProfilePath(name);
  if (!FileSystem::FileExists(path.c_str()))
  {
    QMessageBox::critical(this, tr("Error"), tr("The controller preset named '%1' cannot be found.").arg(name_qstr));
    return;
  }

  std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(path));
  sif->Load();

  m_profile_settings_interface = std::move(sif);
  m_editing_settings_interface = m_profile_settings_interface.get();

  createWidgets();
}
