/* 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
 */


/////////////////////// Qt includes
#include <QMessageBox>
#include <QSettings>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/CrossLink.hpp>


/////////////////////// Local includes
#include "MonomerCrossLinkDlg.hpp"


namespace MsXpS
{

namespace MassXpert
{


MonomerCrossLinkDlg::MonomerCrossLinkDlg(SequenceEditorWnd *editorWnd,
                                         /* no polymer **/
                                         /* no libXpertMassCore::PolChemDef **/
                                         const QString &settings_file_path,
                                         const QString &application_name,
                                         const QString &description)
  : AbstractSeqEdWndDependentDlg(editorWnd,
                                 0, /*polymer **/
                                 0, /*polChemDef **/
                                 settings_file_path,
                                 "MonomerCrossLinkDlg",
                                 application_name,
                                 description)
{
  if(editorWnd == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  msp_polymer = editorWnd->getPolymerSPtr();

  if(msp_polymer == nullptr || msp_polymer.get() == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(!initialize())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
}


MonomerCrossLinkDlg::~MonomerCrossLinkDlg()
{
}


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

  setWindowIcon(qApp->windowIcon());

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

  populateAvailableCrossLinkerList();
  qDebug() << "Done populating the CrossLinker list.";

  populateListOfCrossLinkedMonomers();
  qDebug() << "Done populating the cross-linked Monomer list.";

  populateListOfCrossLinks();
  qDebug() << "Done populating the CrossLink list.";

  readSettings();

  connect(m_ui.crossLinkerListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(crossLinkerListWidgetItemSelectionChanged()));

  connect(m_ui.crossLinkedMonomerListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(crossLinkedMonomerListWidgetItemSelectionChanged()));

  connect(m_ui.crossLinkedMonomerListWidget,
          SIGNAL(itemActivated(QListWidgetItem *)),
          this,
          SLOT(crossLinkedMonomerListWidgetItemActivated(QListWidgetItem *)));

  connect(m_ui.crossLinkListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(crossLinkListWidgetItemSelectionChanged()));

  connect(m_ui.crossLinkListWidget,
          SIGNAL(itemActivated(QListWidgetItem *)),
          this,
          SLOT(crossLinkListWidgetItemActivated(QListWidgetItem *)));

  m_ui.displayAllCrossLinksCheckBox->setChecked(true);
  connect(m_ui.displayAllCrossLinksCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(displayAllCrossLinksChanged(int)));

  connect(m_ui.crossLinkPushButton, SIGNAL(clicked()), this, SLOT(crossLink()));

  connect(
    m_ui.uncrossLinkPushButton, SIGNAL(clicked()), this, SLOT(uncrossLink()));

  connect(this, SIGNAL(rejected()), this, SLOT(close()));

  return true;
}


void
MonomerCrossLinkDlg::readSettings()
{

  QSettings settings(mp_editorWnd->configSettingsFilePath(),
                     QSettings::IniFormat);


  settings.beginGroup("MonomerCrossLinkDlg");
  restoreGeometry(settings.value("geometry").toByteArray());
  m_ui.leftSplitter->restoreState(
    settings.value("leftSplitterSize").toByteArray());
  m_ui.rightSplitter->restoreState(
    settings.value("leftSplitterSize").toByteArray());
  settings.endGroup();
}


void
MonomerCrossLinkDlg::writeSettings()
{

  QSettings settings(mp_editorWnd->configSettingsFilePath(),
                     QSettings::IniFormat);

  settings.beginGroup("MonomerCrossLinkDlg");

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

  settings.setValue("leftSplitterSize", m_ui.leftSplitter->saveState());
  settings.setValue("rightSplitterSize", m_ui.rightSplitter->saveState());

  settings.endGroup();
}


bool
MonomerCrossLinkDlg::populateAvailableCrossLinkerList()
{
  // Provide the user with a list of all the available cross-links
  // as defined in the polymer chemistry definition.

  libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp =
    mp_editorWnd->getPolChemDefRenderingCstRPtr()->getPolChemDefCstSPtr();

  // First-off remove all the items.,
  m_ui.crossLinkerListWidget->clear();

  for(libXpertMassCore::CrossLinkerSPtr cross_linker_sp :
      pol_chem_def_csp->getCrossLinkersCstRef())
    m_ui.crossLinkerListWidget->addItem(cross_linker_sp->getName());

  return true;
}


bool
MonomerCrossLinkDlg::populateListOfCrossLinkedMonomers()
{
  // First-off remove all the items.
  m_ui.crossLinkedMonomerListWidget->clear();

  //  For each one of the CrossLink instances in the Polymer,
  //  get the list of Monomer instances involved in the CrossLink
  //  and display the list of these instances in the form of a string
  //  <monomer code>/<position in sequence>.

  // Because we do not need the Uuid of the cross-link itself, we can
  // iterate in the <object> container and not the <uuid-object> pair container.

  // qDebug() << "The polymer has" << msp_polymer->getCrossLinksCstRef().size()
  //          << "cross-links.";

  for(const libXpertMassCore::CrossLinkCstSPtr cross_link_csp :
      msp_polymer->getCrossLinksCstRef())
    {
      // For the currently iterated CrossLink, iterate in the
      // <uuid-object> pair container and for each monomer get to know the code
      // and index.

      //  Remember, the Monomers involved in a CrossLink are stored
      // in two containers: as a SPtr in a <object> container and as a
      // Uuid-WPtr pair in another container. The WPtr is a link to the SPtr
      // that has the peculiarity not to increment the SPtr reference count. The
      // Uuid (QString) of the pair unambiguously identifies the SPtr.

      for(const QString &uuid : cross_link_csp->getAllMonomerUuids())
        {
          libXpertMassCore::MonomerCstSPtr monomer_csp =
            cross_link_csp->getMonomerForUuid(uuid);

          if(monomer_csp == nullptr)
            qFatal() << "Inconsistency between the <object>_s and "
                              "<uuid-object> pairs.";

          bool ok   = false;
          int index = msp_polymer->getSequenceCstRef().monomerIndex(
            monomer_csp.get(), ok);

          if(!index && !ok)
            qFatal()
              << "Programming error. Failed to get the Monomer index in "
                 "the Sequence.";

          // Convert from index to position(+ 1 below).
          QString item_text =
            QString("%1/%2").arg(monomer_csp->getCode()).arg(index + 1);

          QListWidgetItem *item = new QListWidgetItem(item_text);
          item->setData(Qt::UserRole, uuid);

          m_ui.crossLinkedMonomerListWidget->addItem(item);
        }
    }

  return true;
}


bool
MonomerCrossLinkDlg::populateListOfCrossLinks(bool all)
{
  // First-off remove all the items.
  m_ui.crossLinkListWidget->clear();

  if(all)
    {
      // We should list all the cross-links in the polymer, and not
      // only the ones that are involved by the currently selected
      // cross-linked monomer.

      // We want to make sure we can trace back to a CrossLink instance
      // when later the user clicks onto a list widget item and ask that
      // that corresponding CrossLink be destroyed. To allow this to happen
      // we need to stored in the list widget item a Uuid string that
      // unambiguously identifies the CrossLink instance. There are provisions
      // for this in the Polyemr class.

      for(const QString &uuid : msp_polymer->getAllCrossLinkUuids())
        {
          libXpertMassCore::CrossLinkCstSPtr cross_link_csp =
            msp_polymer->getCrossLinkFromUuid(uuid);

          if(cross_link_csp == nullptr)
            qFatal() << "Inconsistency between the <object>_s and "
                              "<uuid-object> pairs.";

          QString item_text =
            QString("%1/%2")
              .arg(cross_link_csp->getCrossLinkerName())
              .arg(cross_link_csp->locationsOfOnlyExtremeSequenceMonomersAsText(
                libXpertMassCore::Enums::LocationType::POSITION));

          QListWidgetItem *item = new QListWidgetItem(item_text);
          item->setData(Qt::UserRole, uuid);

          m_ui.crossLinkListWidget->addItem(item);
        }

      return true;
    }
  else
    {
      // We are interested only in the crossLinks for the currently
      // selected cross-linked monomer (if any) in the
      // crossLinkedMonomerListWidget.

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

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

      // Get the index of the current item.
      std::size_t index = m_ui.crossLinkedMonomerListWidget->currentRow();

      // What's the item ?
      QListWidgetItem *item = m_ui.crossLinkedMonomerListWidget->item(index);

      // What's the text of the item ?
      QString item_text = item->text();

      // And now deconstruct the text that is in the form "C/3" if for
      // example, the cross-linked monomer has code 'C' and is a
      // position 3 (that is index 2).

      QStringList stringList =
        item_text.split('/', Qt::SkipEmptyParts, Qt::CaseSensitive);

      //      qDebug() << __FILE__ << __LINE__
      // 		<< "stringList:" << stringList.at(0) << stringList.at(1);

      // The monomer position is the second string in the list.
      bool ok              = false;
      int monomer_position = stringList.at(1).toInt(&ok);

      if(!monomer_position && !ok)
        return false;

      // Reuse the index variable to compute the monomer index in lieu of
      // the monomer position.
      index = --monomer_position;

      // What if the sequence changed and the monomer is no more in a
      // valid range? We want to avoid a crash.
      if(index >= msp_polymer->size() + 1)
        {
          QMessageBox::warning(this,
                               tr("MassXpert3 - Cross-link monomers"),
                               tr("%1@%2\n"
                                  "The monomer index does not correspond "
                                  "to a valid polymer sequence range.\n"
                                  "Avoid modifying the sequence while "
                                  "working with cross-links.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                               QMessageBox::Ok);

          return false;
        }

      //       qDebug() << __FILE__ << __LINE__
      // 		<< "Monomer index in the Sequence is:" << index;

      const libXpertMassCore::MonomerCstSPtr monomer_csp =
        msp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(index);

      // At this point we have to find all the crosslinks that involve
      // the monomer. We also need the Uuid associated to the CrossLink,
      // so follow the indirection below.
      for(const QString &uuid : msp_polymer->getAllCrossLinkUuids())
        {
          libXpertMassCore::CrossLinkCstSPtr cross_link_csp =
            msp_polymer->getCrossLinkFromUuid(uuid);

          if(cross_link_csp == nullptr)
            qFatal() << "Inconsistency between the <object>_s and "
                              "<uuid-object> pairs.";

          std::size_t in_count;
          std::size_t out_count;

          if(libXpertMassCore::Enums::CrossLinkEncompassed::PARTIALLY ==
             cross_link_csp->isEncompassedByIndexRange(
               index, index, in_count, out_count))
            {
              // This crossLink involves our monomer, make an item out
              // of it.

              QString item_text =
                QString("%1/%2")
                  .arg(cross_link_csp->getCrossLinkerName())
                  .arg(cross_link_csp
                         ->locationsOfOnlyExtremeSequenceMonomersAsText(
                           libXpertMassCore::Enums::LocationType::POSITION));

              QListWidgetItem *item = new QListWidgetItem(item_text);
              item->setData(Qt::UserRole, uuid);

              m_ui.crossLinkListWidget->addItem(item);
            }
        }
    }

  return true;
}


void
MonomerCrossLinkDlg::crossLinkerListWidgetItemSelectionChanged()
{
  // We have to update the modif list widget that details the
  // modifications that are defined in the currently selected
  // crosslinker item.

  // The crosslinker list is a single-selection list, thus the list
  // below might contain either 0 or 1 at most selected item.

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

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

  // That's the name of the selected crosslinker.
  QString name = selectedList.at(0)->text();

  // Find the crosslinker object in the polymer chemistry definition.

  libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp =
    mp_editorWnd->getPolChemDefRenderingCstRPtr()->getPolChemDefCstSPtr();

  libXpertMassCore::CrossLinkerCstSPtr cross_linker_csp =
    pol_chem_def_csp->getCrossLinkerCstSPtrByName(name);

  if(cross_linker_csp == nullptr)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "CrossLinker '%3' is not known.")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(name),
                           QMessageBox::Ok);

      return;
    }

  // Make sure we remove all the items in the crossLinkModifListWidget !

  m_ui.crossLinkModifListWidget->clear();

  for(libXpertMassCore::ModifCstSPtr modif_csp :
      cross_linker_csp->getModifsCstRef())
    m_ui.crossLinkModifListWidget->addItem(modif_csp->getName());

  m_ui.crossLinkerNameLineEdit->setText(cross_linker_csp->getName());

  // Finally, we have to remove bits of data from the details of the
  // crossLink.

  // The crossLink comment.
  m_ui.crossLinkCommentLineEdit->setText("");
  // The monomers involved in the crossLink.
  m_ui.targetPositionListWidget->clear();
  // The target position.
  m_ui.targetPositionLineEdit->setText("");
}


void
MonomerCrossLinkDlg::crossLinkedMonomerListWidgetItemSelectionChanged()
{
  // When an item is selected in the list of crossLinked monomers,
  // then that means that the user does not want *all* the crossLinks
  // to be listed.

  m_ui.displayAllCrossLinksCheckBox->setChecked(false);

  // Update the crossLink list data by listing only the crossLinks of
  // the currently selected monomer.

  populateListOfCrossLinks(false);

  // Remove all the data about the crossLink.

  // We want that the list of crossLinkers has no active selection.
  QListWidgetItem *item = m_ui.crossLinkerListWidget->currentItem();
  item->setSelected(false);

  // The crossLinker name.
  m_ui.crossLinkerNameLineEdit->setText("");
  // The modifications in the CrossLinker.
  m_ui.crossLinkModifListWidget->clear();
  // The crossLink comment.
  m_ui.crossLinkCommentLineEdit->setText("");
  // The monomers involved in the crossLink.
  m_ui.targetPositionListWidget->clear();
  // The target position.
  m_ui.targetPositionLineEdit->setText("");
}


void
MonomerCrossLinkDlg::crossLinkedMonomerListWidgetItemActivated(
  QListWidgetItem *item)
{
  //  A cross-linked monomer item has been activated. We need to
  //  get to the MonomerCstSPtr that belongs to the Polymer's Sequence
  //  container of MonomerSPtr. These Monomer instances are stored as
  // const shared pointers in the CrossLink.

  QString text = item->text();
  //  The visible text was formatted like this:
  // QString itemText = QString("%1/%2").arg(monomer_csp->getCode()).arg(index +
  // 1);

  QStringList stringList =
    text.split('/', Qt::SkipEmptyParts, Qt::CaseSensitive);

  //  We have stored in the item (that represents the data for a given
  //  Monomer being involved in the CrossLink),  a string that is a Uuid
  // relating the item to  a specific std::weak_ptr,  itself representing
  //  a std::shared_ptr<const Monomer> itself.
  QString uuid = item->data(Qt::UserRole).toString();

  libXpertMassCore::MonomerCstSPtr monomer_csp =
    msp_polymer->getCrossLinkedMonomerCstSPtrFromUuid(uuid);

  if(monomer_csp == nullptr)
    {
      QMessageBox::warning(this,
                           "MassXpert - Cross-link monomers",
                           QString("%1@%2\n"
                                   "The polymer sequence has changed.\n"
                                   "Avoid modifying the sequence while "
                                   "working with cross-links.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      return;
    }

  bool ok = false;

  int index = msp_polymer->getSequenceCstRef().monomerIndex(monomer_csp, ok);

  if(monomer_csp->getCode() != stringList.at(0) ||
     (index + 1) != stringList.at(1).toInt(&ok))
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "The polymer sequence has changed.\n"
                              "Avoid modifying the sequence while "
                              "working with cross-links.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);
    }

  mp_editorWnd->mpa_editorGraphicsView->resetSelection();

  mp_editorWnd->mpa_editorGraphicsView->setSelection(
    index, index, false, false);
}

void
MonomerCrossLinkDlg::crossLinkListWidgetItemSelectionChanged()
{
  // When a crossLink is selected, then its data have to be displayed
  // in the details widgets.

  // Let's start by clearing all the data about the crossLink, so that
  // the data that we'll display below are not crippled with
  // previous-operation-dirtyness.

  // The crossLinker name.
  m_ui.crossLinkerNameLineEdit->setText("");
  // The modifications in the CrossLinker.
  m_ui.crossLinkModifListWidget->clear();
  // The crossLink comment.
  m_ui.crossLinkCommentLineEdit->setText("");
  // The monomers involved in the crossLink.
  m_ui.targetPositionListWidget->clear();
  // The target position.
  m_ui.targetPositionLineEdit->setText("");

  // We want that the list of crossLinkers has no active selection.
  QListWidgetItem *item = m_ui.crossLinkerListWidget->currentItem();
  item->setSelected(false);

  // The crossLinkListWidget is a single-selection list.

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

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

  // Get the index of the current item.
  int index = m_ui.crossLinkListWidget->currentRow();
  // What's the item ?
  item = m_ui.crossLinkListWidget->item(index);
  // What's the text of the item(like "DisulfideBond/;2;6;/136958312")?
  QString text = item->text();

  //  We had set the item's visible text like this:
  // QString itemText =
  // QString("%1/%2")
  //   .arg(cross_link_csp->getCrossLinkerName())
  //   .arg(cross_link_csp->sequenceMonomerPositionsAsText());

  // And now deconstruct the text that is in the form
  // "DisulfideBond/;2;6;" if for example, the cross-linked
  // monomers are at positions 2 and 6 and are crossLinked with a
  // DisulfideBond. Also, the crossLink in memory has a pointer of
  // which the integer representation is 136958312.

  QStringList stringList =
    text.split('/', Qt::SkipEmptyParts, Qt::CaseSensitive);

  // The pointer to the CrossLink was stored hidden like this:
  // item->setData(
  //           Qt::UserRole,
  //           libXpertMassCore::Utils::pointerAsString(cross_link_csp.get()));

  QString uuid = item->data(Qt::UserRole).toString();

  libXpertMassCore::CrossLinkCstSPtr cross_link_csp =
    msp_polymer->getCrossLinkFromUuid(uuid);

  if(cross_link_csp == nullptr)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "The polymer sequence has changed.\n"
                              "Avoid modifying the sequence while "
                              "working with cross-links.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);
      return;
    }

  // At this point, we can simply show all the details of the
  // crossLink.

  // The name of the crossLink(or crossLinker, is the same).
  m_ui.crossLinkerNameLineEdit->setText(
    cross_link_csp->getCrossLinkerCstSPtr()->getName());

  // The modifications of the crossLinker(parent class of
  // libXpertMassCore::CrossLink).  Make sure we remove all the items in the
  // crossLinkModifListWidget !
  m_ui.crossLinkModifListWidget->clear();

  const std::vector<libXpertMassCore::ModifCstSPtr> &modifs_ref =
    cross_link_csp->getCrossLinkerCstSPtr()->getModifsCstRef();

  for(const libXpertMassCore::ModifCstSPtr &modif_csp : modifs_ref)
    m_ui.crossLinkModifListWidget->addItem(modif_csp->getName());

  // The crossLink comment
  m_ui.crossLinkCommentLineEdit->setText(cross_link_csp->getComment());

  // The monomers involved in the crossLink.
  m_ui.targetPositionListWidget->clear();

  for(const libXpertMassCore::MonomerCstSPtr &monomer_csp :
      cross_link_csp->getMonomersCstRef())
    {
      bool ok = false;
      int index =
        msp_polymer->getSequenceCstRef().monomerIndex(monomer_csp, ok);

      if(!ok)
        {
          QMessageBox::warning(this,
                               tr("MassXpert3 - Cross-link monomers"),
                               tr("%1@%2\n"
                                  "The polymer sequence has changed.\n"
                                  "Avoid modifying the sequence while "
                                  "working with cross-links.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                               QMessageBox::Ok);

          return;
        }

      QString itemText =
        QString("%1/%2").arg(monomer_csp->getCode()).arg(index + 1);

      m_ui.targetPositionListWidget->addItem(itemText);
    }
}


void
MonomerCrossLinkDlg::crossLinkListWidgetItemActivated(QListWidgetItem *item)
{
  QString text = item->text();

  //  We had set the item's visible text like this:
  // QString itemText =
  // QString("%1/%2")
  //   .arg(cross_link_csp->getCrossLinkerName())
  //   .arg(cross_link_csp->sequenceMonomerPositionsAsText());

  // And now deconstruct the text that is in the form
  // "DisulfideBond/;2;6;" if for example, the cross-linked
  // monomers are at positions 2 and 6 and are crossLinked with a
  // DisulfideBond. Also, the crossLink in memory has a pointer of
  // which the integer representation is 136958312.

  QStringList stringList =
    text.split('/', Qt::SkipEmptyParts, Qt::CaseSensitive);

  // The pointer to the CrossLink was stored hidden like this:
  // item->setData(
  //           Qt::UserRole,
  //           libXpertMassCore::Utils::pointerAsString(cross_link_csp.get()));

  QString uuid = item->data(Qt::UserRole).toString();

  libXpertMassCore::CrossLinkCstSPtr cross_link_csp =
    msp_polymer->getCrossLinkFromUuid(uuid);

  if(cross_link_csp == nullptr)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "The polymer sequence has changed.\n"
                              "Avoid modifying the sequence while "
                              "working with cross-links.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);
      return;
    }

  libXpertMassCore::IndexRangeCollection index_ranges;

  for(const libXpertMassCore::MonomerCstSPtr &monomer_csp :
      cross_link_csp->getMonomersCstRef())
    {
      bool ok = false;
      int index =
        msp_polymer->getSequenceCstRef().monomerIndex(monomer_csp, ok);

      if(!ok)
        {
          QMessageBox::warning(this,
                               tr("MassXpert3 - Cross-link monomers"),
                               tr("%1@%2\n"
                                  "The polymer sequence has changed.\n"
                                  "Avoid modifying the sequence while "
                                  "working with cross-links.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                               QMessageBox::Ok);

          return;
        }

      // Convert from index to position(+ 1 below).
      QString itemText =
        QString("%1/%2").arg(monomer_csp->getCode()).arg(index + 1);

      libXpertMassCore::IndexRange index_range(index, index);
      index_ranges.appendIndexRange(index_range);
    }

  mp_editorWnd->mpa_editorGraphicsView->resetSelection();

  mp_editorWnd->mpa_editorGraphicsView->setSelection(index_ranges, true, true);
}


void
MonomerCrossLinkDlg::displayAllCrossLinksChanged(int checkState)
{
  // When checked, we should list all the crossLinks in the
  // crossLinkListWidget, and not only the crossLinks for the
  // currently selected cross-linked monomer.

  if(checkState == Qt::Checked)
    populateListOfCrossLinks(true);
  else
    populateListOfCrossLinks(false);
}


std::vector<libXpertMassCore::MonomerSPtr>
MonomerCrossLinkDlg::parseTargetMonomerPositionsString()
{
  //  The shared pointer cannot be const because we are going to cross-link
  //  these monomers and thus apply vignettes on them, thus they cannot be
  //  const.
  std::vector<libXpertMassCore::MonomerSPtr> monomers;

  // This widget holds Monomer positions in the polymer sequence editor widget
  // of the monomer to cross-linked together.
  QString text = m_ui.targetPositionLineEdit->text();

  // Unspacify text.
  text.remove(QRegularExpression("\\s+"));

  //  Each string in the list is the position (an integer in text form) of
  //  one of the Monomers involved in the CrossLink.
  QStringList positionList = text.split(";", Qt::SkipEmptyParts);

  if(!positionList.size())
    return monomers;

  // For each position, make sure that we can get a monomer pointer
  // for it.

  for(int iter = 0; iter < positionList.size(); ++iter)
    {
      QString position = positionList.at(iter);

      // Convert to std::size_t.
      bool ok           = false;
      std::size_t index = position.toULongLong(&ok, 10) - 1;

      // Check conversion and msp_polymer sequence array boundaries.

      if((!index && !ok) || index >= msp_polymer->size())
        {
          QMessageBox::warning(this,
                               tr("MassXpert3 - Cross-link monomers"),
                               tr("%1@%2\n"
                                  "One monomer position is erroneous:")
                                 .arg(__FILE__)
                                 .arg(__LINE__)
                                 .arg(position),
                               QMessageBox::Ok);
          monomers.clear();
          return monomers;
        }

      const libXpertMassCore::MonomerSPtr monomer_csp =
        msp_polymer->getSequenceRef().getMonomerSPtrAt(index);

      monomers.push_back(monomer_csp);
    }

  return monomers;
}


void
MonomerCrossLinkDlg::crossLink()
{
  if(m_polymerSequenceModified)
    {
      int ret = QMessageBox::warning(this,
                                     tr("MassXpert3 - Cross-link monomers"),
                                     tr("%1@%2\n"
                                        "The polymer sequence has changed, "
                                        "continue cross-link?")
                                       .arg(__FILE__)
                                       .arg(__LINE__),
                                     QMessageBox::Yes | QMessageBox::No);

      if(ret == QMessageBox::No)
        return;
    }

  // Get the CrossLinker's name currently selected.
  QList<QListWidgetItem *> selectedList =
    m_ui.crossLinkerListWidget->selectedItems();

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

  QString cross_linker_name = selectedList.at(0)->text();
  Q_ASSERT(!cross_linker_name.isEmpty());

  // With the name of the cross-link get to the libXpertMassCore::CrossLink
  // proper.
  libXpertMassCore::CrossLinkerCstSPtr cross_linker_csp =
    msp_polymer->getPolChemDefCstSPtr()->getCrossLinkerCstSPtrByName(
      cross_linker_name);

  if(cross_linker_csp == nullptr)
    qFatal()
      << QString(
           "No CrossLinker by name %1 is known in the polymer chemistry "
           "definition.")
           .arg(cross_linker_name);

  // The user entered a string in the form "3;33" or more ("3;33;66",
  // for example) to represent the positions at which the monomers are
  // getting cross-linked. Deconstruct that string and converting to
  // indices (2 and 32 if string is "3;33") to get to the pointers to the
  // monomers. The shared pointer cannot be const because we are going
  // to modify them!

  std::vector<libXpertMassCore::MonomerSPtr> to_be_cross_linked_monomers =
    parseTargetMonomerPositionsString();

  // The logic here is that if the crosslinker has no modification,
  // the user might enter 2 monomers or more. Instead, if the
  // crosslinker defines x modifications(with x >= 2), then the
  // number of monomers must match that x. The numer of modifications
  // cannot be 1 because the number of target monomers cannot be less
  // than 2.

  if(to_be_cross_linked_monomers.size() < 2)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "The number of monomers engaged in the ."
                              "cross-link cannot be less than 2.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);
      return;
    }

  std::size_t modif_count = m_ui.crossLinkModifListWidget->count();

  if(modif_count && to_be_cross_linked_monomers.size() != modif_count)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "The number of monomers engaged in the ."
                              "cross-link must match the number of "
                              "modifications defined in the CrossLinker.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);
      return;
    }

  // The parsing of the user data seems correct, thus we can finally
  // create a CrossLink instance. First get the comment, if any, so
  // that we can initialize the CrossLink correctly.

  QString comment = m_ui.crossLinkCommentLineEdit->text();

  libXpertMassCore::CrossLinkSPtr cross_link_sp =
    std::make_shared<libXpertMassCore::CrossLink>(
      cross_linker_csp, msp_polymer, comment);

  // Now use the list of monomer pointers that point to the
  // monomers to crossLink in the polymer sequence: we want to copy these
  // monomer pointers into the list of monomer pointers that belongs to the
  // CrossLink instance allocated above. Attention, order of the monomers is
  // relevant, so append() is required, so that the monomers are set accordingly
  // to the order set by the user in the line edit widget.

  for(libXpertMassCore::MonomerSPtr monomer_sp : to_be_cross_linked_monomers)
    cross_link_sp->appendMonomer(monomer_sp);

  // Now ask the polymer sequence to perform the crossLink using the
  // CrossLink fully qualified here.

  QString uuid = msp_polymer->crossLink(cross_link_sp);

  if(uuid.isEmpty())
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "Cross-link with %3 failed.")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(cross_linker_name),
                           QMessageBox::Ok);
      return;
    }

