/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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
 */


#include <omp.h>
#include <limits>


/////////////////////// Qt includes
#include <QDebug>
#include <QFile>
#include <qmath.h>


/////////////////////// Local includes
#include <libmass/Trace.hpp>
#include <globals/globals.hpp>

#include <pwiz/data/msdata/MSDataFile.hpp>
#include <pwiz/data/msdata/DefaultReaderList.hpp>

#if defined(WIN64) || defined(APPLE)
#else // UNIX/GNU/LINUX
#include <pwiz/utility/misc/BinaryData.hpp>
#endif

namespace msXpSlibmass
{


int traceMetaTypeId =
  qRegisterMetaType<msXpSlibmass::Trace>("msXpSlibmass::Trace");
int tracePtrMetaTypeId =
  qRegisterMetaType<msXpSlibmass::Trace *>("msXpSlibmass::Trace*");


//! Construct a totally empty Trace
/*!
 */
Trace::Trace()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "default constructor"
  //<< this;
}


//! Construct a Trace with \p title
/*!
 */
Trace::Trace(const QString &title)
{
  m_title = title;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "title-initializing constructor"
  //<< this;
}


//! Construct a copy of \p other
/*!

  The copy is deep, with all the DataPoint instances being allocated anew.

*/
Trace::Trace(const Trace &other)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "copy constructor, other :" << &other << "this:" << this;

  m_title         = other.m_title;
  m_lastModifIdx  = other.m_lastModifIdx;
  m_decimalPlaces = other.m_decimalPlaces;

  for(int iter = 0; iter < other.size(); ++iter)
    append(new DataPoint(*(other.at(iter))));
}


Trace::Trace(const QVector<double> &keys, const QVector<double> &values)
{
  initialize(keys, values, qSNaN(), qSNaN());
}


//! Destruct the Trace instance.
/*!
  This function actually calls deleteDataPoints().
  */
Trace::~Trace()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Trace" << this << "destructor going to call deleteDataPoints";

  deleteDataPoints();
}


//! Assignment operator that performs a deep copy of \p other into \c this.
/*!

  This function starts by deleting all of \c this instance DataPoint instances.
  Then all of the DataPoint instances in \p other are duplicated into \e new
  DataPoint instances into \c this Trace object.

  Note\: the first use of this assignment operator was for JavaScripting
  because translation between JS object and C++ object required deep copy of
  all the data members of Trace.

*/
Trace &
Trace::operator=(const Trace &other)
{
  if(this == &other)
    return *this;

  // Make sure we start by deleting all of our peaks.
  deleteDataPoints();

  m_title         = other.m_title;
  m_lastModifIdx  = other.m_lastModifIdx;
  m_decimalPlaces = other.m_decimalPlaces;

  initialize(other);

  return *this;
}


//! Reset the Trace
/*!

  This function calls deleteDataPoints() to delete all the DataPoint instances
  it contains and clears the title. The other member data are reset to default
  values.

*/
void
Trace::reset()
{
  deleteDataPoints();

  m_title.clear();

  m_lastModifIdx = 0;

  m_decimalPlaces = -1;
}


//! Iterate in the DataPoint list and for each item deletes it
/*!

  The various DataPoint items of the list are deleted and the list is
  clear()'ed.
  */

void
Trace::deleteDataPoints()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "this trace:" << this << "with size:" << size();

#pragma omp parallel for
  for(int iter = 0; iter < size(); ++iter)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__<< "()"
      //<< "Deleting DataPoint at " << iter << ":" << at(iter);

      delete at(iter);
    }

  // Call the parent class clear() to actually empty the QList<DataPoint>

  clear();
}


//! Get the title.
QString
Trace::title() const
{
  return m_title;
}


//! Set the decimal places member datum value
/*!

  Cannot be less than -1. If less than -1, the member value is set to -1.
  */
void
Trace::setDecimalPlaces(int decimalPlaces)
{
  if(decimalPlaces < -1)
    m_decimalPlaces = -1;
  else
    m_decimalPlaces = decimalPlaces;
}


//! Get the decimal places member datum value
int
Trace::decimalPlaces()
{
  return m_decimalPlaces;
}


//! Get a reference to the decimal places member datum value
int &
Trace::rdecimalPlaces()
{
  return m_decimalPlaces;
}


//! Initialize the trace using an XY-formatted string.
/*!

  The \p xyFormatString contains a number of lines of text having the
  following generic format: <number1><non-number><number2> with <number1> a
  string representation of key, <non-number> any string element that is not a
  number and <number2> a string representation of i. The decomposition of each
  line into key and value and crafting of a new DataPoint instance is
  performed using the DataPoint specialized constructor.

  Note that the crafting of \c this Trace is performed using the combine(const
  DataPoint &) call, which means that two data lines having the same key will
  be combined into a single DataPoint instance in the final Trace. Also, if
  the keys are not sorted in the initializing string, these will be sorted in
  \c this Trace instance.

  \param xyFormatString string containing a series of lines describing
  (key,value) pairs

  \return the number of added DataPoint instances.

*/
int
Trace::initialize(const QString &xyFormatString)
{
  if(xyFormatString.isEmpty())
    return 0;

  deleteDataPoints();

  int dataPointCount = 0;
  if(xyFormatString.size() == 0)
    {
      return dataPointCount;
    }

  QStringList lineList = xyFormatString.split("\n");

  for(int iter = 0; iter < lineList.size(); ++iter)
    {
      QString line = lineList.at(iter);

      if(line.isEmpty())
        continue;

      // If the line has only a newline character, go on.
      if(msXpS::gEndOfLineRegExp.match(line).hasMatch())
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Was only end of line";
          continue;
        }

      if(line.startsWith("#"))
        continue;

      // At this point, if the line does not match the real data regexp, then
      // that would be an error.

      DataPoint dataPoint(line);
      if(dataPoint.isValid())
        {
          // Because we want the DataPoint instances to be sorted in increasing
          // order, we do not make an append() call, but a combine call.
          // Further, this has the advantage that if two same keys appear
          // in the initializing text, then they are combined and not simply
          // appended. Note that combining a O-val DataPoint is not performed.

          int res = combine(dataPoint);
          if(res != -1)
            ++dataPointCount;
        }
      else
        qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                 << "Attention, data point at string list item index:" << iter
                 << "is not valid.";
    }

  return dataPointCount;
}


