/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright 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
#include <unordered_map>


/////////////////////// Qt includes
#include <QString>
#include <QUuid>


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


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/CrossLink.hpp"
#include "MsXpS/libXpertMassCore/Polymer.hpp"

int crossLinkMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::CrossLink>(
  "MsXpS::libXpertMassCore::CrossLink");

int crossLinkPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMassCore::CrossLink *>(
    "MsXpS::libXpertMassCore::CrossLinkPtr");

namespace MsXpS
{
namespace libXpertMassCore
{

/*!
\class MsXpS::libXpertMassCore::CrossLink
\inmodule libXpertMassCore
\ingroup PolChemDefAqueousChemicalReactions
\inheaderfile CrossLink.hpp

\brief The CrossLink class provides abstractions to work with
a cross-link entity between \l Monomer instances.

The notion of a cross-link is that it is a chemical reaction that involves at
least two monomers in a \l Polymer or in an \l Oligomer sequence.

Polymer sequences might contain more than one CrossLink and these are stored in
a container.
*/


/*!
\variable MsXpS::libXpertMassCore::CrossLink::mcsp_crossLinker

\brief The \l CrossLinker instance defining the chemistry of this CrossLink.
*/

/*!
\variable MsXpS::libXpertMassCore::CrossLink::mcsp_polymer

\brief The \l Polymer instance of which this CrossLink is part.
*/

/*!
\variable MsXpS::libXpertMassCore::CrossLink::m_monomers

\brief The container of \l Monomer instances that are involved in the formation
of this CrossLink.

\note

The Monomer pointers stored in the m_monomers member are MonomerCstSPtr that
point to MonomerSPtr items stored in the \l{Polymer}'s \l{Sequence}'s vector of
of MonomerSPtr (authorized implicit to-const cast).

Because it is not possible to perform the to-non-const back-cast, when needed
to access the \l{Sequence}'s MonomerSPtr, the search is performed by providing
the Monomer raw pointer managed by the MonomerSPtr in m_monomers.

The reason why the CrossLink stores the involved \l Monomer instances as
pointers to these very Monomer instances in the polymer sequence is that in this
way, even if the sequence is edited, the cross-link does not loose the relation
to the monomers. The only way that the sequence editing modifies a CrossLink
instance is by removing at least one \l Monomer instance involved in the
CrossLink. If that occurs, the CrossLink is informed and it is destructed.
*/

/*!
\variable MsXpS::libXpertMassCore::CrossLink::m_comment

\brief The comment that might be associated to this CrossLink.
*/


/*!
\typedef MsXpS::libXpertMassCore::CrossLinkSPtr

\relates MsXpS::libXpertMassCore::CrossLink
Synonym for std::shared_ptr<CrossLink>.
*/

/*!
\typedef MsXpS::libXpertMassCore::CrossLinkCstSPtr

\relates MsXpS::libXpertMassCore::CrossLink
Synonym for std::shared_ptr<const CrossLink>.
*/

/*!
\typedef MsXpS::libXpertMassCore::CrossLinkWPtr

\relates MsXpS::libXpertMassCore::CrossLink
Synonym for std::weak_ptr<CrossLink>.
*/

/*!
\typedef MsXpS::libXpertMassCore::CrossLinkCstWPtr

\relates MsXpS::libXpertMassCore::CrossLink
Synonym for std::weak_ptr<const CrossLink>.
*/
/*!
\typealias MsXpS::libXpertMassCore::UuidCrossLinkCstWPtrPair

\relates MsXpS::libXpertMassCore::CrossLink
Synonym for  std::pair<QString, CrossLinkCstWPtr>.
 */

/*!
\typealias MsXpS::libXpertMassCore::UuidCrossLinkWPtrPair

\relates MsXpS::libXpertMassCore::CrossLink

\brief Synonym for  std::pair<QString, CrossLinkWPtr>.

These pairs are used to store a unique identifier (Uuid) string related to
a std::shared_ptr<CrossLink> type. This kind of pair is used in a
container in the \l Polymer class. The fact that the std::shared_ptr is
converted to a std::weak_ptr is interesting because the item in the pair will
not increase the reference count.
*/


/*!
\brief Constructs a totally empty CrossLink instance
*/
CrossLink::CrossLink()
{
}

/*!
\brief Constructs a CrossLink instance

\list
\li \a pol_chem_def_csp: the polymer chemistry definition (\l PolChemDef).
\li \a polymer_cqsp: the \l Polymer instance in which this CrossLink was formed.
\li \a name: the name of this CrossLink instance.
\li \a formula: the \l Formula that describes the reaction that is the basis of
the chemical reaction leading to the formation of this CrossLink.
\li \a comment: a comment that might be associated to this CrossLink.
\endlist

\note Providing a nullptr for \a polymer_cqsp is fatal.
*/
CrossLink::CrossLink(PolChemDefCstSPtr pol_chem_def_csp,
                     PolymerCstQSPtr polymer_cqsp,
                     const QString &name,
                     const QString &formula,
                     const QString &comment)
  : mcsp_crossLinker(
      std::make_shared<const CrossLinker>(pol_chem_def_csp, name, formula)),
    mcsp_polymer(polymer_cqsp),
    m_comment(comment)
{
  if(polymer_cqsp == nullptr || polymer_cqsp.get() == nullptr)
    qFatal()
      << "Programming error. The pointer cannot be nullptr. If that pointer "
         "was gotten from Polymer::getCstSharedPtr(), ensure that raw pointer "
         "is "
         "indeed managed by a std::shared_ptr< Polymer> shared pointer.";
}

/*!
\brief Constructs a CrossLink instance

\list
\li \a cross_linker_csp: CrossLinker instance used to initialize this CrossLink.
\li \a polymer_cqsp: the \l Polymer instance in which this CrossLink was formed
\li \a comment: a comment that might be associated to this CrossLink
\endlist

\note Providing a nullptr for \a polymer_cqsp is fatal.
*/
CrossLink::CrossLink(CrossLinkerCstSPtr cross_linker_csp,
                     PolymerCstQSPtr polymer_cqsp,
                     const QString &comment)
  : mcsp_crossLinker(cross_linker_csp),
    mcsp_polymer(polymer_cqsp),
    m_comment(comment)
{
  if(polymer_cqsp == nullptr || polymer_cqsp.get() == nullptr)
    qFatal()
      << "Programming error. The pointer cannot be nullptr. If that pointer "
         "was gotten from Polymer::getCstSharedPtr(), ensure that raw pointer "
         "is "
         "indeed managed by a std::shared_ptr< Polymer> shared pointer.";
}

/*!
\brief Constructs a CrossLink instance as a copy of \a other.
*/
CrossLink::CrossLink(const CrossLink &other)
  : mcsp_crossLinker(other.mcsp_crossLinker),
    mcsp_polymer(other.mcsp_polymer),
    m_comment(other.m_comment),
    m_monomers(other.m_monomers)
{
}

/*!
\brief Destructs this CrossLink instance.

No entity needs to be destructed, since the \l Monomer instances in the list
are not owned by this CrossLinker instance.
*/
CrossLink::~CrossLink()
{
}

//////////////// THE POLYMER /////////////////////

/*!
\brief Returns the \l Polymer instance in which this CrossLink has
been formed.
*/
PolymerCstQSPtr
CrossLink::getPolymerCstSPtr() const
{
  return mcsp_polymer;
}

//////////////// THE CROSS-LINKER /////////////////////

/*!
\brief Returns the member CrossLinker.
*/
const CrossLinkerCstSPtr
CrossLink::getCrossLinkerCstSPtr() const
{
  return mcsp_crossLinker;
}

//////////////// THE COMMENT /////////////////////
/*!
\brief Sets the \a comment.
*/
void
CrossLink::setComment(const QString &comment)
{
  m_comment = comment;
}

/*!
\brief Returns the comment.
*/
const QString &
CrossLink::getComment() const
{
  return m_comment;
}

//////////////// THE NAME /////////////////////
/*!
\brief Returns the name of the member \l CrossLinker.
*/
QString
CrossLink::getCrossLinkerName() const
{
  if(mcsp_crossLinker != nullptr)
    return mcsp_crossLinker->getName();

  return QString();
}

//////////////// THE MONOMERS /////////////////////

/*!
\brief Returns a const reference to the Monomer container.
*/
const std::vector<MonomerCstSPtr> &
CrossLink::getMonomersCstRef() const
{
  return m_monomers;
}

/*!
\brief Returns a reference to the Monomer container.
*/
std::vector<MonomerCstSPtr> &
CrossLink::getMonomersRef()
{
  return m_monomers;
}

/*!
\brief Adds \a monomer_csp to the member container of Monomer instances and
returns the matching QUuid value string.
*/
QString
CrossLink::appendMonomer(MonomerCstSPtr monomer_csp)
{
  return storeMonomer(monomer_csp);
}

/*!
\brief Returns the Monomer at \a index in the member container of \l Monomer
instances.

\a index cannot be out of bounds.
*/
MonomerCstSPtr
CrossLink::getMonomerAt(std::size_t index)
{
  if(index >= m_monomers.size())
    qFatal() << "Programming error. Index is out of bounds.";

  return m_monomers.at(index);
}

/*!
\brief Removes the Monomer at \a index in the member container of \l Monomer
instances.

\a index cannot be out of bounds.

Returns true if a Monomer was indeed removed, false otherwise.
*/
bool
CrossLink::removeMonomerAt(std::size_t index)
{
  if(index >= m_monomers.size())
    qFatal() << "Programming error. Index is out of bounds.";

  MonomerCstSPtr monomer_csp = *(m_monomers.cbegin() + index);

  if(monomer_csp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  return removeMonomer(monomer_csp);
}

/*!
\brief Removes Monomer \a monomer_csp from the member container of Monomer
 intances.

 Returns true if a Monomer was indeed removed, false otherwise.
 */
bool
CrossLink::removeMonomer(MonomerCstSPtr monomer_csp)
{
  if(monomer_csp == nullptr || monomer_csp.get() == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  // We will need this anyway.
  QString uuid = getUuidForMonomer(monomer_csp);

  std::vector<MonomerCstSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_monomers.begin(),
                 m_monomers.end(),
                 [monomer_csp](MonomerCstSPtr iter_modif_sp) {
                   return iter_modif_sp == monomer_csp;
                 });

  if(the_iterator_cst == m_monomers.end())
    {
      qDebug() << "The MonomerCstSPtr was not found in the container.";

      if(!uuid.isEmpty())
        qFatal()
          << "Inconsistency between m_monomers and m_uuidMonomerPairs.";

      return false;
    }

  // At this point, both containers contain modif_sp.

  m_monomers.erase(the_iterator_cst);

  std::vector<UuidMonomerCstWPtrPair>::const_iterator the_pair_iterator_cst =
    std::find_if(m_uuidMonomerPairs.cbegin(),
                 m_uuidMonomerPairs.cend(),
                 [uuid](const UuidMonomerCstWPtrPair &the_pair) {
                   // Do not query the modif_sp managed object because it can
                   // be nullptr!
                   return the_pair.first == uuid;
                 });

  if(the_pair_iterator_cst == m_uuidMonomerPairs.cend())
    qFatal()
      << "Inconsistency between m_monomers and m_uuidMonomerPairs.";

  m_uuidMonomerPairs.erase(the_pair_iterator_cst);

  return true;
}

/*!
\brief Sets in the Monomer container, the Monomer instances occurring at the
indices listed in \a monomer_indices_text.

\a monomer_indices_text contains a list of \l Monomer instance indices separated
by ';' characters. The corresponding monomers (MonomerSPtr) found in the member
Polymer's Sequence object are copied to the member list of \l Monomer instances
  as MonomerCstSPtr using the UuidMonomerCstWPtrPair-based logic. This
    process effectively documents a CrossLink in that member Polymer's Sequence.

If the process is successful, \a ok is set to true, otherwise it is set to
false.

Returns the new size of the member container of Monomer instances.
*/
std::size_t
CrossLink::fillInMonomers(QString monomer_indices_text, bool &ok)
{
  m_monomers.clear();

  QStringList indices_list =
    monomer_indices_text.split(';', Qt::SkipEmptyParts);

  // There must be at least 2 monomers to make a cross-link !

  if(indices_list.size() < 2)
    {
      qDebug() << "The count of indices must be greater than 2.";

      ok = false;
      return 0;
    }

  for(auto &index_string : indices_list)
    {
      bool res          = false;
      std::size_t index = index_string.toInt(&res);

      if(!res)
        {
          ok = false;
          return 0;
        }

      if(index >= mcsp_polymer->size())
        {
          ok = false;
          m_monomers.clear();
          return 0;
        }

      storeMonomer(
        mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(index));
    }

  ok = true;

  return m_monomers.size();
}

/*!
\brief Returns a container with the locations of the Monomer instances
involved in this CrossLink.

If \a location_type is Enums::LocationType::INDEX, the locations are numbered as
indices. If \a location_type is Enums::LocationType::POSITION, the locations are
numbered as positions (that is, index + 1).

For example, if the cross-link involves Monomer instances at indices 20, 45, 89,
then the returned indices are: {20;45;89}.
*/
std::vector<std::size_t>
CrossLink::locationsOfInvolvedSequenceMonomers(
  Enums::LocationType location_type) const
{
  // First get indices.
  std::vector<std::size_t> indices;

  // qDebug() << "There are" << m_monomers.size()
  //     << "Monomer pointers in this cross-link.";

  for(const MonomerCstSPtr &monomer_csp : m_monomers)
    {
      bool ok = false;
      std::size_t monomer_index =
        mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_csp.get(), ok);

      if(ok)
        {
          if(location_type == Enums::LocationType::POSITION)
            indices.push_back(++monomer_index);
          else
            indices.push_back(monomer_index);
        }
    }

  // Sort the indices.
  std::sort(indices.begin(), indices.end());

  return indices;
}

/*!
\brief Returns a string containing a ';'-separated list of the locations of
the \l Monomer instances involved in this CrossLink.

If \a location_type is Enums::LocationType::INDEX, indices are formatted. If \a
location_type is Enums::LocationType::POSITION, positions are formatted (that
is, indices + 1).

For example, if the cross-link involves Monomer instances at indices 20, 45, 89,
then the returned indices are: "20;45;89".
*/
QString
CrossLink::locationsOfInvolvedSequenceMonomersAsText(
  Enums::LocationType location_type) const
{
  std::vector<std::size_t> locations =
    continuumOfLocationsOfInclusiveSequenceMonomers(location_type);

  return formatContainerOfMonomerLocationsAsText(locations);
}

/*!
\brief Returns a container with all the locations of the Monomer instances
involved in this CrossLink.

If \a location_type is Enums::LocationType::INDEX, the locations are numbered as
indices. If \a location_type is Enums::LocationType::POSITION, the locations are
numbered as positions (that is, index + 1).

For example, if the cross-link involves Monomer instances at indices 20, 45, 89,
then the indices are this continuum of indices: {20;21;22;...;87;88;89}.
*/
std::vector<std::size_t>
CrossLink::continuumOfLocationsOfInclusiveSequenceMonomers(
  Enums::LocationType location_type) const
{
  // First get indices.
  std::vector<std::size_t> indices;

  // qDebug() << "There are" << m_monomers.size()
  //     << "Monomer pointers in this cross-link.";

  for(const MonomerCstSPtr &monomer_csp : m_monomers)
    {
      bool ok = false;
      std::size_t monomer_index =
        mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_csp.get(), ok);

      if(ok)
        indices.push_back(monomer_index);
    }

