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


/////////////////////// libmass includes


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/OligomerCollection.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


/*!
\class MsXpS::libXpertMassCore::OligomerCollection
\inmodule libXpertMassCore
\ingroup PolChemDefBuildingdBlocks
\inheaderfile MsXpS/libXpertMassCore/OligomerCollection.hpp

\brief The OligomerCollection class provides abstractions to work with Oligomer
instances that have been produced in specific contexts, like Polymer cleavages
or Oligomer fragmentations.
*/

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

\brief The \l Polymer instance about which this OligomerCollection is about.
*/

/*!
\variable MsXpS::libXpertMassCore::OligomerCollection::m_name

\brief The name of the OligomerCollection.
*/

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

\brief A comment associated to the OligomerCollection.
*/

/*!
\variable MsXpS::libXpertMassCore::OligomerCollection::m_massType

\brief The type of mass that is dealt with in a number of calculations.
*/

/*!
\variable MsXpS::libXpertMassCore::OligomerCollection::m_oligomers

\brief The container of Oligomer instances in this OligomerCollection.
*/

/*!
\typedef MsXpS::libXpertMassCore::OligomerCollectionSPtr
\relates OligomerCollection

Synonym for std::shared_ptr<OligomerCollection>.
*/

/*!
\typedef MsXpS::libXpertMassCore::OligomerCollectionCstSPtr
\relates OligomerCollection

Synonym for std::shared_ptr<const OligomerCollection>.
*/

/*!
\brief Constructs an OligomerCollection instance with a number of parameters.

\list
\li \a name The name of the OligomerCollection

\li \a polymer_cqsp The Polymer about which this OligomerCollection is about

\li \a mass_type The type of mass that is dealt with in a number of calculations

\endlist

*/
OligomerCollection::OligomerCollection(const QString &name,
                                       PolymerCstQSPtr polymer_cqsp,
                                       Enums::MassType mass_type)
  : m_name(name), mcsp_polymer(polymer_cqsp), m_massType(mass_type)
{
}

/*!
\brief Constructs an OligomerCollection instance as a copy of \a other.

The Oligomer instances in the member container are instantiated anew (deep
copy).
*/
OligomerCollection::OligomerCollection(const OligomerCollection &other)
  : QObject(),
    m_name(other.m_name),
    m_comment(other.m_comment),
    mcsp_polymer(other.mcsp_polymer),
    m_massType(other.m_massType)
{
  for(const OligomerSPtr &oligomer_sp : other.m_oligomers)
    m_oligomers.emplace_back(std::make_shared<Oligomer>(*oligomer_sp.get()));
}

/*!
\brief Destructs the OligomerCollection.
*/
OligomerCollection::~OligomerCollection()
{
  m_oligomers.clear();
}

/*!
\brief Sets the the polymer to \a polymer_cqsp.
*/
void
OligomerCollection::setPolymer(PolymerCstQSPtr polymer_cqsp)
{
  mcsp_polymer = polymer_cqsp;
}

/*!
\brief Returns the polymer.
*/
const PolymerCstQSPtr
OligomerCollection::getPolymer() const
{
  return mcsp_polymer;
}

/*!
\brief Sets the \a name.
*/
void
OligomerCollection::setName(const QString &name)
{
  if(!name.isEmpty())
    m_name = name;
}

/*!
\brief Returns the name.
*/
const QString &
OligomerCollection::getName() const
{
  return m_name;
}

/*!
\brief Sets the \a comment.
*/
void
OligomerCollection::setComment(const QString &comment)
{
  if(!comment.isEmpty())
    m_comment = comment;
}

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

/*!
\brief Returns a const reference to the Oligomer container.
*/
const std::vector<OligomerSPtr> &
OligomerCollection::getOligomersCstRef() const
{
  return m_oligomers;
}

/*!
\brief Returns a reference to the Oligomer container.
*/
std::vector<OligomerSPtr> &
OligomerCollection::getOligomersRef()
{
  return m_oligomers;
}

/*!
\brief Sets the type of mass \a mass_type to be dealt with by default.
*/
void
OligomerCollection::setMassType(Enums::MassType mass_type)
{
  m_massType = mass_type;
  if(m_massType == Enums::MassType::BOTH)
    qFatal()
      << "Programming error. The mass type cannot be Enums::MassType::BOTH.";
}

/*!
\brief Returns the type of mass to be dealt with by default.
*/
Enums::MassType
OligomerCollection::getMassType() const
{
  return m_massType;
}

