/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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 <QMessageBox>
#include <QSettings>

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


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/PolChemDef.hpp>
#include <MsXpS/libXpertMassCore/Modif.hpp>
#include <MsXpS/libXpertMassCore/CrossLinker.hpp>

/////////////////////// libXpertMassGui includes


/////////////////////// Local includes
#include "ModifDefDlg.hpp"
#include "PolChemDefWnd.hpp"


namespace MsXpS
{

namespace MassXpert
{


ModifDefDlg::ModifDefDlg(libXpertMassCore::PolChemDefSPtr pol_chem_def_sp,
                         PolChemDefWnd *pol_chem_def_wnd_p,
                         const QString &settings_file_path,
                         const QString &application_name,
                         const QString &description)
  : AbstractPolChemDefDependentDlg(pol_chem_def_sp,
                                   pol_chem_def_wnd_p,
                                   settings_file_path,
                                   "ModifDefDlg",
                                   application_name,
                                   description),
    mref_modifs(pol_chem_def_sp->getModifsRef())
{
  if(msp_polChemDef == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";
  if(pol_chem_def_wnd_p == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(!initialize())
    {
      qCritical() << "Failed to initialize the modif definition window";
    }
}


ModifDefDlg::~ModifDefDlg()
{
}


void
ModifDefDlg::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  // No real close, because we did not ask that
  // close==destruction. Thus we only hide the dialog remembering its
  // position and size.

  mp_pol_chem_def_wnd_p->m_ui.modifPushButton->setChecked(false);

  readSettings();
}


void
ModifDefDlg::readSettings()
{
  QSettings settings(m_settings_file_path, QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  restoreGeometry(settings.value("geometry").toByteArray());
  m_ui.splitter->restoreState(settings.value("splitter").toByteArray());
  settings.endGroup();
}


void
ModifDefDlg::writeSettings()
{

  QSettings settings(m_settings_file_path, QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  restoreGeometry(settings.value("geometry").toByteArray());
  settings.setValue("splitter", m_ui.splitter->saveState());
  settings.endGroup();
}


bool
ModifDefDlg::initialize()
{
  m_ui.setupUi(this);

  setWindowIcon(qApp->windowIcon());

  // Now we need to actually show the window title (that element is empty in
  // m_ui)
  displayWindowTitle();

  // Set all the modifs to the list widget.

  for(libXpertMassCore::ModifSPtr modif_sp : mref_modifs)
    m_ui.modifListWidget->addItem(modif_sp->getName());


  readSettings();

  // Make the connections.

  connect(m_ui.addModifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(addModifPushButtonClicked()));

  connect(m_ui.removeModifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(removeModifPushButtonClicked()));

  connect(m_ui.moveUpModifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveUpModifPushButtonClicked()));

  connect(m_ui.moveDownModifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveDownModifPushButtonClicked()));

  connect(m_ui.applyModifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(applyModifPushButtonClicked()));

  connect(m_ui.validatePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(validatePushButtonClicked()));

  connect(m_ui.modifListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(modifListWidgetItemSelectionChanged()));


  return true;
}


void
ModifDefDlg::addModifPushButtonClicked()
{
  // We are asked to add a new modif. We'll add it right after the
  // current item.

  // Returns -1 if the list is empty.
  int index = m_ui.modifListWidget->currentRow();

  libXpertMassCore::ModifSPtr modif_sp = std::make_shared<libXpertMassCore::Modif>(
    msp_polChemDef, "Type Name", "Type Formula");

  mref_modifs.insert(mref_modifs.begin() + index, modif_sp);
  m_ui.modifListWidget->insertItem(index, modif_sp->getName());

  setModified();

  // Needed so that the setCurrentRow() call below actually set the
  // current row!
  if(index <= 0)
    index = 0;

  m_ui.modifListWidget->setCurrentRow(index);

  // Set the focus to the lineEdit that holds the name of the modif.
  m_ui.nameLineEdit->setFocus();
  m_ui.nameLineEdit->selectAll();
}


void
ModifDefDlg::removeModifPushButtonClicked()
{
  QList<QListWidgetItem *> selectedList = m_ui.modifListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the current modif.
  int index = m_ui.modifListWidget->currentRow();

  // Get a pointer to the modification, as we have to make one
  // check.

  libXpertMassCore::ModifSPtr modif_sp = mref_modifs.at(index);
  Q_ASSERT(modif_sp);

  // It is essential that we check if the modification being removed
  // is already used or not in a cross-linker, as it would not be
  // possible to tolerate this.

  const std::vector<libXpertMassCore::CrossLinkerSPtr>::const_iterator
    the_iterator = std::find_if(
      msp_polChemDef->getCrossLinkersCstRef().begin(),
      msp_polChemDef->getCrossLinkersCstRef().end(),
      [modif_sp](const libXpertMassCore::CrossLinkerSPtr cross_linker_sp) {
        return cross_linker_sp->hasModif(modif_sp->getName());
      });

  if(the_iterator != msp_polChemDef->getCrossLinkersCstRef().end())
    {
      QMessageBox::warning(this,
                           "MassXpert - Modif definition",
                           QString(tr("A modif with same name is used"
                                      " in cross-linker %1. Cannot remove it.")
                                     .arg((*the_iterator)->getName())),
                           QMessageBox::Ok);

      return;
    }

  // At this point we know we can delete the modif, after we have
  // removed it from the modification list of the polymer chemistry
  // definition.
  mref_modifs.erase(mref_modifs.begin() + index);

  // And the item.
  QListWidgetItem *item = m_ui.modifListWidget->takeItem(index);
  delete item;

  setModified();

  // If there are remaining items, we want to set the next item the
  // currentItem. If not, then, the currentItem should be the one
  // preceding the modif that we removed.

  if(m_ui.modifListWidget->count() >= index + 1)
    {
      m_ui.modifListWidget->setCurrentRow(index);
      modifListWidgetItemSelectionChanged();
    }

  // If there are no more items in the list, remove all the items
  // from the isotopeList.

  if(!m_ui.modifListWidget->count())
    {
      clearAllDetails();
    }
}


void
ModifDefDlg::moveUpModifPushButtonClicked()
{
  // Move the current row to one index less.

  // If no modif is selected, just return.

  QList<QListWidgetItem *> selectedList = m_ui.modifListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the modif and the modif itself.
  int index = m_ui.modifListWidget->currentRow();

  auto it = mref_modifs.begin() + index;
  // If the item is already at top of list, do nothing.
  if(it == mref_modifs.begin())
    return;
  std::iter_swap(it, it - 1); // Swap with the previous element

  QListWidgetItem *item = m_ui.modifListWidget->takeItem(index);

  m_ui.modifListWidget->insertItem(index - 1, item);
  m_ui.modifListWidget->setCurrentRow(index - 1);
  modifListWidgetItemSelectionChanged();

  setModified();
}


void
ModifDefDlg::moveDownModifPushButtonClicked()
{
  // Move the current row to one index less.

  // If no modif is selected, just return.

  QList<QListWidgetItem *> selectedList = m_ui.modifListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the modif and the modif itself.
  int index = m_ui.modifListWidget->currentRow();

  auto it = mref_modifs.begin() + index;
  // If the item is already at bottom of list, do nothing.
  if(it == mref_modifs.end())
    return;
  std::iter_swap(it, it + 1); // Swap with the next element

  QListWidgetItem *item = m_ui.modifListWidget->takeItem(index);
  m_ui.modifListWidget->insertItem(index + 1, item);
  m_ui.modifListWidget->setCurrentRow(index + 1);
  modifListWidgetItemSelectionChanged();

  setModified();
}


void
ModifDefDlg::applyModifPushButtonClicked()
{
  // We are asked to apply the data for the modif.

  // If no modif is selected, just return.

  QList<QListWidgetItem *> selectedList = m_ui.modifListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the current modif item.
  int index = m_ui.modifListWidget->currentRow();

  libXpertMassCore::ModifSPtr modif_sp = mref_modifs.at(index);

  // We do not want more than one modif by the same name or the same
  // symbol.

  QString editName = m_ui.nameLineEdit->text();

  QString editFormula = m_ui.formulaLineEdit->text();

  QString editTargets = m_ui.targetLineEdit->text();

  int maxCount = m_ui.maxCountSpinBox->value();

  // If a modif is found in the list with the name string, and that
  // modif is not the one that is current in the modif list, then we
  // are making a double entry, which is not allowed.

  std::vector<libXpertMassCore::ModifSPtr>::iterator the_iterator = std::find_if(
    mref_modifs.begin(),
    mref_modifs.end(),
    [editName](const libXpertMassCore::ModifSPtr iter_cross_linker_sp) {
      return iter_cross_linker_sp->getName() == editName;
    });

  if(the_iterator != mref_modifs.end() &&
     std::distance(mref_modifs.begin(), the_iterator) != index)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Modif definition"),
                           tr("A modification with same name "
                              "exists already."),
                           QMessageBox::Ok);
      return;
    }