  // Sort the indices.
  std::sort(indices.begin(), indices.end());

  // Now that we have the minIndex and the maxIndex of the region
  // involved by the cross-link, we can fill-in the integer list
  // with all the values contained between these two borders.

  std::size_t begin_index = (*indices.cbegin());
  std::size_t end_index   = (*std::prev(indices.cend()));

  // Only if positions are required, do we increment the indices by one.
  if(location_type == Enums::LocationType::POSITION)
    {
      ++begin_index;
      ++end_index;
    }

  std::vector<std::size_t> locations;

  for(std::size_t iter = begin_index; iter <= end_index; ++iter)
    {
      // If we had a cross-link between monomers [4] and [10] of the
      // polymer, then the indices appended to the list would be 4,
      // 5, 6, 7, 8, 9 and 10.
      locations.push_back(iter);
    }

  return locations;
}

/*!
\brief Returns a string containing a ';'-separated list of the locations of all
the \l Monomer instances involved in this CrossLink.

If \a location_type is Enums::LocationType::INDEX, indices are formatted. If \a
location_type is Enums::LocationType::POSITION, positions are formatted (that
is, indices + 1).

For example, if the cross-link involves Monomer instances at indices 20, 45, 89,
then the indices are this continuum of indices: "20;21;22;...;87;88;89".
*/
QString
CrossLink::continuumOfLocationsOfInclusiveSequenceMonomersAsText(
  Enums::LocationType location_type) const
{
  std::vector<std::size_t> locations =
    continuumOfLocationsOfInclusiveSequenceMonomers(location_type);

  return formatContainerOfMonomerLocationsAsText(locations);
}

/*!
\brief Returns a container with the locations of the two extreme Monomer
instances involved in this CrossLink.

If \a location_type is Enums::LocationType::INDEX, the locations are numbered as
indices. If \a location_type is Enums::LocationType::POSITION, the locations are
numbered as positions (that is, index + 1).

For example, if the cross-link involves Monomer instances at indices 20, 45, 89,
then the indices are this range of indices: [20--89].
*/
std::vector<std::size_t>
CrossLink::locationsOfOnlyExtremeSequenceMonomers(
  Enums::LocationType location_type) const
{
  std::vector<std::size_t> continuum_locations =
    continuumOfLocationsOfInclusiveSequenceMonomers(location_type);

  if(continuum_locations.size() < 2)
    qFatal()
      << "Programming error. Cannot be that less than two monomers are "
         "involved in CrossLink.";

  std::vector<std::size_t> extreme_locations;
  // Only keep first and last.
  extreme_locations.push_back(continuum_locations.front());
  extreme_locations.push_back(continuum_locations.back());

  return extreme_locations;
}

/*!
\brief Returns a string containing a ';'-separated list of the locations of
the two extreme \l Monomer instances involved in this CrossLink.

If \a location_type is Enums::LocationType::INDEX, indices are formatted. If \a
location_type is Enums::LocationType::POSITION, positions are formatted (that
is, indices + 1).

For example, if the cross-link involves Monomer instances at indices 20, 45, 89,
then the indices are this range of indices: "20;89".
*/
QString
CrossLink::locationsOfOnlyExtremeSequenceMonomersAsText(
  Enums::LocationType location_type) const
{
  std::vector<std::size_t> locations =
    locationsOfOnlyExtremeSequenceMonomers(location_type);

  return formatContainerOfMonomerLocationsAsText(locations);
}

/*!
\brief Returns the index of \a monomer_csp as found in the member container of
Monomer instances.

\a monomer_csp is typically a Monomer found in the member Polymer for which this
function establishes if it is involved in this CrossLink.

If \a monomer_csp is not found, \a ok is set to false and 0 is returned,
otherwise \a ok is set to true and the index of the found Monomer is returned.
*/
std::size_t
CrossLink::monomerIndex(MonomerCstSPtr monomer_csp, bool &ok) const
{
  if(monomer_csp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  std::vector<MonomerCstSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_monomers.cbegin(),
                 m_monomers.cend(),
                 [&](const MonomerCstSPtr &iter_monomer_csp) {
                   return iter_monomer_csp == monomer_csp;
                 });

  if(the_iterator_cst == m_monomers.cend())
    {
      // Let the caller know.
      ok = false;
      return 0;
    }

  // Let the caller know.
  ok = true;
  return std::distance(m_monomers.cbegin(), the_iterator_cst);
}

/*!
\brief Returns the index of \a monomer_crp as found in the container of Monomer
instances.

\a monomer_crp is typically a Monomer found in the member Polymer for which this
function establishes if it is involved in this CrossLink.

If \a monomer_crp is not found, \a ok is set to false and 0 is returned,
otherwise
\a ok is set to true and the index of the found Monomer is returned.
*/
std::size_t
CrossLink::monomerIndex(MonomerCstRPtr monomer_crp, bool &ok) const
{
  if(monomer_crp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  std::vector<MonomerCstSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_monomers.cbegin(),
                 m_monomers.cend(),
                 [&](const MonomerCstSPtr &iter_monomer_csp) {
                   return iter_monomer_csp.get() == monomer_crp;
                 });

  if(the_iterator_cst == m_monomers.cend())
    {
      // Let the caller know.
      ok = false;
      return 0;
    }

  // Let the caller know.
  ok = true;
  return std::distance(m_monomers.cbegin(), the_iterator_cst);
}

/*!
\brief Returns the first \l Monomer instance in the member container of
monomers. If the container is empty; returns nullptr.
*/
MonomerCstSPtr
CrossLink::getFirstMonomer() const
{
  if(!m_monomers.size())
    return nullptr;

  return m_monomers.front();
}

//////////////// OPERATORS /////////////////////
/*!
\brief Assigns \a other to this CrossLink instance.

After having set the member data, this CrossLink instance is validated and the
result is set to m_isValid.

Returns a reference to this CrossLink instance.
*/
CrossLink &
CrossLink::operator=(const CrossLink &other)
{
  if(&other == this)
    return *this;

  mcsp_crossLinker = other.mcsp_crossLinker;
  mcsp_polymer     = other.mcsp_polymer;
  m_comment        = other.m_comment;

  // The Monomer instances are deeply duplicated.
  for(const MonomerCstSPtr &monomer_csp : other.m_monomers)
    storeMonomer(monomer_csp);

  ErrorList error_list;

  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "The assignment operator copying produced a CrossLink that is not "
         "valid, with errors:"
      << Utils::joinErrorList(error_list, ", ");

  return *this;
}

/*!
\brief Returns true if this CrossLink instance and \a other are identical,
false otherwise.

The monomers are compared deeply (non on the basis of the pointer value).
*/
bool
CrossLink::operator==(const CrossLink &other) const
{
  if(&other == this)
    return true;

  if(mcsp_crossLinker != other.mcsp_crossLinker ||
     mcsp_polymer != other.mcsp_polymer || m_comment != other.m_comment)
    return false;

  // Because we are dealing with Monomer shared pointers to Monomer instances
  // found in the containing Polymer, we must compare the pointers, not the
  // Monomer chemical entities.

  if(other.m_monomers.size() != m_monomers.size())
    return false;

  for(std::size_t iter = 0; iter < other.m_monomers.size(); ++iter)
    {
      if(*m_monomers.at(iter) != *other.m_monomers.at(iter))
        {
          // qDebug() << "At least one Monomer instance differs in both
          // CrossLink instances.";
          return false;
        }
    }

  return true;
}

/*!
\brief Returns true if this CrossLink instance and \a other are different,
false otherwise.

Returns the negated result of operator==(other);
*/
bool
CrossLink::operator!=(const CrossLink &other) const
{
  if(&other == this)
    return false;

  return !operator==(other);
}

//////////////// CROSS-LINK LOGIC /////////////////////
/*!
\brief Tells if this CrossLink instance is encompassed (partially or fully) or
not at all by the Polymer sequence region defined by the [\a start - \a end ]
\l Monomer index range.

In other words, this function tells if a given set of Monomers are involved in
this CrossLink.

The count of monomers involved in this cross-link that:

\list
\li Are contained in the range is stored in \a in_count.
\li Are not contained in the range is stored in \a out_count.
\endlist

If all the monomers are listed as \a in_count, then this CrossLink is fully
encompassed by the Polymer sequence region defined by the [\a start - \a end ]
\l Monomer index range and Enums::CrossLinkEncompassed::FULLY is returned.

If all the monomers are listed as \a out_count, then this CrossLink is not at
all encompassed by the Polymer sequence region and
Enums::CrossLinkEncompassed::NOT is returned.

If both kinds of monomers were found, then
Enums::CrossLinkEncompassed::PARTIALLY is returned.
*/
Enums::CrossLinkEncompassed
CrossLink::isEncompassedByIndexRange(std::size_t start,
                                     std::size_t end,
                                     std::size_t &in_count,
                                     std::size_t &out_count) const
{
  if(mcsp_polymer == nullptr)
    qFatal()
      << "Programming error. The Polymer pointer cannot be nullptr.";

  // Iterate in the list of monomers, and for each monomer check if
  // their index is contained in the stretch.

  in_count  = 0;
  out_count = 0;

  std::size_t local_start = std::min(start, end);
  std::size_t local_end   = std::max(start, end);

  for(const MonomerCstSPtr &monomer_csp : m_monomers)
    {
      bool ok = false;
      std::size_t monomer_index =
        mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_csp.get(), ok);

      if(ok)
        {
          if(monomer_index >= local_start && monomer_index <= local_end)
            ++in_count;
          else
            ++out_count;
        }
    }