/*!
\brief Returns the Oligomer instance that encompasses a Monomer at \a
monomer_index.

This function starts searching for Oligomer instances in the member Oligomer
container at \a oligomer_index.

If \a oligomer_index is out of bounds, nullptr is returned.

When an Oligomer is found, its index in the member Oligomer container is set to
\a oligomer_index and its pointer is returned.

If no Oligomer is found, returns nullptr;
*/
OligomerSPtr
OligomerCollection::findOligomerEncompassing(std::size_t monomer_index,
                                             std::size_t &oligomer_index)
{
  // Is there any oligomer that emcompasses the index ? If so return
  // its pointer. Start searching in the list at index oligomer_index.

  if(oligomer_index >= m_oligomers.size())
    return nullptr;

  std::vector<OligomerSPtr>::const_iterator the_begin_iterator_cst =
    m_oligomers.cbegin() + oligomer_index;
  std::vector<OligomerSPtr>::const_iterator the_end_iterator_cst =
    m_oligomers.cend();
  std::vector<OligomerSPtr>::const_iterator the_iterator_cst =
    m_oligomers.cbegin() + oligomer_index;

  while(the_iterator_cst != the_end_iterator_cst)
    {
      if((*the_iterator_cst)->encompasses(monomer_index))
        {
          oligomer_index =
            std::distance(the_begin_iterator_cst, the_iterator_cst);
          return *the_iterator_cst;
        }

      ++the_iterator_cst;
    }

  return nullptr;
}

/*!
\brief Returns the Oligomer instance that encompasses Monomer \a
monomer_csp.

This function starts searching for Oligomer instances in the member Oligomer
container at \a oligomer_index.

If \a oligomer_index is out of bounds, nullptr is returned.

When an Oligomer is found, its index in the member Oligomer container is set to
\a oligomer_index and its pointer is returned.

If no Oligomer is found, returns nullptr;
*/
OligomerSPtr
OligomerCollection::findOligomerEncompassing(MonomerCstSPtr monomer_csp,
                                             std::size_t &oligomer_index)
{
  // Is there any oligomer that emcompasses the monomer_csp ? If so return
  // its pointer. Start searching in the list at index oligomer_index.

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

  if(oligomer_index >= m_oligomers.size())
    return nullptr;

  std::vector<OligomerSPtr>::const_iterator the_begin_iterator_cst =
    m_oligomers.cbegin() + oligomer_index;
  std::vector<OligomerSPtr>::const_iterator the_end_iterator_cst =
    m_oligomers.cend();
  std::vector<OligomerSPtr>::const_iterator the_iterator_cst =
    m_oligomers.cbegin() + oligomer_index;

  while(the_iterator_cst != the_end_iterator_cst)
    {
      if((*the_iterator_cst)->encompasses(monomer_csp.get()))
        {
          oligomer_index =
            std::distance(the_begin_iterator_cst, the_iterator_cst);
          return *the_iterator_cst;
        }

      ++the_iterator_cst;
    }

  return nullptr;
}

/*!
\brief Returns the Oligomer instance that encompasses Monomer \a
monomer_sp.

This function starts searching for Oligomer instances in the member Oligomer
container at \a oligomer_index.

If \a oligomer_index is out of bounds, nullptr is returned.

When an Oligomer is found, its index in the member Oligomer container is set to
\a oligomer_index and its pointer is returned.

If no Oligomer is found, returns nullptr;
*/
OligomerSPtr
OligomerCollection::findOligomerEncompassing(MonomerSPtr monomer_sp,
                                             std::size_t &oligomer_index)
{
  // Is there any oligomer that emcompasses the monomer_sp ? If so return
  // its pointer. Start searching in the list at index oligomer_index.

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

  if(oligomer_index >= m_oligomers.size())
    return nullptr;

  std::vector<OligomerSPtr>::const_iterator the_begin_iterator_cst =
    m_oligomers.cbegin() + oligomer_index;
  std::vector<OligomerSPtr>::const_iterator the_end_iterator_cst =
    m_oligomers.cend();
  std::vector<OligomerSPtr>::const_iterator the_iterator_cst =
    m_oligomers.cbegin() + oligomer_index;

  while(the_iterator_cst != the_end_iterator_cst)
    {
      if((*the_iterator_cst)->encompasses(monomer_sp))
        {
          oligomer_index =
            std::distance(the_begin_iterator_cst, the_iterator_cst);
          return *the_iterator_cst;
        }

      ++the_iterator_cst;
    }

  return nullptr;
}