//! Initialize the Trace with data
/*!

  The trace is initialized using the data in \p other using a deep copy of the
  DataPoint instances.

  \return the number of DataPoint instances after completion of the
  initialization or -1 if an error occurred.

*/
int
Trace::initialize(const Trace &other)
{
  if(this == &other)
    return -1;

  // Make sure we start by deleting all of our peaks.
  deleteDataPoints();

  // And now create as many *new* data points as there are in other.
  for(int iter = 0; iter < other.size(); ++iter)
    append(new DataPoint(*(other.at(iter))));

  return size();
}


//! Initialize the Trace with data
/*!

  The spectrum is first cleared by calling deleteDataPoints(). Then, new
  DataPoint instances are allocated and initialized using the values in \p
  keyList and \p valList.

  The \p keyList and \p valList lists cannot have different sizes otherwise
  the program crashes.

  Note that the crafting of \c this Trace is performed using the
  combine(const DataPoint &) call, which means that two \p keyList elements
  having the same key will be combined into a single DataPoint instance
  in the final Trace. Also, if the \p keyList values are not sorted in
  the initializing string, these will be sorted in \c this Trace
  instance.

  \param keyList list of keys
  \param valList list of values

  \return the size of \c this Trace after initialization.
  */
int
Trace::initialize(const QList<double> &keyList, const QList<double> &valList)
{
  if(keyList.size() != valList.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "The key and value lists cannot be of different size."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  deleteDataPoints();

  for(int iter = 0; iter < keyList.size(); ++iter)
    {
      DataPoint dataPoint(keyList.at(iter), valList.at(iter));
      combine(dataPoint);
    }

  return size();
}


//! Initialize the Trace with data
/*!

  This is an overload of initialize() allowing to only combine DataPoints if
  the key of the (key,value) pair is contained in the key range [\p keyStart,
  \p keyEnd].

  \param keyList list of keys
  \param valList list of values
  \param keyStart start of the authorized key range
  \param keyEnd end of the authorized key range
  */
int
Trace::initialize(const QList<double> &keyList,
                  const QList<double> &valList,
                  double keyStart,
                  double keyEnd)
{
  if(keyList.size() != valList.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "The key and value lists cannot be of different size."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  deleteDataPoints();

  for(int iter = 0; iter < keyList.size(); ++iter)
    {
      double key = keyList.at(iter);

      if(key >= keyStart && key <= keyEnd)
        {
          DataPoint dataPoint(key, valList.at(iter));
          combine(dataPoint);
        }
    }

  return size();
}


//! Initialize the Trace with data
/*!

  This is an overload of initialize() that uses vectors of keys and values
  instead of lists. \p keyStart and \p keyEnd are optional.

  \param keyVector vector of keys
  \param valVector vector of values
  \param keyStart start value of the authorized key range
  \param keyEnd end value of the authorized key range
  */
int
Trace::initialize(const QVector<double> &keyVector,
                  const QVector<double> &valVector,
                  double keyStart,
                  double keyEnd)
{
  if(keyVector.size() != valVector.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  deleteDataPoints();

  // There are two possibilities: either we are feeding a new trace
  // with key,value data with a precondition of key or not.

  if(qIsNaN(keyStart) || qIsNaN(keyEnd))
    {
      // No key precondition.

      for(int iter = 0; iter < keyVector.size(); ++iter)
        combine(DataPoint(keyVector.at(iter), valVector.at(iter)));
    }
  else
    {
      // Only feed the trace if key is in the key range
      // precondition.

      for(int iter = 0; iter < keyVector.size(); ++iter)
        {
          double key = keyVector.at(iter);

          if(key >= keyStart && key <= keyEnd)
            combine(DataPoint(keyVector.at(iter), valVector.at(iter)));
        }
    }

  return size();
}


//! Initialize \c this Trace with data.
/*!

  This is an overload of initialize() that uses byte arrays of keys and values
  instead of lists. \p keyStart and \p keyEnd are optional.

  The data in the arrays are first decoded/uncompressed in vectors.

  Note that, if after decoding/uncompressing of the byte arrays, the obtained
  data vectors do not have the same size, the program crashes.

  Initialization of \c this Trace with the data contained in the
  vectors is performed by calling a specialized initialize function.

  \param keyByteArray byte array containing the keys

  \param valByteArray byte array containing the values

  \return the size of this Trace after initialization.

  \sa initialize(const QVector<double> &keyVector, const QVector<double>
  &valVector,
  double keyStart = qSNaN(), double keyEnd = qSNaN()).
  */
int
Trace::initialize(const QByteArray *keyByteArray,
                  const QByteArray *valByteArray,
                  int compressionType,
                  double keyStart,
                  double keyEnd)
{
  if(keyByteArray == Q_NULLPTR || valByteArray == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  deleteDataPoints();

  // Because pwiz provides the mass spectral data in the form of
  // BinaryData data, we need to encode these data before we
  // can inject them to the database.

  pwiz::msdata::BinaryDataEncoder::Config config;

  config.precision = pwiz::msdata::BinaryDataEncoder::Precision::Precision_64;

  // keyML standard stipulates that data are always
  // ByteOrder::ByteOrder_LittleEndian.

  config.byteOrder =
    pwiz::msdata::BinaryDataEncoder::ByteOrder::ByteOrder_LittleEndian;

  if(compressionType == msXpS::DataCompression::DATA_COMPRESSION_ZLIB)
    config.compression =
      pwiz::msdata::BinaryDataEncoder::Compression::Compression_Zlib;

  // Allocate the decoder
  pwiz::msdata::BinaryDataEncoder decoder(config);

  // Crunching the key (mz) data.

  std::string keyString = keyByteArray->toStdString();

#if defined(WIN64) || defined(APPLE)
  std::vector<double> keyStdVector;
  decoder.decode(keyString, keyStdVector);
#else // UNIX/GNU/LINUX
  pwiz::util::BinaryData<double> keyStdVector;
  decoder.decode(keyString, keyStdVector);
#endif

  QVector<double> keyVector = QVector<double>::fromStdVector(keyStdVector);

  // Clear the standard vector data.
  keyStdVector.clear();

  // qDebug() << __FILE__ << __LINE__
  //<< "Done creating keyVector of items: " << keyVector.size();


  // Crunching the val (i) data.

  std::string valString = valByteArray->toStdString();

#if defined(WIN64) || defined(APPLE)
  std::vector<double> valStdVector;
  decoder.decode(valString, valStdVector);
#else // UNIX/GNU/LINUX
  pwiz::util::BinaryData<double> valStdVector;
  decoder.decode(valString, valStdVector);
#endif

  QVector<double> valVector = QVector<double>::fromStdVector(valStdVector);

  // Clear the standard vector data.
  valStdVector.clear();

  // qDebug() << __FILE__ << __LINE__
  //<< "Done creating valVector of items: " << valVector.size();

  // At this point we have all the data required to initialize the trace:

  // Sanity check.
  if(keyVector.size() != valVector.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  initialize(keyVector, valVector, keyStart, keyEnd);

  // qDebug() << __FILE__ << __LINE__
  // << "Allocated new trace of " << keyVector.size() << "peaks";

  return size();
}


//! Remove all DataPoint instances having value equal to zero.
/*!

  return the number of removed DataPoint instances.

*/
int
Trace::removeZeroValDataPoints()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Removing from this Trace all the DataPoint instances that have a value
  // equal to 0.";

  int removedCount = 0;

  int iter = size() - 1;
  while(iter >= 0)
    {
      if(at(iter)->m_val == 0)
        {
          delete takeAt(iter);
          ++removedCount;
        }

      --iter;
    }

  return removedCount;
}


//! Compute the sum of all the values of \c this Trace
/*!

  The sum of the values is computed for all the DataPoint instances in \c this
  Trace.

  \return the sum of the values of \c this Trace.

*/
double
Trace::valSum() const
{
  // Iterate in each data point of this spectrum and sum up its intensity with
  // all the other data point' value. Return that value.

  double valSum = 0;

#pragma omp parallel
  {
    // int maxNumbThreads = omp_get_max_threads();
    // omp_set_num_threads(10);
    // int numbThreads = omp_get_num_threads();
    // qDebug() << __FILE__ << __LINE__
    // << "Number of threads: " << numbThreads << "over" << maxNumbThreads;

#pragma omp for reduction(+ : valSum)
    // 20160921 - This code was tested to provide identical results to the
    // serial version
    // and to provide an increase in the execution speed of roughly 2x with
    // 4 threads.

    for(int iter = 0; iter < size(); ++iter)
      {
        // int numbThreads = omp_get_num_threads();
        // qDebug() << __FILE__ << __LINE__
        // << "Number of threads: " << numbThreads;

        valSum += at(iter)->m_val;
      }
  }

  return valSum;
}


//! Compute the sum of the values of \c this Trace
/*!

  This is an overload of valSum() that only accounts DataPoint instances that
  have a key contained in the [\p keyStart,\p keyEnd] range.

  \sa valSum()

  \return the sum of the values of \c this Trace.

*/
double
Trace::valSum(double keyStart, double keyEnd) const
{
  // Iterate in each data point of this spectrum and sum up its intensity with
  // all the other data points' values. Return that value. Do that only if the
  // key is in the ranged passed as parameters.

  double valSum = 0;

#pragma omp parallel
  {
    // int maxNumbThreads = omp_get_max_threads();
    // omp_set_num_threads(10);
    // int numbThreads = omp_get_num_threads();
    // qDebug() << __FILE__ << __LINE__
    // << "Number of threads: " << numbThreads << "over" << maxNumbThreads;

#pragma omp for reduction(+ : valSum)
    for(int iter = 0; iter < size(); ++iter)
      {
        double key = at(iter)->m_key;

        if(key >= keyStart && key <= keyEnd)
          valSum += at(iter)->m_val;
      }
  }

  return valSum;
}


//! Combine \p dataPoint into \c this Trace
/*!

  A combine operation is a merge of a DataPoint into a preexisting set of
  DataPoint instances. If, in the current spectrum, a DataPoint instance
  exists that has the same key of \p dataPoint, then the value of \p dataPoint
  is added to the preexisting DataPoint instance value. If there is no
  DataPoint instance having the same key of \p dataPoint, then \p dataPoint is
  copied and inserted at the right place in the list of DataPoint instances of
  \c this Trace.

  The \p dataPoint key is copied and modified according to this:

  - if m_decimalPlaces is not -1, then the decimal places are taken into
  account for the computation.

  To speed up the combine process, each time a DataPoint instance is modified
  (created or updated), its index in the list is recorded in m_lastModifIdx.
  This variable is tested for the value it contains and if that value is
  meaningful, then it is used to access the list of DataPoint instances right
  from that index. This process works in speeding up combine operations
  because a trace is inherently sorted in ascending order of the key and the
  next DataPoint combination into \c this Trace will occur most certainly
  below the m_lastModifIdx index value.

  \param dataPoint the DataPoint instance to be combined to \c this Trace

  \return 1 if a new DataPoint instance was inserted in \c this Trace; 0 if a
  preexisting DataPoint instance was found having the same key as that in \p
  dataPoint (the value was thus incremented), -1 if nothing was performed
  because the value of the dataPoint was 0.

*/
int
Trace::combine(const DataPoint &dataPoint)
{

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "(const DataPoint
  // &dataPoint)";

  // This function only provide very fast additions, insertion, because a
  // trace is inherently sorted in increasing order. Otherwise this
  // function would not work.

  // Returns 0 if the peak was not added but only the intensity was
  // increment, and 1 if the peak was actually added (either prepended,
  // inserted or appended).

  // We get a DataPoint instance and we must integrate it into *this list. If
  // a data point by the same key is found, then the intensity is increased.
  // Otherwise the data point is integrated in such a manner that the
  // spectrum sorts correctly in ascending order with respect to keys.

  // The index of the modified element of the list, be that modification an
  // insertion or an intensity increment, the m_lastModifIdx is set to that
  // index such that the next call can make use of it to accelerate the
  // search process.

  // If the value is 0, then no reason to do anything
  if(dataPoint.m_val == 0)
    return -1;

  double key = dataPoint.key();

  // Check if decimals are specified.
  if(m_decimalPlaces != -1)
    {
      key = ceil((key * pow(10, m_decimalPlaces)) - 0.49) /
            pow(10, m_decimalPlaces);

      // if no decimal at all is required: int key = (double) (int) key;

      // qDebug("Rounding from %.5f to %.5f\n",
      // dataPoint.key(), key);
    }

  if(!size())
    {
      append(new DataPoint(key, dataPoint.val()));

      m_lastModifIdx = 0;
      return 1;
    }

  if(size() == 1)
    {
      if(key == last()->m_key)
        {
          last()->rval() += dataPoint.val();

          m_lastModifIdx = 0;
          return 0;
        }
      else if(key > last()->m_key)
        {
          append(new DataPoint(key, dataPoint.val()));

          m_lastModifIdx = 1;
          return 1;
        }
      else
        {
          prepend(new DataPoint(key, dataPoint.val()));

          m_lastModifIdx = 1;
          return 1;
        }
    }

  // At this point we know that there are at least two DataPoint in this
  // spectrum.

  if(m_lastModifIdx >= size())
    {
      m_lastModifIdx = size() / 2;
    }

  if(key < at(m_lastModifIdx)->m_key)
    {
      m_lastModifIdx = 0;
    }

  int oneLessThanSpecSize = size() - 1;

  for(int iter = m_lastModifIdx; iter < oneLessThanSpecSize; ++iter)
    {
      if(key < at(iter)->m_key)
        {
          insert(iter, new DataPoint(key, dataPoint.val()));
          m_lastModifIdx = iter;
          return 1;
        }

      if(key == at(iter)->m_key)
        {

          // There is a DataPoint that has that same key, only simply increment
          // the intensity.

          at(iter)->rval() += dataPoint.val();
          m_lastModifIdx = iter;
          return 0;
        }

      if(key > at(iter)->m_key && key < at(iter + 1)->m_key)
        {
          insert(iter + 1, new DataPoint(key, dataPoint.val()));
          m_lastModifIdx = iter + 1;
          return 1;
        }
    }

  // At this point, we did not insert the data point, nor did we increment a
  // preexisting data point. There are two possibilities:

  if(key == last()->m_key)
    {
      last()->rval() += dataPoint.val();
      m_lastModifIdx = size() - 1;
      return 0;
    }
  else
    {
      // The key is greater than the last value of the
      // spectrum. Simply perform an append.
      append(new DataPoint(key, dataPoint.val()));
      m_lastModifIdx = size() - 1;

      // qWarning() << __FILE__ << __LINE__
      //<< "modified index:" << m_lastModifIdx
      //<< "mz:" << at(m_lastModifIdx)->m_mz
      //<< "i:" <<  at(m_lastModifIdx)->m_i;

      return 1;
    }
}


//! Subtract \p dataPoint from \c this Trace
/*!

  This is the reverse operation of the combine(const DataPoint &dataPoint)
  operation. Here, the value of \p dataPoint has its sign inverted by
  multiplying it by -1. Then combine(DataPoint) is called as for a normal
  combine operation.

  \param dataPoint the DataPoint instance to be subtracted from \c this Trace

  \return 1 if a new DataPoint instance was inserted in \c this Trace; 0 if a
  preexisting DataPoint instance was found having the same key as that in \p
  dataPoint (the value was thus incremented), -1 if nothing was performed
  because the value of the dataPoint was 0..

*/
int
Trace::subtract(const DataPoint &dataPoint)
{
  // If the value is 0, then no reason to do anything
  if(dataPoint.m_val == 0)
    return -1;

  DataPoint localDataPoint(dataPoint);

  localDataPoint.rval() *= -1;

  return combine(localDataPoint);
}


//! Combine \p trace into \c this Trace.
/*!

  The trace is combined into \c this Trace by iterating in all the DataPoint
  instances it contains and calling combine(const DataPoint &).

  \param trace Trace instance to be combined to \c this Trace

  \return an integer containing the number of combined DataPoint instances

*/
int
Trace::combine(const Trace &trace)
{
  qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  // Reset the index anchors
  m_lastModifIdx = 0;

  int iter = 0;

  for(iter = 0; iter < trace.size(); ++iter)
    {
      // If the value is 0, then no reason to do anything
      DataPoint *p_dp = trace.at(iter);

      if(p_dp->m_val == 0)
        continue;

      combine(*p_dp);
    }

  // Return the number of combined data points.
  return iter;
}


//! Subtract \p trace from \c this Trace.
/*!

  This is the reverse operation of combine(const Trace &trace).

*/
int
Trace::subtract(const Trace &trace)
{

  // Reset the index anchors
  m_lastModifIdx = 0;

  int iter = 0;

  for(iter = 0; iter < trace.size(); ++iter)
    {
      // If the value is 0, then no reason to do anything
      DataPoint *p_dp = trace.at(iter);

      if(p_dp->m_val == 0)
        continue;

      subtract(*p_dp);
    }

  // Return the number of subtracted data points.
  return iter;
}


//! Combine \p trace into \c this Trace.
/*!

  Same as combine(const Trace &trace) unless that only the
  DataPoint instances having a key comtained in the [\p keyStart -- \p
  keyEnd] range are taken into account.

  \param trace Trace instance to be combined to \c this Trace

  \param keyStart left value of the key range

  \param keyEnd right value of the key range

  \return the number of combined DataPoint_s

*/
int
Trace::combine(const Trace &trace, double keyStart, double keyEnd)
{

  // Reset the index anchors
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < trace.size(); ++iter)
    {
      DataPoint *p_dp = trace.at(iter);

      // If the value is 0, then no reason to do anything
      if(p_dp->m_val == 0)
        continue;

      if(p_dp->m_key >= keyStart && p_dp->m_key <= keyEnd)
        {
          combine(*p_dp);

          ++count;
        }

      // Because the spectra are ordered, if the currently iterated data point
      // has a mass greater than the accepted keyEnd, then break the loop as
      // this cannot be any better later.

      else if(p_dp->m_key > keyEnd)
        break;
    }
  // qDebug() << __FILE__ << __LINE__
  // << "Done combining a new spectrum in this spectrum, current with "
  // << size() << "data points";

  return count;
}


//! Subtract \p trace from \c this Trace.
/*!

  This is the reverse operation of combine(const Trace &trace,
  double keyStart, double keyEnd).

*/
int
Trace::subtract(const Trace &trace, double keyStart, double keyEnd)
{

  // Reset the index anchors
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < trace.size(); ++iter)
    {
      DataPoint *p_dp = trace.at(iter);

      // If the value is 0, then no reason to do anything
      if(p_dp->m_val == 0)
        continue;

      if(p_dp->m_key >= keyStart && p_dp->m_key <= keyEnd)
        {
          subtract(*p_dp);

          ++count;
        }

      // Because the spectra are ordered, if the currently iterated data point
      // has a mass greater than the accepted keyEnd, then break the loop as
      // this cannot be any better later.

      else if(p_dp->m_key > keyEnd)
        break;
    }

  // Return the number of combined data points.
  return count;
}


//! Combine the Trace instances in the \p traces list into \c this Trace (serial
//! version)
/*!

  This function might be called recursively with multiple different
  lists of Trace instances.

  The workings of this function are the following:

  - if the \p traces list is empty, return 0

  - iterate in the \p traces list of Trace instances and for each
  instance call combine(const Trace &).

  \param traceList list of Trace instances to be combined to \c this
  Trace

  \return an int containing the number of combined Trace instances.

  \sa combine(const Trace &)
  */
int
Trace::combineMonoT(const QList<Trace *> &traceList)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "";

  // We receive a list of traces and we need to combine all the
  // spectra thereof in this trace.

  int traceListSize = traceList.size();

  if(!traceListSize)
    return 0;

  // count is the number of traces that were effectively combined.
  int count = 0;

  for(int iter = 0; iter < traceListSize; ++iter)
    {
      combine(*traceList.at(iter));

      ++count;

#if 0
				QString fileName = QString("/tmp/unranged-combined-trace-after-%1-combinations.txt")
					.arg(iter + 1);
				exportToFile(fileName);

#endif
    }
    // qDebug() << __FILE__ << __LINE__
    // << "Effectively combined " << count
    // << "Trace instances in this Trace for a total of "
    // << size() << "data points";

#if 0
			QString fileName = "/tmp/unranged-combined-trace-after-finishing-combinations.txt";
			exportToFile(fileName);

#endif

  return count;
}


//! Combine the Trace instances in the \p traces list into \c this Trace
//! (parallel version)
/*!

  This function might be called recursively with multiple different
  lists of Trace instances.

  The workings of this function are the following:

  - if the \p traces list is empty, return 0

  - iterate in the \p traces list of Trace instances and for each
  instance call combine(const Trace &).

  \param traces list of Trace instances to be combined to \c this
  Trace

  \return an int containing the number of combined Trace instances.

  \sa combine(const Trace &)
  */
int
Trace::combine(const QList<Trace *> &traceList)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  int traceListSize = traceList.size();

  if(!traceListSize)
    return 0;

  // count is the number of traces that were effectively combined.
  int count = 0;

  int maxThreads = omp_get_max_threads();
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "Max threads: " << maxThreads;

  if(traceListSize < 10 * maxThreads)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Number of traces to combine:" << traceListSize
               << "Too small a number of traces, not using the parallel code.";

      for(int iter = 0; iter < traceListSize; ++iter)
        {
#if 0
					exportToFile(QString("/tmp/trace-%1-for-combinations.txt")
							.arg(iter + 1));
#endif

          combine(*traceList.at(iter));

          ++count;
        }
    }
  else
    /* of if(traceListSize < 10 * numbThreads) */
    {
      omp_set_num_threads(maxThreads);

      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Number of traces sufficient to use parallel code."
               << "Set number of threads: " << maxThreads;

      // Allocate as many combined traces as we are using threads.
      QList<Trace *> combinedTraceList;

      // Set the various slices of traceList so that each thread knows what
      // slice to handle.
      QList<QPair<int, int> *> indexRangeList;

      double specPerThread = traceListSize / maxThreads;
      // Integer part of the fractional result.
      double intPart;
      // We won't use fracPart, but anyway.
      double fracPart = std::modf(specPerThread, &intPart);
      Q_UNUSED(fracPart);

      for(int iter = 0; iter < maxThreads; ++iter)
        {
          combinedTraceList.append(new Trace());

          if(iter != maxThreads - 1)
            {
              if(!iter)
                // We actually start from 0 = (iter * intPat), with iter = 0.
                indexRangeList.append(
                  new QPair<int, int>(iter * intPart, (iter + 1) * intPart));
              else
                // We need to increment by one index value the startIdx,
                // otherwise
                // we would combine twice the trace at index (iter * intPart)
                indexRangeList.append(new QPair<int, int>(
                  (iter * intPart) + 1, (iter + 1) * intPart));
            }
          else
            {

              // Now is the time to complete to the fractional part, without
              // bothering: simply set the lastIdx of QPair to
              // traceListSize - 1.

              indexRangeList.append(
                new QPair<int, int>(iter * intPart, traceListSize - 1));
            }
        }

        // At this point we have setup all the slices to combine them in their
        // own combined trace.

#pragma omp parallel for shared(count)
      for(int iter = 0; iter < maxThreads; ++iter)
        {
          Trace *p_combinedTrace        = combinedTraceList.at(iter);
          QPair<int, int> *p_indexRange = indexRangeList.at(iter);

          int combineCount = p_combinedTrace->combineSlice(
            traceList, p_indexRange->first, p_indexRange->second);

          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << QString::asprintf("Combined slice %d-%d\n",
                                        p_indexRange->first,
                                        p_indexRange->second);

          count += combineCount;
        }
      // End of
      // #pragma omp parallel

      // At this point we need to conclude and combine into *this mass
      // trace all the traces in the list of combine traces. Take
      // advantage of the loop to also free each combine trace in
      // turn.

      while(combinedTraceList.size())
        {
          Trace *trace = combinedTraceList.takeFirst();
          combine(*trace);
          delete trace;
        }

      // At this point, we need to free all the QPair instances
      while(indexRangeList.size())
        delete indexRangeList.takeFirst();
    }
  // End of
  // else /* of if(traceListSize < 10 * maxThreads) */

  // The combination work is finished, however it has been performed.

  if(m_removeZeroValDataPoints)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Now removing all 0-m/z data points.";

      removeZeroValDataPoints();
    }
