/* 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 <QDebug>
#include <QMainWindow>
#include <QColor>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/MassDataCborMassSpectrumHandler.hpp>
#include <pappsomspp/core/massspectrum/massspectrum.h>
#include <pappsomspp/core/massspectrum/qualifiedmassspectrum.h>
#include <pappsomspp/core/msrun/msrundatasettree.h>
#include <pappsomspp/core/msrun/msrunid.h>
#include <pappsomspp/core/processing/combiners/integrationscope.h>


/////////////////////// pappsomspp includes


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

namespace MsXpS
{
namespace MineXpert
{


//! Construct an MassSpecTracePlotWnd instance.
MassSpecTracePlotWnd::MassSpecTracePlotWnd(QWidget *parent,
                                           const QString &title,
                                           const QString &description)
  : BaseTracePlotWnd(parent, title, description)
{
  extendMainMenu();
}

//! Destruct \c this MassSpecTracePlotWnd instance.
MassSpecTracePlotWnd::~MassSpecTracePlotWnd()
{
  // qDebug();
}

void
MassSpecTracePlotWnd::extendMainMenu()
{
  // Extend the tool bar menu with some mass spec-specific submenus.

  if(mp_mainMenu == nullptr)
    qFatal() << "Programming error.";

  // The toolbar menu button

  // The main menu in the form of a button with an icon like thoses found for
  // the menus in mobile phone applications.

  mp_deconvolutionsMenu = new QMenu("Deconvolutions");

  if(mp_beforePlotWidgetManagementSeparatorAction == nullptr)
    qFatal() << "Programming error.";

  QAction *menu_action_p = mp_mainMenu->insertMenu(
    mp_beforePlotWidgetManagementSeparatorAction, mp_deconvolutionsMenu);

  if(menu_action_p == nullptr)
    qFatal() << "Programming error.";


  QAction *mp_setMinimalChargeFractionalPartAction = new QAction(
    "Set charge minimal fractional part for all pinned-down widgets",
    dynamic_cast<QObject *>(this));
  mp_setMinimalChargeFractionalPartAction->setStatusTip(
    "Set the charge minimal fractional part for all pinned-down widgets "
    "(typically 0.99)");

  connect(mp_setMinimalChargeFractionalPartAction,
          &QAction::triggered,
          this,
          [this]() {
            bool ok = false;

            double charge_minimal_fractional_part = QInputDialog::getDouble(
              this,
              "Set the charge minimal fractional part",
              "Charge (z) minimal fractional part (typically 0.99)",
              m_minimalFractionalPart,
              0.5,
              0.9999,
              4,
              &ok);

            if(ok)
              {
                m_minimalFractionalPart = charge_minimal_fractional_part;

                setMinimalChargeFractionalPartPinnedDownWidgets(
                  m_minimalFractionalPart);
              }
          });

  mp_deconvolutionsMenu->addAction(mp_setMinimalChargeFractionalPartAction);

  QAction *mp_setMassPeakSpanAction = new QAction(
    "Set the span between two mass peaks for all pinned-down widgets",
    dynamic_cast<QObject *>(this));
  mp_setMassPeakSpanAction->setStatusTip(
    "Set the span between two peaks of a charge state envelope for all "
    "pinned-down widgets (typically 1 or "
    "2)");

  connect(mp_setMassPeakSpanAction, &QAction::triggered, this, [this]() {
    bool ok = false;

    double peak_span =
      QInputDialog::getInt(this,
                           "Set the span between two peaks of a charge state "
                           "envelope",
                           "Span between two peaks (typically 1 or 2)",
                           m_massPeakSpan,
                           1,
                           20,
                           1,
                           &ok);

    if(ok)
      {

        m_massPeakSpan = peak_span;

        setChargeStateEnvelopePeakSpanPinnedDownWidgets(m_massPeakSpan);
      }
  });

  mp_deconvolutionsMenu->addAction(mp_setMassPeakSpanAction);

  QAction *action_rp =
    new QAction("Perform low mass deconvolution for all pinned-down widgets",
                dynamic_cast<QObject *>(this));
  mp_setMassPeakSpanAction->setStatusTip(
    "Perform low mass deconvolution for all pinned-down widgets");

  connect(action_rp, &QAction::triggered, this, [this]() {
    lowMassDeconvolvePinnedDownWidgets();
  });

  mp_deconvolutionsMenu->addAction(action_rp);

  mp_mainMenu->addSeparator();
}

void
MassSpecTracePlotWnd::keyPressEvent(QKeyEvent *event)
{
  // qDebug() << "Key press event:" << event->key();

  if(event->key() == Qt::Key_Delete)
    {
      // qDebug() << "Key: Key_Delete";
    }
}

void
MassSpecTracePlotWnd::keyReleaseEvent(QKeyEvent *event)
{
  // qDebug() << "Key press event:" << event->key();

  if(event->key() == Qt::Key_Delete)
    {
      // qDebug() << "Key: Key_Delete";

      // Get the composite plot widget that has the focus.
      std::vector<BasePlotCompositeWidget *> all_widgets = allWidgets();

      // qDebug() << "There are " << all_widgets.size()
      //          << "base plot composite widgets.";

      BasePlotCompositeWidget *focussed_widget_p = nullptr;

      for(std::size_t iter = 0; iter < all_widgets.size(); ++iter)
        {
          if(all_widgets.at(iter)->isFocussed())
            {
              focussed_widget_p = all_widgets.at(iter);
              break;
            }
        }

      Q_ASSERT(focussed_widget_p != nullptr);

      QList<QCPAbstractPlottable *> plottables =
        focussed_widget_p->plottablesToBeUsedAsIntegrationDestination();

      // qDebug() << "The number of plottables to handle:" << plottables.size();

      if(!plottables.size())
        return;

      if(plottables.size() == 1)
        requestPlottableDestruction(
          focussed_widget_p->getPlotWidget(),
          plottables.first(),
          focussed_widget_p->getPlotWidget()->getContext());
      else
        {
          // qDebug() << "More than one plottable";

          for(int iter = 0; iter < plottables.size(); ++iter)
            requestPlottableDestruction(
              focussed_widget_p->getPlotWidget(),
              plottables.at(iter),
              focussed_widget_p->getPlotWidget()->getContext());
        }
    }
}

// This function does not check if there are widgets that are pinned-down so
// as to gather new trace data. This function creates a brand new plot
// composite widget.
QCPGraph *
MassSpecTracePlotWnd::addTracePlot(
  const pappso::Trace &trace,
  [[maybe_unused]] MsRunDataSetCstSPtr ms_run_data_set_csp,
  const ProcessingFlow &processing_flow,
  const QColor &color,
  QCPAbstractPlottable *parent_plottable_p)
{
  // Allocate a mass spectrum-specific composite plot widget.

  MassSpecTracePlotCompositeWidget *composite_widget_p =
    new MassSpecTracePlotCompositeWidget(this, "m/z", "counts (a.u.)");

  QCPGraph *new_graph_p = finalNewTracePlotConfiguration(
    composite_widget_p, trace, processing_flow, color, parent_plottable_p);

  // Make sure we do create a new JS item for both the plot widget and the
  // graph.

  QJSValue returned_js_value;

  getProgramWindow()
    ->getScriptingWnd()
    ->getScriptingEnvironment()
    ->exposeQObject("plotWidget" /*name*/,
                    QString() /*alias*/,
                    "The newly created MassSpecTracePlot widget",
                    dynamic_cast<QObject *>(composite_widget_p),
                    dynamic_cast<QObject *>(this),
                    returned_js_value,
                    QJSEngine::CppOwnership);

  QString sample_name = ms_run_data_set_csp->getMsRunId()->getSampleName();

  // qDebug() << "The color:" << color;

  getProgramWindow()
    ->getScriptingWnd()
    ->getScriptingEnvironment()
    ->exposeQObject("plotGraph" /*name*/,
                    sample_name /*alias*/,
                    "The newly created MassSpecTracePlot graph",
                    dynamic_cast<QObject *>(new_graph_p),
                    dynamic_cast<QObject *>(composite_widget_p),
                    returned_js_value,
                    QJSEngine::CppOwnership);

  return new_graph_p;
}

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

  // Get the ms run data set for the graph that 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)
    {
      // It is imaginable that the data come not from a previous RT step,
      // that is, that the data are indeed MZ data not coming from at least
      // one integration from TIC chromatogram if the mass spectrum (MZ)
      // comes from the net (source processing type is SOCKET_MZ).

      std::vector<ProcessingStepCstSPtr> steps =
        processing_flow.stepsMatchingSrcTypeX("SOCKET_MZ");

      if(!steps.size())
        qFatal(
          "Programming error: the data have a problematic ProcessingFlow.");
    }

  // qDebug() << qSetRealNumberPrecision(10)
  //          << "Innermost RT range:" << 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.

  // What integration parameters are we going to use ?
  // We can ask that the most relevant MzIntegrationParams be returned
  // by the ProcessingFlow.

  // Get a copy to the MzIntegrationParams in the most recent step.
  const pappso::MzIntegrationParams *mz_integration_params_p =
    processing_flow.getSensibleMzIntegrationParams();

  // FIXME This might be the place where we need to change the
  // mz integration params to reflect the m/z range to be integrated ?
  // Question question

  // qDebug().noquote() << "Got sensible m/z integration params:\n"
  //                    << mz_integration_params_p->toString(0, "  ");

  if(mz_integration_params_p == nullptr || !mz_integration_params_p->isValid())
    qFatal(
      "Programming error. Cannot be that integration to m/z has no valid mz "
      "integration parameters set.");

  // 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.

  QualifiedMassSpectrumVectorMassDataIntegratorToMz *mass_data_integrator_p =
    new QualifiedMassSpectrumVectorMassDataIntegratorToMz(
      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);

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

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

          static_cast<void (MassSpecTracePlotWnd::*)(
            QualifiedMassSpectrumVectorMassDataIntegratorToMz *)>(
            &MassSpecTracePlotWnd::integrateToMzSignal),

          mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegratorToMz *)>(
            &MassDataIntegratorTask::integrateToMz),

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

  // 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->finishedIntegratingToMz(mass_data_integrator_p,
                                          parent_plottable_p);
          });


  // qDebug() << "the integrator pointer:" << mass_data_integrator_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->mp_taskMonitorWnd->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 mass spectrum");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections

  // When the MsRunReadTask 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.

  qRegisterMetaType<std::size_t>("std::size_t");

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mass_data_integrator_p);

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

  emit integrateToMzSignal(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,

             static_cast<void (MassSpecTracePlotWnd::*)(
               QualifiedMassSpectrumVectorMassDataIntegratorToMz *)>(
               &MassSpecTracePlotWnd::integrateToMzSignal),

             mass_data_integrator_task_p,

             static_cast<void (MassDataIntegratorTask::*)(
               QualifiedMassSpectrumVectorMassDataIntegratorToMz *)>(
               &MassDataIntegratorTask::integrateToMz));
}

