
#include <iostream>
#include <ctime>
#include <random>

#include <catch.hpp>

#include <MassSpectrum.hpp>
#include <globals/globals.hpp>

#include <QDebug>
#include <QFile>

using std::cout;

#define TEST_DATA_DIR "/home/rusconi/devel/msxpertsuite/tests/data"

// qDebug() << __FILE__ << __LINE__ << __FUNCTION__
//<< "Should compile libmass tests.";

namespace msXpSlibmass
{

TEST_CASE("mass spectra can have a title ", "[MassSpectrum]")
{
  QString title("spectrum title");
  MassSpectrum spectrum(title);

  REQUIRE(spectrum.title() == "spectrum title");
}


TEST_CASE("mass spectra have rt and dt", "[MassSpectrum]")
{
  MassSpectrum spectrum;

  double rt = 4.258;
  double dt = 12.9854;

  spectrum.rrt() = rt;
  spectrum.rdt() = dt;

  REQUIRE(spectrum.rt() == rt);
  REQUIRE(spectrum.dt() == dt);
}


TEST_CASE("mass spectra have decimal places", "[MassSpectrum]")
{
  MassSpectrum spectrum;
  int decimalPlaces = 5;

  spectrum.setDecimalPlaces(decimalPlaces);
  REQUIRE(spectrum.decimalPlaces() == decimalPlaces);

  decimalPlaces = 10;

  spectrum.rdecimalPlaces() = decimalPlaces;
  REQUIRE(spectrum.decimalPlaces() == decimalPlaces);
}


TEST_CASE("Mass spectra can be initialized with double lists", "[MassSpectrum]")
{
  MassSpectrum massSpectrum;

  REQUIRE(massSpectrum.size() == 0);

  SECTION("The spectrum is initialized with key and val double lists")
  {
    QList<double> keyList = {1.00, 2.100, 3.200, 4.300, 5.400};
    QList<double> valList = {1300, 2600, 5200, 10400, 20800};
    Q_ASSERT(keyList.size() == valList.size());
    int itemCount = keyList.size();

    massSpectrum.initialize(keyList, valList);

    REQUIRE(massSpectrum.size() == itemCount);

    int failedKeyComparisons = 0;
    int failedValComparisons = 0;

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

        if(p_dp->key() != keyList.at(iter))
          ++failedKeyComparisons;

        if(p_dp->val() != valList.at(iter))
          ++failedValComparisons;
      }

    REQUIRE(failedKeyComparisons == 0);
    REQUIRE(failedValComparisons == 0);
  }

  SECTION(
    "The spectrum is initialized with key and val double lists "
    "and start-end range values")
  {
    QList<double> keyList = {
      725.639, 987.4569, 1235.1234, 1236.1234, 2598.456, 2599.457, 3569.12458};
    QList<double> valList = {1250, 2500, 5000, 10000, 20000, 40000, 80000};
    Q_ASSERT(keyList.size() == valList.size());
    int itemCount = keyList.size();

    double start = 1235.12341;
    double end   = 2599.456;

    massSpectrum.initialize(keyList, valList, start, end);

    REQUIRE(massSpectrum.size() == 2);

    REQUIRE(massSpectrum[0]->key() == 1236.1234);
    REQUIRE(massSpectrum[0]->val() == 10000);

    REQUIRE(massSpectrum[1]->key() == 2598.456);
    REQUIRE(massSpectrum[1]->val() == 20000);
  }
}


TEST_CASE(
  "Mass spectra can be initialized with double "
  "lists and a drift time value",
  "[MassSpectrum]")
{
  MassSpectrum massSpectrum;

  REQUIRE(massSpectrum.size() == 0);

  SECTION(
    "The spectrum is initialized with key and val double "
    "lists and one drift time value")
  {
    QList<double> keyList = {1.00, 2.100, 3.200, 4.300, 5.400};
    QList<double> valList = {1300, 2600, 5200, 10400, 20800};
    Q_ASSERT(keyList.size() == valList.size());
    int itemCount = keyList.size();

    double driftTime = 27.7895;

    massSpectrum.initializeDt(keyList, valList, driftTime);

    REQUIRE(massSpectrum.size() == itemCount);
    REQUIRE(massSpectrum.dt() == driftTime);

    int failedKeyComparisons = 0;
    int failedValComparisons = 0;

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

        if(p_dp->key() != keyList.at(iter))
          ++failedKeyComparisons;

        if(p_dp->val() != valList.at(iter))
          ++failedValComparisons;
      }

    REQUIRE(failedKeyComparisons == 0);
    REQUIRE(failedValComparisons == 0);
  }

  SECTION(
    "The spectrum is initialized with key and val double lists, "
    "drift time value and and start-end range values")
  {
    QList<double> keyList = {
      725.639, 987.4569, 1235.1234, 1236.1234, 2598.456, 2599.457, 3569.12458};
    QList<double> valList = {1250, 2500, 5000, 10000, 20000, 40000, 80000};
    Q_ASSERT(keyList.size() == valList.size());
    int itemCount = keyList.size();

    double driftTime = 27.7895;

    double start = 1235.12341;
    double end   = 2599.456;

    massSpectrum.initializeDt(keyList, valList, driftTime, start, end);

    REQUIRE(massSpectrum.size() == 2);
    REQUIRE(massSpectrum.dt() == driftTime);

    REQUIRE(massSpectrum[0]->key() == 1236.1234);
    REQUIRE(massSpectrum[0]->val() == 10000);

    REQUIRE(massSpectrum[1]->key() == 2598.456);
    REQUIRE(massSpectrum[1]->val() == 20000);
  }
}


TEST_CASE(
  "Mass spectra can be initialized with double vectors "
  "and start-end range values",
  "[MassSpectrum]")
{
  MassSpectrum massSpectrum;

  REQUIRE(massSpectrum.size() == 0);

  SECTION(
    "The spectrum is initialized with key and val double "
    "vectors and no start-end range values")
  {
    QVector<double> keyVector = {1.00, 2.100, 3.200, 4.300, 5.400};
    QVector<double> iVector   = {1300, 2600, 5200, 10400, 20800};
    Q_ASSERT(keyVector.size() == iVector.size());
    int itemCount = keyVector.size();

    massSpectrum.initialize(keyVector, iVector);

    REQUIRE(massSpectrum.size() == itemCount);

    int failedKeyComparisons = 0;
    int failedValComparisons = 0;

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

        if(p_dp->key() != keyVector.at(iter))
          ++failedKeyComparisons;

        if(p_dp->val() != iVector.at(iter))
          ++failedValComparisons;
      }

    REQUIRE(failedKeyComparisons == 0);
    REQUIRE(failedValComparisons == 0);
  }


  SECTION(
    "The spectrum is initialized with key and val double "
    "vectors and start-end range values")
  {
    QVector<double> keyVector = {
      725.639, 987.4569, 1235.1234, 1236.1234, 2598.456, 2599.457, 3569.12458};
    QVector<double> iVector = {1250, 2500, 5000, 10000, 20000, 40000, 80000};
    Q_ASSERT(keyVector.size() == iVector.size());
    int itemCount = keyVector.size();

    double start = 1235.12341;
    double end   = 2599.456;

    massSpectrum.initialize(keyVector, iVector, start, end);

    REQUIRE(massSpectrum.size() == 2);

    REQUIRE(massSpectrum[0]->key() == 1236.1234);
    REQUIRE(massSpectrum[0]->val() == 10000);

    REQUIRE(massSpectrum[1]->key() == 2598.456);
    REQUIRE(massSpectrum[1]->val() == 20000);
  }
}


