
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>


#include <QDebug>

#include <limits>
#include <pappsomspp/processing/filters/savgolfilter.h>
#include <pappsomspp/processing/combiners/mzintegrationparams.h>
#include <pappsomspp/utils.h>


using namespace pappso;


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

  SECTION("Construct a MzIntegrationParams", "[MzIntegrationParams]")
  {
    MzIntegrationParams mz_integration_params1;
    PrecisionPtr precision_p = pappso::PrecisionFactory::getPpmInstance(20);

    REQUIRE(mz_integration_params1.getBinningType() == BinningType::NONE);

    REQUIRE(precision_p == mz_integration_params1.getPrecision());

    double smallestMz            = 1800.373856;
    double greatestMz            = 2600.956480;
    BinningType binningType      = BinningType::ARBITRARY;
    int decimalPlaces            = 3;
    bool binSizeDivisor          = 6;
    bool removeZeroValDataPoints = true;


    MzIntegrationParams mz_integration_params2(smallestMz,
                                               greatestMz,
                                               binningType,
                                               decimalPlaces,
                                               precision_p,
                                               binSizeDivisor,
                                               removeZeroValDataPoints);

    REQUIRE(mz_integration_params2.getSmallestMz() == smallestMz);
    REQUIRE(mz_integration_params2.getGreatestMz() == greatestMz);
    REQUIRE(mz_integration_params2.getBinningType() == binningType);
    REQUIRE(mz_integration_params2.getDecimalPlaces() == decimalPlaces);
    REQUIRE(mz_integration_params2.getPrecision() == precision_p);
    REQUIRE(mz_integration_params2.getBinSizeDivisor() == binSizeDivisor);
    REQUIRE(mz_integration_params2.isRemoveZeroValDataPoints() ==
            removeZeroValDataPoints);
  }


  SECTION(
    "Construct a MzIntegrationParams from another "
    "MzIntegrationParams",
    "[MzIntegrationParams]")
  {

    double smallestMz            = 1800.373856;
    double greatestMz            = 2600.956480;
    BinningType binningType      = BinningType::ARBITRARY;
    int decimalPlaces            = 3;
    PrecisionPtr precision_p     = pappso::PrecisionFactory::getPpmInstance(20);
    bool binSizeDivisor          = 6;
    bool removeZeroValDataPoints = true;

    MzIntegrationParams mz_integration_params1(smallestMz,
                                               greatestMz,
                                               binningType,
                                               decimalPlaces,
                                               precision_p,
                                               binSizeDivisor,
                                               removeZeroValDataPoints);

    MzIntegrationParams mz_integration_params2(mz_integration_params1);

    REQUIRE(mz_integration_params2.getSmallestMz() == smallestMz);
    REQUIRE(mz_integration_params2.getGreatestMz() == greatestMz);
    REQUIRE(mz_integration_params2.getBinningType() == binningType);
    REQUIRE(mz_integration_params2.getDecimalPlaces() == decimalPlaces);
    REQUIRE(mz_integration_params2.getPrecision() == precision_p);
    REQUIRE(mz_integration_params2.getBinSizeDivisor() == binSizeDivisor);
    REQUIRE(mz_integration_params2.isRemoveZeroValDataPoints() ==
            removeZeroValDataPoints);
  }
}


