#include <iostream>
#include <iomanip>

#include <catch2/catch_test_macros.hpp>


#include <QDebug>

#include <pappsomspp/utils.h>
#include <pappsomspp/trace/datapoint.h>
#include <pappsomspp/trace/trace.h>
#include <pappsomspp/trace/maptrace.h>
#include <pappsomspp/massspectrum/massspectrum.h>
#include <pappsomspp/processing/combiners/massdatacombinerinterface.h>
#include <pappsomspp/processing/combiners/massspectrumpluscombiner.h>

#include <pappsomspp/processing/combiners/mzintegrationparams.h>

namespace pappso
{

TEST_CASE("Constructing MassSpectrumPlusCombiner objects.",
          "[MassSpectrumPlusCombiner][constructors]")
{

  SECTION("Construct a MassSpectrumPlusCombiner", "[MassSpectrumPlusCombiner]")
  {
    MassSpectrumPlusCombiner mass_spectrum_combiner1;

    REQUIRE(mass_spectrum_combiner1.getDecimalPlaces() == -1);

    int decimal_places = 3;
    MassSpectrumPlusCombiner mass_spectrum_combiner2(decimal_places);

    REQUIRE(mass_spectrum_combiner2.getDecimalPlaces() == decimal_places);
  }

  SECTION(
    "Construct a MassSpectrumPlusCombiner from another "
    "MassSpectrumPlusCombiner",
    "[MassSpectrumPlusCombiner]")
  {
    int decimal_places = 3;
    MassSpectrumPlusCombiner mass_spectrum_combiner(decimal_places);
    MassSpectrumPlusCombiner mass_spectrum_combiner1(mass_spectrum_combiner);

    REQUIRE(mass_spectrum_combiner1.getDecimalPlaces() == decimal_places);

    // Test the handling of the bins.

    // Create the bins
    std::vector<double> bins{0.001, 0.002, 0.003, 0.004, 0.005};

    MassSpectrumPlusCombiner mass_spectrum_combiner2(decimal_places);
    mass_spectrum_combiner2.setBins(bins);

    MassSpectrumPlusCombiner mass_spectrum_combiner3(mass_spectrum_combiner2);
    REQUIRE(mass_spectrum_combiner3.getBins().size() == 5);
    REQUIRE(mass_spectrum_combiner3.getBins().front() == 0.001);
    REQUIRE(mass_spectrum_combiner3.getBins().back() == 0.005);
  }
}


#if !defined(__i386__)

// This test unbelievable does not pass on i386...

TEST_CASE("Performing combinations with MassSpectrumPlusCombiner",
          "[MassSpectrumPlusCombiner][combinations]")
{

  SECTION("Combine two complementary MassSpectrum objects into a MapTrace.",
          "[MassSpectrumPlusCombiner]")
  {
    // Create a MassSpectrum that looks like something real.

    std::vector<DataPoint> data_point_vector;

    // clang-format off
    data_point_vector.push_back(DataPoint("610.02000 12963.5715942383"));
    data_point_vector.push_back(DataPoint("610.07000 15639.6568298340"));
    data_point_vector.push_back(DataPoint("610.12000 55999.7628784180"));
    data_point_vector.push_back(DataPoint("610.17000 9335990.3578681946"));
    data_point_vector.push_back(DataPoint("610.2200000 635674266.1611375809"));
    data_point_vector.push_back(DataPoint("610.27000 54459.4762458801"));
    data_point_vector.push_back(DataPoint("610.32000 205580.6580276489"));
    data_point_vector.push_back(DataPoint("610.37000 84627.3196716309"));
    data_point_vector.push_back(DataPoint("610.42000 51061.7636718750"));
    data_point_vector.push_back(DataPoint("610.47000 0.0000000000"));
    data_point_vector.push_back(DataPoint("610.52000 29848.3424072266"));
    data_point_vector.push_back(DataPoint("610.57000 27266.8031616211"));
    data_point_vector.push_back(DataPoint("610.62000 36922.3937377930"));
    data_point_vector.push_back(DataPoint("610.67000 44082.4265441895"));
    data_point_vector.push_back(DataPoint("610.72000 32580.1677703857"));
    data_point_vector.push_back(DataPoint("610.77000 39453.8380126953"));
    data_point_vector.push_back(DataPoint("610.82000 63698.7124328613"));
    data_point_vector.push_back(DataPoint("610.87000 34406.7755126953"));
    data_point_vector.push_back(DataPoint("610.92000 33385.7486267090"));
    data_point_vector.push_back(DataPoint("610.97000 22105.9357604980"));
    // clang-format on

    REQUIRE(20 == data_point_vector.size());

    MassSpectrum mass_spectrum1(data_point_vector);

    REQUIRE(20 == mass_spectrum1.size());

    data_point_vector.clear();

    // Create another mass spectrum that is the "continuation" of the first one.

    // clang-format off
    data_point_vector.push_back(DataPoint("611.02000 12963.5715942383"));
    data_point_vector.push_back(DataPoint("611.07000 15639.6568298340"));
    data_point_vector.push_back(DataPoint("611.12000 55999.7628784180"));
    data_point_vector.push_back(DataPoint("611.17000 9335990.3578681946"));
    data_point_vector.push_back(DataPoint("611.2200000 635674266.1611375809"));
    data_point_vector.push_back(DataPoint("611.27000 54459.4762458801"));
    data_point_vector.push_back(DataPoint("611.32000 205580.6580276489"));
    data_point_vector.push_back(DataPoint("611.37000 84627.3196716309"));
    data_point_vector.push_back(DataPoint("611.42000 51061.7636718750"));
    data_point_vector.push_back(DataPoint("611.47000 0.00000000"));
    data_point_vector.push_back(DataPoint("611.52000 29848.3424072266"));
    data_point_vector.push_back(DataPoint("611.57000 27266.8031616211"));
    data_point_vector.push_back(DataPoint("611.62000 36922.3937377930"));
    data_point_vector.push_back(DataPoint("611.67000 44082.4265441895"));
    data_point_vector.push_back(DataPoint("611.72000 32580.1677703857"));
    data_point_vector.push_back(DataPoint("611.77000 39453.8380126953"));
    data_point_vector.push_back(DataPoint("611.82000 63698.7124328613"));
    data_point_vector.push_back(DataPoint("611.87000 34406.7755126953"));
    data_point_vector.push_back(DataPoint("611.92000 33385.7486267090"));
    data_point_vector.push_back(DataPoint("611.97000 22105.9357604980"));
    // clang-format on

    REQUIRE(20 == data_point_vector.size());

    MassSpectrum mass_spectrum2(data_point_vector);

    REQUIRE(20 == mass_spectrum2.size());


    // Prepare the bins on the basis of the spectrum.

    int decimal_places = 5;
    pappso::PrecisionPtr precision_p =
      pappso::PrecisionFactory::getDaltonInstance(0.05);
    bool binSizeDivisor = 6;

    MzIntegrationParams mz_integration_params(610.02,
                                              611.97,
                                              BinningType::ARBITRARY,
                                              decimal_places,
                                              precision_p,
                                              binSizeDivisor,
                                              true);

    // Check that it does not compute bins, since BinningType::NONE.

    std::vector<double> bins = mz_integration_params.createBins();

    // Creation of the same number of data points plus a last one bin.
    REQUIRE(bins.size() == mass_spectrum1.size() + mass_spectrum2.size() + 1);
    REQUIRE(bins[0] == 610.02000);

    // The last bin matches the last DataPoint of the second spectrum + one bin
    // size.
    REQUIRE(bins.back() == 612.02000);

    // Now compute the combination without binning.

    MassSpectrumPlusCombiner mass_spectrum_combiner(decimal_places);
    mass_spectrum_combiner.setBins(bins);

    MapTrace map_trace;

    mass_spectrum_combiner.combine(map_trace, mass_spectrum1);

    // std::cout << std::setprecision(10)
    //<< "First map item x: " << map_trace.begin()->first
    //<< " y: " << map_trace.begin()->second << std::endl;

    REQUIRE(map_trace.size() == 20);
    REQUIRE(map_trace[610.02000] == 12963.5715942383);
    REQUIRE(map_trace[610.27000] == 54459.4762458801);
    REQUIRE(map_trace[610.97000] == 22105.9357604980);

    // Now combine the second mass spectrum.

    mass_spectrum_combiner.combine(map_trace, mass_spectrum2);

    REQUIRE(map_trace.size() == 40);
    REQUIRE(map_trace[610.02000] == 12963.5715942383);
    REQUIRE(map_trace[610.27000] == 54459.4762458801);
    REQUIRE(map_trace[610.97000] == 22105.9357604980);
  }

  SECTION("Combine two intermixed MassSpectrum objects into a MapTrace.",
          "[MassSpectrumPlusCombiner]")
  {
    // Create a MassSpectrum that looks like something real.

    std::vector<DataPoint> data_point_vector;

    // clang-format off
    data_point_vector.push_back(DataPoint("610.02000 12963.5715942383"));
    data_point_vector.push_back(DataPoint("610.07000 15639.6568298340"));
    data_point_vector.push_back(DataPoint("610.12000 55999.7628784180"));
    data_point_vector.push_back(DataPoint("610.17000 9335990.3578681946"));
    data_point_vector.push_back(DataPoint("610.2200000 635674266.1611375809"));
    data_point_vector.push_back(DataPoint("610.27000 54459.4762458801"));
    data_point_vector.push_back(DataPoint("610.32000 205580.6580276489"));
    data_point_vector.push_back(DataPoint("610.37000 84627.3196716309"));
    data_point_vector.push_back(DataPoint("610.42000 51061.7636718750"));
    data_point_vector.push_back(DataPoint("610.47000 0.0000000000"));
    data_point_vector.push_back(DataPoint("610.52000 29848.3424072266"));
    data_point_vector.push_back(DataPoint("610.57000 27266.8031616211"));
    data_point_vector.push_back(DataPoint("610.62000 36922.3937377930"));
    data_point_vector.push_back(DataPoint("610.67000 44082.4265441895"));
    data_point_vector.push_back(DataPoint("610.72000 32580.1677703857"));
    data_point_vector.push_back(DataPoint("610.77000 39453.8380126953"));
    data_point_vector.push_back(DataPoint("610.82000 63698.7124328613"));
    data_point_vector.push_back(DataPoint("610.87000 34406.7755126953"));
    data_point_vector.push_back(DataPoint("610.92000 33385.7486267090"));
    data_point_vector.push_back(DataPoint("610.97000 22105.9357604980"));
    // clang-format on

    REQUIRE(20 == data_point_vector.size());

    MassSpectrum mass_spectrum1(data_point_vector);

    REQUIRE(20 == mass_spectrum1.size());

    data_point_vector.clear();

    // Create another mass spectrum that intermixes with the first one by adding
    // 0.005 to the previous m/z values.

    // clang-format off
    data_point_vector.push_back(DataPoint("610.02500 12963.5715942383"));
    data_point_vector.push_back(DataPoint("610.07500 15639.6568298340"));
    data_point_vector.push_back(DataPoint("610.12500 55999.7628784180"));
    data_point_vector.push_back(DataPoint("610.17500 9335990.3578681946"));
    data_point_vector.push_back(DataPoint("610.2200000 635674266.1611375809"));
    data_point_vector.push_back(DataPoint("610.27500 54459.4762458801"));
    data_point_vector.push_back(DataPoint("610.32500 205580.6580276489"));
    data_point_vector.push_back(DataPoint("610.37500 84627.3196716309"));
    data_point_vector.push_back(DataPoint("610.42500 51061.7636718750"));
    data_point_vector.push_back(DataPoint("610.47500 0.0000000000"));
    data_point_vector.push_back(DataPoint("610.52500 29848.3424072266"));
    data_point_vector.push_back(DataPoint("610.57500 27266.8031616211"));
    data_point_vector.push_back(DataPoint("610.62500 36922.3937377930"));
    data_point_vector.push_back(DataPoint("610.67500 44082.4265441895"));
    data_point_vector.push_back(DataPoint("610.72500 32580.1677703857"));
    data_point_vector.push_back(DataPoint("610.77500 39453.8380126953"));
    data_point_vector.push_back(DataPoint("610.82500 63698.7124328613"));
    data_point_vector.push_back(DataPoint("610.87500 34406.7755126953"));
    data_point_vector.push_back(DataPoint("610.92500 33385.7486267090"));
    data_point_vector.push_back(DataPoint("610.97500 22105.9357604980"));
    // clang-format on

    REQUIRE(20 == data_point_vector.size());

    MassSpectrum mass_spectrum2(data_point_vector);

    REQUIRE(20 == mass_spectrum2.size());


    // Prepare the bins on the basis of the spectrum.

    int decimal_places = 5;
    pappso::PrecisionPtr precision_p =
      pappso::PrecisionFactory::getDaltonInstance(0.05);
    bool binSizeDivisor = 6;

    MzIntegrationParams mz_integration_params(610.02,
                                              610.975,
                                              BinningType::ARBITRARY,
                                              decimal_places,
                                              precision_p,
                                              binSizeDivisor,
                                              true);

    // Check that it does not compute bins, since BinningType::NONE.

    std::vector<double> bins = mz_integration_params.createBins();
    REQUIRE(bins.size() == 21);
    REQUIRE(bins[0] == 610.02000);

    REQUIRE(bins.back() == 610.97000 + 0.05);

    MassSpectrumPlusCombiner mass_spectrum_combiner(decimal_places);
    mass_spectrum_combiner.setBins(bins);

    MapTrace map_trace;

    mass_spectrum_combiner.combine(map_trace, mass_spectrum1);

    REQUIRE(map_trace.size() == 20);
  }
}

#endif

} // namespace pappso