  mp_editorWnd->setWindowModified(true);
  // But we know we have done that modif:
  m_polymerSequenceModified = false;

  // At this point the cross-link was chemically performed, but we
  // still need to make sure that the cross-link is rendered
  // graphically on each partner monomer.
  for(libXpertMassCore::MonomerSPtr monomer_sp : to_be_cross_linked_monomers)
    {
      // Get the index in the polymer's sequence of the currently iterated
      // monomer.
      bool ok = false;
      std::size_t index =
        msp_polymer->getSequenceRef().monomerIndex(monomer_sp, ok);

      if(!ok)
        qFatal()
          << "Programming error. Failed to get the index of a Monomer "
             "in the Sequence.";

      // At this point we can ask for the monomer's vignette
      // cross-link.

      // We have to make sure that the vignette knows for which
      // chemical entity it is created.

      ok = mp_editorWnd->mpa_editorGraphicsView->crossLinkVignetteAt(
        index, cross_link_sp, uuid);

      if(!ok)
        qFatal()
          << "Programming error. Failed to update Monomer vignette upon "
             "cross-linking.";
    }

  mp_editorWnd->updateWholeSequenceMasses(true);
  mp_editorWnd->updateSelectedSequenceMasses(true);
}


void
MonomerCrossLinkDlg::uncrossLink()
{
  if(m_polymerSequenceModified)
    {
      int ret = QMessageBox::warning(this,
                                     tr("MassXpert3 - Cross-link monomers"),
                                     tr("%1@%2\n"
                                        "The polymer sequence has changed, "
                                        "continue uncross-link?")
                                       .arg(__FILE__)
                                       .arg(__LINE__),
                                     QMessageBox::Yes | QMessageBox::No);

      if(ret == QMessageBox::No)
        return;
    }

  // The crossLinkListWidget is a single-selection list.

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

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

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

  // VERY FIRST STEP IS TO REMOVE THE ITEM FROM THE LIST
  QListWidgetItem *item = m_ui.crossLinkListWidget->takeItem(index);

  // Next get the two useful text strings. The one from the visible text (name
  // and targets like "DisulfideBond/;2;6;") and the one from the invisible text
  // (Uuid of the cross-link).f

  // What's the text of the item?
  QString item_text = item->text();
  qDebug() << "The text of the item to use for the uncrosLink:" << item_text;

  QString uuid = item->data(Qt::UserRole).toString();
  qDebug() << "The uuid of the item:" << uuid;

  // Clear the crossLink data, as these are no more faithful of the
  // newly selected item, as we have removed the item in question from
  // the listwidget.

  //  The crossLinker name.
  m_ui.crossLinkerNameLineEdit->setText("");
  // The modifications in the CrossLinker.
  m_ui.crossLinkModifListWidget->clear();
  // The crossLink comment.
  m_ui.crossLinkCommentLineEdit->setText("");
  // The monomers involved in the crossLink.
  m_ui.targetPositionListWidget->clear();
  // The target position.
  m_ui.targetPositionLineEdit->setText("");

  // And now deconstruct the text that is in the form
  // "DisulfideBond/;2;6;" if for example, the cross-linked
  // monomers are at positions 2 and 6 and are crossLinked with a
  // DisulfideBond.

  QStringList stringList =
    item_text.split('/', Qt::SkipEmptyParts, Qt::CaseSensitive);

  //  Store these two strings for verification below.

  qDebug() << "Before";

  // Get to the CrossLink instance using uuid.
  libXpertMassCore::CrossLinkSPtr cross_link_sp =
    msp_polymer->getCrossLinkFromUuid(uuid);

  qDebug() << "After";

  if(cross_link_sp == nullptr)
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "The polymer sequence has changed.\n"
                              "Avoid modifying the sequence while "
                              "working with cross-links.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      return;
    }

  qDebug() << "The name of the CrossLinker:"
           << cross_link_sp->getCrossLinkerCstSPtr()->getName();

  // For each monomer in the cross-link we have to uncrossLink the
  // vignette. Next, we'll destroy the cross-link from the polymer
  // sequence itself.

  qDebug() << "Now uncrosslinking the vignettes.";

  for(libXpertMassCore::MonomerCstSPtr monomer_csp :
      cross_link_sp->getMonomersCstRef())
    {
      // Get the index in the polymer of the currently iterated
      // monomer.
      bool ok = false;
      std::size_t index =
        msp_polymer->getSequenceCstRef().monomerIndex(monomer_csp, ok);

      if(!ok)
        qFatal() << "Programming error.";

      qDebug() << "Iterating in cross-linked Monomer:" << monomer_csp->getCode()
               << "at index" << index
               << "Now uncrosslinking the vignette at that index.";

      // At this point we can ask for the monomer's vignette
      // uncross-link.

      //// CAUTION, note that we are using the crossLink pointer here
      //// to identify the crossLink of which the vignettes should be
      //// removed. While the pointer is still "valid", that is non-0,
      //// what it points to is now deleted...

      bool val = mp_editorWnd->mpa_editorGraphicsView->uncrossLinkVignetteAt(
        index, uuid);

      if(!val)
        qFatal() << "Programming error.";
    }

  qDebug() << "Done uncrosslinking the vignettes.";

  // Now ask the polymer sequence to perform the uncrossLink using
  // the crossLink fully qualified here.

  qDebug() << "Now uncrosslinking the Monomers in the Polymer.";

  if(!msp_polymer->uncrossLink(cross_link_sp))
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - Cross-link monomers"),
                           tr("%1@%2\n"
                              "Uncross-link for %3 failed.")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(item_text),
                           QMessageBox::Ok);

      return;
    }


  qDebug() << "Done uncrosslinking the Monomers in the Polymer.";

  mp_editorWnd->setWindowModified(true);
  // But we know we have done that modif:
  m_polymerSequenceModified = false;

  // No need to delete the crossLink, as the uncrossLink'ing has done
  // that work.

  mp_editorWnd->updateWholeSequenceMasses(true);
  mp_editorWnd->mpa_editorGraphicsView->resetSelection();
  mp_editorWnd->updateSelectedSequenceMasses(true);
}


void
MonomerCrossLinkDlg::crossLinkChangedSlot(libXpertMassCore::Polymer *polymer_p)
{
  if(polymer_p == nullptr)
    qFatal() << "Programming error.";

  if(polymer_p != mp_editorWnd->getPolymerSPtr().get())
    qFatal() << "Programming error.";

  // We simply have to redisplay all the stuff.

  populateListOfCrossLinkedMonomers();
  populateListOfCrossLinks(/*all*/ true);
}

void
MonomerCrossLinkDlg::polymerSequenceModifiedSlot()
{
  m_polymerSequenceModified = true;
}

} // namespace MassXpert

} // namespace MsXpS
