/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Qt includes
#include <QObject>
#include <QDebug>
#include <QWidget>
#include <QMessageBox>
#include <QSettings>
#include <QLineEdit>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/processing/combiners/integrationscopebase.h>
#include <pappsomspp/core/processing/combiners/integrationscoperect.h>


/////////////////////// libXpertMassCore includes


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/WindowGeometryManager.hpp>


/////////////////////// Local includes
#include "../nongui/globals.hpp"
#include "Application.hpp"
#include "ProgramWindow.hpp"
#include "XicExtractionWnd.hpp"
#include "ui_XicExtractionWnd.h"

namespace MsXpS
{
namespace MineXpert
{

XicExtractionWnd::XicExtractionWnd(ProgramWindow *program_window_p,
                                   const QString &title,
                                   const QString &settings_title)
  : QMainWindow(dynamic_cast<QWidget *>(program_window_p)),
    mp_programWindow(program_window_p),
    m_title(title),
    m_settingsTitle(settings_title),
    m_ui(new ::Ui::XicExtractionWnd)
{
  qDebug();

  if(program_window_p == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  m_ui->setupUi(this);

  initialize();
}

XicExtractionWnd::~XicExtractionWnd()
{
  writeSettings();
}

void
XicExtractionWnd::closeEvent(QCloseEvent *event)
{
  writeSettings();
  event->accept();
}

void
XicExtractionWnd::initialize()
{
  qDebug();

  setWindowIcon(qApp->windowIcon());

  // The precision widget required to specify the precursor m/z values
  // search tolerance.
  mp_precisionWidget = new pappso::PrecisionWidget(this);
  m_ui->precisionWidgetHorizontalLayout->addWidget(mp_precisionWidget);
  mp_precisionWidget->setToolTip(
    "Set the tolerance with which the m/z value match is performed.");

  connect(mp_precisionWidget,
          &pappso::PrecisionWidget::precisionChanged,
          [this](pappso::PrecisionPtr precision_p) {
            mp_precision = precision_p;
          });

  connect(m_ui->runPushButton, &QPushButton::clicked, [this]() {
    runPushButtonClicked();
  });

  connect(m_ui->abortPushButton,
          &QPushButton::clicked,
          this,
          &XicExtractionWnd::abortPushButtonClicked);

  connect(m_ui->closePushButton,
          &QPushButton::clicked,
          this,
          &XicExtractionWnd::closePushButtonClicked);

  readSettings();
}

void
XicExtractionWnd::writeSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

//  qDebug() << "Saving window geometry.";

  libXpertMassGui::WindowGeometryManager::save(
    dynamic_cast<const QWidget *>(this), "XicExtractionWnd", settings);

  settings.beginGroup("XicExtractionWnd");

  settings.setValue("isVisible", isVisible());

  settings.endGroup();
}

void
XicExtractionWnd::readSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  qDebug() << "Restoring window geometry.";

  libXpertMassGui::WindowGeometryManager::restore(this, "XicExtractionWnd", settings);

  settings.beginGroup("XicExtractionWnd");

  setVisible(settings.value("isVisible").toBool());

  settings.endGroup();
}

void
XicExtractionWnd::runPushButtonClicked()
{
  QString all_mz_values = m_ui->mzValuesPlainTextEdit->toPlainText();
  // qDebug() << "All the m/z values:" << all_mz_values;
  QStringList all_mz_values_string_list =
    all_mz_values.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);

  if(!all_mz_values_string_list.size())
    return;

  using MzRangePair = std::pair<double, double>;

  std::vector<MzRangePair> mz_ranges;

  for(int iter = 0; iter < all_mz_values_string_list.size(); ++iter)
    {
      bool ok = false;

      double mz_value = all_mz_values_string_list.at(iter).toDouble(&ok);

      if(!ok)
        {
          statusBar()->showMessage(
            "Please enter valid m/z values and try again", 2000);

          return;
        }

      double delta = mp_precision->delta(mz_value);
      // qDebug() << "For mz_value:" << mz_value << "the delta is:" << delta;

      // Old version for which the delta was considered full at left
      // and full at right of the m/z value. This is not very
      // conventional. The new implementation give a window around
      // m/z of delta/2 on the left and delta/2 on the right.
      // mz_ranges.push_back(
      //   MzRangePair(mz_value - delta, mz_value + delta));

      mz_ranges.push_back(
        MzRangePair(mz_value - (delta / 2), mz_value + (delta / 2)));
    }