#if 0
			exportToFile(QString("/tmp/combined-trace.txt"));
#endif

  return count;
}


//! Combine the [\p startIdx--\p endIdx] slice of \p traceList into \c this
//! Trace
/*!
  \return the number of combined Trace instances.
  */
int
Trace::combineSlice(const QList<Trace *> &traceList, int startIdx, int endIdx)
{
  if(traceList.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "List of traces to combine is empty."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int firstIdx = std::min(startIdx, endIdx);
  int lastIdx  = std::max(startIdx, endIdx);

  if(firstIdx < 0)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Index cannot be less than 0."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(lastIdx > traceList.size())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Out of bounds error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int count = 0;

  for(int iter = firstIdx; iter < lastIdx; ++iter)
    {
      combine(*(traceList.at(iter)));
      ++count;
    }

  return count;
}


//! Subtract \p traces from \c this Trace.
/*!

  This is the reverse operation of combine(const QList<Trace *> &traces).

*/
int
Trace::subtractMonoT(const QList<Trace *> &traceList)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ ;

  int traceListSize = traceList.size();

  if(!traceListSize)
    return 0;

  // count is the number of traces that were effectively combined.
  int count = 0;

  for(int iter = 0; iter < traceListSize; ++iter)
    {
      subtract(*traceList.at(iter));

      ++count;
    }

  // qDebug() << __FILE__ << __LINE__
  // << "Effectively combined " << count
  // << "Trace instances in this Trace for a total of "
  // << size() << "data points";

  return count;
}