TEST_CASE("Mass spectra can be initialized with simple XY-formatted text.",
          "[MassSpectrum]")
{
  MassSpectrum massSpectrum;

  REQUIRE(massSpectrum.size() == 0);

  QStringList xyFormatStringList;
  xyFormatStringList << "199.928558349609 1840.123456700000"
                     << "199.934738159180 2471.456780000000"
                     << "199.940917968750 3497.000000056985"
                     << "199.947082519531 5236.000000000000"
                     << "199.953262329102 8631.000000000000"
                     << "199.959442138672 31505.000000000000"
                     << "199.965606689453 75716.000000000000"
                     << "199.971786499023 79882.000000000000"
                     << "199.977951049805 47849.000000000000"
                     << "199.984130859375 33539.000000000000"
                     << "199.990295410156 29096.000000000000"
                     << "199.996475219727 29104.000000000000"
                     << "200.002655029297 26650.000000000000"
                     << "200.008819580078 24243.000000000000"
                     << "200.014999389648 25983.000000000000"
                     << "200.021179199219 28867.000000000000"
                     << "200.027359008789 23874.000000000000"
                     << "200.033523559570 19043.000000000000"
                     << "200.039703369141 16450.000000000000"
                     << "200.045883178711 17226.000000000000"
                     << "200.052062988281 30648.000000000000"
                     << "200.058242797852 44396.000000000000"
                     << "200.064407348633 34241.000000000000"
                     << "200.070587158203 23241.000000000000"
                     << "200.076766967773 17745.000000000000"
                     << "200.082946777344 16258.000000000000"
                     << "200.089126586914 30343.000000000000";

  int xyFormatStringListSize = xyFormatStringList.size();

  QString xyFormatString = xyFormatStringList.join("\n");

  massSpectrum.initialize(xyFormatString);

  REQUIRE(xyFormatStringListSize == massSpectrum.size());

  int failedKeyComparisons = 0;
  int failedValComparisons = 0;

  for(int iter = 0; iter < xyFormatStringListSize; ++iter)
    {
      DataPoint *iterDataPoint = massSpectrum.at(iter);
      DataPoint *dataPoint     = new DataPoint(xyFormatStringList.at(iter));

      if(iterDataPoint->key() != dataPoint->key())
        ++failedKeyComparisons;

      if(iterDataPoint->val() != dataPoint->val())
        ++failedValComparisons;
    }

  REQUIRE(failedKeyComparisons == 0);
  REQUIRE(failedValComparisons == 0);
}


TEST_CASE("All the mass peaks of a mass spectrum can be deleted.",
          "[MassSpectrum]")
{
  MassSpectrum massSpectrum;
  REQUIRE(massSpectrum.size() == 0);

  QVector<double> keyVector = {
    725.639, 987.4569, 1235.1234, 1236.1234, 2598.456, 2599.457, 3569.12458};
  QVector<double> iVector = {1250, 2500, 5000, 10000, 20000, 40000, 80000};
  Q_ASSERT(keyVector.size() == iVector.size());
  int itemCount = keyVector.size();

  massSpectrum.initialize(keyVector, iVector);
  REQUIRE(massSpectrum.size() == itemCount);

  massSpectrum.deleteDataPoints();
  REQUIRE(massSpectrum.size() == 0);
}


TEST_CASE("Mass spectra can be reset to an empty state.", "[MassSpectrum]")
{
  MassSpectrum massSpectrum;
  REQUIRE(massSpectrum.size() == 0);

  QVector<double> keyVector = {
    725.639, 987.4569, 1235.1234, 1236.1234, 2598.456, 2599.457, 3569.12458};
  QVector<double> iVector = {1250, 2500, 5000, 10000, 20000, 40000, 80000};
  Q_ASSERT(keyVector.size() == iVector.size());
  int itemCount = keyVector.size();

  massSpectrum.initialize(keyVector, iVector);
  REQUIRE(massSpectrum.size() == itemCount);

  massSpectrum.reset();
  REQUIRE(massSpectrum.size() == 0);
}


TEST_CASE(
  "When combining mass spectra, binning might be required. "
  "Testing that feature (non-binnable test with mass spectrum list of only one "
  "mass spectrum).",
  "[MassSpectrum]")
{
  MassSpectrum massSpectrum;
  REQUIRE(massSpectrum.size() == 0);

  massSpectrum.setBinningType(msXpS::BinningType::BINNING_TYPE_NONE);

  QList<MassSpectrum *> massSpectra;

  massSpectra.append(new MassSpectrum);

  bool result = massSpectrum.setupBinnability(massSpectra);

  REQUIRE(result == true);
  REQUIRE(massSpectrum.binningType() == msXpS::BinningType::BINNING_TYPE_NONE);
}


TEST_CASE(
  "When combining mass spectra, binning might be required. "
  "Testing that feature (non-binnable test with empty mass spectrum list).",
  "[MassSpectrum]")
{
  MassSpectrum massSpectrum;
  REQUIRE(massSpectrum.size() == 0);

  massSpectrum.setBinningType(msXpS::BinningType::BINNING_TYPE_ARBITRARY);

  QList<MassSpectrum *> massSpectra;

  bool result = massSpectrum.setupBinnability(massSpectra);

  REQUIRE(result == false);
}


TEST_CASE(
  "When combining mass spectra, binning might be required. "
  "Testing that feature (non-binnable test with BINNING_TYPE_NONE).",
  "[MassSpectrum]")
{
  MassSpectrum massSpectrum;
  REQUIRE(massSpectrum.size() == 0);

  massSpectrum.setBinningType(msXpS::BinningType::BINNING_TYPE_NONE);

  QList<MassSpectrum *> massSpectra;

  massSpectra.append(new MassSpectrum);
  massSpectra.append(new MassSpectrum);

  bool result = massSpectrum.setupBinnability(massSpectra);

  REQUIRE(result == true);
}


TEST_CASE("Bin population with arbitrary binning type", "[MassSpectrum]")
{
  MassSpectrum massSpectrum;
  REQUIRE(massSpectrum.size() == 0);

  // Seed the m/z keys.

  double minMz = 100;
  massSpectrum.setMinMz(minMz);

  double maxMz = 1000;
  massSpectrum.setMaxMz(maxMz);

  double binSize = 0.005;
  massSpectrum.setBinSize(binSize);

  massSpectrum.setBinningType(msXpS::BinningType::BINNING_TYPE_ARBITRARY);
  massSpectrum.setBinSizeType(msXpS::MassToleranceType::MASS_TOLERANCE_MZ);

  massSpectrum.populateSpectrumWithArbitraryBins();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Mass spectrum as text:" << massSpectrum.asText()
  //<< "with bin size type:" << massSpectrum.binSizeType()
  //<< "and mass spectrum size:" << massSpectrum.size();

  REQUIRE(massSpectrum.size() == ((1 / binSize) * (maxMz - minMz)) + 1);

  REQUIRE(massSpectrum.first()->key() == minMz);

  REQUIRE(massSpectrum.at(1)->key() == minMz + binSize);
  REQUIRE(massSpectrum.at(massSpectrum.size() - 2)->key() == maxMz - binSize);

  REQUIRE(massSpectrum.last()->key() == maxMz);
}