void
MassSpecTracePlotWnd::finishedIntegratingToMz(
  QualifiedMassSpectrumVectorMassDataIntegrator *mass_data_integrator_p,
  QCPAbstractPlottable *parent_plottable_p)
{

  // This function is actually a slot that is called when the integration to
  // 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.";

  pappso::Trace integrated_trace =
    mass_data_integrator_p->getMapTrace().toTrace();
  // qDebug() << "mass spectrum trace size:" << integrated_trace.size();

  if(!integrated_trace.size())
    {
      qInfo() << "There is not a single point in the integrated trace.";
      return;
    }

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

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

  // The new trace checks if there are push-pinned widgets. If so, the new
  // trace will be apportioned to the widgets according to the required
  // mechanics: new plot, sum, combine...

  newTrace(integrated_trace,
           ms_run_data_set_csp,
           *temp_processing_flow_p,
           plot_color,
           static_cast<QCPGraph *>(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;
}

void
MassSpecTracePlotWnd::integrateToRt(QCPAbstractPlottable *parent_plottable_p,
                                    const ProcessingFlow &processing_flow)
{
  if(parent_plottable_p == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  mp_programWindow->integrateToRt(parent_plottable_p, nullptr, processing_flow);
}

void
MassSpecTracePlotWnd::integrateToDt(QCPAbstractPlottable *parent_plottable_p,
                                    const ProcessingFlow &processing_flow)
{
  if(parent_plottable_p == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  mp_programWindow->integrateToDt(parent_plottable_p, nullptr, processing_flow);
}

void
MassSpecTracePlotWnd::integrateToMzRt(QCPAbstractPlottable *plottable_p,
                                      const ProcessingFlow &processing_flow)
{
  if(plottable_p == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  mp_programWindow->integrateToMzRt(plottable_p, nullptr, processing_flow);
}

void
MassSpecTracePlotWnd::integrateToDtMz(QCPAbstractPlottable *plottable_p,
                                      const ProcessingFlow &processing_flow)
{
  if(plottable_p == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  mp_programWindow->integrateToDtMz(plottable_p, nullptr, processing_flow);
}

void
MassSpecTracePlotWnd::integrateToDtRt(QCPAbstractPlottable *plottable_p,
                                      const ProcessingFlow &processing_flow)
{
  if(plottable_p == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  mp_programWindow->integrateToDtRt(plottable_p, nullptr, processing_flow);
}

void
MassSpecTracePlotWnd::setMinimalChargeFractionalPartPinnedDownWidgets(
  double value)
{
  std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

  for(auto &&widget_p : widget_list)
    {
      static_cast<MassSpecTracePlotCompositeWidget *>(widget_p)
        ->setMinimalChargeFractionalPartPinnedDownWidgets(value);
    }
}

void
MassSpecTracePlotWnd::setChargeStateEnvelopePeakSpanPinnedDownWidgets(
  std::size_t value)
{
  std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

  for(auto &&widget_p : widget_list)
    {
      static_cast<MassSpecTracePlotCompositeWidget *>(widget_p)
        ->setChargeStateEnvelopePeakSpanPinnedDownWidgets(value);
    }
}

void
MassSpecTracePlotWnd::lowMassDeconvolvePinnedDownWidgets()
{
  std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

  qDebug() << "There are" << widget_list.size() << "pinned down widgets.";

  for(auto &&widget_p : widget_list)
    {
      // qDebug() << "Starting iterating in one widget.";

      // We need to set the focus to the plot widget, otherwise the
      // lowMassDeconvolve() won't run.

      const_cast<pappso::BasePlotWidget *>(widget_p->getPlotWidget())
        ->setFocus();

      static_cast<MassSpecTracePlotCompositeWidget *>(widget_p)
        ->lowMassDeconvolve();

      // qDebug() << "Done iterating in one widget.";
    }
}

void
MassSpecTracePlotWnd::handleReceivedData(const QByteArray &byte_array)
{
  libXpertMassCore::MassDataCborMassSpectrumHandler cbor_handler(this);

  if(!cbor_handler.readByteArray(byte_array))
    {
      qCritical() << "Failed to read CBOR mass spectrum data.";

      return;
    }

  pappso::Trace trace = cbor_handler.getTrace();
  // qDebug() << "Received trace of size:" << trace.size();

  qDebug() << "First datapoint:" << trace.front().x
           << "Last datapoint:" << trace.back().x;

  QString title               = cbor_handler.getTitle();
  // QByteArray color_byte_array = cbor_handler.getTraceColor();
  QString color_name          = cbor_handler.getTraceColor();

  qDebug() << "The data are of type:" << (int)cbor_handler.getMassDataType()
           << "with title:"
           << title
           // << "trace:" << trace.toString()
           << "and color name:" << color_name;

  qDebug() << "The requested mass spectrum color:" << color_name
           << "and is valid:" << QColor(color_name).isValid();

  handleReceivedData(
    title, color_name, std::make_shared<const pappso::Trace>(trace));
}

void
MassSpecTracePlotWnd::handleReceivedData(const QString &title,
                                         const QString &color_name,
                                         pappso::TraceCstSPtr trace_sp)
{

  // Create a new ms run data set piece by piece.

  qDebug() << "The requested mass spectrum color:" << color_name
           << "and is valid:" << QColor(color_name).isValid();

  // qDebug() << " Handling received data, create a ms run data set "
  //             "piecemeal.";

  pappso::MsRunIdSPtr ms_run_id_sp = std::make_shared<pappso::MsRunId>();
  ms_run_id_sp->setSampleName(title);
  ms_run_id_sp->setFileName(title);

  // Create the mass spectrum id instance we'll need later.

  pappso::MassSpectrumId mass_spectrum_id =
    pappso::MassSpectrumId(ms_run_id_sp);

  // This call creates the ms run data set tree inside the mas run data set.
  MsRunDataSetSPtr ms_run_data_set_sp =
    std::make_shared<MsRunDataSet>(nullptr /*parent*/, ms_run_id_sp);

  // Now create the mass spectrum with the trace.

  qDebug() << "The trace's first point:" << trace_sp->minXDataPoint().x;
  qDebug() << "The trace's last point:" << trace_sp->maxXDataPoint().x;

  pappso::MassSpectrumSPtr mass_spectrum_sp =
    std::make_shared<pappso::MassSpectrum>(*trace_sp);

  // qDebug().noquote()
  //   << "Before computing statistics for ms run data set:"
  //   << ms_run_data_set_sp->getMsRunDataSetStats().toString();

  ms_run_data_set_sp->incrementStatistics(mass_spectrum_sp);

  // qDebug().noquote() << "After:"
  //                    <<
  //                    ms_run_data_set_sp->getMsRunDataSetStats().toString();

  // Now create the qualified mass spectrum that we'll add to the ms run data
  // set.

  pappso::QualifiedMassSpectrumSPtr qualified_mass_spectrum_sp =
    std::make_shared<pappso::QualifiedMassSpectrum>();

  qualified_mass_spectrum_sp->setMassSpectrumId(mass_spectrum_id);
  qualified_mass_spectrum_sp->setDtInMilliSeconds(-1);
  qualified_mass_spectrum_sp->setRtInSeconds(60);
  qualified_mass_spectrum_sp->setMassSpectrumSPtr(mass_spectrum_sp);
  qualified_mass_spectrum_sp->setEmptyMassSpectrum(false);

  ms_run_data_set_sp->mapMassSpectrum(qualified_mass_spectrum_sp);

  // qDebug().noquote() << "Mapped the mass spectrum to the data set:"
  //                    << qualified_mass_spectrum_sp->toString();

  // We will need the MzIntegrationParams and we set them specifically
  // for this MsRunDataSet:
  // Heap-allocated
  pappso::MzIntegrationParams *ms_run_data_set_mz_integration_params_p =
    ms_run_data_set_sp->craftMzIntegrationParams();

  // qDebug().noquote() << "The MzIntegrationParams from the data set:"
  //                    <<
  //                    ms_run_data_set_mz_integration_params_p->toString();

  // Create a temporary ProcessingFlow with nullptr parent because we'll
  // pass it to downstream code as a reference. That will be used to allocate
  // a new instance with proper parentship. We'll delete the temporary
  // instance when done.
  ProcessingFlow *temp_processing_flow_p = new ProcessingFlow(nullptr);
  temp_processing_flow_p->setMsRunDataSetCstSPtr(ms_run_data_set_sp);

  // We are creating a mass spectrum from a single trace. We arbitrarily set
  // the retention time to 1 min (60 s). We *need* to craft a fake processing
  // flow step because when the user will use the graph that we will show in
  // the plot widget to perform an integration, if there is no RT_ANY-related
  // step in the processing flow, then the integration will invariably yield
  // no data: no mass spectrum will be found to have a proper retention time.

  ///////////// THE RETENTION TIME (RT) STEP //////////////////
  ProcessingStepSPtr new_step_sp = std::make_shared<ProcessingStep>();
  new_step_sp->setSrcProcessingType(pappso::Enums::Axis::x, "SOCKET_RT");
  new_step_sp->setDestProcessingType(ProcessingType("MZ"));
  // Craft the data for the creation of the IntegrationScope.
  QPointF rt_point(0, 0);
  double width = 1 - 0;
  // Allocate a brand new integration scope (mono-dimensional, starting at
  // rt_point with a width of 1 on the horizontal axis).
  pappso::IntegrationScopeBaseCstSPtr integration_scope_csp =
    std::make_shared<pappso::IntegrationScope>(rt_point, width);
  // qDebug() << "Created integration scope. Is it 1D ?:"
  //          << integration_scope_csp->is1D()
  //          << "is it 2D ?:" << integration_scope_csp->is2D();
  // Finally, set the IntegrationScope to the step.
  new_step_sp->setIntegrationScope(integration_scope_csp);
  // Should perform automatically the conversion from non-const to const.
  temp_processing_flow_p->getStepsRef().push_back(new_step_sp);

  ///////////// THE MASS SPEC (MZ) STEP //////////////////
  new_step_sp = std::make_shared<ProcessingStep>();
  new_step_sp->setSrcProcessingType(pappso::Enums::Axis::x, "SOCKET_MZ");
  new_step_sp->setDestProcessingType(ProcessingType("MZ"));
  // We now want to create an IntegrationScope that matches the full m/z
  // range.
  QPointF mz_point(ms_run_data_set_sp->getMsRunDataSetStats().m_minMz, 0);
  width = ms_run_data_set_sp->getMsRunDataSetStats().m_maxMz -
          ms_run_data_set_sp->getMsRunDataSetStats().m_minMz;
  // Allocate a brand new integration scope (mono-dimensional, starting at
  // mz_point with a width of m/z full range on the horizontal axis).
  integration_scope_csp =
    std::make_shared<pappso::IntegrationScope>(mz_point, width);
  // qDebug() << "Created integration scope. Is it 1D ?:"
  //          << integration_scope_csp->is1D()
  //          << "is it 2D ?:" << integration_scope_csp->is2D();
  // Finally, set the IntegrationScope to the step.
  new_step_sp->setIntegrationScope(integration_scope_csp);
  new_step_sp->setMzIntegrationParams(*ms_run_data_set_mz_integration_params_p);
  // Should perform automatically the conversion from non-const to const.
  temp_processing_flow_p->getStepsRef().push_back(new_step_sp);

  // We need to const_cast the step in order to overwrite is
  // MsFragmentationSpec.
  MsFragmentationSpec fragmentation_spec =
    temp_processing_flow_p->getDefaultMsFragmentationSpec();

  // Set the MS level it to 0 because all the later integrations need to be
  // capable of involving any MS level. 0 states that MS level matches.

  fragmentation_spec.setMsLevel(0);

  temp_processing_flow_p->setDefaultMsFragmentationSpec(fragmentation_spec);

  // First thing is create the data plot graph tree, because this is the entry
  // point to the whole set of data plot graphs : the tree is root into the
  // TIC chromatogram plot graph because this is where the data exploration
  // starts.

  // At this point, we need to get a color for the plot.
  // Set true to ask that the color be selected randomly if no color is
  // available.

  QColor plot_color(color_name);

  if(!plot_color.isValid())
    plot_color = libXpertMassGui::ColorSelector::getColor(true);

  // Now we should document the mz integration parameters in the processing
  // flow that is mapped to the graph in such a way that the default mz
  // integration parameters that were calculated while loading the ms run from
  // file are available for the very first integration to mz that might occur.

  temp_processing_flow_p->setDefaultMzIntegrationParams(
    *ms_run_data_set_mz_integration_params_p);

  // We have used that object twice, we do not need it anymore.
  delete ms_run_data_set_mz_integration_params_p;

  // #ifndef NDEBUG
  ms_run_data_set_mz_integration_params_p =
    temp_processing_flow_p->getDefaultMzIntegrationParams();
  // We have set them above, they must be found!
  Q_ASSERT(ms_run_data_set_mz_integration_params_p);
  // qDebug().noquote()
  //   << "The mz integration params set to the processing flow:\n"
  //   << ms_run_data_set_mz_integration_params_p->toString(0, "  ");
  // #endif


  // Let's see the usage count of the MsRunDataSetCstSPtr.
  //	qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // And now that these params are documented, create the graph! Note the
  // nullptr parameter that shows that there is no parent graph here.

  // qDebug() << "going to call newTrace()";

  newTrace(*mass_spectrum_sp,
           ms_run_data_set_sp,
           *temp_processing_flow_p,
           plot_color,
           nullptr);

  // Finally delete the temporary instance:
  delete temp_processing_flow_p;
}

} // namespace MineXpert


} // namespace MsXpS