  // A modif name cannot have the same name as a crossLinker. This is
  // because the name of the modif cannot clash with the name of a
  // crossLinker when setting the graphical vignette of the
  // modif.
  if(msp_polChemDef->getCrossLinkerCstSPtrByName(editName))
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Modif definition"),
                           tr("The name of the modification is already "
                              "used by a cross-linker."),
                           QMessageBox::Ok);
      return;
    }

  // At this point, validate the formula:

  libXpertMassCore::Formula formula(editFormula);

  libXpertMassCore::IsotopicDataCstSPtr isotopic_data_csp =
    msp_polChemDef->getIsotopicDataCstSPtr();

  libXpertMassCore::ErrorList error_list;
  if(!formula.validate(isotopic_data_csp, &error_list))
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Modif definition"),
                           tr("The formula failed to validate."),
                           QMessageBox::Ok);
      return;
    }

  // Validate the max count value.

  if(maxCount <= 0)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Modif definition"),
                           tr("The max count value is invalid."),
                           QMessageBox::Ok);
      return;
    }

  modif_sp->setMaxCount(maxCount);
  modif_sp->setName(editName);
  modif_sp->setFormula(editFormula);
  modif_sp->setTargets(editTargets);

  if(!modif_sp->isValid())
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Modif definition"),
                           tr("The targets string is invalid."),
                           QMessageBox::Ok);
      return;
    }

  // Update the list widget item.

  QListWidgetItem *item = m_ui.modifListWidget->currentItem();
  item->setData(Qt::DisplayRole, modif_sp->getName());

  setModified();
}


