/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QSettings>
#include <QDebug>
#include <QMainWindow>


/////////////////////// pappsomspp includes
#include <pappsomspp/gui/plotwidget/colormapplotconfig.h>


/////////////////////// Local includes
#include "ProgramWindow.hpp"
#include "DriftSpecMassSpecColorMapWnd.hpp"
#include "DriftSpecMassSpecColorMapPlotCompositeWidget.hpp"
#include <MsXpS/libXpertMassGui/ColorSelector.hpp>
#include "../nongui/MassDataIntegratorTask.hpp"

namespace MsXpS
{
namespace MineXpert
{


//! Construct an DriftSpecMassSpecColorMapWnd instance.
DriftSpecMassSpecColorMapWnd::DriftSpecMassSpecColorMapWnd(
  QWidget *parent,
  const QString &title,
  const QString &settingsTitle,
  const QString &description)
  : BaseColorMapPlotWnd(parent, title, settingsTitle, description)
{
}

//! Destruct \c this DriftSpecMassSpecColorMapWnd instance.
DriftSpecMassSpecColorMapWnd::~DriftSpecMassSpecColorMapWnd()
{
}

QCPColorMap *
DriftSpecMassSpecColorMapWnd::addColorMapPlot(
  std::shared_ptr<std::map<double, pappso::MapTrace>> &double_map_trace_map_sp,
  const pappso::ColorMapPlotConfig &color_map_plot_config,
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const ProcessingFlow &processing_flow,
  const QColor &color,
  QCPAbstractPlottable *parent_plottable_p)
{
  // We want to add a new color map plot.


  // Allocate a DT / m/z -specific composite plot widget.

  // Note that if the data are from the Bruker timsTOF instrument, the mobility
  // data are not drift times (in milliseconds) but 1/K0. So we need to check
  // that to set the mobility axis label accordingly.

  QString ion_mobility_axis_label = "drift time (ms)";

  if(ms_run_data_set_csp->getMsRunId()->getMsDataFormat() ==
     pappso::Enums::MsDataFormat::brukerTims)
    {
      ion_mobility_axis_label = "inverse mobility (1/K0)";
    }

  DriftSpecMassSpecColorMapPlotCompositeWidget *composite_widget_p =
    new DriftSpecMassSpecColorMapPlotCompositeWidget(
      this, ion_mobility_axis_label, "m/z");

  return finalNewColorMapPlotConfiguration(composite_widget_p,
                                           double_map_trace_map_sp,
                                           color_map_plot_config,
                                           ms_run_data_set_csp,
                                           processing_flow,
                                           color,
                                           parent_plottable_p);
}

void
DriftSpecMassSpecColorMapWnd::integrateToDtMz(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  // qDebug().noquote() << "Integrating to dt | mz with processing flow:"
  //<< processing_flow.toString();

  // Get the ms run data set that for the graph we are going to base the
  // integration on.
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();
  if(ms_run_data_set_csp == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  // FIXME: BEGIN Sanity check that might be removed when the program is
  // stabilized.

  std::pair<double, double> range_pair;

  bool integration_rt = processing_flow.innermostRange("ANY_RT", range_pair);

  if(!integration_rt)
    qFatal() << "Programming error.";

  // qDebug() << qSetRealNumberPrecision(10) << "Innermost RT range:"
  //          << "for y axis: " << range_pair.first << "-" << range_pair.second;
  //
  // bool integration_dt = processing_flow.innermostRange("ANY_DT", range_pair);
  //
  // if(integration_dt)
  //   qDebug() << qSetRealNumberPrecision(10) << "Innermost DT range:"
  //            << "for y axis: " << range_pair.first << "-" <<
  //            range_pair.second;

  // FIXME: END Sanity check that might be removed when the program is
  // stabilized.

  bool integration_mz = processing_flow.innermostRange("ANY_MZ", range_pair);

  if(integration_mz)
    {
      // qDebug() << qSetRealNumberPrecision(10)
      //          << "Innermost m/z range:" << range_pair.first << "-"
      //          << range_pair.second;
    }
  else
    {
      // qDebug() << "There is no m/z integration. We need to get the range from
      // "
      //             "the MsRunDataSet instance.";

      MsRunDataSetStats ms_run_data_stats =
        ms_run_data_set_csp->getMsRunDataSetStats();

      range_pair.first  = ms_run_data_stats.m_minMz;
      range_pair.second = ms_run_data_stats.m_maxMz;

      // qDebug() << qSetRealNumberPrecision(10)
      //          << "Smallest m/z: " << range_pair.first
      //          << "Greatest m/z: " << range_pair.second;
    }

  // First prepare a vector of QualifiedMassSpectrumCstSPtr. If the
  // qualified_mass_spectra_sp is not empty, the
  // fillInQualifiedMassSpectraVector() function below does not change the
  // vector and returns its size.

  std::size_t qualified_mass_spectra_count = fillInQualifiedMassSpectraVector(
    ms_run_data_set_csp, qualified_mass_spectra_sp, processing_flow);

  // qDebug()
  //   << "The number of selected mass spectra on the basis of rough RT/DT
  //   values:"
  //   << qualified_mass_spectra_count;

  if(!qualified_mass_spectra_count)
    return;

  // Now start the actual integration work.

  // Allocate a mass data integrator to integrate the data.

  QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz
    *mass_data_integrator_p =
      new QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz(
        ms_run_data_set_csp, processing_flow, qualified_mass_spectra_sp);

  mass_data_integrator_p->setMaxThreadUseCount(
    mp_programWindow->getMaxThreadUseCount());

  // Ensure the mass data integrator messages are used.

  connect(
    mass_data_integrator_p,
    &QualifiedMassSpectrumVectorMassDataIntegrator::logTextToConsoleSignal,
    mp_programWindow,
    &ProgramWindow::logTextToConsole);

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  qRegisterMetaType<std::size_t>("std::size_t");
  qRegisterMetaType<pappso::Enums::DataKind>("pappso::Enums::DataKind");

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(this,

          &DriftSpecMassSpecColorMapWnd::integrateToDtRtMzSignal,

          mass_data_integrator_task_p,

          &MassDataIntegratorTask::integrateToDtRtMz,

          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // When the read task finishes, it sends a signal that we trap to go on with
  // the plot widget creation stuff.

  // Since we allocated the QThread dynamically we need to be able to destroy it
  // later, so make the connection.
  connect(mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),

          this,

          [this,
           thread_p,
           mass_data_integrator_p,
           parent_plottable_p,
           mass_data_integrator_task_p]() {
            // Do not forget that we have to delete the MassDataIntegratorTask
            // allocated instance.
            mass_data_integrator_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can stop
            // the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
            this->finishedIntegratingToDtMz(mass_data_integrator_p,
                                            parent_plottable_p);
          });

  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_programWindow->getTaskMonitorWnd()->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Integrating to drift spectrum / mass spectrum color map");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections

  // When the integrator task instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some seconds,
  // the monitor widget.

  connect(mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),

          task_monitor_composite_widget_p,

          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::cancelOperation);

  // We need to register the meta type for std::size_t because otherwise it
  // cannot be shipped though signals. Same for pappso::Enums::DataKind.

  qRegisterMetaType<std::size_t>("std::size_t");
  qRegisterMetaType<pappso::Enums::DataKind>("pappso::Enums::DataKind");

  // Now make all the connections that will allow the integrator to provide
  // dynamic feedback to the user via the task monitor widget.

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mass_data_integrator_p);