//! Subtract the Trace instances in the \p traces list from \c this Trace
//! (parallel version)
/*!

  This function might be called recursively with multiple different
  lists of Trace instances.

  The workings of this function are the following:

  - if the \p traces list is empty, return 0

  - iterate in the \p traces list of Trace instances and for each
  instance call subtract(const Trace &).

  \param traces list of Trace instances to be subtractd to \c this
  Trace

  \return an int containing the number of subtractd Trace instances.

  \sa subtract(const Trace &)
  */
int
Trace::subtract(const QList<Trace *> &traceList)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  int traceListSize = traceList.size();

  if(!traceListSize)
    return 0;

  // count is the number of traces that were effectively subtracted.
  int count = 0;

  int maxThreads = omp_get_max_threads();
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "Max threads: " << maxThreads;

  if(traceListSize < 10 * maxThreads)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Number of traces to subtract:" << traceListSize
               << "Too small a number of traces, not using the parallel code.";

      for(int iter = 0; iter < traceListSize; ++iter)
        {
          Trace *p_trace = traceList.at(iter);

#if 0
					exportToFile(QString("/tmp/trace-%1-for-combinations.txt")
							.arg(iter + 1));
#endif

          subtract(*p_trace);

          ++count;
        }
    }
  else
    /* of if(traceListSize < 10 * numbThreads) */
    {
      omp_set_num_threads(maxThreads);

      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Number of traces sufficient to use parallel code."
               << "Set number of threads: " << maxThreads;

      // Allocate as many subtractd traces as we are using threads.
      QList<Trace *> subtractedTraceList;

      // Set the various slices of traceList so that each thread knows what
      // slice to handle.
      QList<QPair<int, int> *> indexRangeList;

      double specPerThread = traceListSize / maxThreads;
      // Integer part of the fractional result.
      double intPart;
      // We won't use fracPart, but anyway.
      double fracPart = std::modf(specPerThread, &intPart);
      Q_UNUSED(fracPart);

      for(int iter = 0; iter < maxThreads; ++iter)
        {
          subtractedTraceList.append(new Trace());

          if(iter != maxThreads - 1)
            {
              if(!iter)
                // We actually start from 0 = (iter * intPat), with iter = 0.
                indexRangeList.append(
                  new QPair<int, int>(iter * intPart, (iter + 1) * intPart));
              else
                // We need to increment by one index value the startIdx,
                // otherwise
                // we would subtract twice the trace at index (iter * intPart)
                indexRangeList.append(new QPair<int, int>(
                  (iter * intPart) + 1, (iter + 1) * intPart));
            }
          else
            {

              // Now is the time to complete to the fractional part, without
              // bothering: simply set the lastIdx of QPair to
              // traceListSize - 1.

              indexRangeList.append(
                new QPair<int, int>(iter * intPart, traceListSize - 1));
            }
        }

        // At this point we have setup all the slices to subtract them in their
        // own subtractd trace.

#pragma omp parallel for shared(count)
      for(int iter = 0; iter < maxThreads; ++iter)
        {
          Trace *p_subtractedTrace      = subtractedTraceList.at(iter);
          QPair<int, int> *p_indexRange = indexRangeList.at(iter);

          int subtractCount = p_subtractedTrace->subtractSlice(
            traceList, p_indexRange->first, p_indexRange->second);

          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << QString::asprintf("Subtractd slice %d-%d\n",
                                        p_indexRange->first,
                                        p_indexRange->second);

          count += subtractCount;
        }
      // End of
      // #pragma omp parallel

      // At this point we need to conclude and subtract into *this mass
      // trace all the traces in the list of subtract traces. Take
      // advantage of the loop to also free each subtract trace in
      // turn.

      while(subtractedTraceList.size())
        {
          Trace *trace = subtractedTraceList.takeFirst();
          subtract(*trace);
          delete trace;
        }

      // At this point, we need to free all the QPair instances
      while(indexRangeList.size())
        delete indexRangeList.takeFirst();
    }
  // End of
  // else /* of if(traceListSize < 10 * maxThreads) */

  // The combination work is finished, however it has been performed.

  if(m_removeZeroValDataPoints)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Now removing all 0-m/z data points.";

      removeZeroValDataPoints();
    }