  if((in_count + out_count) != m_monomers.size())
    qFatal()
      << "The count of monomers in and out should sum to the size of "
         "the Monomer container.";

  if(out_count && in_count)
    return Enums::CrossLinkEncompassed::PARTIALLY;

  if(out_count)
    return Enums::CrossLinkEncompassed::NOT;

  if(in_count)
    return Enums::CrossLinkEncompassed::FULLY;

  return Enums::CrossLinkEncompassed::NOT;
}

/*!
\brief Tells if this CrossLink instance is encompassed (partially or fully) or
not at all by various \l IndexRange instances in the \a index_ranges \l
IndexRangeCollection.

In other words, this function tells if a given set of Monomers are involved in
this CrossLink. For examle, when \a index_ranges contains a single index range
that resolves to a single Monomer index in the Sequence, this function simply
says if the CrossLink involves that monomer.

The count of monomers involved in this cross-link that:

\list
\li Are contained in the regions defined in \a index_ranges is stored in \a
in_count.
\li Are not contained in the range is stored in \a out_count.
\endlist

If all the monomers are listed as \a in_count, then this CrossLink is fully
encompassed by the Polymer sequence regions defined in \a index_ranges and
Enums::CrossLinkEncompassed::FULLY is returned.

If all the monomers are listed as \a out_count, then this CrossLink is not at
all encompassed by the Polymer sequence region and
Enums::CrossLinkEncompassed::NOT is returned.

If both kinds of monomers were found, then
Enums::CrossLinkEncompassed::PARTIALLY is returned.
*/
Enums::CrossLinkEncompassed
CrossLink::isEncompassedByIndexRangeCollection(
  const IndexRangeCollection &index_ranges,
  std::size_t &in_count,
  std::size_t &out_count) const
{
  if(mcsp_polymer == nullptr)
    qFatal()
      << "Programming error. The Polymer pointer cannot be nullptr.";

  // Iterate in the list of monomers involved in *this crossLink,
  // and for each monomer check if their index is contained in any
  // of the Coordinates [start--end] of the coordinateList passed as
  // parameter.

  in_count  = 0;
  out_count = 0;

  for(const MonomerCstSPtr &monomer_csp : m_monomers)
    {
      bool was_counted_in = false;

      bool ok = false;
      qsizetype monomer_index =
        mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_csp.get(), ok);

      if(ok)
        {
          // Is the index encompassed by any of the SequenceRange instances of
          // the SequenceRanges ?

          foreach(const IndexRange *item, index_ranges.getRangesCstRef())
            {
              if(monomer_index >= item->m_start &&
                 monomer_index <= item->m_stop)
                {
                  ++in_count;
                  was_counted_in = true;
                  break;
                }
            }

          if(!was_counted_in)
            ++out_count;
        }
    }

  if((in_count + out_count) != m_monomers.size())
    qFatal()
      << "The count of monomers in and out should sum to the size of "
         "the Monomer container.";

  if(out_count && in_count)
    return Enums::CrossLinkEncompassed::PARTIALLY;

  if(out_count)
    return Enums::CrossLinkEncompassed::NOT;

  if(in_count)
    return Enums::CrossLinkEncompassed::FULLY;

  return Enums::CrossLinkEncompassed::NOT;
}