  // task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
  //   mass_data_integrator_p);

  // qDebug() << "going to emit integrateToDtRtMzSignal with mass "
  //"data integrator:"
  //<< mass_data_integrator_p;

  emit integrateToDtRtMzSignal(mass_data_integrator_p);

  // We do not want to make signal/slot calls more than once. This is because
  // one user might well trigger more than one integration from this window to a
  // mass spectrum. Thus we do not want that *this window be still connected to
  // the specific mass_data_integrator_task_p when a new integration is
  // triggered. We want the signal/slot pairs to be contained to specific
  // objects. Each MassSpecTracePlotWnd::integrateToMz() call must be contained
  // to a this/mass_data_integrator_task_p specific signal/slot pair.
  disconnect(

    this,

    &DriftSpecMassSpecColorMapWnd::integrateToDtRtMzSignal,

    mass_data_integrator_task_p,

    &MassDataIntegratorTask::integrateToDtRtMz);
}

void
DriftSpecMassSpecColorMapWnd::finishedIntegratingToDtMz(
  QualifiedMassSpectrumVectorMassDataIntegrator *mass_data_integrator_p,
  QCPAbstractPlottable *parent_plottable_p)
{

  // This function is actually a slot that is called when the integration to rt
  // or dt | mz is terminated in a QThread.

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // We get back the integrator that was made to work in another thread. Now get
  // the obtained data and work with them.

  // Make sure we make a copy of the ProcessingFlow stored in the
  // mass data integrator, because that ProcessingFlow is owned by the
  // mass data integrator and the latter will be destroyed below. We can make
  // the copy without parentship because we'll pass it as reference to
  // downstream code that will use that ref to allocate a new fully parented
  // instance.

  ProcessingFlow *temp_processing_flow_p =
    mass_data_integrator_p->getProcessingFlow()->clone(nullptr);

  // Store the MsRunDataSetSPtr for later use before deleting the integrattor.

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    mass_data_integrator_p->getMsRunDataSet();

  // Sanity check
  if(ms_run_data_set_csp != temp_processing_flow_p->getMsRunDataSetCstSPtr())
    qFatal() << "Programming error. Not possible that the MsRunDataSet "
                "pointers do not match.";

  // We need some specific function from the specialized integrator.
  QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz
    *mass_data_integrator_to_rtdtmz_p =
      static_cast<QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz *>(
        mass_data_integrator_p);

  // Get a pointer to the double maptrace map that will serve as the data to
  // fill-in the color map.
  std::shared_ptr<std::map<double, pappso::MapTrace>> double_map_trace_map_sp =
    static_cast<QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz *>(
      mass_data_integrator_to_rtdtmz_p)
      ->getDoubleMapTraceMapSPtr();

  // qDebug() << "dt / mz map size:" << double_map_trace_map_sp->size();

  if(!double_map_trace_map_sp->size())
    {
      // qDebug() << "There is not a single item in the dt / mz map.";

      return;
    }

  // We need to have the specifics of the color map: the number of m/z cells,
  // the minimum/maximum key value (the RT values)
  // the minimum/maximum mz value.

  pappso::ColorMapPlotConfig color_map_plot_config(
    pappso::Enums::DataKind::dt,
    pappso::Enums::DataKind::mz,
    pappso::Enums::AxisScale::orig,
    pappso::Enums::AxisScale::orig,
    pappso::Enums::AxisScale::orig,
    mass_data_integrator_to_rtdtmz_p->getColorMapKeyCellCount(),
    mass_data_integrator_to_rtdtmz_p->getColorMapMzCellCount(),
    mass_data_integrator_to_rtdtmz_p->getColorMapMinKey(),
    mass_data_integrator_to_rtdtmz_p->getColorMapMaxKey(),
    mass_data_integrator_to_rtdtmz_p->getColorMapMinMz(),
    mass_data_integrator_to_rtdtmz_p->getColorMapMaxMz(),
    /*the min Z value will be determined later*/ 0,
    /*the max Z value will be determined later*/ 0);

  // qDebug() << "color_map_plot_config:" << color_map_plot_config.toString();

  // Now that we got all we need and that we have made a safe copy
  // of the ProcessingFlow, we can delete the integrator.
  delete mass_data_integrator_p;
  mass_data_integrator_p = nullptr;

  // At this point, we need to get a color for the plot. That color needs to
  // be that of the parent.

  QColor plot_color;

  // There are two situations: either the integration started from a plot
  // widget and the color is certainly obtainable via the pen of the plot.
  if(parent_plottable_p != nullptr)
    plot_color = parent_plottable_p->pen().color();
  else
    // Or the integration started at the MsRunDataSetTableView and the color
    // can only be obtained via the MsRunDataSet in the OpenMsRunDataSetsDlg.
    plot_color = mp_programWindow->getColorForMsRunDataSet(ms_run_data_set_csp);

  // Note that we do not support multiple color maps stacked one on top of the
  // other. So we do not use the newTrace() function.
  addColorMapPlot(double_map_trace_map_sp,
                  color_map_plot_config,
                  ms_run_data_set_csp,
                  *temp_processing_flow_p,
                  plot_color,
                  static_cast<QCPAbstractPlottable *>(parent_plottable_p));

  // At this point, if the window is really not visible, then show it, otherwise
  // I was told that the user does not think that they can ask for the window to
  // show up in the main program window.

  if(!isVisible())
    {
      showWindow();
    }

  delete temp_processing_flow_p;

  return;
}


} // namespace MineXpert

} // namespace MsXpS
