/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009,...,2026 by Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the MsXpertSuite project.
 *
 * The MsXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - MassXpert, model polymer chemistries and simulate mass spectrometric data;
 * - MineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QDebug>
#include <QObject>
#include <QDialog>
#include <QAction>
#include <QSpinBox>
#include <QSettings>
#include <QMainWindow>
#include <QSvgWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QFormLayout>
#include <QtCore/QVariant>
#include <QtWidgets/QScrollArea>
#include <QThread>
#include <QFileDialog>
#include <QMessageBox>


/////////////////////// pappsomspp includes


/////////////////////// libXpertMassCore includes
#include "MsXpS/libXpertMassCore/Utils.hpp"


/////////////////////// libXpertMassGui includes
#include "MsXpS/libXpertMassGui/ActionManagerTableWidget.hpp"

/////////////////////// Local includes
#include "ApplicationPreferencesWnd.hpp"
#include "Application.hpp"
#include "ProgramWindow.hpp"
#include "FileSystemHierarchyPreferencesWidget.hpp"

#include "ui_ApplicationPreferencesWnd.h"
#include "ui_ApplicationPreferencesWndGeneralPageWidget.h"
#include "ui_DecimalPlacesPreferencesWidget.h"
#include "ui_MaxThreadCountPreferencesWidget.h"

namespace MsXpS
{
namespace MassXpert
{

ApplicationPreferencesWnd::ApplicationPreferencesWnd(
  const QString &application_name,
  const QString &description,
  QWidget *parent_p)
  : QMainWindow(parent_p),
    mp_ui(new Ui::ApplicationPreferencesWnd),
    mp_generalPage_ui(new Ui::ApplicationPreferencesWndGeneralPageWidget),
    mp_decimalPlacesPreferencesWidget_ui(
      new Ui::DecimalPlacesPreferencesWidget),
    mp_maxThreadCountPreferencesWidget_ui(
      new Ui::MaxThreadCountPreferencesWidget),
    m_applicationName(application_name),
    m_windowDescription(description)
{
  if(parent_p == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  mp_ui->setupUi(this);
  // The general page widget is preformed and we'll need to set
  // the corresponding widget as the first section.
  mp_generalPage_ui->setupUi(this);
  // The decimal places widget that we use the group box from
  // to stuff in the generalPage.
  mp_decimalPlacesPreferencesWidget_ui->setupUi(this);
  // The maximum thread count widget -> generalPage.
  mp_maxThreadCountPreferencesWidget_ui->setupUi(this);

  mp_ui->mainTitleLabel->setText(
    QString("Preferences for %1").arg(m_applicationName));

  // connect(mp_ui->sectionsListWidget,
  //         &QListWidget::currentRowChanged,
  //         mp_ui->sectionsStackedWidget,
  //         &QStackedWidget::setCurrentIndex);

  // connect(mp_ui->sectionsListWidget,
  //         &QListWidget::itemClicked,
  //         this,
  //         [&](QListWidgetItem *item) {
  //           qDebug() << "Item clicked at index:"
  //                    << mp_ui->sectionsStackedWidget->currentIndex();
  //         });

  connect(mp_ui->sectionsListWidget,
          &QListWidget::currentRowChanged,
          this,
          [&](int index) {
            qDebug() << "Current index changed in the list widget:" << index;

            mp_ui->sectionsStackedWidget->setCurrentIndex(index);
          });

  connect(mp_ui->applyPushButton,
          &QPushButton::clicked,
          this,
          &ApplicationPreferencesWnd::apply);

  connect(mp_ui->closePushButton,
          &QPushButton::clicked,
          this,
          &ApplicationPreferencesWnd::hide);

  // Update the window title because the window title element in mp_ui->might be
  // either erroneous or empty.
  setWindowTitle(
    QString("%1 - %2").arg(m_applicationName, m_windowDescription));

  mp_ui->sectionsListWidget->setViewMode(QListView::IconMode);
  mp_ui->sectionsListWidget->setFlow(QListView::TopToBottom);
  mp_ui->sectionsListWidget->setWrapping(false);
  mp_ui->sectionsListWidget->setResizeMode(QListView::Adjust);
  mp_ui->sectionsListWidget->setMaximumWidth(85);
  mp_ui->sectionsListWidget->setIconSize(QSize(80, 80)); // adjust as needed
  mp_ui->sectionsListWidget->setGridSize(
    QSize(80, 100)); // must be large enough for icon + text
  mp_ui->sectionsListWidget->setWordWrap(true);
  mp_ui->sectionsListWidget->setTextElideMode(Qt::ElideNone);

  setupWindow();
}

ApplicationPreferencesWnd::~ApplicationPreferencesWnd()
{
  // qDebug() << "Is visible:" << isVisible();
  writeSettings();
}

bool
ApplicationPreferencesWnd::eventFilter(QObject *obj, QEvent *event)
{
  if(obj == mp_ui->sectionsListWidget)
    {
      if(event->type() == QEvent::MouseButtonPress)
        {
          QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
          qDebug() << "Mouse press on listWidget:" << mouseEvent->pos()
                   << "button:" << mouseEvent->button();
        }
      else if(event->type() == QEvent::MouseButtonRelease)
        {
          qDebug() << "Mouse release on listWidget";
        }
    }
  return QMainWindow::eventFilter(obj, event);
}

/*!
\brief Saves the settings of this dialog window to the application
configuration file (\a config_settings_file_path).

The saved configuration is read from the file to set back the dialog window in
the same status.
*/
void
ApplicationPreferencesWnd::writeSettings() const
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("ApplicationPreferencesWnd");

  settings.setValue("geometry", saveGeometry());

  // Sanity check. Works.
  // int current_stacked_widget_page_index =
  //   mp_ui->sectionsStackedWidget->currentIndex();
  // qDebug() << "Current page index:" << current_stacked_widget_page_index;

  int current_list_widget_page_index =
    mp_ui->sectionsListWidget->currentIndex().row();
  // qDebug() << "Current page index:" << current_list_widget_page_index;

  settings.setValue("currentPageIndex", current_list_widget_page_index);
  settings.setValue("isVisible", isVisible());

  settings.endGroup();

  settings.sync();

  qDebug() << "Done writing the application preferences wnd settings to"
           << settings_file_path;
}

/*!
\brief Reads the settings of this dialog window from the application
configuration file (\a config_settings_file_path).

The configuration is read from the file to set back the dialog window in
the same status.
*/
void
ApplicationPreferencesWnd::readSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("ApplicationPreferencesWnd");

