
/*******************************************************************************
 * Copyright (c) 2015 Olivier Langella <Olivier.Langella@moulon.inra.fr>.
 *
 * This file is part of the PAPPSOms++ library.
 *
 *     PAPPSOms++ 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.
 *
 *     PAPPSOms++ 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 PAPPSOms++.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Olivier Langella <Olivier.Langella@moulon.inra.fr> - initial API and
 *implementation
 ******************************************************************************/


// make test ARGS="-V -I 1,1"

// ./tests/catch2-only-tests [XIC] -s
// ./tests/catch2-only-tests [tracepeaklist] -s


// #define CATCH_CONFIG_MAIN

#include <catch2/catch_message.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>

#include <iostream>
#include <odsstream/odsdocreader.h>
#include <odsstream/odsdocwriter.h>
#include <odsstream/odsexception.h>

#include <pappsomspp/core/processing/filters/filtermorpho.h>
#include <pappsomspp/core/processing/detection/tracedetectionzivy.h>
#include <pappsomspp/core/processing/detection/tracedetectionmoulon.h>
#include <pappsomspp/core/processing/detection/tracepeaklist.h>
#include <pappsomspp/core/pappsoexception.h>
#include <pappsomspp/core/exception/exceptionnotpossible.h>
#include <pappsomspp/core/xic/xic.h>
#include <QDebug>
#include <QtCore>
#include <QFile>
#include <qbuffer.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qlogging.h>
#include "pappsomspp/core/processing/cbor/cborstreamreader.h"
#include "pappsomspp/core/processing/cbor/cborstreamwriter.h"
#include "tests/tests-config.h"
#include "common.h"

// make test ARGS="-V -I 3,3"

using namespace std;
using namespace pappso;


class CustomHandler : public OdsDocHandlerInterface
{
  public:
  CustomHandler(Trace &xic) : _xic(xic)
  {
  }
  /**
   * callback that indicates the begining of a data sheet. Override it in
   * order to retrieve information about the current data sheet.
   *
   */
  virtual void startSheet(const QString &sheet_name [[maybe_unused]]) override {};

  /**
   * callback that indicates the end of the current data sheet. Override it if
   * needed
   */
  virtual void endSheet() override {
    // qDebug() << "endSheet";
  };

  /**
   * callback that indicates a new line start. Override it if needed.
   */

  virtual void
  startLine() override
  {
    _xic_element.x = -1;
  };

  /**
   * callback that indicates a line ending. Override it if needed.
   */

  virtual void
  endLine() override
  {
    if(!_is_title)
      {
      }
    _is_title = false;
  };

  /**
   * callback that report the content of the current cell in a dedicated Cell
   * object. Override it if you need to retrieve cell content.
   */
  virtual void
  setCell(const OdsCell &cell) override
  {
    qDebug() << "CustomHandler::setCell " << cell.toString();
    if(cell.isDouble())
      {
        if(_xic_element.x < 0)
          {
            _xic_element.x = cell.getDoubleValue();
          }
        else
          {
            _xic_element.y = cell.getDoubleValue();
            _xic.push_back(_xic_element);
            _xic_element.x = -1;
          }
      }
  };

  /**
   * callback that report the end of the ODS document. Override it if you need
   * to know that reading is finished.
   */
  virtual void endDocument() override {};

  private:
  bool _is_title = true;
  Trace &_xic;
  DataPoint _xic_element;
};


class TraceDetectionMaxSink : public TraceDetectionSinkInterface
{
  public:
  void
  setTracePeak(TracePeak &xic_peak) override
  {
    _count++;
    qDebug() << "XicDetectionMaxSink::setXicPeak begin=" << xic_peak.getLeftBoundary().x
             << " area=" << xic_peak.getArea() << " end=" << xic_peak.getRightBoundary().x;
    if(xic_peak.getArea() > _peak_max.getArea())
      {
        _peak_max = xic_peak;
      }
  };
  const TracePeak &
  getTracePeak() const
  {
    if(_count == 0)
      throw PappsoException(QObject::tr("no peak detected"));
    return _peak_max;
  };