#if 0
			exportToFile(QString("/tmp/subtractd-trace.txt"));
#endif

  return count;
}


//! Subtract the [\p startIdx--\p endIdx] slice of \p traceList from \c this
//! Trace
/*!
  \return the number of subtractd Trace instances.
  */
int
Trace::subtractSlice(const QList<Trace *> &traceList, int startIdx, int endIdx)
{
  if(traceList.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "List of traces to subtract is empty."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int firstIdx = std::min(startIdx, endIdx);
  int lastIdx  = std::max(startIdx, endIdx);

  if(firstIdx < 0)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Index cannot be less than 0."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(lastIdx > traceList.size())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Out of bounds error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int count = 0;

  for(int iter = firstIdx; iter < lastIdx; ++iter)
    {
      subtract(*(traceList.at(iter)));
      ++count;
    }

  return count;
}


//! Combine the Trace instances of \c traces into \c this Trace
/*!

  This function acts like

  combine(const QList<Trace *> &traces) with the difference that it calls

  combine(const Trace &trace, double keyStart, double keyEnd) to
  only account for DataPoint instances in the spectra that have their key
  contained in the [\p keyStart -- \p keyEnd] range.

  \param traces list of Trace instances to be combined to \c this
  Trace

  \param keyStart start of the acceptable key range

  \param keyEnd end of the acceptable key range

  \return the number of combined Trace instances.

  \sa combine(const QList<Trace *> &traces)

*/
int
Trace::combine(const QList<Trace *> &traces, double keyStart, double keyEnd)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "";

  // We receive a list of mass spectra, that is a list of (list of
  // DataPoint*) and wee need to combine all the spectra thereof in this mass
  // spectrum. However, we need to check each individual DataPoint of the
  // Trace instances for their key, because it can only be
  // combined if its value is in between both keyStart and keyEnd arguments to
  // this function.

  if(traces.size() == 0)
    return 0;

  int count = 0;

  for(int iter = 0; iter < traces.size(); ++iter)
    {
      Trace *iterTrace = traces.at(iter);

      combine(*iterTrace, keyStart, keyEnd);

      ++count;
    }

    // qDebug() << __FILE__ << __LINE__
    // << "Effectively combined " << count
    // << "in this Trace for a total of "
    // << size() << "data points";

#if 0
			QString fileName = "/tmp/mzranged-combined-trace-after-finishing-combinations.txt";
			exportToFile(fileName);
#endif

  return count;
}