  // for(auto mz_range : mz_ranges)
  //   qDebug() << "New range:" << mz_range.first << "--" << mz_range.second
  //            << "with range span:" << mz_range.second - mz_range.first;

  // At this point, get the list of all the mass data files that have their
  // item selected in the OpenSpectraDlg window.

  std::vector<MsRunDataSetCstSPtr> vector_of_ms_run_data_set_csp =
    mp_programWindow->allSelectedOrUniqueMsRunDataSet();

  if(!vector_of_ms_run_data_set_csp.size())
    {
      QMessageBox::information(this,
                               "XIC chromatogram extraction",
                               "Please select at least one MS run data set as "
                               "described in the instructions text above.");

      return;
    }

  // Craft a rectangular integration scope that will be used to specify the RT
  // and MZ ranges.

  pappso::IntegrationScopeBaseSPtr integration_scope_base_sp = nullptr;

  for(auto &&ms_run_data_set_csp : vector_of_ms_run_data_set_csp)
    {
      for(auto mz_range : mz_ranges)
        {

          // Create a temporary instance of the ProcessingFlow (with no
          // parentship) so that we can modify it with new steps and then pass
          // it as reference to downstream code that will allocate a fully
          // parented instance. We'll delete that temporary instance.

          ProcessingFlow *temp_processing_flow_p =
            new ProcessingFlow(ms_run_data_set_csp, nullptr);

          ProcessingStepSPtr processing_step_sp =
            std::make_shared<ProcessingStep>();

          processing_step_sp->setSrcProcessingType(pappso::Enums::Axis::x,
                                                   "RT");
          processing_step_sp->setSrcProcessingType(pappso::Enums::Axis::y,
                                                   "DATA_XIC_MZ");

          processing_step_sp->setDataKind(pappso::Enums::Axis::x,
                                          pappso::Enums::DataKind::rt);
          processing_step_sp->setDataKind(pappso::Enums::Axis::y,
                                          pappso::Enums::DataKind::mz);

          double rt_start = ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
                              ->getRootNodes()
                              .front()
                              ->getQualifiedMassSpectrum()
                              ->getRtInMinutes();
          double rt_end = ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
                            ->getRootNodes()
                            .back()
                            ->getQualifiedMassSpectrum()
                            ->getRtInMinutes();

          // Craft the integration scope as a 2D rectangle with x:RT and y:MZ.

          pappso::IntegrationScopeRectSPtr integration_scope_rect_sp =
            std::make_shared<pappso::IntegrationScopeRect>(
              QPointF(rt_start, mz_range.first),
              (rt_end - rt_start),
              (mz_range.second - mz_range.first));

          // qDebug() << "Created scope:" <<
          // integration_scope_rect_sp->toString();

          processing_step_sp->setIntegrationScope(
            std::static_pointer_cast<pappso::IntegrationScopeBase>(
              integration_scope_rect_sp));

          processing_step_sp->setDestProcessingType("RT");

          // Should perform automatically the conversion from non-const to
          // const.
          temp_processing_flow_p->getStepsRef().push_back(processing_step_sp);

          mp_programWindow->integrateToRt(
            nullptr, nullptr, *temp_processing_flow_p);

          delete temp_processing_flow_p;
        }
      // End of
      // for(auto mz_range : mz_ranges)
    }
  // End of
  // for(auto &&ms_run_data_set_csp : vector_of_ms_run_data_set_csp)

  return;
}

void
XicExtractionWnd::closePushButtonClicked()
{
  writeSettings();
  QMainWindow::close();
}

void
XicExtractionWnd::abortPushButtonClicked()
{
  // qDebug() << "emit cancelOperationSignal();";
  emit cancelOperationSignal();
}

void
XicExtractionWnd::showWindow()
{
  activateWindow();
  raise();
  show();
}


} // namespace MineXpert

} // namespace MsXpS