TEST_CASE("Bin population with data-based binning type", "[MassSpectrum]")
{

  // Create the "template spectrum" using the binning facility already tested.

  MassSpectrum massSpectrum;
  REQUIRE(massSpectrum.size() == 0);

  // Seed the m/z keys.

  double minMz = 100;
  massSpectrum.setMinMz(minMz);

  double maxMz = 1000;
  massSpectrum.setMaxMz(maxMz);

  double binSize = 0.005;
  massSpectrum.setBinSize(binSize);

  massSpectrum.setBinningType(msXpS::BinningType::BINNING_TYPE_DATA_BASED);
  massSpectrum.setBinSizeType(msXpS::MassToleranceType::MASS_TOLERANCE_MZ);

  // Fill in the spectrum with arbitrary data.
  massSpectrum.populateSpectrumWithArbitraryBins();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Mass spectrum as text:" << massSpectrum.asText();

  // Set aside a mass spectrum in which we'll setup the bins according to the
  // other mass spectrum data.
  MassSpectrum combinedSpectrum;

  // In this procedure, only the m_minMz value of the combine spectrum is
  // used. As the bin calculation stops when the template mass spectrum has
  // been iterated over.
  double combMinMz = 200.000000;
  combinedSpectrum.setMinMz(combMinMz);

  combinedSpectrum.populateSpectrumWithDataBasedBins(massSpectrum);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Combined mass spectrum as text:" << combinedSpectrum.asText()
  //<< "with bin size type:" << combinedSpectrum.binSizeType()
  //<< "and mass spectrum size:" << combinedSpectrum.size();

  REQUIRE(combinedSpectrum.size() == massSpectrum.size());

  REQUIRE(combinedSpectrum.first()->key() == combMinMz);

  REQUIRE(combinedSpectrum.at(1)->key() ==
          combMinMz + (massSpectrum.at(1)->key() - massSpectrum.at(0)->key()));
}