  setVisible(settings.value("isVisible").toBool());
  restoreGeometry(settings.value("geometry").toByteArray());

  bool ok;
  int current_page_index = settings.value("currentPageIndex", 0).toInt(&ok);
  // qDebug() << "Current page index:" << current_page_index;
  if(!ok)
    qFatal() << "Programming error.";

  mp_ui->sectionsListWidget->setCurrentRow(current_page_index);

  settings.endGroup();
}

void
ApplicationPreferencesWnd::setupWindow()
{
  setupGeneralPage();
  setupShortcutsPage();
  setupFileSystemHierarchyPreferencesWidget();

  // Now that all the section pages have been created, we can
  // read the settings.
  readSettings();

  // debugWidgetBounds();
}

void
ApplicationPreferencesWnd::setupGeneralPage()
{
  // The page is available as a widget (see *this constructor)
  // as mp_generalPage_ui->scrollArea in which we need to pack
  // all the widgets we need in order. Each configuration bit
  // needs to be in a QGroupBox.

  // The general page has widgets that can be configured here.
  setupMaxThreadCountWidget();
  setupDecimalPlacesPreferencesWidget();

  // We want to push all the group boxes to the top.

  insertSectionListItem("General",
                        QIcon(":images/icons/svg/MassXpert3.svg"),
                        mp_generalPage_ui->scrollArea,
                        Pages::GENERAL);
}

void
ApplicationPreferencesWnd::setupMaxThreadCountWidget()
{
  // This is the main page scroll area widget contents layout.
  // We will end up packing into this layout (towards the bottom) each newly
  // created QGroupBox widget.
  QVBoxLayout *layout_p = mp_generalPage_ui->mainWidgetContentsLayout;

  // The group box widget that will contain all we need to provide the user
  // with for them to configure the max count of threads.
  QGroupBox *group_box_p =
    mp_maxThreadCountPreferencesWidget_ui->maxThreadCountPreferencesGroupBox;

  // We need to setup the icon in the corresponding frame.
  QFrame *icon_frame_p = mp_maxThreadCountPreferencesWidget_ui->iconFrame;
  icon_frame_p->setFrameShape(QFrame::StyledPanel);
  icon_frame_p->setFrameShadow(QFrame::Raised);
  QHBoxLayout *icon_frame_layout_p = new QHBoxLayout(icon_frame_p);
  icon_frame_layout_p->setContentsMargins(4, 4, 4, 4);
  auto *svg =
    new QSvgWidget(":/images/svg/thread-count-slider.svg", icon_frame_p);
  svg->setFixedSize(48, 48); // choose size explicitly
  icon_frame_layout_p->addWidget(svg);

  std::size_t ideal_thread_count = QThread::idealThreadCount();
  QSpinBox *max_thread_count_spin_box_p =
    mp_maxThreadCountPreferencesWidget_ui->maxThreadCountSpinBox;
  max_thread_count_spin_box_p->setRange(1, ideal_thread_count);
  max_thread_count_spin_box_p->setValue(ideal_thread_count);

  connect(max_thread_count_spin_box_p,
          &QSpinBox::valueChanged,
          this,
          [&](const int &value) {
            static_cast<Application *>(qApp)
              ->getProgramWindow()
              ->setMaxThreadUseCount(value);
          });

  layout_p->addWidget(group_box_p);
}


void
ApplicationPreferencesWnd::writeDecimalPlacesPreferences()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("DecimalPlacesPreferences");