//! Subtract \p traces from \c this Trace.
/*!

  This is the reverse operation of combine(const QList<Trace *> &traces,
  double keyStart, double keyEnd).

*/
int
Trace::subtract(const QList<Trace *> &traces, double keyStart, double keyEnd)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ ;

  if(traces.size() == 0)
    return 0;

  int count = 0;

  for(int iter = 0; iter < traces.size(); ++iter)
    {
      Trace *iterTrace = traces.at(iter);

      subtract(*iterTrace, keyStart, keyEnd);

      ++count;
    }

#if 0
			QString fileName = "/tmp/mzranged-combined-trace-after-finishing-combinations.txt";
			exportToFile(fileName);
#endif

  return count;
}


//! Combine the trace represented by \p keyList and \valList in \c this Trace.
/*!

  The \p keyList and \p valList represent a trace. Both the lists must
  contain at least one value and have the same number of items. If \p keyList
  is empty, the function returns immediately. If both lists do not have the
  same size, the program crashes.

  \param keyList list of double values representing the keys of the mass
  spectrum.

  \param valList list of double values representing the values of the
  trace.

  \return -1 if \p keyList is empty, otherwise the number of combined
  (key,value) pairs.

*/
int
Trace::combine(const QList<double> &keyList, const QList<double> &valList)
{

  // Sanity checks

  int listSize = keyList.size();
  if(listSize < 1)
    return -1;

  if(listSize != valList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Reset the index anchors
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < listSize; ++iter)
    count += combine(DataPoint(keyList.at(iter), valList.at(iter)));

  return count;
}