TEST_CASE("Create bins with Da-based bin size", "[MzIntegrationParams]")
{
  double smallestMz        = 1800.373856;
  double greatestMz        = 1800.373856;
  BinningType binningType  = BinningType::ARBITRARY;
  int decimalPlaces        = 3;
  PrecisionPtr precision_p = pappso::PrecisionFactory::getDaltonInstance(0.05);
  bool binSizeDivisor      = 6;
  bool removeZeroValDataPoints = true;

  MzIntegrationParams mz_integration_params(smallestMz,
                                            greatestMz,
                                            binningType,
                                            decimalPlaces,
                                            precision_p,
                                            binSizeDivisor,
                                            removeZeroValDataPoints);

  SECTION("Create bins in ARBITRARY mode", "[MzIntegrationParams]")
  {

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

    // Do not forget that we round to 3 decimal places:
    REQUIRE(bins.front() == 1800.374);

    // We create two bins: the first mz value (1800.374) and a new one that is
    // that mz value incremented by the delta (that is the size of the bin that
    // is created using an increment of 0.05).

    double manual_value = bins.front() + precision_p->delta(bins.front());

    // Below: if abs(1.0 - 1.2) is less or equal to 0.2 -> success
    // REQUIRE_THAT(1.0, Catch::Matchers::WithinAbs(1.2, 0.2)); // success
    // REQUIRE_THAT(1.2, Catch::Matchers::WithinAbs(1.1, 0.2)); // success
    // REQUIRE_THAT(1.0, Catch::Matchers::WithinAbs(1.3, 0.2)); // failure
    // REQUIRE_THAT(1.3, Catch::Matchers::WithinAbs(1.0, 0.2)); // failure

    REQUIRE(bins.back() == Catch::Approx(manual_value));
  }
}


TEST_CASE("Create bins with ppm-based bin sizes", "[MzIntegrationParams]")
{
  double smallestMz            = 1800.373856;
  double greatestMz            = 1800.373856;
  BinningType binningType      = BinningType::ARBITRARY;
  int decimalPlaces            = 3;
  PrecisionPtr precision_p     = pappso::PrecisionFactory::getPpmInstance(20);
  bool binSizeDivisor          = 6;
  bool removeZeroValDataPoints = true;

  MzIntegrationParams mz_integration_params(smallestMz,
                                            greatestMz,
                                            binningType,
                                            decimalPlaces,
                                            precision_p,
                                            binSizeDivisor,
                                            removeZeroValDataPoints);

  SECTION("Create bins in ARBITRARY mode", "[MzIntegrationParams]")
  {

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

    // Do not forget that we round to 3 decimal places:
    REQUIRE(bins.front() == 1800.374);

    // Check that the precision delta is computed correctly:
    double precision_delta = 1800.374 * 20 / 1000000;
    REQUIRE(pappso::Utils::almostEqual(
      precision_p->delta(1800.374), precision_delta, /*decimal_places*/ 15));

    double rounded_precision_delta =
      ceil((precision_delta * pow(10, decimalPlaces)) - 0.49) /
      pow(10, decimalPlaces);

    // We create two bins: the first mz value (1800.374) and a new one that is
    // that mz value incremented by the delta (that is the size of the bin that
    // is created using relative proportion of 20 ppm).

    REQUIRE(bins.back() == 1800.374 + rounded_precision_delta);
  }
}


TEST_CASE("Create bins with res-based bin sizes", "[MzIntegrationParams]")
{
  double smallestMz        = 1800.373856;
  double greatestMz        = 1800.373856;
  BinningType binningType  = BinningType::ARBITRARY;
  int decimalPlaces        = 3;
  PrecisionPtr precision_p = pappso::PrecisionFactory::getResInstance(20000);
  bool binSizeDivisor      = 6;
  bool removeZeroValDataPoints = true;

  MzIntegrationParams mz_integration_params(smallestMz,
                                            greatestMz,
                                            binningType,
                                            decimalPlaces,
                                            precision_p,
                                            binSizeDivisor,
                                            removeZeroValDataPoints);

  SECTION("Create bins in ARBITRARY mode", "[MzIntegrationParams]")
  {

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

    // Do not forget that we round to 3 decimal places:
    REQUIRE(bins.front() == 1800.374);

    // Check that the precision delta is computed correctly:
    double precision_delta = 1800.374 / 20000;
    REQUIRE(pappso::Utils::almostEqual(
      precision_p->delta(1800.374), precision_delta, /*decimal_places*/ 15));

    double rounded_precision_delta =
      ceil((precision_delta * pow(10, decimalPlaces)) - 0.49) /
      pow(10, decimalPlaces);

    // We create two bins: the first mz value (1800.374) and a new one that is
    // that mz value incremented by the delta (that is the size of the bin that
    // is created using relative proportion of 20 ppm).

    REQUIRE(bins.back() == 1800.374 + rounded_precision_delta);
  }
}