  settings.setValue(
    "AtomDecPlaces",
    mp_decimalPlacesPreferencesWidget_ui->atomDecimalPlacesSpinBox->value());
  settings.setValue(
    "PhPkaPiDecPlaces",
    mp_decimalPlacesPreferencesWidget_ui->pKaPhPiDecimalPlacesSpinBox->value());
  settings.setValue("OligomerDecPlaces",
                    mp_decimalPlacesPreferencesWidget_ui
                      ->oligomerDecimalPlacesSpinBox->value());
  settings.setValue(
    "PolymerDecPlaces",
    mp_decimalPlacesPreferencesWidget_ui->polymerDecimalPlacesSpinBox->value());

  settings.endGroup();
}

void
ApplicationPreferencesWnd::readDecimalPlacesPreferences()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  bool ok   = false;
  int value = 0;

  settings.beginGroup("DecimalPlacesPreferences");

  // libXpertMassCore::ATOM_DEC_PLACES
  value = settings.value("AtomDecPlaces", 6).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->atomDecimalPlacesSpinBox->setValue(
    value);

  // libXpertMassCore::INTENSITY_DEC_PLACES
  value = settings.value("PhPkaPiDecPlaces", 1).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->pKaPhPiDecimalPlacesSpinBox->setValue(
    value);

  // libXpertMassCore::OLIGOMER_DEC_PLACES
  value = settings.value("OligomerDecPlaces", 4).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->oligomerDecimalPlacesSpinBox->setValue(
    value);

  // libXpertMassCore::POLYMER_DEC_PLACES
  value = settings.value("PolymerDecPlaces", 2).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->polymerDecimalPlacesSpinBox->setValue(
    value);

  settings.endGroup();
}

void
ApplicationPreferencesWnd::setupDecimalPlacesPreferencesWidget()
{
  // This is the main page scroll area widget contents layout.
  // We will end up packing into this layout (towards the bottom) each newly
  // created QGroupBox widget.
  QVBoxLayout *layout_p = mp_generalPage_ui->mainWidgetContentsLayout;

  // The group box widget that will contain all we need to provide the user
  // with for them to configure the max count of threads.
  QGroupBox *group_box_p =
    mp_decimalPlacesPreferencesWidget_ui->decimalPlacesPreferencesGroupBox;

  layout_p->addWidget(group_box_p);

  readDecimalPlacesPreferences();
}

void
ApplicationPreferencesWnd::setupShortcutsPage()
{
  // The management of the actions, via the libXpertMassGui::ActionManager and
  // via the libXpertMassGui::ActionManagerWidget is performed in Application.

  libXpertMassGui::ActionManagerTableWidget *action_manager_table_widget_p =
    new libXpertMassGui::ActionManagerTableWidget(this);

  QIcon svg_icon(":/images/svg/keyboard-keys.svg");

  insertSectionListItem(
    "Shortcuts", svg_icon, action_manager_table_widget_p, Pages::SHORTCUTS);

  // qDebug() << "Done, inserting the section list item.";

  // We now have to fill in the table view.
  // ProgramWindow *program_window_p =
  //   static_cast<Application *>(qApp)->getProgramWindow();
  // if(program_window_p == nullptr)
  //   qFatal() << "The program window is nullptr !!!";

  libXpertMassGui::ActionManager *action_manager_p =
    static_cast<Application *>(qApp)->getProgramWindow()->getActionManager();
  if(action_manager_p == nullptr)
    qFatal() << "The action manager is nullptr !!!";

  qDebug() << "The action manager manages" << action_manager_p->actionCount()
           << "actions.";

  action_manager_table_widget_p->setActionManager(action_manager_p);

  action_manager_table_widget_p->fillInTableWithData();

  int row_count = action_manager_table_widget_p->fillInTableWithData();
  qDebug() << "Added" << row_count
           << "rows in the action manager table widget.";

  // At this point we have filled the table.
}

