
/*******************************************************************************
 * 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.hpp>
#include <iostream>
#include <odsstream/odsdocreader.h>
#include <odsstream/odsdocwriter.h>
#include <odsstream/odsexception.h>

#include <pappsomspp/processing/filters/filtermorpho.h>
#include <pappsomspp/processing/detection/tracedetectionzivy.h>
#include <pappsomspp/processing/detection/tracedetectionmoulon.h>
#include <pappsomspp/processing/detection/tracepeaklist.h>
#include <pappsomspp/pappsoexception.h>
#include <pappsomspp/exception/exceptionnotpossible.h>
#include <pappsomspp/xic/xic.h>
#include <QDebug>
#include <QtCore>
#include <QFile>
#include "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(CalcWriterInterface &output) : _output(output)
  {
    _output.writeCell("begin");
    _output.writeCell("end");
    _output.writeCell("maxrt");
    _output.writeCell("maxint");
    _output.writeCell("area");
    _output.writeLine();
  };
  void
  setTracePeak(TracePeak &xic_peak) override
  {
    _output.writeCell(xic_peak.getLeftBoundary().x);
    _output.writeCell(xic_peak.getRightBoundary().x);
    _output.writeCell(xic_peak.getMaxXicElement().x);
    _output.writeCell(xic_peak.getMaxXicElement().y);
    _output.writeCell(xic_peak.getArea());
    _output.writeLine();
  };

  private:
  CalcWriterInterface &_output;
};


class XicOdsWriter
{
  public:
  XicOdsWriter(CalcWriterInterface &output) : _output(output)
  {
    _output.writeCell("rt");
    _output.writeCell("intensity");
    _output.writeLine();
  };

  void
  write(const Trace &xic)
  {
    auto it = xic.begin();
    while(it != xic.end())
      {

        _output.writeCell(it->x);
        _output.writeCell(it->y);
        _output.writeLine();
        it++;
      }
  };

  private:
  CalcWriterInterface &_output;
};

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";
    cout << std::endl << "..:: Test XIC ::.." << std::endl;
    // BSA
    cout << std::endl << "..:: read XIC xic.ods ::.." << std::endl;
    Xic xic_test;
    try
      {
        readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/xic.ods"),
                   xic_test);
      }
    catch(OdsException &error)
      {
        INFO("error reading XIC in ODS file: " << error.qwhat().toStdString());
        throw error;
      }

    cout << std::endl << "..:: Test smooth filter ::.." << std::endl;
    FilterMorphoMean smooth(3);
    Xic xic_smooth(xic_test);
    smooth.filter(xic_smooth);
    writeOdsXic(
      QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/xic_smooth.ods"),
      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()"));

    }
    */
    cout << std::endl << "..:: spike filter ::.." << std::endl;
    qDebug() << "spike filter";
    FilterMorphoAntiSpike spike(3);
    Xic xic_spike(xic_test);
    spike.filter(xic_spike);
    writeOdsXic(
      QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/xic_spike.ods"),
      xic_spike);

    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);

    INFO("readOdsXic OK");
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    TraceDetectionZivy _zivy(2, 3, 3, 5000, 6000);
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    TraceDetectionMaxSink max_peak_tic;
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    try
      {
        _zivy.detect(xicprm_test, max_peak_tic);
        qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
      }
    catch(PappsoException &error)
      {
        INFO("error TraceDetectionZivy : " << error.qwhat().toStdString());
        throw error;
      }
    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;

    QFile fileods(QString(CMAKE_SOURCE_DIR)
                    .append("/tests/data/xic/xicprm_test_detect.ods"));
    OdsDocWriter writer(&fileods);
    TracePeakOdsWriterSink ods_sink(writer);

    _zivy.detect(xicprm_test, ods_sink);
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    writer.close();
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    fileods.close();
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;

    Xic realxic_test;
    readOdsXic(QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/real_xic.ods"),
               realxic_test);
    INFO("readOdsXic OK");
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    QFile realfileods(
      QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/real_xic_detect.ods"));
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    OdsDocWriter realwriter(&realfileods);
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    TracePeakOdsWriterSink real_ods_sink(realwriter);
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    //_zivy.setSink(&max_peak_tic);
    TraceDetectionZivy _zivyb(2, 4, 3, 30000, 50000);
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    try
      {
        _zivyb.detect(realxic_test, real_ods_sink);
      }
    catch(PappsoException &error)
      {
        INFO("error TraceDetectionZivy : " << error.qwhat().toStdString());
        throw error;
      }
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    realwriter.close();
    realfileods.close();
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;

    QFile realfilemoulondetectods(
      QString(CMAKE_SOURCE_DIR)
        .append("/tests/data/xic/real_xic_detect_moulon.ods"));
    OdsDocWriter realdetectmoulonwriter(&realfilemoulondetectods);
    TracePeakOdsWriterSink real_detect_moulonods_sink(realdetectmoulonwriter);

    TraceDetectionMoulon moulon(4, 60000, 40000);

    moulon.detect(realxic_test, real_detect_moulonods_sink);

    realdetectmoulonwriter.close();
    realfilemoulondetectods.close();


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

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

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

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

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

    REQUIRE(onexicpeak.getMsPointDistance(3758.26, 3759.61) == 1);
    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);
    writeOdsXic(
      QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/onexicpeak_minmax.ods"),
      xic_minmax);

    FilterMorphoMaxMin maxmin(3);
    Xic xic_maxmin(onexicpeak); //"close" courbe du haut
    maxmin.filter(xic_maxmin);
    writeOdsXic(
      QString(CMAKE_SOURCE_DIR).append("/tests/data/xic/onexicpeak_maxmin.ods"),
      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);
    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);
    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);
    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);
    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);
    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);
    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);
    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);

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

    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() == 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() == Approx(3698171.81499959));
    REQUIRE(nb_match == 1);
    REQUIRE(
      findBestTracePeakGivenRtList(
        peak_list.begin(), peak_list.end(), {4754, 4760, 4761, 5000}, nb_match)
        ->getArea() == 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);
  }
}
