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