  private:
  unsigned int _count = 0;
  TracePeak _peak_max;
};


class TracePeakOdsWriterSink : public TraceDetectionSinkInterface
{
  public:
  TracePeakOdsWriterSink()
  {
    m_beginList.clear();
    m_endList.clear();
    m_maxrtList.clear();
    m_maxintList.clear();
    m_areaList.clear();
  };
  void
  setTracePeak(TracePeak &xic_peak) override
  {
    m_beginList.push_back(xic_peak.getLeftBoundary().x);
    m_endList.push_back(xic_peak.getRightBoundary().x);
    m_maxrtList.push_back(xic_peak.getMaxXicElement().x);
    m_maxintList.push_back(xic_peak.getMaxXicElement().y);
    m_areaList.push_back(xic_peak.getArea());
  };

  public:
  std::vector<double> m_beginList;
  std::vector<double> m_endList;
  std::vector<double> m_maxrtList;
  std::vector<double> m_maxintList;
  std::vector<double> m_areaList;
};


void
readOdsXic(const QString &filepath, Trace &xic)
{
  qDebug() << "readOdsXic begin " << filepath;
  QFile realfile(filepath);
  CustomHandler handler_realxic(xic);
  OdsDocReader realreader_prm(handler_realxic);
  realreader_prm.parse(&realfile);
  realfile.close();
  // qDebug() << "readOdsXic copy " << filepath;
  // xic = handler_realxic.getXic();

  qDebug() << "readOdsXic end " << filepath;
}

void
writeOdsXic(const QString &filepath, Trace &xic)
{
  qDebug() << "writeOdsXic begin " << filepath;
  QFile fileods(filepath);
  OdsDocWriter writer(&fileods);
  XicOdsWriter xic_writer(writer);
  xic_writer.write(xic);
  writer.close();
  fileods.close();
  // qDebug() << "readOdsXic copy " << filepath;
  // xic = handler_realxic.getXic();

  qDebug() << "writeOdsXic end " << filepath;
}