TEST_CASE(
  "When combining mass spectra, find an key shift between "
  "spectra might be required. Testing that feature.",
  "[MassSpectrum]")
{
  // Prepare the mass data to initialize the mass spectrum.
  // Prepare a local test bin list that must be discovered to be identical
  // to the bin list computed by MassSpectrum::fillInBins()

  QList<double> keyList = {
    694.5166264003, 694.5333824250, 694.5501386518, 694.5668950807,
    694.5836517118, 694.6004085450, 694.6171655803, 694.6339228177,
    694.6506802573, 694.6674378989, 694.6841957428, 694.7009537887,
    694.7177120368, 694.7344704869, 694.7512291393, 694.7679879937,
    694.7847470503, 694.8015063090, 694.8182657698, 694.8350254327};

  QList<double> valList = {
    133.0000000000, 195.0000000000, 225.0000000000, 220.0000000000,
    286.0000000000, 332.0000000000, 282.0000000000, 164.0000000000,
    140.0000000000, 156.0000000000, 153.0000000000, 89.0000000000,
    104.0000000000, 143.0000000000, 196.0000000000, 295.0000000000,
    396.0000000000, 399.0000000000, 369.0000000000, 345.0000000000};

  if(keyList.size() != valList.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "The key and val lists have not the same size."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int itemCount = keyList.size();

  MassSpectrum massSpectrum;
  massSpectrum.initialize(keyList, valList);

  // Check that initialization is correct.
  REQUIRE(massSpectrum.size() == itemCount);

  // Now prepare the other mass spectrum that is to be combined into the
  // previous. We just increase the first mass spectrum key values by a fake
  // shift value.

  double fakeShift = 0.001234;
  QList<double> keyListOther;

  for(int iter = 0; iter < itemCount; ++iter)
    keyListOther << keyList.at(iter) + fakeShift;

  MassSpectrum massSpectrumOther;
  massSpectrumOther.initialize(keyListOther, valList);

  // Check that initialization is correct.
  REQUIRE(massSpectrumOther.size() == itemCount);
  REQUIRE(massSpectrumOther.at(0)->key() - fakeShift ==
          massSpectrum.at(0)->key());

  // Now test the shift calculation.
  double result = 0;

  result = massSpectrum.determineMzShift(massSpectrumOther);
  // It is odd that REQUIRE(result == fakeShift) fails!
  REQUIRE(massSpectrumOther.at(0)->key() - result == massSpectrum.at(0)->key());

  result = massSpectrum.determineMzShift(massSpectrumOther.at(0)->key());
  // It is odd that REQUIRE(result == fakeShift) fails!
  REQUIRE(massSpectrumOther.at(0)->key() - result == massSpectrum.at(0)->key());
}


TEST_CASE("TIC calculations.", "[MassSpectrum]")
{
  QList<double> keyList = {
    694.5166264003, 694.5333824250, 694.5501386518, 694.5668950807,
    694.5836517118, 694.6004085450, 694.6171655803, 694.6339228177,
    694.6506802573, 694.6674378989, 694.6841957428, 694.7009537887,
    694.7177120368, 694.7344704869, 694.7512291393, 694.7679879937,
    694.7847470503, 694.8015063090, 694.8182657698, 694.8350254327};

  QList<double> valList = {
    133.0000000000, 195.0000000000, 225.0000000000, 220.0000000000,
    286.0000000000, 332.0000000000, 282.0000000000, 164.0000000000,
    140.0000000000, 156.0000000000, 153.0000000000, 89.0000000000,
    104.0000000000, 143.0000000000, 196.0000000000, 295.0000000000,
    396.0000000000, 399.0000000000, 369.0000000000, 345.0000000000};

  if(keyList.size() != valList.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "The key and val lists have not the same size."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int itemCount = keyList.size();

  MassSpectrum massSpectrum;
  massSpectrum.initialize(keyList, valList);

  // Check that initialization is correct.
  REQUIRE(massSpectrum.size() == itemCount);

  SECTION("Compute the TIC with no key borders.", "[MassSpectrum]")
  {
    // We computed the TIC in SpeedCrunch: (4622.00000000).
    REQUIRE(massSpectrum.tic() == 4622.00000000);
  }

  SECTION("Compute the TIC with key range values.", "[MassSpectrum]")
  {
    // Want to leave out the first four and last four key values of the
    // spectrum. SpeedCrunch value: 2340.00000000
    double keyStart = 694.5668950808; // slightly more than fourth from top
    double keyEnd   = 694.7847470502; // slightly less fourth from bottom

    REQUIRE(massSpectrum.tic(keyStart, keyEnd) == 2340.00000000);
  }
}


TEST_CASE("Mass spectrum combination tests.", "[MassSpectrum]")
{
  QList<double> keyList = {
    694.5166264003, 694.5333824250, 694.5501386518, 694.5668950807,
    694.5836517118, 694.6004085450, 694.6171655803, 694.6339228177,
    694.6506802573, 694.6674378989, 694.6841957428, 694.7009537887,
    694.7177120368, 694.7344704869, 694.7512291393, 694.7679879937,
    694.7847470503, 694.8015063090, 694.8182657698, 694.8350254327};

  QList<double> valList = {
    133.0000000000, 195.0000000000, 225.0000000000, 220.0000000000,
    286.0000000000, 332.0000000000, 282.0000000000, 164.0000000000,
    140.0000000000, 156.0000000000, 153.0000000000, 89.0000000000,
    104.0000000000, 143.0000000000, 196.0000000000, 295.0000000000,
    396.0000000000, 399.0000000000, 369.0000000000, 345.0000000000};

  if(keyList.size() != valList.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "The key and val lists have not the same size."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int itemCount = keyList.size();

  MassSpectrum massSpectrum;
  massSpectrum.initialize(keyList, valList);

  // Create an identical copy to later check the differences.
  MassSpectrum otherMassSpectrum;
  otherMassSpectrum.initialize(keyList, valList);

  // Check that initialization is correct.
  REQUIRE(massSpectrum.size() == itemCount);

  SECTION(
    "Combine a mass peak into the mass spectrum when "
    "key is already in the spectrum.",
    "[MassSpectrum]")
  {

    // In this test, we should not create a new DataPoint in the spectrum, but
    // increment the val value of one of the DataPoint items.
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(10, itemCount - 10);
    int keyIndex = distribution(
      generator); // generates number in the range 1..6srand(time(NULL));
    // cout << "Random index:" << keyIndex << "\n";
    int prevVal = massSpectrum.at(keyIndex)->val();
    // cout << std::setprecision(15) << "m/z value for index:" <<
    // massSpectrum.at(keyIndex)->key() << "\n"; cout << std::setprecision(15) <<
    // "Intensity for index:" << prevVal << "\n";

    double newKeyValue = massSpectrum.at(keyIndex)->key();

    DataPoint dataPoint(newKeyValue, 1234);

    massSpectrum.combine(dataPoint);

    // The only differing DataPoint item should be at index keyIndex. Let's
    // check that.

    QMap<int, double> changedIdxVsKeyMap;
    QMap<int, double> changedIdxVsValMap;

    for(int iter = 0; iter < itemCount; ++iter)
      {
        double key = massSpectrum.at(iter)->key();
        double val = massSpectrum.at(iter)->val();

        double otherKey = otherMassSpectrum.at(iter)->key();
        double otherVal = otherMassSpectrum.at(iter)->val();

        if(key != otherKey)
          changedIdxVsKeyMap.insert(iter, key);

        if(val != otherVal)
          changedIdxVsValMap.insert(iter, val);
      }

    REQUIRE(changedIdxVsKeyMap.size() == 0);
    REQUIRE(changedIdxVsValMap.size() == 1);

    REQUIRE(changedIdxVsValMap.first() /* value in the map */ ==
            prevVal + 1234);
    REQUIRE(changedIdxVsValMap.key(changedIdxVsValMap.first()) == keyIndex);

    // At this point we have tested that we have updated the val value of a
    // DataPoint located at the intended position.
  }

  SECTION(
    "Combine a mass peak into the mass spectrum when "
    "key is not already in the spectrum.",
    "[MassSpectrum]")
  {
    // In this test, we know we should create a new DataPoint item inside the
    // spectrum one index after the targeted one.

    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(10, itemCount - 10);
    int keyIndex = distribution(
      generator); // generates number in the range 1..6srand(time(NULL));
    // cout << "Random index:" << keyIndex << "\n";
    double origPrevKey = massSpectrum.at(keyIndex)->key();
    double origNextKey = massSpectrum.at(keyIndex + 1)->key();

    double origPrevVal = massSpectrum.at(keyIndex)->val();
    double origNextVal = massSpectrum.at(keyIndex + 1)->val();

    // cout << std::setprecision(15) << "orig prev: (" << origPrevKey << "," <<
    // origPrevVal << ")\n"; cout << std::setprecision(15) << "orig next: (" <<
    // origNextKey << "," << origNextVal << ")\n";

    // We want to insert the new DataPoint between origPrevKey and origNextKey.
    double newKeyValue =
      origPrevKey + (origNextKey - origPrevKey) - 0.0000000001;

    DataPoint dataPoint(newKeyValue, 1234);

    massSpectrum.combine(dataPoint);

    // Now that spectrum should be longer by one item.

    REQUIRE(massSpectrum.size() == itemCount + 1);

    // The only differing DataPoint item should be at index (keyIndex + 1).
    // Let's check that.

    int failedComparisons = 0;

    for(int iter = 0; iter <= keyIndex; ++iter)
      {
        if(massSpectrum.at(iter)->key() != otherMassSpectrum.at(iter)->key())
          {
            // cout << std::setprecision(5) << "failed key comparison at iter:"
            // << iter << "\n";

            ++failedComparisons;
          }

        // Also compare the intensity, since they should not have changed
        // neither.

        if(massSpectrum.at(iter)->val() != otherMassSpectrum.at(iter)->val())
          {
            // cout << std::setprecision(5) << "failed val comparison at iter:"
            // << iter << "\n";

            ++failedComparisons;
          }
      }

    REQUIRE(failedComparisons == 0);

    // The inserted DataPoint should be at (keyIndex + 1)
    REQUIRE(massSpectrum.at(keyIndex + 1)->key() == newKeyValue);

    // And now all the remaining items should be identical, but dephased.

    failedComparisons = 0;

    for(int iter = keyIndex + 1; iter < itemCount; ++iter)
      {
        if(otherMassSpectrum.at(iter)->key() !=
           massSpectrum.at(iter + 1)->key())
          ++failedComparisons;

        if(otherMassSpectrum.at(iter)->val() !=
           massSpectrum.at(iter + 1)->val())
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);

    // At this point we have tested the we inserted a new DataPoint at the
    // intended position.
  }

  SECTION(
    "Combine a mass peak into the mass spectrum when "
    "key is not already in the spectrum and new one should be on top.",
    "[MassSpectrum]")
  {

    // In this test, we should create a new DataPoint in the spectrum, that
    // should become the first DataPoint of the spectrum after insertion
    // at index 0.

    int keyIndex        = 0;
    double origFirstKey = massSpectrum.at(keyIndex)->key();
    double origFirstVal = massSpectrum.at(keyIndex)->val();

    // cout << std::setprecision(15) << "m/z value for index:" << origFirstKey
    // << "\n"; cout << std::setprecision(15) << "Intensity for index:" <<
    // origFirstVal << "\n";


    // Prepare a new key value that is very slightly inferior to the key value
    // of the first DataPoint of the spectrum;
    double newKeyValue = massSpectrum.at(keyIndex)->key() - 0.0000000001;
    DataPoint dataPoint(newKeyValue, 1234);

    massSpectrum.combine(dataPoint);

    // The only differing DataPoint item should be at index keyIndex. Let's
    // check that.

    REQUIRE(massSpectrum.at(0)->key() == newKeyValue);
    REQUIRE(massSpectrum.at(0)->val() == 1234);

    // Now, all the other items, should be identical. Let's check that. Skip
    // first mass peak of massSpectrum, because we know it's the new one.
    // Watch the dephasing in the for loop.

    int failedComparisons = 0;

    for(int iter = 1; iter <= itemCount; ++iter)
      {
        double key = massSpectrum.at(iter)->key();
        double val = massSpectrum.at(iter)->val();

        double otherKey = otherMassSpectrum.at(iter - 1)->key();
        double otherVal = otherMassSpectrum.at(iter - 1)->val();

        if(key != otherKey || val != otherVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);

    // At this point we have tested that we have inserted at index 0 a new
    // DataPoint instance.
  }


  SECTION(
    "Combine a mass peak into the mass spectrum when "
    "key is not already in the spectrum and new one should be on bottom.",
    "[MassSpectrum]")
  {

    // In this test, we should create a new DataPoint in the spectrum, that
    // should become the last DataPoint of the spectrum after insertion
    // at index (itemCount - 1).

    int keyIndex       = itemCount - 1;
    double origLastKey = massSpectrum.at(keyIndex)->key();
    double origLastVal = massSpectrum.at(keyIndex)->val();

    // cout << std::setprecision(15) << "m/z value for index:" << origLastKey <<
    // "\n"; cout << std::setprecision(15) << "Intensity for index:" <<
    // origLastVal << "\n";

    // Prepare a new key value that is very slightly greater to the key value
    // of the last DataPoint of the spectrum;
    double newKeyValue = massSpectrum.at(keyIndex)->key() + 0.0000000001;
    DataPoint dataPoint(newKeyValue, 1234);

    massSpectrum.combine(dataPoint);

    // We should have added a new DataPoint instance, the mass spectrum should
    // have grown by 1 unit. Thus, the last index is now itemCount.

    int newItemCount = massSpectrum.size();
    REQUIRE(newItemCount == itemCount + 1);

    REQUIRE(massSpectrum.at(newItemCount - 1)->key() == newKeyValue);
    REQUIRE(massSpectrum.at(newItemCount - 1)->val() == 1234);

    // Now, all the other items, should be identical. Let's check that. Skip
    // last mass peak of massSpectrum, because we know it's the new one.

    int failedComparisons = 0;

    for(int iter = 0; iter < newItemCount - 1 /* skip last item */; ++iter)
      {
        double key = massSpectrum.at(iter)->key();
        double val = massSpectrum.at(iter)->val();

        double otherKey = otherMassSpectrum.at(iter)->key();
        double otherVal = otherMassSpectrum.at(iter)->val();

        if(key != otherKey || val != otherVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);

    // At this point we have tested that we have inserted a new mass peak as
    // the very last item in the masspectrum.
  }
}


TEST_CASE("Mass spectrum data-based binned combination test", "[MassSpectrum]")
{

  QList<double> keyList = {
    694.5166264003, 694.5333824250, 694.5501386518, 694.5668950807,
    694.5836517118, 694.6004085450, 694.6171655803, 694.6339228177,
    694.6506802573, 694.6674378989, 694.6841957428, 694.7009537887,
    694.7177120368, 694.7344704869, 694.7512291393, 694.7679879937,
    694.7847470503, 694.8015063090, 694.8182657698, 694.8350254327};

  QList<double> valList = {
    133.0000000000, 195.0000000000, 225.0000000000, 220.0000000000,
    286.0000000000, 332.0000000000, 282.0000000000, 164.0000000000,
    140.0000000000, 156.0000000000, 153.0000000000, 89.0000000000,
    104.0000000000, 143.0000000000, 196.0000000000, 295.0000000000,
    396.0000000000, 399.0000000000, 369.0000000000, 345.0000000000};

  if(keyList.size() != valList.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "The key and val lists have not the same size."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int itemCount = keyList.size();

  MassSpectrum massSpectrum;
  massSpectrum.initialize(keyList, valList);

  // Create an identical copy to later check the differences.
  MassSpectrum otherMassSpectrum;
  otherMassSpectrum.initialize(keyList, valList);

  // Check that initialization is correct.
  REQUIRE(massSpectrum.size() == itemCount);

  // We know that the data used to seed the new spectra below are correct
  // for binnability.

  QList<MassSpectrum *> massSpectra;

  // We need two allocate mass spectrum instances in the mass spectrum
  // list.

  MassSpectrum *newSpectrum = new MassSpectrum;
  newSpectrum->initialize(keyList, valList);
  massSpectra.append(newSpectrum);

  newSpectrum = new MassSpectrum;
  newSpectrum->initialize(keyList, valList);
  massSpectra.append(newSpectrum);


  // This spectrum will be the host of the bins once computed by looking
  // into the new mass spectra allocated below. Once the bins will be setup
  // in this spectrum, it will be the spectrum into which we test the binned
  // combination of a DataPoint.

  MassSpectrum binMassSpectrum;
  binMassSpectrum.setBinningType(msXpS::BinningType::BINNING_TYPE_DATA_BASED);
  binMassSpectrum.setBinSizeType(msXpS::MassToleranceType::MASS_TOLERANCE_MZ);

  bool res = binMassSpectrum.setupBinnability(massSpectra);
  REQUIRE(res == true);

  // At this point, binMassSpectrum must have as many DataPoint elements as
  // what was in newSpectrum. This is because when populating binMassSpectrum
  // in the setupBinnability() function, the smallest key value found in all
  // the mass spectra of the QList<MassSpectrum *> is first seeded as such,
  // and is thus the first mass peak item of the mass spectrum. Then, for each
  // bin, a new mass peak is appended to the spectrum. At the end, there are
  // bins + 1 mass peaks in the spectrum, which is identical to itemCount.

  REQUIRE(binMassSpectrum.size() == itemCount);

  // Prepare a copy spectrum for later comparisons.

  MassSpectrum otherBinMassSpectrum;
  otherBinMassSpectrum.setBinningType(
    msXpS::BinningType::BINNING_TYPE_DATA_BASED);
  otherBinMassSpectrum.setBinSizeType(
    msXpS::MassToleranceType::MASS_TOLERANCE_MZ);


  res = otherBinMassSpectrum.setupBinnability(massSpectra);
  REQUIRE(res == true);


  SECTION(
    "Combine (in a binned manner) a mass peak into "
    "the mass spectrum (combination occurs roughly in the "
    "middle of the spectrum",
    "[MassSpectrum]")
  {

    // Generate a random index
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(10, itemCount - 10);
    int keyIndex = distribution(generator);
    // cout << "Random index: " << keyIndex << "\n";

    double origPrevBinKey = binMassSpectrum.at(keyIndex)->key();
    double origPrevBinVal = binMassSpectrum.at(keyIndex)->val();

    double origNextBinKey = binMassSpectrum.at(keyIndex + 1)->key();
    double origNextBinVal = binMassSpectrum.at(keyIndex + 1)->val();

    double origBinInterval = origNextBinKey - origPrevBinKey;

    // cout << std::setprecision(15) << "orig prev bin: (" << origPrevBinKey <<
    // "," << origPrevBinVal << ")\n"; cout << std::setprecision(15) << "orig
    // next bin: (" << origNextBinKey << "," << origNextBinVal << ")\n"; cout <<
    // std::setprecision(15) << "orig bin interval: " << origBinInterval << "\n";

    // Create a new DataPoint having an key value that sits between both bins.
    // It should thus be set to origPrevKey.

    DataPoint newDataPoint(origPrevBinKey + (origBinInterval / 2), 1234);

    binMassSpectrum.combineBinned(newDataPoint);

    // The only item that should have changed is the bin at keyIndex
    // (origPrevBinKey).

    int failedComparisons = 0;

    for(int iter = 0; iter < itemCount; ++iter)
      {
        double key = binMassSpectrum.at(iter)->key();
        double val = binMassSpectrum.at(iter)->val();

        double otherKey = otherBinMassSpectrum.at(iter)->key();
        double otherVal = otherBinMassSpectrum.at(iter)->val();

        if(iter == keyIndex)
          {
            REQUIRE(val == 1234);
            continue;
          }

        if(key != otherKey || val != otherVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);

    delete massSpectra.at(1);
    delete massSpectra.at(0);

    massSpectra.clear();

    // At this point we have tested the binned combination of a mass peak into
    // the inner part of a spectrum (not at the first or at the last bin of
    // the spectrum).
  }

  SECTION(
    "Combine (in a binned manner) a mass peak into "
    "the mass spectrum (combination occurs right at top).",
    "[MassSpectrum]")
  {

    int keyIndex = 0;
    // cout << "First bin index: " << keyIndex << "\n";

    double origPrevBinKey = binMassSpectrum.at(keyIndex)->key();
    double origPrevBinVal = binMassSpectrum.at(keyIndex)->val();

    double origNextBinKey = binMassSpectrum.at(keyIndex + 1)->key();
    double origNextBinVal = binMassSpectrum.at(keyIndex + 1)->val();

    double origBinInterval = origNextBinKey - origPrevBinKey;

    // cout << std::setprecision(15) << "orig prev bin: (" << origPrevBinKey <<
    // "," << origPrevBinVal << ")\n"; cout << std::setprecision(15) << "orig
    // next bin: (" << origNextBinKey << "," << origNextBinVal << ")\n"; cout <<
    // std::setprecision(15) << "orig bin interval: " << origBinInterval << "\n";
    // cout << std::setprecision(15) << "new key value: " << origPrevBinKey <<
    // "\n";

    // Create a new DataPoint having an key value that is identical to the one
    // of the first bin. Note that it is illegal to provide a key value less
    // than the key value of the first bin, because that would means that
    // setupBinnability() had not be called properly.

    DataPoint newDataPoint(origPrevBinKey, 1234);

    binMassSpectrum.combineBinned(newDataPoint);

    // The only item that should have changed is the bin at keyIndex, that is
    // the very first key value has changed and is the one combined.

    REQUIRE(binMassSpectrum.at(keyIndex)->key() == origPrevBinKey);
    REQUIRE(binMassSpectrum.at(keyIndex)->val() == origPrevBinVal + 1234);

    // Now, skipping the first mass peak of binMassSpectrum, check the
    // remaining elements of the spectra. They should be identical. Note that
    // there is not dephased iteration, because when combining in binned mode,
    // the number of bins never changes.

    int failedComparisons = 0;

    for(int iter = 1; iter < itemCount; ++iter)
      {
        double key = binMassSpectrum.at(iter)->key();
        double val = binMassSpectrum.at(iter)->val();

        double otherKey = otherBinMassSpectrum.at(iter)->key();
        double otherVal = otherBinMassSpectrum.at(iter)->val();

        if(key != otherKey || val != otherVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);

    delete massSpectra.at(1);
    delete massSpectra.at(0);

    massSpectra.clear();

    // At this point we have tested the binned combination of a mass peak as
    // the new top mass peak of a spectrum.
  }

  SECTION(
    "Combine (in a binned manner) a mass peak into "
    "the mass spectrum (combination occurs right at bottom).",
    "[MassSpectrum]")
  {

    int keyIndex = itemCount - 1;
    // cout << "First bin index: " << keyIndex << "\n";

    double origPrevBinKey = binMassSpectrum.at(keyIndex)->key();
    double origPrevBinVal = binMassSpectrum.at(keyIndex)->val();

    double origPrevPrevBinKey = binMassSpectrum.at(keyIndex - 1)->key();
    double origPrevPrevBinVal = binMassSpectrum.at(keyIndex - 1)->val();

    double origBinInterval = origPrevPrevBinKey - origPrevBinKey;

    // cout << std::setprecision(15) << "orig prev bin: (" << origPrevBinKey <<
    // "," << origPrevBinVal << ")\n"; cout << std::setprecision(15) << "orig
    // prev prev bin: (" << origPrevPrevBinKey << "," << origPrevPrevBinVal <<
    // ")\n"; cout << std::setprecision(15) << "orig bin interval: " <<
    // origBinInterval << "\n"; cout << std::setprecision(15) << "new key value:
    // " << origPrevBinKey << "\n";

    // Create a new DataPoint having an key value that is identical to the one
    // of the last bin.
    DataPoint newDataPoint(origPrevBinKey, 1234);

    binMassSpectrum.combineBinned(newDataPoint);

    // The only item that should have changed is the bin at keyIndex, that is
    // the very last key value has changed and is the one combined.

    REQUIRE(binMassSpectrum.at(keyIndex)->key() == origPrevBinKey);
    REQUIRE(binMassSpectrum.at(keyIndex)->val() == origPrevBinVal + 1234);

    // Now, skipping the last mass peak of binMassSpectrum, check the
    // remaining elements of the spectra. They should be identical. Note that
    // there is not dephased iteration, because when combining in binned mode,
    // the number of bins never changes.

    int failedComparisons = 0;

    for(int iter = 1; iter < itemCount - 1; ++iter)
      {
        double key = binMassSpectrum.at(iter)->key();
        double val = binMassSpectrum.at(iter)->val();

        double otherKey = otherBinMassSpectrum.at(iter)->key();
        double otherVal = otherBinMassSpectrum.at(iter)->val();

        if(key != otherKey || val != otherVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);

    delete massSpectra.at(1);
    delete massSpectra.at(0);

    massSpectra.clear();

    // At this point we have tested the binned combination of a mass peak as
    // the new top mass peak of a spectrum.
  }
}


TEST_CASE("Combination of mass spectrum list into a mass spectrum",
          "[MassSpectrum]")
{
  // Preparation of various binnable spectra (conventional qTOF experiments on
  // proteins (see
  // tests/data/xy/protein-microtofq-bruker.keyml-spec-index-<0--4>.xy)

  QList<MassSpectrum *> massSpectra;

  for(int iter = 0; iter < 5; ++iter)
    {
      QFile file(
        TEST_DATA_DIR +
        QString("/xy/small-protein-microtofq-bruker.mzml-spec-index-%1.xy")
          .arg(iter));

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Current data file:" << file.fileName();

      QString xyFormatString;

      if(!file.open(QFile::ReadOnly))
        {
          qFatal(
            "Fatal error at %s@%d -- %s(). "
            "File %s cannot be found."
            "Program aborted.",
            __FILE__,
            __LINE__,
            __FUNCTION__,
            file.fileName().toLatin1().data());
        }

      QTextStream in(&file);

      xyFormatString = in.readAll();

      file.close();

      MassSpectrum *newSpectrum = new MassSpectrum;
      newSpectrum->initialize(xyFormatString);
      massSpectra.append(newSpectrum);
    }


  SECTION("No bin-combination with no key range.", "[MassSpectrum]")
  {
    // We now have five mass spectra in the list.

    // We now have to load the combined mass spectra from disk
    QFile file(TEST_DATA_DIR +
               QString("/xy/"
                       "small-protein-microtofq-bruker.mzml-spec-combine-no-"
                       "binning-indices-0-4.xy"));

    if(!file.open(QFile::ReadOnly))
      {
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "File %s cannot be found."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__,
          file.fileName().toLatin1().data());
      }

    QTextStream in(&file);

    QString xyFormatString = in.readAll();

    file.close();

    MassSpectrum theorCombinedSpectrum;
    theorCombinedSpectrum.initialize(xyFormatString);

    // Finally actually test the combination function.

    MassSpectrum combinedSpectrum;

    combinedSpectrum.setBinningType(msXpS::BinningType::BINNING_TYPE_NONE);

    combinedSpectrum.combine(massSpectra);

    // Check that both the theor and the actual combined spectra have the same
    // number of mass peak instances.

    REQUIRE(theorCombinedSpectrum.size() == combinedSpectrum.size());

    // Now test that the obained mass combined mass spectrum is identical to the
    // one computed apart.

    int failedComparisons = 0;

    for(int iter = 0; iter < combinedSpectrum.size(); ++iter)
      {
        double key = combinedSpectrum.at(iter)->key();
        double val = combinedSpectrum.at(iter)->val();

        double theorKey = theorCombinedSpectrum.at(iter)->key();
        double theorVal = theorCombinedSpectrum.at(iter)->val();

        // cout << std::setprecision(15)
        //<< "(" << key << "," << val << ") vs (" << theorKey << "," << theorVal
        //<< ")\n";

        // It is important to use the almostEqual() because otherwise we get
        // spurious messages.
        if(!msXpS::almostEqual(key, theorKey, 15))
          {
            cout << std::setprecision(15) << "failed comparison:"
                 << "test (" << key << " vs theor " << theorKey << "\n";

            ++failedComparisons;
          }

        if(!msXpS::almostEqual(val, theorVal, 15))
          {
            cout << std::setprecision(15) << "failed comparison:"
                 << "test (" << val << " vs theor " << theorVal << "\n";

            ++failedComparisons;
          }
      }

    REQUIRE(failedComparisons == 0);
  }
  // At this point we have tested the no-binned combination into an empty mass
  // spectrum of a mass spectrum list containing five mass spectra.
}


TEST_CASE("Various utility functions about mass spectra.", "[MassSpectrum]")
{
  QStringList xyFormatStringList;
  xyFormatStringList << "694.5165997465 1500.0000000000"
                     << "694.5333557712 1332.0000000000"
                     << "694.5501119980 1410.0000000000"
                     << "694.5668684269 1363.0000000000"
                     << "694.5836250580 1424.0000000000"
                     << "694.6003818912 1503.0000000000"
                     << "694.6171389265 1341.0000000000"
                     << "694.6338961639 1089.0000000000"
                     << "694.6506536035 983.0000000000"
                     << "694.6674112451 999.0000000000"
                     << "694.6841690890 1043.0000000000"
                     << "694.7009271349 1015.0000000000"
                     << "694.7176853830 1057.0000000000"
                     << "694.7344438331 1140.0000000000"
                     << "694.7512024855 1284.0000000000"
                     << "694.7679613399 1479.0000000000"
                     << "694.7847203965 1661.0000000000"
                     << "694.8014796552 1703.0000000000"
                     << "694.8182391160 1541.0000000000"
                     << "694.8349987789 879.0000000000";

  REQUIRE(xyFormatStringList.size() == 20);
  QString xyFormatString = xyFormatStringList.join("\n");
  MassSpectrum massSpectrum;
  massSpectrum.initialize(xyFormatString);
  REQUIRE(xyFormatStringList.size() == massSpectrum.size());

  SECTION("Tell if a mass spectrum contains a given m/z value.",
          "[MassSpectrum]")
  {
    int index  = 10;
    double key = massSpectrum.at(index)->key();

    // 20170926 add one more decimal to 0.0000000000001 and it fails.
    REQUIRE(massSpectrum.contains(key + 0.0000000000001) < 0);
    REQUIRE(massSpectrum.contains(key) >= 0);
    REQUIRE(massSpectrum.contains(key) == 10);
  }

  SECTION("Provide the mass spectral m/z data as a list of double values.",
          "[MassSpectrum]")
  {
    QList<double> keyList = massSpectrum.keyList();

    int failedComparisons = 0;

    for(int iter = 0; iter < massSpectrum.size(); ++iter)
      {
        double iterKey = massSpectrum.at(iter)->key();

        double listKey = keyList.at(iter);

        if(iterKey != listKey)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);
  }

  SECTION("Provide the mass spectral val data as a list of double values.",
          "[MassSpectrum]")
  {
    QList<double> valList = massSpectrum.valList();

    int failedComparisons = 0;

    for(int iter = 0; iter < massSpectrum.size(); ++iter)
      {
        double iterVal = massSpectrum.at(iter)->val();

        double listVal = valList.at(iter);

        if(iterVal != listVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);
  }

  SECTION(
    "Provide the mass spectral m/z and val data as two lists of double values.",
    "[MassSpectrum]")
  {
    QList<double> keyList;
    QList<double> valList;

    massSpectrum.toLists(&keyList, &valList);

    int failedComparisons = 0;

    for(int iter = 0; iter < massSpectrum.size(); ++iter)
      {
        double iterKey = massSpectrum.at(iter)->key();
        double listKey = keyList.at(iter);

        double iterVal = massSpectrum.at(iter)->val();
        double listVal = valList.at(iter);

        if(iterKey != listKey)
          ++failedComparisons;
        if(iterVal != listVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);
  }

  SECTION(
    "Provide the mass spectral m/z and val data as two vectors of double "
    "values.",
    "[MassSpectrum]")
  {
    QList<double> keyList;
    QList<double> valList;

    // We have tested that this works correctly.
    massSpectrum.toLists(&keyList, &valList);

    QVector<double> keyVector;
    QVector<double> iVector;

    massSpectrum.toVectors(&keyVector, &iVector);

    int failedComparisons = 0;

    for(int iter = 0; iter < massSpectrum.size(); ++iter)
      {
        double iterKey   = massSpectrum.at(iter)->key();
        double vectorKey = keyVector.at(iter);

        double iterVal   = massSpectrum.at(iter)->val();
        double vectorVal = iVector.at(iter);

        if(iterKey != vectorKey)
          ++failedComparisons;
        if(iterVal != vectorVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);
  }

  SECTION(
    "Provide the mass spectral m/z and val data as a <m/z,i> map of double "
    "values.",
    "[MassSpectrum]")
  {
    QList<double> keyList;
    QList<double> valList;

    // We have tested that this works correctly.
    massSpectrum.toLists(&keyList, &valList);

    QMap<double, double> map;
    massSpectrum.toMap(&map);

    int failedComparisons = 0;

    QList<double> mapKeyList = map.keys();

    REQUIRE(mapKeyList.size() == keyList.size());

    for(int iter = 0; iter < massSpectrum.size(); ++iter)
      {
        double iterKey = massSpectrum.at(iter)->key();
        double mapKey  = mapKeyList.at(iter);

        double iterVal = massSpectrum.at(iter)->val();
        double mapVal  = map.value(mapKey);

        if(iterKey != mapKey)
          ++failedComparisons;
        if(iterVal != mapVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);
  }

  SECTION(
    "Create a textual representation of a region of a mass "
    "spectrum (without key range).",
    "[MassSpectrum]")
  {
    QString text = massSpectrum.asText(-1, -1);
    REQUIRE(!text.isEmpty());

    MassSpectrum reconstructedMassSpectrum;
    reconstructedMassSpectrum.initialize(text);

    REQUIRE(massSpectrum.size() == reconstructedMassSpectrum.size());

    // Now perform a deep comparison of the spectra.
    int failedComparisons = 0;

    for(int iter = 0; iter < massSpectrum.size(); ++iter)
      {
        double iterKey        = massSpectrum.at(iter)->key();
        double reconstructKey = reconstructedMassSpectrum.at(iter)->key();

        double iterVal        = massSpectrum.at(iter)->val();
        double reconstructVal = reconstructedMassSpectrum.at(iter)->val();

        if(iterKey != reconstructKey)
          ++failedComparisons;
        if(iterVal != reconstructVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);
  }

  SECTION(
    "Create a textual representation of a region of a mass "
    "spectrum (with key range).",
    "[MassSpectrum]")
  {

    // Now do the same with a key range. We know that massSpectrum contains
    // this:
    //
    //<< "694.5165997465 1500.0000000000"
    //<< "694.5333557712 1332.0000000000"
    //<< "694.5501119980 1410.0000000000"
    //<< "694.5668684269 1363.0000000000"
    //<< "694.5836250580 1424.0000000000"
    //<< "694.6003818912 1503.0000000000"
    //<< "694.6171389265 1341.0000000000"
    //<< "694.6338961639 1089.0000000000"
    //<< "694.6506536035 983.0000000000"
    //<< "694.6674112451 999.0000000000"
    //<< "694.6841690890 1043.0000000000"
    //<< "694.7009271349 1015.0000000000"
    //<< "694.7176853830 1057.0000000000"
    //<< "694.7344438331 1140.0000000000"
    //<< "694.7512024855 1284.0000000000"
    //<< "694.7679613399 1479.0000000000"
    //<< "694.7847203965 1661.0000000000"
    //<< "694.8014796552 1703.0000000000"
    //<< "694.8182391160 1541.0000000000"
    //<< "694.8349987789 879.0000000000" ;

    // Skip the 5 top-most and bottom-most peaks.
    QString text = massSpectrum.asText(5, 14);
    REQUIRE(!text.isEmpty());

    MassSpectrum reconstructedMassSpectrum;
    reconstructedMassSpectrum.initialize(text);

    // The reconstructed mass spectrum has 10 peaks less than the original
    // one.
    REQUIRE(massSpectrum.size() == reconstructedMassSpectrum.size() + 10);

    // Now perform a deep comparison of the spectra. Note the dephasing during
    // the iteration in both spectra.
    int failedComparisons = 0;

    for(int iter = 0; iter < reconstructedMassSpectrum.size(); ++iter)
      {
        double iterKey        = massSpectrum.at(iter + 5)->key();
        double reconstructKey = reconstructedMassSpectrum.at(iter)->key();

        double iterVal        = massSpectrum.at(iter + 5)->val();
        double reconstructVal = reconstructedMassSpectrum.at(iter)->val();

        if(iterKey != reconstructKey)
          ++failedComparisons;
        if(iterVal != reconstructVal)
          ++failedComparisons;
      }

    REQUIRE(failedComparisons == 0);
  }
}
// End of
// TEST_CASE("Various utility functions about mass spectra.", "[MassSpectrum]")


TEST_CASE("Create mass spectrum from base64-encoded data in byte arrays",
          "[MassSpectrum]")
{
  // Prepare the byte arrays from reading data from specific test files.

  QFile key64File(
    QString(
      "%1/base64/protein-mobility-waters-synapt-hdms.db-index-47-mzlist.base64")
      .arg(TEST_DATA_DIR));
  REQUIRE(key64File.open(QFile::ReadOnly));
  QTextStream key64Stream(&key64File);
  QByteArray key64ByteArray    = key64Stream.readAll().toLatin1();
  QByteArray keyDec64ByteArray = QByteArray::fromBase64(key64ByteArray);

  QFile i64File(
    QString(
      "%1/base64/protein-mobility-waters-synapt-hdms.db-index-47-ilist.base64")
      .arg(TEST_DATA_DIR));
  REQUIRE(i64File.open(QFile::ReadOnly));
  QTextStream i64Stream(&i64File);
  QByteArray i64ByteArray    = i64Stream.readAll().toLatin1();
  QByteArray iDec64ByteArray = QByteArray::fromBase64(i64ByteArray);

  // We know that the files correspond to zlib-compressed data, so the size of
  // the encoded byte arrays can be different (and must in this specific
  // case).
  REQUIRE(keyDec64ByteArray.size() != iDec64ByteArray.size());

  // As per Qt documentation is decompressing zlib-compressed data from
  // outside of Qt, then we have to prepend 4 bytes to the arrays before
  // decompression.
  QByteArray header("aaaa");
  keyDec64ByteArray.prepend(header);
  iDec64ByteArray.prepend(header);

  QByteArray finalKeyArray = qUncompress(keyDec64ByteArray);
  QByteArray finalIArray   = qUncompress(iDec64ByteArray);

  // Now , yes, after decompression, the number of key items needs to be
  // identical to the number of val items.
  REQUIRE(finalKeyArray.size() == finalIArray.size());

  MassSpectrum massSpectrum;
  massSpectrum.initialize(&key64ByteArray,
                          &i64ByteArray,
                          msXpS::DataCompression::DATA_COMPRESSION_ZLIB);

  // This one contains both key and val lists in the xy format.
  QFile xyFile(
    QString("%1/xy/protein-mobility-waters-synapt-hdms.db-spectrum-index-47.xy")
      .arg(TEST_DATA_DIR));
  REQUIRE(xyFile.open(QFile::ReadOnly));
  QTextStream xyStream(&xyFile);
  QString xyText = xyStream.readAll();

  MassSpectrum testSpectrum;
  testSpectrum.initialize(xyText);

  REQUIRE(massSpectrum.size() == testSpectrum.size());

  int failedComparisons = 0;

  for(int iter = 0; iter < testSpectrum.size(); ++iter)
    {
      double key     = massSpectrum.at(iter)->key();
      double testKey = testSpectrum.at(iter)->key();

      double val     = massSpectrum.at(iter)->val();
      double testVal = testSpectrum.at(iter)->val();

      if(abs(key - testKey) > 0.0000000001)
        // if(!msXpS::almostEqual(key, testKey, 10))
        {
          cout << std::setprecision(10) << key << " vs " << testKey
               << "\n"
                  "\t\t difference:"
               << key - testKey << "\n";

          ++failedComparisons;
        }

      // if(!msXpS::almostEqual(val, testI, 10))
      //++failedComparisons;
    }

  REQUIRE(failedComparisons == 0);
}
// End of
// TEST_CASE("Create mass spectrum from base64-encoded data in byte arrays.",
// "[MassSpectrum]")


TEST_CASE(
  "Create mass spectrum from base64-encoded data in byte arrays (with key "
  "range)",
  "[MassSpectrum]")
{
  // Prepare the byte arrays from reading data from specific test files.

  QFile key64File(QString("%1/base64/"
                          "protein-mobility-waters-synapt-hdms.db-index-47-mz-"
                          "2000-2400-mzlist.base64")
                    .arg(TEST_DATA_DIR));
  REQUIRE(key64File.open(QFile::ReadOnly));
  QTextStream key64Stream(&key64File);
  QByteArray key64ByteArray    = key64Stream.readAll().toLatin1();
  QByteArray keyDec64ByteArray = QByteArray::fromBase64(key64ByteArray);

  QFile i64File(QString("%1/base64/"
                        "protein-mobility-waters-synapt-hdms.db-index-47-mz-"
                        "2000-2400-ilist.base64")
                  .arg(TEST_DATA_DIR));
  REQUIRE(i64File.open(QFile::ReadOnly));
  QTextStream i64Stream(&i64File);
  QByteArray i64ByteArray    = i64Stream.readAll().toLatin1();
  QByteArray iDec64ByteArray = QByteArray::fromBase64(i64ByteArray);

  // We know that the files correspond to zlib-compressed data, so the size of
  // the encoded byte arrays can be different (and must in this specific
  // case).
  REQUIRE(keyDec64ByteArray.size() != iDec64ByteArray.size());

  // As per Qt documentation is decompressing zlib-compressed data from
  // outside of Qt, then we have to prepend 4 bytes to the arrays before
  // decompression.
  QByteArray header("aaaa");
  keyDec64ByteArray.prepend(header);
  iDec64ByteArray.prepend(header);

  QByteArray finalKeyArray = qUncompress(keyDec64ByteArray);
  QByteArray finalIArray   = qUncompress(iDec64ByteArray);

  // Now , yes, after decompression, the number of key items needs to be
  // identical to the number of val items.
  REQUIRE(finalKeyArray.size() == finalIArray.size());

  MassSpectrum massSpectrum;
  massSpectrum.initialize(&key64ByteArray,
                          &i64ByteArray,
                          msXpS::DataCompression::DATA_COMPRESSION_ZLIB,
                          2000,
                          2400);

  // This one contains both key and val lists in the xy format.
  QFile xyFile(QString("%1/xy/"
                       "protein-mobility-waters-synapt-hdms.db-spectrum-index-"
                       "47-mz-2000-2400.xy")
                 .arg(TEST_DATA_DIR));
  REQUIRE(xyFile.open(QFile::ReadOnly));
  QTextStream xyStream(&xyFile);
  QString xyText = xyStream.readAll();

  MassSpectrum testSpectrum;
  testSpectrum.initialize(xyText);

  REQUIRE(massSpectrum.size() == testSpectrum.size());

  int failedComparisons = 0;

  for(int iter = 0; iter < testSpectrum.size(); ++iter)
    {
      double key     = massSpectrum.at(iter)->key();
      double testKey = testSpectrum.at(iter)->key();

      double val     = massSpectrum.at(iter)->val();
      double testVal = testSpectrum.at(iter)->val();

      if(abs(key - testKey) > 0.0000000001)
        // if(!msXpS::almostEqual(key, testKey, 10))
        {
          cout << std::setprecision(10) << key << " vs " << testKey
               << "\n"
                  "\t\t difference:"
               << key - testKey << "\n";

          ++failedComparisons;
        }

      // if(!msXpS::almostEqual(val, testVal, 10))
      //++failedComparisons;
    }

  REQUIRE(failedComparisons == 0);
}


// TEST_CASE("", "[MassSpectrum]")
//{

//}

} // namespace msXpSlibmass