/*!
\brief Returns a string in which all the Oligomer instances present in the
member container are represented as text.

The masses of the Oligomer instances are reported for the \a mass_type. If \a
mass_type is Enums::MassType::BOTH,  the the member mass type is used.
*/
QString
OligomerCollection::allOligomerMassesToString(Enums::MassType mass_type)
{
  QString text;

  if(mass_type == Enums::MassType::BOTH && m_massType == Enums::MassType::BOTH)
    return text;

  Enums::MassType local_mass_type;

  if(mass_type == Enums::MassType::BOTH)
    local_mass_type = m_massType;
  else
    local_mass_type = mass_type;

  for(const OligomerSPtr &oligomer_sp : m_oligomers)
    {
      text += QString("%1\n").arg(
        oligomer_sp->getMass(local_mass_type), 0, 'f', OLIGOMER_DEC_PLACES);
    }

  return text;
}

/*!
\brief Returns true if the monoisotopic mass of \a o1_sp is smaller than that of
\a o2_sp, else returns false.
*/
bool
OligomerCollection::monoMassCompare(const OligomerSPtr o1_sp,
                                    const OligomerSPtr o2_sp)
{
  // qDebug() << "o1_sp is:" << o1_sp.get() << "o2_sp is:" << o2_sp.get();

  // std:sort compare function:
  // Binary function that accepts two elements in the range as arguments, and
  // returns a value convertible to bool. The value returned indicates whether
  // the element passed as first argument is considered to go before the second
  // in the specific strict weak ordering it defines.

  return o1_sp->getMass(Enums::MassType::MONO) <
         o2_sp->getMass(Enums::MassType::MONO);
}

/*!
\brief Returns true if the average mass of \a o1_sp is smaller than that of \a
o2_sp, else returns false.
*/
bool
OligomerCollection::avgMassCompare(const OligomerSPtr o1_sp,
                                   const OligomerSPtr o2_sp)
{
  qDebug() << "o1_sp is:" << o1_sp.get() << "o2_sp is:" << o2_sp.get();

  return o1_sp->getMass(Enums::MassType::AVG) <
         o2_sp->getMass(Enums::MassType::AVG);
}

/*!
\brief Sorts the Oligomer instances in the member container according to
ascending masses of the default member Enums::MassType.
*/
void
OligomerCollection::sortAscending()
{
  // We only can sort if this instance knows which mass type it should
  // handle, and that cannot be anything other than Enums::MassType::MASS_MONO
  // or MASS_AVG.

  if(m_massType == Enums::MassType::BOTH)
    {
      qDebug() << "Could not sort the oligomer list: "
                  "m_massType cannot be Enums::MassType::BOTH.";

      return;
    }

  if(m_massType == Enums::MassType::MONO)
    std::sort(m_oligomers.begin(),
              m_oligomers.end(),
              OligomerCollection::monoMassCompare);
  else
    std::sort(m_oligomers.begin(),
              m_oligomers.end(),
              OligomerCollection::avgMassCompare);
}

/*!
\brief Returns the size of this OligomerCollection as the count of Oligomer
instances in the member container.
*/
std::size_t
OligomerCollection::size() const
{
  return m_oligomers.size();
}

/*!
\brief Returns a text string representing this OligomerCollection's Oligomer
instances.

\sa Oligomer::toString
*/
QString
OligomerCollection::toString() const
{
  QString text;

  for(OligomerSPtr oligomer_sp : m_oligomers)
    text += oligomer_sp->toString();

  return text;
}

/*!
\brief Clears the member Oligomer container.
*/
void
OligomerCollection::clear()
{
  m_oligomers.clear();
}

/*!
Assigns \a other to this instance.
*/
OligomerCollection &
OligomerCollection::operator=(const OligomerCollection &other)
{
  if(this == &other)
    return *this;

  m_name       = other.m_name;
  m_comment    = other.m_comment;
  mcsp_polymer = other.mcsp_polymer;
  m_massType   = other.m_massType;

  m_oligomers.assign(other.m_oligomers.begin(), other.m_oligomers.end());

  return *this;
}

/*!
\brief Returns true if this OligomerCollection instance and \a other are
identical, false otherwise.
*/
bool
OligomerCollection::operator==(const OligomerCollection &other) const
{
  if(this == &other)
    return true;
  if(m_name != other.m_name || m_comment != other.m_comment ||
     mcsp_polymer != other.mcsp_polymer || m_massType != other.m_massType ||
     m_oligomers != other.m_oligomers)
    return false;

  return true;
}

/*!
 \brief Returns true if this OligomerCollection instance and \a other differ,
 false otherwise.

 Returns the negated value returned from \l{operator==()}.
 */
bool
OligomerCollection::operator!=(const OligomerCollection &other) const
{
  return !operator==(other);
}

} // namespace libXpertMassCore
} // namespace MsXpS