//////////////// VALIDATIONS /////////////////////
/*!
\brief Returns true if this CrossLink instance validates successfully, false
otherwise.

The validation is successful if:

\list
\li The count of \l Monomer instances listed in the member list is > 1.

\li The member \l Polymer instance exists.

\li The count of \l Monomer instances listed in the member list is equal to
the number of \l Modif instance in the \l CrossLinker base class.

\li If the list of \l Modif instances in the \l CrossLinker base class is not
empty, there must be a colinearity between the order in which these instances
are listed and the order in which the \l Monomer instances are listed in the
member list. The monomer instance must be a target of the modification.
\endlist

Errors are reported as messages in \a error_list_p.
*/
bool
CrossLink::validate(ErrorList *error_list_p) const
{
  Q_ASSERT(error_list_p != nullptr);

  // Set it as valid, then we negate as we validate.
  m_isValid = true;

  if(m_monomers.size() <= 1)
    {
      error_list_p->push_back(
        "A CrossLink involving only one Monomer is invalid");

      qDebug() << "A CrossLink involving only one Monomer is invalid.";

      m_isValid = false;
    }

  if(mcsp_polymer == nullptr)
    {
      error_list_p->push_back("A CrossLink with no Polymer is invalid");

      qDebug() << "A CrossLink with no Polymer is invalid.";

      m_isValid = false;
    }

  for(const MonomerCstSPtr &monomer_csp : m_monomers)
    {
      if(!monomer_csp->validate(error_list_p))
        {
          error_list_p->push_back(
            "A CrossLink involving invalid Monomer instances is invalid");

          qDebug()
            << "A CrossLink involving invalid Monomer instances is invalid.";

          m_isValid = false;
        }
    }

  if(mcsp_crossLinker == nullptr)
    {
      error_list_p->push_back("A CrossLink with no CrossLinker is invalid");

      qDebug() << "A CrossLink with no CrossLinker is invalid.";

      m_isValid = false;
      return m_isValid;
    }
  else
    {
      if(!mcsp_crossLinker->validate(error_list_p))
        {
          error_list_p->push_back(
            "A CrossLink with an invalid CrossLinker is invalid");

          qDebug() << "A CrossLink with an invalid CrossLinker is invalid.";

          m_isValid = false;
          return m_isValid;
        }

      // The CrossLinker might have Modif instances in its container. These
      // Modif instances
      // need to be compatible with the monomers involved in this CrossLink,
      // that is, their targets specifications need to be compatible with the
      // Monomers involved in this CrossLink.

      const std::vector<ModifCstSPtr> &cross_linker_modifs =
        mcsp_crossLinker->getModifsCstRef();

      // Indeed, there is some logic here. Either the m_modifList in the
      // parent class CrossLinker contains no item, in which case
      // everything is fine, or it does contain items. In the latter case,
      // then, the number of items must match the number of monomers being
      // crosslinked, and then we get to know which modif is attributable
      // to which monomer, hence the possibility of a check.

      if(cross_linker_modifs.size())
        {
          if(cross_linker_modifs.size() != m_monomers.size())
            {
              error_list_p->push_back(
                "A CrossLink where the number of Modif "
                "does not match the number of "
                "Monomer is invalid");

              qDebug() << "A CrossLink where the number of Modif "
                          "does not match the number of "
                          "Monomer is invalid.";

              m_isValid = false;
              return m_isValid;
            }

          // At this point, we can make the check for each modif/monomer:
          for(std::size_t iter = 0; iter < m_monomers.size(); ++iter)
            {
              MonomerCstSPtr monomer_csp = m_monomers.at(iter);

              if(!monomer_csp->isModifTarget(*cross_linker_modifs.at(iter)))
                {
                  error_list_p->push_back(
                    "A CrossLink where the Monomer is not a target of Modif is "
                    "invalid");

                  qDebug() << "A CrossLink where the Monomer is not a "
                              "target of Modif is invalid";

                  m_isValid = false;
                  return m_isValid;
                }
            }
        }
    }

  return m_isValid;
}