TEST_CASE("XIC test suite.", "[XIC]")
{
  // Set the debugging message formatting pattern.
  qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}"));

  // QCoreApplication a(argc, argv);
  SECTION("..:: Test XIC ::..", "[XIC]")
  {
    qDebug() << "init test XIC";
    std::cout << std::endl << "..:: Test XIC ::.." << std::endl;
    // BSA
    std::cout << std::endl << "..:: read XIC xic.ods ::.." << std::endl;
    Xic xic_test;
    REQUIRE_NOTHROW(
      readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/xic.ods"), xic_test));


    WARN("test xic CBOR");

    QBuffer buffer;
    buffer.open(QIODevice::ReadWrite);
    pappso::cbor::CborStreamWriter writer(&buffer);

    writer.writeTrace(xic_test, "rt", "int");
    buffer.seek(0);
    pappso::cbor::CborStreamReader reader(&buffer);
    QCborMap cbor_map_xic;
    reader.readCborMap(cbor_map_xic);
    QJsonObject json_xic = cbor_map_xic.toJsonObject();
    QJsonDocument doc;
    doc.setObject(json_xic);
    qWarning() << doc.toJson();


    REQUIRE(xic_test.size() == 37);
    std::cout << std::endl << "..:: Test smooth filter ::.." << std::endl;
    FilterMorphoMean smooth(3);
    Xic xic_smooth(xic_test);
    smooth.filter(xic_smooth);
    writeJsonTrace("xic.json", xic_test);
    writeJsonTrace("xic_smooth.json", xic_smooth);

    Xic check_xic_smooth;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/xic_smooth.ods"),
               check_xic_smooth);

    REQUIRE(xic_smooth == check_xic_smooth);

    /*
    file.setFileName(QString(CMAKE_SOURCE_DIR).append("/test/data/xic/xic_smooth_3.ods"));
    CustomHandler handler_xic_smooth_3;
    OdsDocReader reader_xic_smooth_3(handler_xic_smooth_3);
    reader_xic_smooth_3.parse(&file);
    file.close();

    if (handler_xic_smooth_3.getXic() != handler_xic.getXic()) {
             throw PappsoException
        (QObject::tr("handler_xic_smooth_3.getXic() != handler_xic.getXic()"));

    }
    */
    std::cout << std::endl << "..:: spike filter ::.." << std::endl;
    qDebug() << "spike filter";
    FilterMorphoAntiSpike spike(3);
    Xic xic_spike(xic_test);
    spike.filter(xic_spike);

    writeJsonTrace("xic_spike.json", xic_spike);
    Xic check_xic_spike;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/xic_spike.ods"), check_xic_spike);
    REQUIRE(xic_spike == check_xic_spike);

    std::cout << std::endl << "..:: peak detection ::.." << std::endl;
    INFO("peak detection");
    Xic xicprm_test;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/prm_xic.ods"), xicprm_test);
    writeJsonTrace("prm_xic.json", xicprm_test);


    INFO("readOdsXic OK");
    qDebug();
    TraceDetectionZivy _zivy(2, 3, 3, 5000, 6000);
    qDebug();
    TraceDetectionMaxSink max_peak_tic;
    qDebug();

    REQUIRE_NOTHROW(_zivy.detect(xicprm_test, max_peak_tic, true));
    INFO("TraceDetectionZivy OK");
    qDebug() << "max peak begin=" << max_peak_tic.getTracePeak().getLeftBoundary().x
             << " area=" << max_peak_tic.getTracePeak().getArea()
             << " end=" << max_peak_tic.getTracePeak().getRightBoundary().x;

    TracePeakOdsWriterSink ods_sink;

    _zivy.detect(xicprm_test, ods_sink, true);
    qDebug();
    REQUIRE_THAT(ods_sink.m_beginList,
                 Catch::Matchers::Approx(std::vector<double>({1221.79, 1268.65})).margin(0.00001));
    REQUIRE_THAT(
      ods_sink.m_areaList,
      Catch::Matchers::Approx(std::vector<double>({317787751.7834287882, 3251982.1006667931}))
        .margin(0.01));


    Xic realxic_test;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/real_xic.ods"), realxic_test);
    writeJsonTrace("real_xic.json", realxic_test);
    INFO("readOdsXic OK");
    qDebug();
    TracePeakOdsWriterSink real_ods_sink;
    qDebug();
    //_zivy.setSink(&max_peak_tic);
    TraceDetectionZivy _zivyb(2, 4, 3, 30000, 50000);
    qDebug();
    REQUIRE_NOTHROW(_zivyb.detect(realxic_test, real_ods_sink, true));
    qDebug();
    REQUIRE_THAT(
      real_ods_sink.m_beginList,
      Catch::Matchers::Approx(
        std::vector<double>({2374.88, 2385.45, 2490.35, 2653.21, 2730.29, 2750.84, 2761.72, 3262.35,
                             3277.68, 3755.51, 3808.5,  4104.5,  4151.45, 4447.09, 4470.68, 4520.31,
                             4624.51, 4755.6,  4992.96, 5123.82, 5132.58, 5275.57, 5283.03, 5349.8,
                             5357.63, 5369.07, 5392.06, 5441.72, 5498.31, 5520.0,  5526.64, 5535.79,
                             5549.66, 5557.02, 5593.74, 5704.19, 5839.75, 6205.72, 6224.08, 6273.97,
                             6292.98, 6401.19, 6510.97, 6587.42, 6838.34, 6875.56, 7039.36}))
        .margin(0.00001));
    REQUIRE_THAT(real_ods_sink.m_areaList,
                 Catch::Matchers::Approx(std::vector<double>({453563.5099999703,
                                                              4719046.3000000007,
                                                              1090639.7850001103,
                                                              1348880.5900000208,
                                                              7630963.3250000998,
                                                              779452.6100000609,
                                                              2040112.6650001134,
                                                              5756570.0800003475,
                                                              8225201.0500001209,
                                                              105329906.8750005662,
                                                              0.0,
                                                              647415.2500000162,
                                                              421852.445000008,
                                                              10305033.5250001848,
                                                              3621950.2699998645,
                                                              544684.8950001382,
                                                              37944845.6299999952,
                                                              3698171.8149995869,
                                                              5664123.3300000867,
                                                              420474.2849998939,
                                                              1008899.3299999861,
                                                              805031.2899999407,
                                                              335121.1350001062,
                                                              338425.5199998079,
                                                              340164.8050002141,
                                                              12118740.7800003588,
                                                              1643557.1449998224,
                                                              488535.5099999903,
                                                              3979415.1600000304,
                                                              337714.1250001371,
                                                              1028555.1399998174,
                                                              905212.215000038,
                                                              256353.7100000874,
                                                              273830.9199999579,
                                                              10611321.4649997819,
                                                              18939923.7850016505,
                                                              571598.5350001995,
                                                              2796306.4700002563,
                                                              39837543.8999998271,
                                                              3572057.4000001787,
                                                              2218517.9099999457,
                                                              2539285.7649998385,
                                                              881227.1150000142,
                                                              726500.5700001301,
                                                              17818048.1899999045,
                                                              571815.7450000376,
                                                              2592136.4730000873}))
                   .margin(0.01));

    TracePeakOdsWriterSink real_detect_moulonods_sink;

    TraceDetectionMoulon moulon(4, 60000, 40000);

    moulon.detect(realxic_test, real_detect_moulonods_sink, true);
    qDebug();
    REQUIRE_THAT(
      real_detect_moulonods_sink.m_beginList,
      Catch::Matchers::Approx(
        std::vector<double>({2379.83, 2385.1,  2402.82, 2491.04, 2495.16, 2652.79, 2733.2,  3262.35,
                             3276.04, 3755.51, 3807.81, 3814.14, 4103.81, 4152.75, 4449.38, 4521.77,
                             4554.8,  4624.51, 4755.6,  4996.86, 5124.92, 5131.71, 5275.23, 5350.75,
                             5368.42, 5442.64, 5499.77, 5550.67, 5556.69, 5594.34, 5704.19, 5833.0,
                             6208.73, 6276.53, 6401.95, 6415.88, 6443.42, 6512.36, 6593.39, 6620.06,
                             6830.73, 6838.34, 6875.56, 6927.43, 7041.73}))
        .margin(0.00001));
    REQUIRE_THAT(
      real_detect_moulonods_sink.m_areaList,
      Catch::Matchers::Approx(
        std::vector<double>({350636.0699999726,    4719046.3000000007, 1199928.0000002729,
                             232712.6200000431,    857927.1650000673,  1229759.8900000337,
                             19530535.180000186,   5756570.0800003475, 8225201.0500001209,
                             105329906.8750005662, 290680.7350000119,  993908.2649999078,
                             764879.9550000108,    372620.9549999869,  17268838.680000037,
                             416502.5550000306,    307643.9599999893,  37944845.6299999952,
                             3698171.8149995869,   5368429.3300001146, 506868.5349999432,
                             965843.7700000828,    1323079.6650000364, 1448878.8449998836,
                             15169604.6500002556,  488535.5099999903,  0.0,
                             383583.2400000191,    347662.4800001451,  10611321.4649997819,
                             18939923.7850016505,  1223290.7100003334, 43605224.2549999952,
                             6272343.5700001316,   2624241.2699997784, 323902.4699999057,
                             437959.6900001141,    855431.2400000544,  420884.1700001009,
                             424308.9499999187,    323031.4700001751,  17818048.1899999045,
                             571815.7450000376,    115456.9200000693,  2546174.3550001378}))
        .margin(0.01));


    std::cout << std::endl << "..:: Test MinMax on onexicpeak ::.." << std::endl;

    Xic onexicpeak;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/onexicpeak.ods"), onexicpeak);
    writeJsonTrace("onexicpeak.json", onexicpeak);
    std::cout << std::endl << "..:: xic distance ::.." << std::endl;

    std::cout << std::endl
              << "distance 3757, 3758 : " << onexicpeak.getMsPointDistance(3757.0, 3758.0)
              << std::endl;

    REQUIRE(onexicpeak.getMsPointDistance(3757.0, 3758.0) == 2);
    std::cout << std::endl
              << "distance 3757, 3757.14 : " << onexicpeak.getMsPointDistance(3757.0, 3757.14)
              << std::endl;

    REQUIRE(onexicpeak.getMsPointDistance(3757.0, 3757.14) == 0);
    std::cout << std::endl
              << "distance 3758.26, 3759.61: " << onexicpeak.getMsPointDistance(3758.26, 3759.61)
              << std::endl;

    REQUIRE(onexicpeak.getMsPointDistance(3758.26, 3759.61) == 1);
    std::cout << std::endl
              << "distance 3758.26, -1: " << onexicpeak.getMsPointDistance(3758.26, -1)
              << std::endl;

    FilterMorphoMinMax minmax(4);
    Xic xic_minmax(onexicpeak); //"close" courbe du haut
    minmax.filter(xic_minmax);
    writeJsonTrace("onexicpeak_minmax.json", xic_minmax);

    Xic check_xic_minmax;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/onexicpeak_minmax.ods"),
               check_xic_minmax);
    REQUIRE_FALSE(onexicpeak == check_xic_minmax);
    REQUIRE(xic_minmax == check_xic_minmax);


    FilterMorphoMaxMin maxmin(3);
    Xic xic_maxmin(onexicpeak); //"close" courbe du haut
    maxmin.filter(xic_maxmin);
    writeJsonTrace("onexicpeak_maxmin.json", xic_maxmin);

    Xic check_xic_maxmin;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/onexicpeak_maxmin.ods"),
               check_xic_maxmin);
    REQUIRE(xic_maxmin == check_xic_maxmin);

    // writeOdsXic();
    // double free or corruption (out)

    // XicFilterSmoothing smooth;
    // smooth.setSmoothingHalfEdgeWindows(2);

    // writeOdsXic(QString(CMAKE_SOURCE_DIR).append("/test/data/xic/test_write.ods"),
    // onexicpeak);
  }
}

