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


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/Utils.hpp"
#include "MsXpS/libXpertMassCore/MassCollection.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


/*!
\class MsXpS::libXpertMassCore::MassCollection
\inmodule libXpertMassCore
\ingroup XpertMassCoreUtilities
\inheaderfile MassCollection.hpp

\brief The MassCollection class provides a container for masses (as double
values) and for text representing thoses values.

The MassCollection class provides a container for masses (as double values) and
for text representing thoses values. Methods allow to create numerical mass
values starting for a text string representing mass values separated by newline
characters and, reciprocally, create a text string starting from all the mass
values.

The mass values are stored in a container (m_masses) and the text string is
stored in m_massText.
*/


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

\brief The name associated to the MassCollection.
*/

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

\brief The comment associated to the MassCollection.
*/

/*!
\variable MsXpS::libXpertMassCore::MassCollection::m_masses

\brief The container of mass double values.
*/


/*!
\brief Constructs a MassCollection with \a name.

The instance is created in an invalid state.
*/
MassCollection::MassCollection(const QString &name)
{
  m_name = name;
}

/*!
\brief Constructs a MassCollection with \a name and \a mass_text.

The \a mass_text is the textual representation of the mass double values that is
parsed and converted to mass double values. After the parsing, this instance is
validated, and the result is set to m_isValid.
*/
MassCollection::MassCollection(const QString &name, const QString &mass_text)
{
  m_name = name;

  ErrorList error_list;

  if(textToMasses(mass_text, &error_list) == -1)
    qDebug() << "Constructing a MassCollection with text not suitable for "
                "representing mass double values.";

  //  Will set m_isValid to the right value.
  if(!validate(&error_list))
    qDebug() << "Constructing a MassCollection that does not validate "
                "successfully,  with errors:"
             << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Constructs a MassCollection with \a name and \a masses.

The masses are copied to the member container without check.
If the masses container is non-empty, the m_isValid status is set to true, else
to false.
*/
MassCollection::MassCollection(const QString &name,
                               const std::vector<double> &masses)
{
  m_name   = name;
  m_masses = masses;

  m_isValid = m_masses.size();
}

/*!
\brief Destructs this MassCollection.
*/
MassCollection::~MassCollection()
{
}

/*!
\brief Constructs a MassCollection as a copy of \a other.
If the masses container is non-empty, the m_isValid status is set to true, else
to false.
*/
MassCollection::MassCollection(const MassCollection &other)
  : m_name(other.m_name), m_comment(other.m_comment), m_masses(other.m_masses)
{
  m_isValid = m_masses.size();
}

/*!
\brief Assigns \a other to this MassCollection and returns a reference to this
object. If the masses are non-empty, the m_isValid status is set to true, else
to false.
*/
MassCollection &
MassCollection::operator=(const MassCollection &other)
{
  if(&other == this)
    return *this;

  m_name    = other.m_name;
  m_comment = other.m_comment;

  m_masses = other.m_masses;

  m_isValid = m_masses.size();

  return *this;
}

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

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

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

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

/*!
 \brief Returns the size of the container of masses.
 */
std::size_t
MassCollection::size() const
{
  return m_masses.size();
}

/*!
 \brief Returns a const reference to the container of masses.
 */
const std::vector<double> &
MassCollection::getMassesCstRef() const
{
  return m_masses;
}

/*!
 \brief Returns a reference to the container of masses.
 */
std::vector<double> &
MassCollection::getMassesRef()
{
  return m_masses;
}

/*!
 \brief Returns the mass at the \a index in the container.

 An out of bounds index is fatal.
 */
double
MassCollection::getMassAtIndex(std::size_t index) const
{
  if(index >= m_masses.size())
    qFatal() << "Programming error. The index is out of bounds.";

  return m_masses.at(index);
}

/*!
\brief Returns the count of masses successfully obtained by converting the \a
text to double values.

The text is first split at newline characters and each obtained string is
converted to a double value that is added to the container.

The mass container is first emptied and the validity status of the
MassCollection is set to false.

If the \a text is empty, this function returns 0.

If a conversion error is encountered, the faulty text element is appended to \a
error_list_p, the mass container is cleared, m_isValid is set to false and -1 is
returned.

If the container of masses is non-empty at the end of the conversion (without
error), then m_isValid is set to true and the container size is returned.
*/
int
MassCollection::textToMasses(const QString &text, ErrorList *error_list_p)
{
  // qDebug() << "Now converting textual masses to numerical masses:" << text;

  qsizetype error_count = error_list_p->size();

  m_masses.clear();
  m_isValid = false;

  if(text.isEmpty())
    return 0;

  QStringList lines = text.split("\n", Qt::SkipEmptyParts);
  QString line;

  foreach(line, lines)
    {
      bool ok;
      double value = line.toDouble(&ok);

      if(!ok)
        {
          qDebug() << "This text element failed to convert to double:" << line;
          error_list_p->push_back(line);
          break;
        }

      m_masses.push_back(value);
    }

  if(error_list_p->size() - error_count)
    {
      m_masses.clear();
      return -1;
    }

  if(m_masses.size())
    m_isValid = true;

  qDebug() << "Converted to double " << m_masses.size() << "masses";

  return m_masses.size();
}

/*!
\brief Returns a text string representing the mass double values in the
container all separated with a newline character.
*/
QString
MassCollection::massesToText()
{
  QString text;

  for(double mass : m_masses)
    text += QString("%1\n").arg(mass, 0, 'f', OLIGOMER_DEC_PLACES);

  return text;
}

/*!
\brief Sorts the masses in the container in ascending order.
*/
void
MassCollection::sortAscending()
{
  std::sort(m_masses.begin(), m_masses.end());
}

/*!
\brief Sorts the masses in the container in descending order.
*/
void
MassCollection::sortDescending()
{
  std::sort(m_masses.begin(), m_masses.end(), std::greater<double>());
}

/*!
\brief Adds \a mass to each mass in the container.
*/
void
MassCollection::addMass(double mass)
{
  for(double &iter_mass : m_masses)
    iter_mass += mass;
}

/*!
\brief Removes from the container all mass values strictly below \a threshold.
*/
void
MassCollection::removeLessThan(double threshold)
{
  std::vector<double>::iterator the_iterator     = m_masses.begin();
  std::vector<double>::iterator the_end_iterator = m_masses.end();

  while(the_iterator != the_end_iterator)
    {
      if((*the_iterator) < threshold)
        the_iterator = m_masses.erase(the_iterator);
      else
        ++the_iterator;
    }
}

/*!
\brief Validates this MassCollection and returns true if successful or false
otherwise.

Upon validation, error messages are set to \a error_list_p (not emptied).

If validation is successful,  the m_isValid validity status is set to true, to
false otherwise.
*/
bool
MassCollection::validate(ErrorList *error_list_p) const
{
  qsizetype error_count = error_list_p->size();

  if(!m_masses.size())
    error_list_p->push_back(
      "This MassCollection does not validate successfully: there are no "
      "masses.");

  m_isValid = error_count != error_list_p->size() ? false : true;
  return m_isValid;
}

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


} // namespace libXpertMassCore
} // namespace MsXpS