/*!
\brief Returns the validity status of the CrossLink.
*/
bool
CrossLink::isValid() const
{
  return m_isValid;
}

//////////////// MASS OPERATIONS /////////////////////

/*!
\brief Calculates the mass of the CrossLink as the mass of the CrossLinker.

Sets the calculated masses to \a mono and \a avg. If \a isotopic_data_csp, these
reference data are used for the calculation, otherwise those in the PolChemDef
referenced by the CrossLinker are used.

Returns true if the calculation could proceed without error.
*/
bool
CrossLink::calculateMasses(const IsotopicDataCstSPtr &isotopic_data_csp,
                           double &mono,
                           double &avg) const
{
  if(mcsp_crossLinker == nullptr || mcsp_crossLinker.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  return mcsp_crossLinker->calculateMasses(isotopic_data_csp, mono, avg);
}

/*!
\brief Adds this CrossLinker's member masses to \a mono and \a avg,
as compounded by the \a times factor.

Returns a reference to this CrossLink.
*/
const CrossLink &
CrossLink::accountMasses(double &mono, double &avg, int times) const
{
  if(mcsp_crossLinker == nullptr || mcsp_crossLinker.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  mcsp_crossLinker->accountMasses(mono, avg, times);

  // qDebug() << "Done accounting masses for this CrossLink with CrossLinker:"
  //          << mcsp_crossLinker->getName();

  return *this;
}

/*!
\brief Returns the CrossLinker's mass of the type defined by \a mass_type.
*/
double
CrossLink::getMass(Enums::MassType mass_type) const
{
  if(mcsp_crossLinker == nullptr || mcsp_crossLinker.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  return mcsp_crossLinker->getMass(mass_type);
}

////////////////UTILS /////////////////////
/*!
\brief Returns an allocated string describing this CrossLink instance.
*/
QString
CrossLink::prepareResultsTxtString()
{
  if(mcsp_crossLinker == nullptr || mcsp_crossLinker.get() == nullptr)
    qFatal()
      << "Programming error. The CrossLinker pointer cannot be nullptr.";

  if(mcsp_polymer == nullptr)
    qFatal()
      << "Programming error. The Polymer pointer cannot be nullptr.";

  QString text;

  text += QObject::tr(
            "\nCross-link:\n"
            "===============\n"
            "Cross-linker name: %1\n"
            "Cross-link comment: %2\n")
            .arg(mcsp_crossLinker->getName())
            .arg(m_comment);

  int iter = 1;

  for(const MonomerCstSPtr &monomer_csp : m_monomers)
    {
      bool ok = false;
      std::size_t monomer_index =
        mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_csp.get(), ok);

      text += QObject::tr("Partner %1: %2 at position: %3\n")
                .arg(iter)
                .arg(monomer_csp->getCode())
                .arg(monomer_index + 1);

      ++iter;
    }

  return text;
}

/*!
\brief Crafts and returns a string in the form ";<location>;<location>;"
corresponding to the values stored in the \a locations container.

This function is used to format either Monomer indices or Monomer positions,
which is why it has the neutral "locations" term in its name.

If the container is empty, an empty string is returned.

\sa continuumOfLocationsOfInclusiveSequenceMonomers(),
locationsOfOnlyExtremeSequenceMonomers(),
locationsOfOnlyExtremeSequenceMonomersAsText()
*/
QString
CrossLink::formatContainerOfMonomerLocationsAsText(
  const std::vector<std::size_t> &locations) const
{
  if(!locations.size())
    return QString();

  QString text = ";";

  for(std::size_t location : locations)
    text += QString("%1;").arg(location);

  return text;
}

/*!
\brief Stores the Monomer instance \a monomer_csp pointer in the member
container.

The \a monomer_csp is stored as is, without duplication.

Returns the Uuid string associated to the stored Monomer.
*/
QString
CrossLink::storeMonomer(const MonomerCstSPtr &monomer_csp)
{
  if(monomer_csp == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  // qDebug() << "Right before storage, there are currently" <<
  // m_monomers.size()
  //          << "monomers.";

  // Do not store an item twice.
  if(hasMonomer(monomer_csp) || !getUuidForMonomer(monomer_csp).isEmpty())
    qFatal()
      << "It is prohibited to store the same MonomerCstSPtr more than once.";

  // Even if we get a ref to shared_ptr, the reference count increment will
  // occur.
  m_monomers.push_back(monomer_csp);
  QString uuid = QUuid::createUuid().toString();
  m_uuidMonomerPairs.push_back(UuidMonomerCstWPtrPair(uuid, monomer_csp));

  // qDebug() << "Right after storage, there are currently" << m_monomers.size()
  //          << "monomers.";

  return uuid;
}

/*!
\brief Returns true if \a monomer_csp was found in the member container of
Monomer instances, false otherwise.
*/
bool
CrossLink::hasMonomer(const MonomerCstSPtr &monomer_csp) const
{
  if(monomer_csp == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  std::vector<MonomerCstSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_monomers.cbegin(),
                 m_monomers.cend(),
                 [monomer_csp](const MonomerCstSPtr &the_monomer_csp) {
                   return the_monomer_csp == monomer_csp;
                 });

  if(the_iterator_cst == m_monomers.cend())
    return false;

  // No sanity checks with getMonomerFromUuid() or getUuidForMonomer()
  // because that makes circular calls (these functions make sanity
  // checks by calling this hasMonomer().)

  return true;
}

/*!
\brief Returns true if \a monomer_csp was found in the member container of
Uuid-Monomer pairs, false otherwise.
*/
bool
CrossLink::hasUuid(const MonomerCstSPtr &monomer_csp) const
{
  if(monomer_csp == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  std::vector<UuidMonomerCstWPtrPair>::const_iterator the_iterator_cst =
    std::find_if(m_uuidMonomerPairs.cbegin(),
                 m_uuidMonomerPairs.cend(),
                 [monomer_csp](const UuidMonomerCstWPtrPair &the_pair) {
                   return the_pair.second.lock() == monomer_csp;
                 });

  if(the_iterator_cst == m_uuidMonomerPairs.cend())
    return false;

  // Sanity check
  if(!hasMonomer(monomer_csp))
    qFatal()
      << "Inconsistency between m_monomers and m_uuidMonomerPairs.";

  return true;
}

/*!
\brief Returns the Monomer instance pointer in the member container that is
associated to the \a uuid Uuid string.

If no such Monomer instance pointer is found,  nullptr is returned.
*/
MonomerCstSPtr
CrossLink::getMonomerForUuid(const QString &uuid) const
{
  // qDebug() << "There are currently" << m_monomers.size()
  //          << "monomers. The uuid that is asked for:" << uuid;

  std::vector<std::pair<QString, MonomerCstWPtr>>::const_iterator
    the_iterator_cst =
      std::find_if(m_uuidMonomerPairs.cbegin(),
                   m_uuidMonomerPairs.cend(),
                   [uuid](const UuidMonomerCstWPtrPair &the_pair) {
                     return the_pair.first == uuid;
                   });

  if(the_iterator_cst == m_uuidMonomerPairs.cend())
    return nullptr;

  MonomerCstSPtr monomer_csp = (*the_iterator_cst).second.lock();

  // Sanity check

  if(!hasMonomer(monomer_csp))
    qFatal()
      << "Inconsistency between m_monomers and m_uuidMonomerPairs.";

  return monomer_csp;
}

/*!
\brief Returns the UUID string identifying \a monomer_csp in the member
container.

If no such Monomer is found, an empty string is returned.
*/
QString
CrossLink::getUuidForMonomer(const MonomerCstSPtr &monomer_csp) const
{
  if(monomer_csp == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  std::vector<UuidMonomerCstWPtrPair>::const_iterator the_iterator_cst =
    std::find_if(m_uuidMonomerPairs.cbegin(),
                 m_uuidMonomerPairs.cend(),
                 [monomer_csp](const UuidMonomerCstWPtrPair &the_pair) {
                   // Do not query the monomer_sp managed object because it can
                   // be nullptr!
                   return the_pair.second.lock() == monomer_csp;
                 });

  if(the_iterator_cst == m_uuidMonomerPairs.cend())
    {
      // Sanity check
      if(hasMonomer(monomer_csp))
        qFatal() << "Inconsistency between the m_monomers and the "
                          "m_uuidMonomerPairs vectors.";

      return QString();
    }

  // Sanity check
  if(!hasMonomer(monomer_csp))
    qFatal() << "Inconsistency between the m_monomers and the "
                      "m_uuidMonomerPairs vectors.";

  return (*the_iterator_cst).first;
}

/*!
\brief Returns a container of QString instances that correspond to the UUID
strings that identify all the Monomer instances involved in this CrossLink.

If no Monomer is found, an empty container is returned.
*/
std::vector<QString>
CrossLink::getAllMonomerUuids() const
{
  std::vector<QString> the_uuid_strings;

  for(const UuidMonomerCstWPtrPair &pair : m_uuidMonomerPairs)
    the_uuid_strings.push_back(pair.first);

  // Sanity check
  if(the_uuid_strings.size() != m_monomers.size())
    qFatal()
      << "Inconsistency between the <object>_s and <uuid-object> pairs.";

  return the_uuid_strings;
}

/*!
\brief Returns a text string describing this CrossLink instance.
*/
QString
CrossLink::toString() const
{
  QString text =
    QString("CrossLink object of CrossLinker: %1 - ").arg(getCrossLinkerName());

  text +=
    QString("with %1 involved monomers at indices [").arg(m_monomers.size());

  bool ok = false;

  for(MonomerCstSPtr monomer_csp : m_monomers)
    {
      std::size_t index =
        mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_csp, ok);

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

      text += QString(";%1;").arg(index);
    }

  text += "]";

  return text;
}

/*!
\brief Removes from the member container all the Monomer instance pointers that
are not found to still be alive.
*/
void
CrossLink::cleanupMonomers()
{
  qDebug() << "At beginning, count of UUID-Monomer pairs:"
           << m_uuidMonomerPairs.size();

  std::vector<UuidMonomerCstWPtrPair>::iterator the_iterator =
    m_uuidMonomerPairs.begin();
  std::vector<UuidMonomerCstWPtrPair>::iterator the_end_iterator =
    m_uuidMonomerPairs.end();

  while(the_iterator != the_end_iterator)
    {
      if((*the_iterator).second.expired() ||
         (*the_iterator).second.lock() == nullptr ||
         !hasMonomer((*the_iterator).second.lock()))
        the_iterator = m_uuidMonomerPairs.erase(the_iterator);
      else
        ++the_iterator;
    }

  qDebug() << "At end, count of UUID-Monomer pairs:"
           << m_uuidMonomerPairs.size();
}

} // namespace libXpertMassCore
} // namespace MsXpS