//! Subtract trace data materialized in \p keyList and \p valList from \c this
//! Trace.
/*!

  This is the reverse operation of combine(const QList<double> &keyList, const
  QList<double> &valList).

*/
int
Trace::subtract(const QList<double> &keyList, const QList<double> &valList)
{

  // Sanity check

  int listSize = keyList.size();
  if(listSize < 1)
    return -1;

  if(listSize != valList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Reset the index anchors
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < listSize; ++iter)
    count += subtract(DataPoint(keyList.at(iter), valList.at(iter)));

  return count;
}


//! Combine the trace represented by \p keyList and \valList in \c this Trace.
/*!

  Works like combine(const QList<double> &keyList, const QList<double> &valList)
  unless the (key,value) pairs out of the lists are accounted for only if the
  key
  value is contained in the [\p keyStart -- \p keyEnd] range.

  \param keyList list of double values representing the keys of the mass
  spectrum

  \param valList list of double values representing the values of the
  trace

  \param keyStart start value of the acceptable key range

  \param keyEnd end value of the acceptable key range

  \return -1 if \p keyList is empty, otherwise the number of combined
  (key,value) pairs.

  \sa combine(const QList<double> &keyList, const QList<double> &valList).

*/
int
Trace::combine(const QList<double> &keyList,
               const QList<double> &valList,
               double keyStart,
               double keyEnd)
{

  // Sanity check

  int listSize = keyList.size();
  if(listSize < 1)
    return -1;

  if(listSize != valList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Reset the index anchors
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < listSize; ++iter)
    {
      double key = keyList.at(iter);

      if(key >= keyStart && key <= keyEnd)
        count += combine(DataPoint(keyList.at(iter), valList.at(iter)));
    }

  return count;
}