TEST_CASE("test operations on tracepeaklist", "[tracepeaklist]")
{

  // Set the debugging message formatting pattern.
  qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}"));
  SECTION("detection on empty XIC", "[tracepeaklist]")
  {

    INFO("peak detection");
    Xic empty_xic;
    TraceDetectionZivy _zivy(2, 3, 3, 5000, 6000);
    TracePeakList peak_list;
    _zivy.detect(empty_xic, peak_list, true);
    REQUIRE(peak_list.size() == 0);

    empty_xic.resize(2);
    fill(empty_xic.begin(), empty_xic.end(), DataPoint(1, 1));

    _zivy.detect(empty_xic, peak_list, true);
    REQUIRE(peak_list.size() == 0);

    empty_xic.resize(3);
    fill(empty_xic.begin(), empty_xic.end(), DataPoint(1, 1));

    _zivy.detect(empty_xic, peak_list, true);
    REQUIRE(peak_list.size() == 0);

    empty_xic.resize(4);
    fill(empty_xic.begin(), empty_xic.end(), DataPoint(1, 1));

    _zivy.detect(empty_xic, peak_list, true);
    REQUIRE(peak_list.size() == 0);


    empty_xic.resize(5);
    fill(empty_xic.begin(), empty_xic.end(), DataPoint(1, 1));
    std::size_t i = 0;
    for(auto &dp : empty_xic)
      {
        dp.x = i;
        i++;
      }

    _zivy.detect(empty_xic, peak_list, true);
    REQUIRE(peak_list.size() == 0);


    empty_xic.resize(6);
    fill(empty_xic.begin(), empty_xic.end(), DataPoint(1, 1));
    i = 0;
    for(auto &dp : empty_xic)
      {
        dp.x = i;
        i++;
      }

    _zivy.detect(empty_xic, peak_list, true);
    REQUIRE(peak_list.size() == 0);
  }
  SECTION("test for masschroq issue#15", "[tracepeaklist]")
  {
    Xic xic;
    xic.push_back(DataPoint(1434.7806210000, 85.9931205504));
    xic.push_back(DataPoint(1586.4034760000, 162.9869610431));
    xic.push_back(DataPoint(1643.5517360000, 84.9932005440));
    xic.push_back(DataPoint(1645.8830080000, 98.9920806335));
    INFO("MassChroQ issue #15");

    TraceDetectionZivy zivy15(2, 4, 3, 5000, 3000);
    TracePeakList peak_list;
    qDebug();
    zivy15.detect(xic, peak_list, true);
    qDebug();
    REQUIRE(peak_list.size() == 0);
  }
  SECTION("test operations on tracepeaklist", "[tracepeaklist]")
  {

    INFO("peak detection");
    TraceDetectionZivy _zivy(2, 3, 3, 5000, 6000);
    TracePeakList peak_list;


    Xic xicprm_test;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/real_xic.ods"), xicprm_test);

    TracePeak peak(xicprm_test.begin() + 9079, xicprm_test.begin() + 9142, false);

    REQUIRE(peak.getArea() == Catch::Approx(96163224.06));

    TracePeak peak_nobase(xicprm_test.begin() + 9079, xicprm_test.begin() + 9142, true);

    REQUIRE(peak_nobase.getArea() == Catch::Approx(76281621.60));

    REQUIRE(peak_nobase.getLeftBoundary().y *
              (peak_nobase.getRightBoundary().x - peak_nobase.getLeftBoundary().x) ==
            Catch::Approx(peak.getArea() - peak_nobase.getArea()));

    INFO("readOdsXic OK");
    REQUIRE_NOTHROW(_zivy.detect(xicprm_test, peak_list, true));

    REQUIRE(peak_list.size() == 64);

    REQUIRE(findTracePeakGivenRt(peak_list.begin(), peak_list.end(), 2300) == peak_list.end());

    REQUIRE(findTracePeakGivenRt(peak_list.begin(), peak_list.end(), 2380)->getArea() ==
            Catch::Approx(453563.50999997));


    REQUIRE(findTracePeakGivenRt(peak_list.begin(), peak_list.end(), 4754) == peak_list.end());

    REQUIRE(findTracePeakGivenRt(peak_list.begin(), peak_list.end(), 5460) == peak_list.end());
    std::size_t nb_match;
    REQUIRE(
      findBestTracePeakGivenRtList(peak_list.begin(), peak_list.end(), {4754, 4760, 4761}, nb_match)
        ->getArea() == Catch::Approx(3698171.81499959));
    REQUIRE(nb_match == 1);
    REQUIRE(findBestTracePeakGivenRtList(
              peak_list.begin(), peak_list.end(), {4754, 4760, 4761, 5000}, nb_match)
              ->getArea() == Catch::Approx(5664123.33000009));
    REQUIRE(nb_match == 2);
    REQUIRE(findBestTracePeakGivenRtList(
              peak_list.begin(), peak_list.end(), {4754, 5460}, nb_match) == peak_list.end());
    REQUIRE(nb_match == 0);


    TracePeakList empty_peak_list;
    REQUIRE(findBestTracePeakGivenRtList(
              empty_peak_list.begin(), empty_peak_list.end(), {4754, 5460}, nb_match) ==
            empty_peak_list.end());
    REQUIRE(nb_match == 0);
  }
}