bool
ModifDefDlg::validatePushButtonClicked()
{
  // All we have to do is validate the modif definition. For that we'll
  // go in the listwidget items one after the other and make sure that
  // everything is fine and that colinearity is perfect between the
  // modif list and the listwidget.

  std::size_t modifs_count = m_ui.modifListWidget->count();

  if(modifs_count != mref_modifs.size())
    {
      QMessageBox::warning(this,
                           "MassXpert - Modif definition",
                           "\nThe number of modifs in the list widget \n"
                           "and in the list of modifs is not "
                           "identical.\n",
                           QMessageBox::Ok);
      return false;
    }

  libXpertMassCore::ErrorList error_list;

  for(std::size_t iter = 0; iter < mref_modifs.size(); ++iter)
    {
      QListWidgetItem *item = m_ui.modifListWidget->item(iter);

      libXpertMassCore::ModifSPtr modif_sp = mref_modifs.at(iter);

      if(item->text() != modif_sp->getName())
        error_list.push_back(QString("\nModif at index %1 has not the same\n"
                                     "name as the list widget item at the\n"
                                     "same index.\n")
                               .arg(iter));

      modif_sp->validate(&error_list);
    }

  if(error_list.size())
    {
      QMessageBox::warning(
        this,
        "MassXpert - Modif definition",
        QString("The modification failed to validate with errors: %1")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list, "\n")),
        QMessageBox::Ok);
      return false;
    }
  else
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Modif definition"),
                           ("Validation: success\n"),
                           QMessageBox::Ok);
    }

  return true;
}


void
ModifDefDlg::modifListWidgetItemSelectionChanged()
{
  // The modif item has changed. Update the details for the modif.

  // The list is a single-item-selection list.

  QList<QListWidgetItem *> selectedList = m_ui.modifListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the current modif.
  int index = m_ui.modifListWidget->currentRow();

  libXpertMassCore::ModifSPtr modif_sp = mref_modifs.at(index);

  // Set the data of the modif to their respective widgets.
  updateModifDetails(modif_sp);
}


void
ModifDefDlg::updateModifDetails(libXpertMassCore::ModifSPtr modif_sp)
{
  if(modif_sp != nullptr)
    {
      m_ui.nameLineEdit->setText(modif_sp->getName());
      m_ui.formulaLineEdit->setText(modif_sp->getFormula());
      m_ui.targetLineEdit->setText(modif_sp->getTargets());
      m_ui.maxCountSpinBox->setValue(modif_sp->getMaxCount());
    }
  else
    clearAllDetails();
}


void
ModifDefDlg::clearAllDetails()
{
  m_ui.nameLineEdit->setText("");
  m_ui.formulaLineEdit->setText("");
  m_ui.targetLineEdit->setText("");
  m_ui.maxCountSpinBox->setValue(1);
}


// VALIDATION
bool
ModifDefDlg::validate()
{
  return validatePushButtonClicked();
}

} // namespace MassXpert

} // namespace MsXpS