void
ApplicationPreferencesWnd::setupFileSystemHierarchyPreferencesWidget()
{
  // We cannot set 'this' as the parent of the widget below, because all
  // the geometry calculations will be messed-up !!!! (hours to debug this).
  mp_fileSystemHierarchyPreferencesWidget =
    new FileSystemHierarchyPreferencesWidget(m_applicationName,
                                             mp_ui->sectionsStackedWidget);

  // The widget we want to add as a new preferences section is
  QIcon svg_icon(":/images/svg/file-system-hierarchy-preferences.svg");
  insertSectionListItem(
    "File system hierarchy",
    svg_icon,
    mp_fileSystemHierarchyPreferencesWidget->getMainScrollArea(),
    Pages ::FILESYSTEM_HIERARCHY);
}

int
ApplicationPreferencesWnd::insertSectionListItem(const QString &label,
                                                 const QIcon &icon,
                                                 QWidget *section_widget_p,
                                                 int index)
{
  // qDebug() << "Inserting section list item" << label << "at index" << index;

  mp_ui->sectionsListWidget->insertItem(
    index, new QListWidgetItem(icon, label, mp_ui->sectionsListWidget));

  // Returns the index of the inserted widget.
  return mp_ui->sectionsStackedWidget->insertWidget(index, section_widget_p);
}

bool
ApplicationPreferencesWnd::showSectionListItem(int index)
{
  // The first section has index 0.
  Q_ASSERT(index >= 0 && index < Pages::COUNT);
  mp_ui->sectionsListWidget->setCurrentRow(index);

  return true;
}

void
ApplicationPreferencesWnd::apply()
{
  // The very first thing to do is check what is the currently active section.

  int current_index = mp_ui->sectionsStackedWidget->currentIndex();

  switch(current_index)
    {
      case Pages::GENERAL:
        applyGeneralPage();
        break;
      case Pages::SHORTCUTS:
        applyShortcutsPage();
        break;
      case Pages::FILESYSTEM_HIERARCHY:
        applyFileSystemHierarchyPage();
        break;
      default:
        qFatal() << "Programming error.";
    }
}

void
ApplicationPreferencesWnd::applyGeneralPage()
{
  // qDebug() << "The General page is currently displayed.";

  // The decimal places settings
  libXpertMassCore::ATOM_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->atomDecimalPlacesSpinBox->value();
  libXpertMassCore::PKA_PH_PI_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->pKaPhPiDecimalPlacesSpinBox->value();
  libXpertMassCore::OLIGOMER_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->oligomerDecimalPlacesSpinBox->value();
  libXpertMassCore::POLYMER_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->polymerDecimalPlacesSpinBox->value();

  writeDecimalPlacesPreferences();
}

void
ApplicationPreferencesWnd::applyShortcutsPage()
{
  qDebug() << "The Shortcuts page is currently displayed.";

  // At this point, all we have to do is save the shortcuts.

  libXpertMassGui::ActionManager *action_manager_p =
    static_cast<Application *>(qApp)->getProgramWindow()->getActionManager();
  if(action_manager_p == nullptr)
    {
      qFatal() << "The action manager is nullptr !!!";
      return;
    }

  action_manager_p->saveActionData();
}

void
ApplicationPreferencesWnd::applyFileSystemHierarchyPage()
{
  mp_fileSystemHierarchyPreferencesWidget->saveSettings();
}

void
ApplicationPreferencesWnd::debugWidgetBounds()
{
  // Temporarily give each widget a visible border
  QList<QWidget *> allWidgets = this->findChildren<QWidget *>();
  allWidgets.prepend(this); // Include this widget

  foreach(QWidget *w, allWidgets)
    {
      w->setStyleSheet("border: 2px solid red;");
    }

  // Or more selectively, use a timer to restore later
  // QTimer::singleShot(5000, [allWidgets]() {
  //   foreach(QWidget *w, allWidgets)
  //     {
  //       w->setStyleSheet("");
  //     }
  // });
}

} // namespace MassXpert
} // namespace MsXpS