//! Subtract trace data materialized in \p keyList and \p valList from \c this
//! Trace.
/*!

  This is the reverse operation of combine(const QList<double> &keyList, const
  QList<double> &valList, double keyStart, double keyEnd).

*/
int
Trace::subtract(const QList<double> &keyList,
                const QList<double> &valList,
                double keyStart,
                double keyEnd)
{

  // Sanity check

  int listSize = keyList.size();
  if(listSize < 1)
    return -1;

  if(listSize != valList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Reset the index anchors
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < listSize; ++iter)
    {
      double key = keyList.at(iter);

      if(key >= keyStart && key <= keyEnd)
        count += subtract(DataPoint(keyList.at(iter), valList.at(iter)));
    }

  return count;
}


//! Searches for \p key in \c this Trace
/*!

  Each DataPoint instance in \c this Trace is tested for \p key in its \c
  m_key member datum.

  \param key to search as a key of DataPoint instances in \c this Trace

  \return an int containing the index of the matching DataPoint instance; -1
  if the \p key was not found.

*/
int
Trace::contains(double key) const
{
  int specSize = size();

  for(int iter = 0; iter < specSize; ++iter)
    if(at(iter)->m_key == key)
      return iter;

  return -1;
}


double
Trace::valueAt(double key, double tolerance, bool &ok) const
{
  int specSize = size();

  double minMz = key - (tolerance / 2);
  double maxMz = key + (tolerance / 2);

  for(int iter = 0; iter < specSize; ++iter)
    {
      double iterMz = at(iter)->key();

      if(iterMz <= maxMz && iterMz >= minMz)
        {
          ok = true;

          return at(iter)->val();
        }
    }

  ok = false;

  return 0;
}


//! Fills in a list of double values containing all the keys in \c this Trace.
/*!

  \return a QList of all the keys in \c this Trace.

*/
QList<double>
Trace::keyList() const
{
  QList<double> list;

  for(int iter = 0; iter < size(); ++iter)
    {
      list.append(at(iter)->m_key);
    }

  return list;
}


//! Fills in a list of double values containing all the values in \c this Trace.
/*!

  \return a QList of all the values in \c this Trace.

*/
QList<double>
Trace::valList() const
{
  QList<double> list;

  for(int iter = 0; iter < size(); ++iter)
    {
      list.append(at(iter)->m_val);
    }

  return list;
}


//! Fills in lists of double values containing the data of \c this Trace.
/*!

  The program crashes if any of the pointers is Q_NULLPTR.

  \param keyList pointer to a double list where to store all the keys of
  the DataPoint instances of \c this Trace

  \param valList pointer to a double list where to store all the intensity
  values of the DataPoint instances of \c this Trace

  The lists are filled in the same order as the DataPoint instances are listed
  in \c this Trace, thus reflecting perfectly the mass spectral data.

*/
void
Trace::toLists(QList<double> *keyList, QList<double> *valList) const
{
  if(!keyList)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

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

  for(int iter = 0; iter < size(); ++iter)
    {
      keyList->append(at(iter)->m_key);
      valList->append(at(iter)->m_val);
    }

  return;
}


//! Fills in vectors of double values containing the data of \c this Trace.
/*!

    The program crashes if any of the pointers is Q_NULLPTR.

    \param keyVector pointer to a double vector where to store all the keys of
    the DataPoint instances of \c this Trace

    \param valVector pointer to a double vector where to store all the intensity
    values of the DataPoint instances of \c this Trace

    The vectors are filled in the same order as the DataPoint instances are
   listed in \c this Trace, thus reflecting perfectly the mass spectral data.

*/
void
Trace::toVectors(QVector<double> *keyVector, QVector<double> *valVector) const
{
  if(!keyVector || !valVector)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  for(int iter = 0; iter < size(); ++iter)
    {
      keyVector->append(at(iter)->m_key);
      valVector->append(at(iter)->m_val);
    }

  return;
}


//! Fills in a map with the data of \c this Trace.
/*!

    The program crashes if the pointer is Q_NULLPTR.

    \param map pointer to a <double,double> map in which to store all the
    (key,value) pairs of this \c Trace.

    The map is ordered by definition in ascending order with respect to the key
    (the key), irrespective of the value (the value).

*/
void
Trace::toMap(QMap<double, double> *map) const
{
  if(!map)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  for(int iter = 0; iter < size(); ++iter)
    {
      map->insert(at(iter)->m_key, at(iter)->m_val);
    }

  return;
}

//! Allocates a text string filled with \c this Trace data.
/*!

  For each DataPoint instance of \c this Trace, append a textual
  representation of the (key,value) pair to a heap-allocated string.

  \return a string containing a textual representation of \c this
  Trace. The object must be delete when no more in use.

*/
QString
Trace::asText(int startIndex, int endIndex)
{
  QList<double> keys   = keyList();
  QList<double> values = valList();

  int dataPointCount = keys.size();

  // Even if the spectrum is empty, we should return an empty string.
  QString text;

  if(dataPointCount == 0)
    return text;

  int indexStart = (startIndex <= 0 ? 0 : startIndex);
  if(indexStart >= dataPointCount - 1)
    indexStart = 0;

  int indexEnd = (endIndex <= 0 ? dataPointCount - 1 : endIndex);
  if(indexEnd >= dataPointCount - 1)
    indexEnd = dataPointCount - 1;

  for(int iter = indexStart; iter <= indexEnd; ++iter)
    {
      text.append(QString("%1 %2\n")
                    .arg(keys.at(iter), 0, 'f', 10)
                    .arg(values.at(iter), 0, 'f', 10));
    }

  return text;
}


//! Export \c this Trace data to a file as a xy-formatted string.
bool
Trace::exportToFile(const QString &fileName)
{
  if(fileName.isEmpty())
    return false;

  QFile file(fileName);

  if(!file.open(QIODevice::WriteOnly))
    return false;

  QString text = asText(-1, -1);

  QTextStream out(&file);
  out << text;

  file.close();

  return true;
}


} // namespace msXpSlibmass
