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


/////////////////////// StdLib includes
#include <cmath>


/////////////////////// Qt includes
#include <QWidget>
#include <QInputDialog>
#include <QMessageBox>


/////////////////////// QCustomPlot


/////////////////////// pappsomspp includes
#include <pappsomspp/gui/plotwidget/massspectraceplotwidget.h>
#include <pappsomspp/core/processing/combiners/tracepluscombiner.h>
#include <pappsomspp/core/processing/combiners/traceminuscombiner.h>
#include <pappsomspp/core/processing/combiners/massspectrumpluscombiner.h>
#include <pappsomspp/core/processing/combiners/massspectrumminuscombiner.h>
#include <pappsomspp/gui/plotwidget/massspectraceplotcontext.h>
#include <pappsomspp/core/processing/combiners/integrationscope.h>
#include <pappsomspp/core/utils.h>


/////////////////////// libXpertMassCore
#include <MsXpS/libXpertMassCore/MassDataCborMassSpectrumHandler.hpp>
#include <MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp>
#include <MsXpS/libXpertMassCore/MassPeakShaperConfig.hpp>
#include <MsXpS/libXpertMassCore/MassPeakShaper.hpp>
#include <MsXpS/libXpertMassCore/Processing/AbstractLowMassDeconvolver.hpp>
#include <MsXpS/libXpertMassCore/Processing/LowMassDeconvolverNoLabelling.hpp>
#include <MsXpS/libXpertMassCore/Processing/LowMassDeconvolverFullLabelling.hpp>
#include <MsXpS/libXpertMassCore/IsotopicClusterShaper.hpp>
#include <MsXpS/libXpertMassCore/Processing/DeconvolutedMassIdMatcher.hpp>
#include <MsXpS/libXpertMassCore/Utils.hpp>
#include <MsXpS/libXpertMassCore/Math/Generic.hpp>
#include <MsXpS/libXpertMassCore/Math/Gaussian.hpp>
#include <MsXpS/libXpertMassCore/Processing/LocalSignalToNoiseEstimator.hpp>
#include <MsXpS/libXpertMassCore/Processing/HighResPeakPicker.hpp>
#include <MsXpS/libXpertMassCore/IsotopicCluster.hpp>


/////////////////////// libXpertMassGui
#include <MsXpS/libXpertMassGui/ActionManager.hpp>
#include <MsXpS/libXpertMassGui/ColorSelector.hpp>
#include <MsXpS/libXpertMassGui/LabelledCheckBoxAction.hpp>
#include <MsXpS/libXpertMassGui/TaskMonitorCompositeWidget.hpp>


/////////////////////// Local includes
#include "MassSpecTracePlotCompositeWidget.hpp"
#include "MassSpecTracePlotWnd.hpp"
#include "Application.hpp"
#include "ProgramWindow.hpp"
#include "ApplicationPreferencesWnd.hpp"
#include "ui_BasePlotCompositeWidget.h"

namespace MsXpS
{
namespace MineXpert
{


MassSpecTracePlotCompositeWidget::MassSpecTracePlotCompositeWidget(
  QWidget *parent, const QString &x_axis_label, const QString &y_axis_label)
  : BaseTracePlotCompositeWidget(parent, x_axis_label, y_axis_label)
{
  // qDebug();

  MassSpecTracePlotWnd *parent_wnd =
    dynamic_cast<MassSpecTracePlotWnd *>(getParentWnd());
  if(parent_wnd == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  setupWidget();
}

MassSpecTracePlotCompositeWidget::~MassSpecTracePlotCompositeWidget()
{
  // qDebug();
}

void
MassSpecTracePlotCompositeWidget::setupWidget()
{
  // qDebug();

  /************  The QCustomPlot widget *************/
  /************  The QCustomPlot widget *************/
  mp_plotWidget =
    new pappso::MassSpecTracePlotWidget(this, m_axisLabelX, m_axisLabelY);

  connect(mp_plotWidget,
          &pappso::BasePlotWidget::beforeReplotSignal,
          this,
          &BaseTracePlotCompositeWidget::beforeReplot);

  connect(mp_plotWidget,
          &pappso::BasePlotWidget::afterLayoutSignal,
          this,
          &BaseTracePlotCompositeWidget::afterLayout);

  connect(mp_plotWidget,
          &pappso::BasePlotWidget::afterReplotSignal,
          this,
          &BaseTracePlotCompositeWidget::afterReplot);

  connect(mp_plotWidget,
          &pappso::BasePlotWidget::mouseMoveDraggingCursorSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetMouseMoveDraggingCursor);

  connect(mp_plotWidget,
          &pappso::BasePlotWidget::mouseReleaseEventSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetMouseReleaseEvent);

  m_ui->qcpTracePlotWidgetHorizontalLayout->addWidget(mp_plotWidget);

  // We want that the axis labels be output inside the plot wiget area.
  mp_plotWidget->yAxis->setTickLabelSide(QCPAxis::LabelSide::lsInside);

  connect(mp_plotWidget,
          &pappso::MassSpecTracePlotWidget::setFocusSignal,
          this,
          [this]() {
            BaseTracePlotCompositeWidget::mp_parentWnd->plotWidgetGotFocus(
              this);
          });

  connect(mp_plotWidget,
          &pappso::MassSpecTracePlotWidget::lastCursorHoveredPointSignal,
          this,
          &MassSpecTracePlotCompositeWidget::lastCursorHoveredPoint);

  connect(mp_plotWidget,
          &pappso::MassSpecTracePlotWidget::plotRangesChangedSignal,
          mp_parentWnd,
          &BaseTracePlotWnd::plotRangesChanged);

  connect(mp_plotWidget,
          &pappso::MassSpecTracePlotWidget::xAxisMeasurementSignal,
          this,
          &MassSpecTracePlotCompositeWidget::xAxisMeasurement);

  // For this one we need to force the cast to the
  // pappso::MassSpecTracePlotWidget because the context that is passed as
  // argument is specific of that class.

  connect(static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget),
          &pappso::MassSpecTracePlotWidget::keyPressEventSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetKeyPressEvent);

  connect(static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget),
          &pappso::MassSpecTracePlotWidget::keyReleaseEventSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetKeyReleaseEvent);

  connect(static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget),
          &pappso::MassSpecTracePlotWidget::mousePressEventSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetMousePressEvent);

  connect(static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget),
          &pappso::MassSpecTracePlotWidget::mouseReleaseEventSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetMouseReleaseEvent);

  connect(mp_plotWidget,
          &pappso::MassSpecTracePlotWidget::plottableSelectionChangedSignal,
          this,
          &BasePlotCompositeWidget::plottableSelectionChanged);

  connect(static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget),
          &pappso::MassSpecTracePlotWidget::massDeconvolutionSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetMassDeconvolution);

  connect(
    static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget),
    &pappso::MassSpecTracePlotWidget::resolvingPowerComputationSignal,
    this,
    &MassSpecTracePlotCompositeWidget::plotWidgetResolvingPowerComputation);

  connect(mp_plotWidget,
          &pappso::BaseTracePlotWidget::integrationRequestedSignal,
          this,
          &MassSpecTracePlotCompositeWidget::integrationRequested);

  // Create the context menu that should have menu items specific for this mass
  // spec-specific wiget.

  createMainMenu();

  /************ The QCustomPlot widget *************/

  /************ The various widgets in this composite widget ***************/
  /************ The various widgets in this composite widget ***************/

  // The button that triggers the duplication of the trace into another trace
  // plot widget will ask the parent window (MassSpecTracePlotWnd) to perform
  // the duplication.

  connect(
    m_ui->duplicateTracePushButton, &QPushButton::clicked, this, [this]() {
      // qDebug();
      static_cast<BaseTracePlotWnd *>(getParentWnd())->duplicateTracePlot(this);
    });

  // Create the list of peak labels for the labelling session.
  // We have to do this initialization in the derived class, because
  // the base class has a nullptr mp_plotWidget. FIXME.
  TracePlotLabelListQSPtr peak_label_list_qsp =
    QSharedPointer<TracePlotLabelList>::create(
      /*name*/ "Peak labels",
      /*plot_layer_name*/ "labelsLayer",
      mp_plotWidget,
      /*should_manage_layout*/ false,
      /*x_axis_delta_trigger*/ 0,
      /*label_vertical_shift_count_factor*/ 0,
      mp_plotWidget->xAxis->labelFont(),
      mp_plotWidget->xAxis->labelColor());

  m_nameLabelListMap[peak_label_list_qsp->getName()] = peak_label_list_qsp;
}

void
MassSpecTracePlotCompositeWidget::
  peakPickDeconvOnlyForVisibleRangeCheckBoxStateChanged(Qt::CheckState state)
{
  m_isPeakPickDeconvOnlyForVisibleRange = (state == Qt::Checked ? true : false);
}

void
MassSpecTracePlotCompositeWidget::createMainMenu()
{
  // Complete the base class menu with the color-map-specific menu
  // item.

  // qDebug() << "BEGIN extending the main menu:" << mp_mainMenu;

  // Debug: the icon is there
  // QIcon the_icon =  m_ui->mainMenuPushButton->icon();
  // QSize size = m_ui->mainMenuPushButton->iconSize();  // typically what you
  // want QPixmap pixmap = the_icon.pixmap(size);
  // pixmap.save("/tmp/massspecicon.png");

  // Update the menu tooltip.
  m_ui->mainMenuPushButton->setToolTip(
    "Mass spec trace plot widget menu (Ctrl+P, M)");

  // mp_mainMenu->addSeparator();

  QMenu *centroidation_deconvolutions_menu_p =
    mp_mainMenu->addMenu("&Centroidation and deconvolution");

  QMenu *manual_deconvolutions_menu_p =
    centroidation_deconvolutions_menu_p->addMenu("Manual deconvolutions");

  QAction *set_charge_minimal_fractional_part_action_p = new QAction(
    "Set charge &minimal fractional part", dynamic_cast<QObject *>(this));
  set_charge_minimal_fractional_part_action_p->setStatusTip(
    "Set the charge minimal fractional part (typically 0.99)");
  set_charge_minimal_fractional_part_action_p->setShortcut(
    QKeySequence("Ctrl+S, M"));

  connect(set_charge_minimal_fractional_part_action_p,
          &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)",
              static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget)
                ->getChargeMinimalFractionalPart(),
              0.5,
              0.9999,
              4,
              &ok);

            if(ok)
              static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget)
                ->setChargeMinimalFractionalPart(
                  charge_minimal_fractional_part);
          });

  manual_deconvolutions_menu_p->addAction(
    set_charge_minimal_fractional_part_action_p);


  QAction *set_inter_peak_span_action_p = new QAction(
    "Set the span &between two mass peaks", dynamic_cast<QObject *>(this));
  set_inter_peak_span_action_p->setStatusTip(
    "Set the span between two peaks of a charge state envelope (typically 1 or "
    "2)");
  set_inter_peak_span_action_p->setShortcut(QKeySequence("Ctrl+S, B"));

  connect(set_inter_peak_span_action_p, &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)",
      static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget)
        ->getChargeStateEnvelopePeakSpan(),
      1,
      20,
      1,
      &ok);

    if(ok)
      static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget)
        ->setChargeStateEnvelopePeakSpan(peak_span);
  });

  manual_deconvolutions_menu_p->addAction(set_inter_peak_span_action_p);

  QMenu *auto_peak_pick_deconv_menu_p =
    centroidation_deconvolutions_menu_p->addMenu("Automatic processing");

  QWidgetAction *auto_peak_pick_deconv_mode_action_p =
    new libXpertMassGui::LabelledCheckBoxAction(
      "Check to only process visible data range",
      Qt::CheckState::Unchecked,
      "" /*no text here*/,
      dynamic_cast<QObject *>(this));
  auto_peak_pick_deconv_menu_p->addAction(auto_peak_pick_deconv_mode_action_p);
  connect(static_cast<libXpertMassGui::LabelledCheckBoxAction *>(
            auto_peak_pick_deconv_mode_action_p),
          &libXpertMassGui::LabelledCheckBoxAction::checkBoxStateToggledSignal,
          this,
          &MassSpecTracePlotCompositeWidget::
            peakPickDeconvOnlyForVisibleRangeCheckBoxStateChanged);

  QString action_label;
  libXpertMassGui::ActionManager::ActionId action_id;
  QAction *action_p;

  action_label = "Extract centroids";
  action_id.initialize(
    "MassSpectrum", "CompositePlotWidget", "Analysis", action_label);
  action_p =
    getParentWnd()->getProgramWindow()->getActionManager()->installAction(
      action_id, QKeySequence("Ctrl+E, C"));
  action_p->setToolTip("Extract and display the centroids");
  connect(action_p,
          &QAction::triggered,
          this,
          &MassSpecTracePlotCompositeWidget::extractAndDisplayCentroids);
  auto_peak_pick_deconv_menu_p->addAction(action_p);


  action_label = "Low mass deconvolve";
  action_id.initialize(
    "MassSpectrum", "CompositePlotWidget", "Analysis", action_label);
  mrp_lowMassDeconvolveAction =
    getParentWnd()->getProgramWindow()->getActionManager()->installAction(
      action_id, QKeySequence("Ctrl+L, D"));
  mrp_lowMassDeconvolveAction->setToolTip(
    "Deconvolve the (centroided) spectrum");
  connect(mrp_lowMassDeconvolveAction,
          &QAction::triggered,
          this,
          &MassSpecTracePlotCompositeWidget::lowMassDeconvolve);
  auto_peak_pick_deconv_menu_p->addAction(mrp_lowMassDeconvolveAction);

  // mp_mainMenu->addSeparator();

  QAction *gaussian_fit_action_p =
    new QAction("Perform a &Gaussian fit on the visible maxima",
                dynamic_cast<QObject *>(this));
  gaussian_fit_action_p->setShortcut(QKeySequence("Ctrl+G, Ctrl+F"));
  gaussian_fit_action_p->setStatusTip(
    "Perform a Gaussian fit on the visible maxima");

  connect(gaussian_fit_action_p, &QAction::triggered, this, [this]() {
    gaussianFitOnVisibleMaxima();
  });

  mp_mainMenu->addAction(gaussian_fit_action_p);

  // qDebug() << "END extending the main menu:" << mp_mainMenu;
}

void
MassSpecTracePlotCompositeWidget::gaussianFitOnVisibleMaxima()
{

  // First we get the source graph that might be of interest to the user.

  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(this,
                               "Please select *one* trace",
                               "In order to perform a Gaussian fit, the source "
                               "graph needs to be selected.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return;
    }

  // Thus user has zoomed in such a manner that the monoisotopic pics of a
  // cluster are visible only on their upper part (no points at y=0 should be
  // visible).

  // Get the y range, so that we know what the bottom clipping end is (the floor
  // of the visible data).

  QCPRange plot_widget_x_axis_range = mp_plotWidget->xAxis->range();
  QCPRange plot_widget_y_axis_range = mp_plotWidget->yAxis->range();

  double y_floor_value = plot_widget_y_axis_range.lower;

  pappso::Trace data_trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(plot_widget_x_axis_range,
                static_cast<QCPGraph *>(source_plottable_p));

  // Now use the Trace filter to get a new trace with the local maxima points
  // only:

  pappso::Trace local_maxima_trace =
    flooredLocalMaxima(data_trace.begin(), data_trace.end(), y_floor_value);

  // We need to iterate in the data in search for all the points that are
  // between x range and in the same time greater or equal to the y floor value.

  // qDebug().noquote() << "\n" << local_maxima_trace.toString() << "\n";

  pappso::Trace gaussian_fit_trace;
  std::tuple<double, double, double> result_tuple =
    libXpertMassCore::Math::Gaussian::linearLeastSquaresFit(local_maxima_trace,
                                                            gaussian_fit_trace);

  // At this point, add the trace ! But only if it has points in it.

  if(!gaussian_fit_trace.size())
    {
      // qDebug() << "The Gaussian fit produced an empty trace. Plotting
      // nothing.";

      m_ui->statusLineEdit->setText(
        "The Gaussian fit produced an empty trace. Plotting nothing.");

      // qDebug() << "The Gaussian fit produced an empty trace. Plotting
      // nothing.";

      return;
    }

  // Create a temporary copy 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.

  const ProcessingFlow *temp_local_processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p)->clone(nullptr);

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getMsRunDataSetCstSPtrForPlottable(source_plottable_p);

  addTrace(gaussian_fit_trace,
           source_plottable_p,
           ms_run_data_set_csp,
           ms_run_data_set_csp->getMsRunId()->getSampleName(),
           *temp_local_processing_flow_p,
           source_plottable_p->pen().color());

  // Now inform the user about the Gaussian data:

  QString msg = QString(
                  "Gaussian fit:\n"
                  "Apex point: (%1,%2)\n"
                  "FWHM: %3\n")
                  .arg(std::get<0>(result_tuple), 0, 'f', 6)
                  .arg(std::get<1>(result_tuple), 0, 'f', 2)
                  .arg(std::get<2>(result_tuple), 0, 'f', 3);

  mp_parentWnd->getProgramWindow()->logColoredTextToConsole(
    msg, source_plottable_p->pen().color());

  delete temp_local_processing_flow_p;

  return;
}

void
MassSpecTracePlotCompositeWidget::gaussianFitOnCentroids()
{
  // TODO
  return;
}

void
MassSpecTracePlotCompositeWidget::plotWidgetMouseMoveDraggingCursor(
  QMouseEvent *event, const pappso::BasePlotContext &context)
{
  // qDebug() << "";

  Q_UNUSED(event);

  // We use the 'Z' charge determination process only with the left button.
  if(context.m_lastPressedMouseButton & Qt::MouseButton::LeftButton)
    {
      // This is a central point for data analysis features.

      // When the user presses the C keyboard key, that acts as a on/off
      // switch triggering the switch of the widget visibility. If the
      // widget exists and is visible that is the sign that the user is
      // willing to estimate charge/masses on the basis of the isotopic cluster
      // they are "selecting" by left-mouse-button-dragging the curser over.
      // This process ultimately leads to:
      // 1. the determination of the ion charge
      // 2. depending on the option selected by the user, the estimation
      // of the masses according to various algorithms (if selected item
      // is not "NOT_SET".

      if(mp_massModellingMethodSelectorWidget != nullptr &&
         mp_massModellingMethodSelectorWidget->isVisible())
        {
          // There is a subtility here: when the isotopic cluster-based charge
          // determination process is carried out, the centroids are plotted
          // as a QCPBars plot in the member mp_centroidsBars.
          // So getting the trace *graph* plot widget to serve as the mass
          // spectral data starting point is tricky and is the focus of the call
          // below.

          QCPGraph *trace_graph_p = graphPlotToBeUsedAsIntegrationSource();

          if(trace_graph_p == nullptr)
            {
              QMessageBox::information(
                this,
                "Charge determination with centroided isotopic cluster",
                "Make sure the mass spectrum graph plot is either unique or "
                "selected.",
                QMessageBox::Ok);

              return;
            }

          libXpertMassCore::IsotopicCluster::AverageMassDeterminationMethod
            mass_modelling_method = mp_massModellingMethodSelectorWidget
                                      ->getCurrentMassModellingMethod();

          qDebug() << "Mass modelling method"
                   << libXpertMassCore::IsotopicCluster::
                        averageMassDeterminationMethodMap.value(
                          static_cast<int>(mass_modelling_method));

          libXpertMassCore::IsotopicCluster isotopic_cluster;
          int charge = determineChargeOnIsotopicCluster(
            context, trace_graph_p, isotopic_cluster);

          // qDebug() << "Got charge:" << charge << "while isotopic
          // cluster's:"
          //          << isotopic_cluster.getCharge();

          Q_ASSERT(charge == isotopic_cluster.getCharge());

          if(!charge)
            return;

          // qDebug() << "Isotopic cluster centroids count:"
          //          << isotopic_cluster.getCentroidedTrace().size()
          //          << "Determined charge:" << charge;

          // If the C key is pressed, then go on and
          // estimate the mass and m/z (avg/mono) using the
          // intensity-weighted centroids method that provides an estimation
          // of the average m/z.

          bool should_print_mass_details = false;

          if(mass_modelling_method ==
             libXpertMassCore::IsotopicCluster::AverageMassDeterminationMethod::
               MZ_WEIGHTED_INTENSITIES)
            {
              // This is the default way of estimating the masses,
              // with the m/z weighted intensities.
              estimateMassesOnIsotopicClusterWeightedCentroids(
                context, trace_graph_p, isotopic_cluster);
              should_print_mass_details = true;
            }
          else if(mass_modelling_method == libXpertMassCore::IsotopicCluster::
                                             AverageMassDeterminationMethod::
                                               GAUSSIAN_FIT_LEAST_SQUARES ||
                  mass_modelling_method ==
                    libXpertMassCore::IsotopicCluster::
                      AverageMassDeterminationMethod::GAUSSIAN_FIT_LEV_MARQ)
            {
              estimateMassesOnIsotopicClusterGaussianFit(context,
                                                         trace_graph_p,
                                                         isotopic_cluster,
                                                         mass_modelling_method);
              should_print_mass_details = true;
            }
          else if(mass_modelling_method ==
                  libXpertMassCore::IsotopicCluster::
                    AverageMassDeterminationMethod::CLUSTER_MODE)
            {
              estimateMassesOnIsotopicClusterMode(
                context, trace_graph_p, isotopic_cluster);
              should_print_mass_details = true;
            }

          if(should_print_mass_details)
            {
              updateTextDetailsWidget(
                context,
                isotopic_cluster,
                mp_plotWidget->font(),
                libXpertMassCore::IsotopicCluster::OutputStringElements::
                    CHARGE_VALIDATION_RESULTS |
                  libXpertMassCore::IsotopicCluster::OutputStringElements::
                    MASS_ESTIMATIONS);
            }
          else
            {
              if(!isotopic_cluster.getCharge())
                return;

              // Only the Z key was pressed. Print the charge determination
              // data.
              updateTextDetailsWidget(
                context,
                isotopic_cluster,
                mp_plotWidget->font(),
                libXpertMassCore::IsotopicCluster::OutputStringElements::
                  CHARGE_VALIDATION_RESULTS);
            }
        }
    }
}

int
MassSpecTracePlotCompositeWidget::determineChargeOnIsotopicCluster(
  const pappso::BasePlotContext &context,
  QCPGraph *trace_graph_p,
  libXpertMassCore::IsotopicCluster &isotopic_cluster)
{

  // The user wants to calculate the z charge based on the centroids of
  // the currently spanned mass spectrum region.

  // qDebug() << "Ah, and Z is pressed ?!!! with range:"
  //          << context.m_xRegionRangeStart << "-"
  //          << context.m_xRegionRangeStop;/


  QCPRange x_range_of_interest(context.m_xRegionRangeStart,
                               context.m_xRegionRangeStop);

  pappso::Trace initial_trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(x_range_of_interest, trace_graph_p);

  if(initial_trace.size() < 3)
    {
      if(mp_textDetailsWidget != nullptr)
        mp_textDetailsWidget->hide();

      return 0;
    }

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  // qDebug() << "Got ProgramWindow pointer:" << program_wnd_p;

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  // qDebug() << "Got ApplicationPreferencesWnd pointer:"
  //          << application_preferences_wnd_p;

  QString params_message;

  std::tuple<
    libXpertMassCore::Processing::LocalSignalToNoiseEstimator::Parameters,
    libXpertMassCore::Processing::HighResPeakPickerParameters>
    param_tuple =
      application_preferences_wnd_p->getPeakPickingPreferences(params_message);

  libXpertMassCore::Processing::LocalSignalToNoiseEstimator::Parameters
    signal_to_noise_estimator_params = std::get<0>(param_tuple);

  // qDebug() << "Signal to noise estimator params:"
  //          << signal_to_noise_estimator_params.toString();

  libXpertMassCore::Processing::HighResPeakPickerParameters
    high_res_peak_picker_params = std::get<1>(param_tuple);
  // qDebug() << "High res peak picker params:"
  //          << high_res_peak_picker_params.toString();

  // We are not in the context in which we are extracting ALL the
  // centroids from either the full trace or only the visible region
  // of it. We are trying to extract clean centroids to compute a
  // charge. Thus, we need to avoid having contaminating centroids
  // in the calculations. For this, we only account centroids that
  // have a y threshold corresponding to the start drag point y
  // value.
  // high_res_peak_picker_params.minimumIntensity =
  // context.m_currentDragPoint.y();
  high_res_peak_picker_params.minimumIntensity = context.m_startDragPoint.y();

  isotopic_cluster.setProfileTrace(std::move(initial_trace));

  isotopic_cluster.setLocalSignalToNoiseEstimatorParameters(
    signal_to_noise_estimator_params);
  isotopic_cluster.setHighResPeakPickerParameters(high_res_peak_picker_params);

  int centroid_count = isotopic_cluster.extractCentroids();

  if(!centroid_count || centroid_count > 50)
    {
      if(mp_textDetailsWidget != nullptr)
        mp_textDetailsWidget->hide();

      if(mp_centroidsBars != nullptr)
        {
          mp_centroidsBars->setData({}, {});
        }

      return 0;
    }

  // Remember that the end point of the current block is to have the
  // charge of the ion beneath the isotopic cluster being hovered
  // over with the left mouse movement.

  libXpertMassCore::IsotopicCluster::ChargeValidationResults
    charge_validation_results;

  // We need to configure the charge determination process with the
  // user settings.

  std::tuple<double, double, double> user_set_config =
    application_preferences_wnd_p
      ->getIsotopicClusterChargeDeterminationPreferences();

  charge_validation_results.maxPpmRMSE    = std::get<0>(user_set_config);
  charge_validation_results.minR2         = std::get<1>(user_set_config);
  charge_validation_results.maxPairwiseCV = std::get<2>(user_set_config);

  int charge =
    isotopic_cluster.determineChargeAdvanced(charge_validation_results);

  Q_ASSERT(charge == isotopic_cluster.getCharge() &&
           isotopic_cluster.getCharge() ==
             charge_validation_results.putativeCharge);

  if(!charge)
    {
      if(mp_textDetailsWidget != nullptr)
        mp_textDetailsWidget->hide();

      if(mp_centroidsBars != nullptr)
        {
          mp_centroidsBars->setData({}, {});
        }

      return 0;
    }

  // Because we finally know that charge is valid, we can go on
  // and show the centroids.

  // Store the ranges to reinstate them later.
  // Erroneous: gives the whole plot key range.
  // QCPRange range = plottable_p->getKeyRange(got_range_ok);
  // Correct: gives the currently visible key ranges.
  QCPRange x_axis_range = trace_graph_p->keyAxis()->range();
  QCPRange y_axis_range = trace_graph_p->valueAxis()->range();

  if(mp_centroidsBars == nullptr)
    {
      MsRunDataSetCstSPtr ms_run_data_set_csp =
        getCstProcessingFlowPtrForPlottable(trace_graph_p)
          ->getMsRunDataSetCstSPtr();

      const ProcessingFlow *temp_local_processing_flow_p =
        getCstProcessingFlowPtrForPlottable(trace_graph_p)->clone(nullptr);
      // qDebug() << "The processing flow:"
      //          << temp_local_processing_flow_p->toString(0, "  ");

      mp_centroidsBars =
        addBars(isotopic_cluster.getCentroidedTrace(),
                trace_graph_p,
                ms_run_data_set_csp,
                ms_run_data_set_csp->getMsRunId()->getSampleName(),
                *temp_local_processing_flow_p,
                trace_graph_p->pen().color());

      QString plot_name =
        QString("%1/centroids")
          .arg(ms_run_data_set_csp->getMsRunId()->getSampleName());
      mp_centroidsBars->setName(plot_name);
      mp_centroidsBars->setObjectName("CentroidsBarsPlot");

      delete temp_local_processing_flow_p;

      // qDebug() << "Allocated new bars.";
    }
  else
    {
      QVector<double> x_values;
      for(double value : isotopic_cluster.getCentroidedTrace().xValues())
        x_values.append(value);
      QVector<double> y_values;
      for(double value : isotopic_cluster.getCentroidedTrace().yValues())
        y_values.append(value);

      mp_centroidsBars->setVisible(true);

      mp_centroidsBars->setData(x_values, y_values, true /*already_sorted*/);

      // qDebug() << "Set new data to existing bars.";
    }

  trace_graph_p->keyAxis()->setRange(x_axis_range);
  trace_graph_p->valueAxis()->setRange(y_axis_range);

  // The caller function will do this.
  // updateTextDetailsWidget(
  //   context, isotopic_cluster, trace_graph_p->keyAxis()->labelFont());

  // qDebug() << "Returning charge:" << charge;

  return charge;
}

void
MassSpecTracePlotCompositeWidget::
  estimateMassesOnIsotopicClusterWeightedCentroids(
    const pappso::BasePlotContext &context,
    QCPGraph *trace_graph_p,
    libXpertMassCore::IsotopicCluster &isotopic_cluster)
{
  // qDebug() << "m/z weighted intensities !";

  Q_UNUSED(context)
  Q_UNUSED(trace_graph_p)

  // Use the charge to compute the isotopic cluster
  // by knowing the elemental composition formula
  // of the neutral analyte. If we have it. Otherwise,
  // make a model Gaussian of the observed isotopic cluster
  // get to the neutral average mass of the analyte, compute
  // the averagine equivalent of that mass and
  // calculate the elemental composition of the
  // analyte and then compute the mono mass.

  // There should be at least two centroids in the cluster.

  if(isotopic_cluster.getCentroidedTrace().size() < 2)
    return;

  isotopic_cluster.determineAvgMr(
    libXpertMassCore::IsotopicCluster::AverageMassDeterminationMethod::
      MZ_WEIGHTED_INTENSITIES);

  // qDebug() << "Determined average m/z:" << isotopic_cluster.getAvgMz()
  //          << "Determined average Mr:" << isotopic_cluster.getAvgMr();

  if(!isotopic_cluster.getAvgMz() || !isotopic_cluster.getAvgMr())
    return;

  // At this point, estimate the monoisotopic mass using the
  // Averagine configured in the application preferences window.

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  QString averagine_formula =
    application_preferences_wnd_p
      ->getIsotopicClusterChargeDeterminationPreferencesAveragine();

  // qDebug() << "The averagine formula:" << averagine_formula;

  if(averagine_formula.isEmpty())
    {
      QString msg = QString(
        "Please set a correct Averagine formula in the "
        "application preferences window");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  QString isotopic_data_file_path =
    application_preferences_wnd_p
      ->getIsotopicClusterChargeDeterminationPreferencesIsotopicDataFilePath();

  // qDebug() << "The isotopic data file path:" << isotopic_data_file_path;

  QFileInfo file_info(isotopic_data_file_path);

  if(!file_info.exists())
    {
      QString msg = QString(
        "Please, set an existing isotopic data file in the "
        "application preferences window");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  // qDebug() << "Isotopic data file path:" << isotopic_data_file_path;

  // Since we provide the user with the ability to load a specific
  // isotopic data file, that needs to be
  // IsotopicDataUserConfigHandler because otherwise
  // (IsotopicDataLibraryHandler), the file name would be of no use:
  // the data would be loaded from the IsoSpec library material
  // (natural abundances for all the isotopes).
  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler;

  qsizetype loaded_isotope_count =
    isotopic_data_handler.loadData(isotopic_data_file_path);

  if(!loaded_isotope_count)
    {
      QString msg = QString("Failed to load any isotope.");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  // qDebug() << "Loaded isotopes:" << loaded_isotope_count;

  libXpertMassCore::Averagine averagine(
    libXpertMassCore::Formula(averagine_formula),
    isotopic_data_handler.getIsotopicData());

  if(!averagine.isValid())
    {
      QString msg = QString("Failed to allocate valid Averagine.");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  // Because we have already determined avg m/z, we can estimate estimate the
  // monoisotopic Mr.
  double mono_mass =
    // averagine.modelMonoMass(isotopic_cluster.getAvgMr(), ok);
    isotopic_cluster.estimateMonoMr(averagine);

  if(!mono_mass)
    {
      QString msg = QString("Failed to estimate monoisotopic mass (Mr).");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  double mono_mz = isotopic_cluster.estimateMonoMz(averagine);

  if(!mono_mz)
    {
      QString msg = QString("Failed to estimate monoisotopic m/z.");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  // qDebug() << qSetRealNumberPrecision(6)
  //          << "The modelled monoisotopic mass:" << mono_mass
  //          << "monoisotopic m/z:" << mono_mz;
}

void
MassSpecTracePlotCompositeWidget::estimateMassesOnIsotopicClusterGaussianFit(
  const pappso::BasePlotContext &context,
  QCPGraph *trace_graph_p,
  libXpertMassCore::IsotopicCluster &isotopic_cluster,
  libXpertMassCore::IsotopicCluster::AverageMassDeterminationMethod method)
{
  // qDebug() << "Gaussian Fit !";

  Q_UNUSED(context)
  Q_ASSERT(trace_graph_p != nullptr);

  // Use the charge to compute the isotopic cluster
  // by knowing the elemental composition formula
  // of the neutral analyte. If we have it. Otherwise,
  // make a model Gaussian of the observed isotopic cluster
  // get to the neutral average mass of the analyte, compute
  // the averagine equivalent of that mass and
  // calculate the elemental composition of the
  // analyte and then compute the mono mass.

  // There should be at least four centroids in the cluster.

  if(isotopic_cluster.getCentroidedTrace().size() < 4)
    return;

  isotopic_cluster.determineAvgMr(method);

  // qDebug() << "Determined average m/z:" << isotopic_cluster.getAvgMz()
  //          << "Determined average Mr:" << isotopic_cluster.getAvgMr();

  if(!isotopic_cluster.getAvgMz() || !isotopic_cluster.getAvgMr())
    return;

  // At this point, we know we have determined avg mass values, which
  // means the Gaussian fit went fine. Let's display it.

  if(mp_gaussianFitGraph == nullptr)
    {
      MsRunDataSetCstSPtr ms_run_data_set_csp =
        getCstProcessingFlowPtrForPlottable(trace_graph_p)
          ->getMsRunDataSetCstSPtr();

      const ProcessingFlow *temp_local_processing_flow_p =
        getCstProcessingFlowPtrForPlottable(trace_graph_p)->clone(nullptr);
      // qDebug() << "The processing flow:"
      //          << temp_local_processing_flow_p->toString(0, "  ");

      mp_gaussianFitGraph =
        addTrace(isotopic_cluster.getGaussianFitTrace(),
                 trace_graph_p,
                 ms_run_data_set_csp,
                 ms_run_data_set_csp->getMsRunId()->getSampleName(),
                 *temp_local_processing_flow_p,
                 trace_graph_p->pen().color());

      QString plot_name =
        QString("%1/Gaussian fit")
          .arg(ms_run_data_set_csp->getMsRunId()->getSampleName());
      mp_gaussianFitGraph->setName(plot_name);
      mp_gaussianFitGraph->setObjectName("GaussianFitGraphPlot");

      delete temp_local_processing_flow_p;

      // qDebug() << "Allocated new bars.";
    }
  else
    {
      QVector<double> x_values;
      for(double value : isotopic_cluster.getGaussianFitTrace().xValues())
        x_values.append(value);
      QVector<double> y_values;
      for(double value : isotopic_cluster.getGaussianFitTrace().yValues())
        y_values.append(value);

      mp_gaussianFitGraph->setVisible(true);

      mp_gaussianFitGraph->setData(x_values, y_values, true /*already_sorted*/);

      // qDebug() << "Set new data to existing bars.";
    }


  // At this point, estimate the monoisotopic mass using the
  // Averagine configured in the application preferences window.

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  QString averagine_formula =
    application_preferences_wnd_p
      ->getIsotopicClusterChargeDeterminationPreferencesAveragine();

  // qDebug() << "The averagine formula:" << averagine_formula;

  if(averagine_formula.isEmpty())
    {
      QString msg = QString(
        "Please set a correct Averagine formula in the "
        "application preferences window");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  QString isotopic_data_file_path =
    application_preferences_wnd_p
      ->getIsotopicClusterChargeDeterminationPreferencesIsotopicDataFilePath();

  // qDebug() << "The isotopic data file path:" << isotopic_data_file_path;

  QFileInfo file_info(isotopic_data_file_path);

  if(!file_info.exists())
    {
      QString msg = QString(
        "Please, set an existing isotopic data file in the "
        "application preferences window");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  // qDebug() << "Isotopic data file path:" << isotopic_data_file_path;

  // Since we provide the user with the ability to load a specific
  // isotopic data file, that needs to be
  // IsotopicDataUserConfigHandler because otherwise
  // (IsotopicDataLibraryHandler), the file name would be of no use:
  // the data would be loaded from the IsoSpec library material
  // (natural abundances for all the isotopes).
  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler;

  qsizetype loaded_isotope_count =
    isotopic_data_handler.loadData(isotopic_data_file_path);

  if(!loaded_isotope_count)
    {
      QString msg = QString("Failed to load any isotope.");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  // qDebug() << "Loaded isotopes:" << loaded_isotope_count;

  libXpertMassCore::Averagine averagine(
    libXpertMassCore::Formula(averagine_formula),
    isotopic_data_handler.getIsotopicData());

  if(!averagine.isValid())
    {
      QString msg = QString("Failed to allocate valid Averagine.");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  double mono_mass =
    // averagine.modelMonoMass(isotopic_cluster.getAvgMr(), ok);
    isotopic_cluster.estimateMonoMr(averagine);

  if(!mono_mass)
    {
      QString msg = QString("Failed to estimate monoisotopic mass (Mr).");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  double mono_mz = isotopic_cluster.estimateMonoMz(averagine);

  if(!mono_mz)
    {
      QString msg = QString("Failed to estimate monoisotopic m/z.");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }

  // qDebug() << qSetRealNumberPrecision(6)
  //          << "The modelled monoisotopic mass:" << mono_mass
  //          << "monoisotopic m/z:" << mono_mz;
}

void
MassSpecTracePlotCompositeWidget::estimateMassesOnIsotopicClusterMode(
  const pappso::BasePlotContext &context,
  QCPGraph *trace_graph_p,
  libXpertMassCore::IsotopicCluster &isotopic_cluster)
{
  // qDebug() << "Cluster mode!";

  Q_UNUSED(context)
  Q_ASSERT(trace_graph_p != nullptr);

  // Use the charge to first determine the avg m/z using the centroide
  // isotopic cluster's mode (the highest peak).

  // There should be at least two centroids in the cluster.

  if(isotopic_cluster.getCentroidedTrace().size() < 2)
    return;

  isotopic_cluster.determineAvgMr(
    libXpertMassCore::IsotopicCluster::AverageMassDeterminationMethod::
      CLUSTER_MODE);

  // qDebug() << qSetRealNumberPrecision(10)
  //          << "Determined average m/z:" << isotopic_cluster.getAvgMz()
  //          << "Determined average Mr:" << isotopic_cluster.getAvgMr();

  if(!isotopic_cluster.getAvgMz() || !isotopic_cluster.getAvgMr())
    return;

  // Now go on with the mono Mz estimation that will automatically compute
  // the mono Mr value first so that we have both values in the
  // text details widget.

  double mono_mz = isotopic_cluster.estimateMonoMz(
    libXpertMassCore::IsotopicCluster::AverageMassDeterminationMethod::
      CLUSTER_MODE);

  if(!mono_mz)
    {
      QString msg = QString("Failed to estimate monoisotopic m/z.");

      QMessageBox::information(
        this, "Averagine-based monoisotopic mass determination", msg);

      return;
    }
}


void
MassSpecTracePlotCompositeWidget::updateTextDetailsWidget(
  const pappso::BasePlotContext &context,
  const libXpertMassCore::IsotopicCluster &isotopic_cluster,
  const QFont &font,
  libXpertMassCore::IsotopicCluster::OutputStringElements
    output_string_elements)
{

  QPointF pixels_coordinates(
    mp_plotWidget->xAxis->coordToPixel(context.m_currentDragPoint.x()),
    mp_plotWidget->yAxis->coordToPixel(context.m_currentDragPoint.y()));

  QPoint global_mouse_point(
    mp_plotWidget->mapToGlobal(pixels_coordinates).toPoint());

  if(mp_textDetailsWidget == nullptr)
    {
      mp_textDetailsWidget = new libXpertMassGui::TextDetailsWidget(
        font, "Press SHIFT to pin", this);

      // Create the additional button for the copy centroids to
      // clipboard. Mind the setting up of the button so that
      // when clicked the button icon transitorially changes
      // to a check mark icon and then back to the initial button
      // icon.
      QPushButton *copy_centroids_to_clipboard_pushbutton_p =
        new QPushButton(this);

      // Make checkable to ease the icon management (see below).
      copy_centroids_to_clipboard_pushbutton_p->setCheckable(true);

      QIcon button_icon;
      // When QIcon::Off, that means the button has not be clicked, and
      // then that means that we want the copy text to clipboard icon.
      button_icon.addFile(":copy-centroids-to-clipboard-icon.svg",
                          QSize(),
                          QIcon::Normal,
                          QIcon::Off);

      // When QIcon::On, that means the button has been clicked: show
      // temporaneously the check mark icon to tell the user that the
      // button has been clicked and that the text has been copied (see
      // copyTextToClipboard().
      button_icon.addFile(
        ":black-check-mark-icon.svg", QSize(), QIcon::Normal, QIcon::On);

      copy_centroids_to_clipboard_pushbutton_p->setIcon(button_icon);

      copy_centroids_to_clipboard_pushbutton_p->setChecked(false);

      copy_centroids_to_clipboard_pushbutton_p->setIconSize(QSize(24, 24));
      copy_centroids_to_clipboard_pushbutton_p->setToolTip(
        "Copy centroids to clipboard");
      copy_centroids_to_clipboard_pushbutton_p->setFlat(true);
      copy_centroids_to_clipboard_pushbutton_p->setCursor(
        Qt::PointingHandCursor);
      copy_centroids_to_clipboard_pushbutton_p->setFixedSize(26, 26);

      connect(copy_centroids_to_clipboard_pushbutton_p,
              &QPushButton::clicked,
              this,
              [&]() {
                QPushButton *sender_button_p =
                  static_cast<QPushButton *>(sender());

                if(mp_centroidsBars == nullptr)
                  {
                    qDebug()
                      << "Returning because mp_centroidsBars is nullptr.";

                    return;
                  }

                QSharedPointer<QCPBarsDataContainer> data_sp =
                  mp_centroidsBars->data();

                auto iterator     = data_sp->begin();
                auto iterator_end = data_sp->end();

                // qDebug() << "There are " << data_sp->size() << "data points";

                QString text;

                while(iterator != iterator_end)
                  {
                    text += QString("%1 %2\n")
                              .arg(iterator->key, 0, 'f', 6)
                              .arg(iterator->value, 0, 'f', 6);
                    ++iterator;
                  }

                // qDebug() << "Setting" << text << "to the clipboard.";

                QApplication::clipboard()->setText(text);

                sender_button_p->setChecked(true);

                QTimer::singleShot(1000, this, [sender_button_p]() {
                  sender_button_p->setChecked(false);
                });
              });

      mp_textDetailsWidget->addButton(copy_centroids_to_clipboard_pushbutton_p,
                                      /*prepend*/ false);
    }

  bool should_pin_widget = context.m_keyboardModifiers & Qt::ShiftModifier;
  mp_textDetailsWidget->setPinned(should_pin_widget);
  qDebug() << "Pinning the text details widget?" << should_pin_widget;

  // qDebug() << "Going to showText()";

  mp_textDetailsWidget->showText(
    isotopic_cluster.toString(output_string_elements,
                              libXpertMassCore::Enums::TextType::HTML),
    global_mouse_point,
    /*is_html*/ true);

  // qDebug() << "Done showText()";

  // The display fails oddly in the text widget.
  // mp_textDetailsWidget->showText(
  //   isotopic_cluster.toString(libXpertMassCore::Enums::TextType::TEXT),
  //   global_mouse_point,  /*is_html*/ false);
}

int
MassSpecTracePlotCompositeWidget::extractCentroids(
  pappso::Trace &initial_trace,
  pappso::Trace &centroided_trace,
  const QCPRange &x_axis_range,
  QCPGraph **graph_plot_pp,
  double y_threshold)
{
  QCPGraph *trace_graph_p = nullptr;

  if(graph_plot_pp != nullptr && *graph_plot_pp != nullptr)
    trace_graph_p = *graph_plot_pp;
  else
    {
      trace_graph_p = graphPlotToBeUsedAsIntegrationSource();

      if(trace_graph_p == nullptr)
        {
          QMessageBox::information(
            this,
            "Peak picking / centroidation",
            "The plot of interest is not of type graph (curve).",
            QMessageBox::Ok);

          return 0;
        }
      else
        {
          if(graph_plot_pp != nullptr)
            *graph_plot_pp = trace_graph_p;
        }
    }

  initial_trace = static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                    ->toTrace(x_axis_range, trace_graph_p);

  if(initial_trace.size() < 3)
    {
      qCritical()
        << "Cannot extract centroids from a trace of less than 3 points.";
      return 0;
    }

  // qDebug()
  //   << "Now getting the parameters from the Application Preferences window.";

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  // qDebug() << "Got ProgramWindow pointer:" << program_wnd_p;

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  // qDebug() << "Got ApplicationPreferencesWnd pointer:"
  //          << application_preferences_wnd_p;

  QString params_message;

  std::tuple<
    libXpertMassCore::Processing::LocalSignalToNoiseEstimator::Parameters,
    libXpertMassCore::Processing::HighResPeakPickerParameters>
    param_tuple =
      application_preferences_wnd_p->getPeakPickingPreferences(params_message);

  libXpertMassCore::Processing::LocalSignalToNoiseEstimator::Parameters
    signal_to_noise_estimator_params = std::get<0>(param_tuple);

  // qDebug() << "Signal to noise estimator params:"
  //          << signal_to_noise_estimator_params.toString();

  libXpertMassCore::Processing::HighResPeakPickerParameters
    high_res_peak_picker_params = std::get<1>(param_tuple);
  // qDebug() << "High res peak picker params:"
  //          << high_res_peak_picker_params.toString();

  // At this point, we need to make sure we only try to extract centroids
  // for peaks that are meaningful from an isotopic cluster point of view.
  // Thus, the setting in the application preferences window is overridden.
  if(y_threshold < std::numeric_limits<double>::max())
    high_res_peak_picker_params.minimumIntensity = y_threshold;

  libXpertMassCore::Processing::HighResPeakPicker peak_picker(
    high_res_peak_picker_params, signal_to_noise_estimator_params);
  // qDebug() << "Done allocating peak picker.";

  QList<libXpertMassCore::Processing::HighResPeakPicker::PeakRange> peak_ranges;
  peak_picker.pick(initial_trace, centroided_trace, peak_ranges);

  int centroid_count = centroided_trace.size();
  // qDebug() << "Performed the centroidation, with" << centroid_count
  //          << "centroids";

  return centroid_count;
}

int
MassSpecTracePlotCompositeWidget::extractCentroids(
  pappso::Trace &initial_trace,
  pappso::Trace &centroided_trace,
  QCPGraph **graph_plot_pp)
{
  QCPGraph *trace_graph_p = nullptr;

  if(graph_plot_pp != nullptr && *graph_plot_pp != nullptr)
    trace_graph_p = *graph_plot_pp;
  else
    {
      trace_graph_p = graphPlotToBeUsedAsIntegrationSource();

      if(trace_graph_p == nullptr)
        {
          QMessageBox::information(
            this,
            "Peak picking / centroidation",
            "The plot of interest is not of type graph (curve).",
            QMessageBox::Ok);

          return 0;
        }
      else
        {
          if(graph_plot_pp != nullptr)
            *graph_plot_pp = trace_graph_p;
        }
    }

  QCPRange x_range_of_interest;

  if(m_isPeakPickDeconvOnlyForVisibleRange)
    x_range_of_interest = trace_graph_p->keyAxis()->range();
  else
    {
      bool found_range    = false;
      x_range_of_interest = trace_graph_p->getKeyRange(found_range);

      if(!found_range)
        {
          QSharedPointer<QCPGraphDataContainer> graph_data_container_p =
            trace_graph_p->data();

          auto beginIt              = graph_data_container_p->begin();
          x_range_of_interest.lower = beginIt->key;
          auto endIt                = graph_data_container_p->end();
          x_range_of_interest.upper = endIt->key;
        }
    }

  return extractCentroids(
    initial_trace, centroided_trace, x_range_of_interest, &trace_graph_p);
}

std::tuple<QCPBars *, pappso::Trace>
MassSpecTracePlotCompositeWidget::extractAndDisplayCentroids()
{
  // This slot is activated when a QAction is triggered. That unique
  // QAction might be referenced multiple times in various menus
  // inside various base trace plot composite widgets from various
  // plot windows. We thus need
  // to only act if the trigger is while this widget is actually focussed and
  // the window that contains it is actually active.
  if(!static_cast<QWidget *>(parent())->isActiveWindow() || !isFocussed())
    return std::make_tuple(nullptr, pappso::Trace());

  pappso::Trace initial_trace;
  pappso::Trace centroided_trace;

  // We will know in this variable what graph was used.
  QCPGraph *graph_plot_p = nullptr;

  extractCentroids(initial_trace, centroided_trace, &graph_plot_p);

  if(!centroided_trace.size())
    return std::tuple(nullptr, pappso::Trace());

  if(graph_plot_p == nullptr)
    return std::tuple(nullptr, pappso::Trace());

  // qDebug() << "Plottable:" << graph_plot_p
  //          << "with name:" << graph_plot_p->name();

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getCstProcessingFlowPtrForPlottable(graph_plot_p)->getMsRunDataSetCstSPtr();

  const ProcessingFlow *temp_local_processing_flow_p =
    getCstProcessingFlowPtrForPlottable(graph_plot_p)->clone(nullptr);
  // qDebug() << "The processing flow:"
  //          << temp_local_processing_flow_p->toString(0, "  ");

  // pappso::Utils::writeToFile(picked_peaks_trace.toString(),
  // "/tmp/picked-peaks.dat");

  // Store the range to reinstate it later.
  // Erroneous: gives the whole plot key range.
  // QCPRange range = graph_plot_p->getKeyRange(got_range_ok);
  // Correct: give the currently visible key range.
  QCPRange range = graph_plot_p->keyAxis()->range();

  QCPBars *qcp_centroid_bars =
    addBars(centroided_trace,
            graph_plot_p,
            ms_run_data_set_csp,
            ms_run_data_set_csp->getMsRunId()->getSampleName(),
            *temp_local_processing_flow_p,
            graph_plot_p->pen().color());

  QString plot_name =
    QString("%1/Centroids")
      .arg(ms_run_data_set_csp->getMsRunId()->getSampleName());
  qcp_centroid_bars->setName(plot_name);
  qcp_centroid_bars->setObjectName("CentroidBarPlot");

  delete temp_local_processing_flow_p;

  graph_plot_p->keyAxis()->setRange(range);

  // Just output the results in the console.

  QString output_text =
    QString("Extracted centroids for sample %1:\n%2\n\n")
      .arg(ms_run_data_set_csp->getMsRunId()->getSampleName(),
           centroided_trace.toString());

  QColor color = graph_plot_p->pen().color();
  mp_parentWnd->getProgramWindow()->logColoredTextToConsole(output_text, color);

  return std::make_tuple(qcp_centroid_bars, centroided_trace);
}

QCPAbstractPlottable *
MassSpecTracePlotCompositeWidget::togglePlotVisibilityFromMenuComboBox(
  int index)
{
  // Base class handling. Returns the plottable corresponding to the item
  // that was toggled in the combo box menu.
  QCPAbstractPlottable *plottable_p =
    BaseTracePlotCompositeWidget::togglePlotVisibilityFromMenuComboBox(index);

  // The visibility of the plottable has been handled in the base class. But
  // we have some more steps for plot that are specific to this mass spectrum
  // derived class.

  // Specific case for mass spectra traces:
  if(plottable_p != nullptr)
    {
      // Start by checking if the type of plot is QCPBars.
      QCPBars *bars_plot_p = dynamic_cast<QCPBars *>(plottable_p);

      if(bars_plot_p != nullptr && bars_plot_p == mp_centroidsBars)
        {
          if(mp_centroidsBars->visible())
            m_forceCentroidBarsVisibility = true;
          else
            m_forceCentroidBarsVisibility = false;
        }
      else
        {
          QCPGraph *graph_plot_p = dynamic_cast<QCPGraph *>(plottable_p);

          if(graph_plot_p != nullptr && graph_plot_p == mp_gaussianFitGraph)
            {
              if(mp_gaussianFitGraph->visible())
                m_forceGaussianFitGraphVisibility = true;
              else
                m_forceGaussianFitGraphVisibility = false;
            }
        }
    }

  return plottable_p;
}

QList<libXpertMassCore::Processing::DeconvolutedFeature>
MassSpecTracePlotCompositeWidget::lowMassDeconvolve()
{
  // If a deconvolution is already in progress, optionally cancel it or return
  if(m_deconvolutionInProgress)
    {
      QMessageBox::information(
        this,
        "Deconvolution in progress",
        "A deconvolution is already running. Please wait.",
        QMessageBox::Ok);
      return QList<libXpertMassCore::Processing::DeconvolutedFeature>();
    }

  QList<libXpertMassCore::Processing::DeconvolutedFeature>
    deconvoluted_features;

  if(!isFocussed())
    {
      // qDebug() << "The widget is not focussed, returning.";

      return deconvoluted_features;
    }

  // qDebug();

  // There can be a bar plot or a graph plot available. Only one graph/bar
  // plot can be selected.
  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(this,
                               "Please select *one* graph/bar plot",
                               "In order to perform a deconvolution, *one* "
                               "graph or bar plot has to be selected.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return deconvoluted_features;
    }

  pappso::Trace centroids;
  QCPBars *added_qcp_centroid_bars_plot_rp = nullptr;

  const QCPGraph *graph_plot_p = dynamic_cast<QCPGraph *>(source_plottable_p);
  if(graph_plot_p != nullptr)
    {
      // qDebug() << "The source plot is of type QCPGraph. Going to start with
      // "
      //             "extraction of the centroids";
      auto [the_added_qcp_centroid_bars_plot_rp, the_centroids] =
        extractAndDisplayCentroids();

      added_qcp_centroid_bars_plot_rp = the_added_qcp_centroid_bars_plot_rp;
      centroids                       = std::move(the_centroids);

      // qDebug() << "Extracted centroids:" << centroids.size();

      if(added_qcp_centroid_bars_plot_rp == nullptr || !centroids.size())
        {
          QMessageBox::warning(
            this,
            "Mass spectral centroids extraction (peak picking)",
            "Failed to extract any centroid.",
            QMessageBox::Ok);

          return deconvoluted_features;
        }

      // qDebug() << "There was creation of a new QCPBars plot.";
    }

  QCPBars *bars_plot_p = dynamic_cast<QCPBars *>(source_plottable_p);
  if(bars_plot_p != nullptr)
    {
      // qDebug()
      //   << "The source plot is of type QCPBar. Getting centroids out of
      //   it.";

      // We have a bar plot, that is a centroid plot!
      QSharedPointer<QCPBarsDataContainer> data = bars_plot_p->data();

      for(auto it = data->begin(); it != data->end(); ++it)
        {
          centroids.push_back({it->key, it->value});
        }

      // qDebug() << "Got" << centroids.size() << "centroids";
    }

  // We now are sure to have centroids. We can remove them if they were
  // created for this task only.

  if(added_qcp_centroid_bars_plot_rp != nullptr)
    {
      // The centroid bar plot was created here. Remove it.
      added_qcp_centroid_bars_plot_rp->parentPlot()->removePlottable(
        added_qcp_centroid_bars_plot_rp);
    }

  // We need to deconvolve the centroided mass spectrum. First construct stuff
  // needed for the deconvolver parameters.

  // qDebug()
  //   << "Now getting the parameters from the Application Preferences
  //   window.";

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  // qDebug() << "Got ProgramWindow pointer:" << program_wnd_p;

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  // qDebug() << "Got ApplicationPreferencesWnd pointer:"
  //          << application_preferences_wnd_p;

  libXpertMassCore::Processing::LowMassDeconvolverParameters deconvolver_params;

  bool ok = application_preferences_wnd_p->getLowMassDeconvolverPreferences(
    deconvolver_params);

  if(!ok)
    {
      QMessageBox::warning(this,
                           "Low mass deconvolver preference settings",
                           "Failed to get the parameters",
                           QMessageBox::Ok);

      return deconvoluted_features;
    }

  // One important consideration: if the log details file name has been
  // set and the file already exists, we do not want to truncate it (this is
  // what happens in the library), so we first do a name change and then
  // we set it back to the params.

  if(!deconvolver_params.processingDetailsLogFilePath.isEmpty())
    {
      QFileInfo file_info(deconvolver_params.processingDetailsLogFilePath);

      if(file_info.exists())
        {
          QDateTime date_time      = QDateTime::currentDateTime();
          QString date_time_string = date_time.toString("yyyyMMdd@hh_mm_ss");
          deconvolver_params.processingDetailsLogFilePath.append(
            QString("-%1").arg(date_time_string));
        }
    }

  // qDebug().noquote() << "Deconvolver parameters:"
  //                    << deconvolver_params.toString();

  // Configure the worker thread
  const ProcessingFlow *processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p);
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow_p->getMsRunDataSetCstSPtr();

  auto mass_spectrum_title =
    application_preferences_wnd_p->syntheticMassSpectrumSampleName();


  libXpertMassCore::Processing::LowMassDeconvolverWorker::Data worker_data;
  worker_data.centroids                = centroids;
  worker_data.centroidsSourcePlottable = source_plottable_p;
  worker_data.deconvolverParams        = deconvolver_params;
  worker_data.fullHeavyIsotopeLabelling =
    deconvolver_params.fullHeavyIsotopeLabelling;
  worker_data.sampleName = ms_run_data_set_csp->getMsRunId()->getSampleName();
  worker_data.sampleColorName =
    source_plottable_p->pen().color().name(QColor::HexRgb);
  worker_data.massSpectrumTitle = mass_spectrum_title;
  worker_data.shouldSynthesizeMassSpectrum =
    application_preferences_wnd_p->shouldSynthesizeMassSpectrum();
  worker_data.shouldIdentifyAnalytes =
    application_preferences_wnd_p->shouldIdentifyAnalytes();

  // Change cursor
  setCursor(Qt::WaitCursor);

  // Show that a deconvolution is in progress
  m_deconvolutionInProgress = true;

  // Create and start the thread
  mqp_deconvolutionThread = new QThread(this);
  mqp_deconvolutionWorker =
    new libXpertMassCore::Processing::LowMassDeconvolverWorker();

  mqp_deconvolutionWorker->setData(worker_data);
  mqp_deconvolutionWorker->moveToThread(mqp_deconvolutionThread);

  // Connect signals
  connect(mqp_deconvolutionThread,
          &QThread::started,
          mqp_deconvolutionWorker,
          &libXpertMassCore::Processing::LowMassDeconvolverWorker::process);

  connect(
    mqp_deconvolutionWorker,
    &libXpertMassCore::Processing::LowMassDeconvolverWorker::finishedSignal,
    this,
    &MassSpecTracePlotCompositeWidget::onDeconvolutionFinished);

  connect(mqp_deconvolutionWorker,
          &libXpertMassCore::Processing::LowMassDeconvolverWorker::errorSignal,
          this,
          &MassSpecTracePlotCompositeWidget::onDeconvolutionError);

  connect(
    mqp_deconvolutionWorker,
    &libXpertMassCore::Processing::LowMassDeconvolverWorker::progressSignal,
    this,
    &MassSpecTracePlotCompositeWidget::onDeconvolutionProgress);

  connect(mqp_deconvolutionWorker,
          &libXpertMassCore::Processing::LowMassDeconvolverWorker::
            statusMessageSignal,
          this,
          &MassSpecTracePlotCompositeWidget::onDeconvolutionStatus);

  // Clean up threads when done
  connect(
    mqp_deconvolutionWorker,
    &libXpertMassCore::Processing::LowMassDeconvolverWorker::finishedSignal,
    mqp_deconvolutionThread,
    &QThread::quit);

  connect(
    mqp_deconvolutionWorker,
    &libXpertMassCore::Processing::LowMassDeconvolverWorker::finishedSignal,
    mqp_deconvolutionWorker,
    &libXpertMassCore::Processing::LowMassDeconvolverWorker::deleteLater);

  connect(mqp_deconvolutionThread,
          &QThread::finished,
          mqp_deconvolutionThread,
          &QThread::deleteLater);

  // Start the thread
  mqp_deconvolutionThread->start();

  // Return empty list since results will come asynchronously
  return QList<libXpertMassCore::Processing::DeconvolutedFeature>();
}

void
MassSpecTracePlotCompositeWidget::onDeconvolutionFinished(
  QList<libXpertMassCore::Processing::DeconvolutedFeature>
    deconvoluted_featuress,
  void *centroids_source_plottable_p,
  const QString &sample_name,
  const QString &sample_color_name,
  bool should_synthesize,
  const QString &mass_spectrum_title,
  bool should_identify)
{
  // qDebug() << "Finished with" << deconvoluted_featuress.size()
  //          << "deconvoluted features.";
  // Reset UI state
  m_deconvolutionInProgress = false;
  setCursor(Qt::ArrowCursor);

  // Hide progress indicators
  // if(m_progressBar) m_progressBar->setVisible(false);
  // if(m_statusLabel) m_statusLabel->setVisible(false);

  // Re-enable the menu action
  // QAction *action = findChild<QAction*>("deconvolutionAction");
  // if(action) action->setEnabled(true);

  // Update sample name label
  m_ui->sampleNameLabel->setText(sample_name);

  // Format and display results
  QString deconv_text =
    QString("\n%1 deconvoluted feature(s) for sample %2:\n\n")
      .arg(deconvoluted_featuress.size())
      .arg(sample_name);

  if(!deconvoluted_featuress.size())
    {
      // qDebug() << "There were no deconvoluted features.";

      QMessageBox::information(this,
                               "Deconvolution - Minexpert3",
                               "There were no deconvoluted features.",
                               QMessageBox::Ok);
      return;
    }

  deconv_text += QString("\n%1 deconvoluted feature(s):\n\n")
                   .arg(deconvoluted_featuress.size());

  for(const libXpertMassCore::Processing::DeconvolutedFeature
        &deconvoluted_feature : std::as_const(deconvoluted_featuress))
    {
      deconv_text +=
        QString("----->\n%1\n")
          .arg(deconvoluted_feature.toString(true /*with_supporting_ions*/));
      deconv_text += "<-----\n\n";
    }


  // qDebug().noquote() << deconv_text;

  // Show completion message
  getParent()->setStatusTip(
    QString("Deconvolution finished successfully.\nFound %1 features.")
      .arg(deconvoluted_featuress.size()));
  // QMessageBox::information(
  //   this,
  //   "Deconvolution Complete",
  //   QString("Deconvolution finished successfully.\nFound %1 features.")
  //     .arg(results.size()),
  //   QMessageBox::Ok);

  // Log to console with color
  mp_parentWnd->getProgramWindow()->logColoredTextToConsole(deconv_text,
                                                            sample_color_name);

  // Synthesize mass spectrum if requested
  if(should_synthesize)
    {
      // Need to get source_plottable_p again? Better to store a reference
      // or pass it through. For now, you might need to recapture it
      // or store it as a member variable before starting the thread
      synthesizeMassSpectrum(
        deconvoluted_featuress,
        reinterpret_cast<QCPAbstractPlottable *>(centroids_source_plottable_p),
        sample_name,
        sample_color_name,
        mass_spectrum_title,
        should_identify);
    }

  // Identify analytes if requested
  if(should_identify)
    {
      // This will run in its own thread.
      identifyAnalytes(
        deconvoluted_featuress,
        reinterpret_cast<QCPAbstractPlottable *>(centroids_source_plottable_p));
    }
}

void
MassSpecTracePlotCompositeWidget::onDeconvolutionError(const QString &message)
{
  Q_UNUSED(message)
  // qDebug() << "Error:" << message;
}

void
MassSpecTracePlotCompositeWidget::onDeconvolutionProgress(int percent)
{
  Q_UNUSED(percent)
  // qDebug() << "Progress:" << percent;
}

void
MassSpecTracePlotCompositeWidget::onDeconvolutionStatus(const QString &message)
{
  Q_UNUSED(message)
  // qDebug() << "Status:" << message;
}

pappso::Trace
MassSpecTracePlotCompositeWidget::synthesizeMassSpectrumOld(
  QList<libXpertMassCore::Processing::DeconvolutedFeature>
    &deconvoluted_features,
  QCPAbstractPlottable *source_plottable_p)
{
  // qDebug() << "Should synthesize mass spectrum !";

  pappso::Trace mass_spectral_trace;

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  // qDebug() << "Got ProgramWindow pointer:" << program_wnd_p;

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  libXpertMassCore::MassPeakShaperConfig mass_peak_shaper_config;

  if(!application_preferences_wnd_p->getMassPeakShaperConfigPreferences(
       mass_peak_shaper_config))
    return mass_spectral_trace;

  libXpertMassCore::ErrorList error_list;
  if(!mass_peak_shaper_config.resolve(error_list))
    {
      QMessageBox msgBox;
      QString msg =
        QString("Mass peak shaper configuration errors:\n%1")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list, "\n"));
      msgBox.setText(msg);
      msgBox.exec();

      return mass_spectral_trace;
    }

  std::vector<libXpertMassCore::IsotopicClusterChargePair>
    isotopic_cluster_charge_pairs;

  //  At this point we can actually perform the mass spectrum synthesis.

  for(libXpertMassCore::Processing::DeconvolutedFeature &deconvoluted_feature :
      deconvoluted_features)
    {
      libXpertMassCore::IsotopicClusterChargePair isotopic_cluster_charge_pair;

      for(libXpertMassCore::Processing::SupportingIon &supporting_ion :
          deconvoluted_feature.supportingIons)
        {
          isotopic_cluster_charge_pairs.push_back(
            {std::make_shared<pappso::Trace>(
               *(supporting_ion.matchingIsotopicClusterQSPtr)),
             1});
        }
    }

  libXpertMassCore::IsotopicClusterShaper isotopic_cluster_shaper(
    isotopic_cluster_charge_pairs, mass_peak_shaper_config);

  // Now that all the isotopic clusters have been set to the shaper, we can
  // force the m/z range, if so is requested. The bins will be created
  // once for that m/z range, which is good for combination because
  // the bins are always the same.

  QPair<double, double> mz_range_pair =
    application_preferences_wnd_p->getMassPeakShaperConfigWidget()
      ->getMzRangeValuePair();

  if(mz_range_pair.first >= 0 && mz_range_pair.second > 0)
    isotopic_cluster_shaper.forciblySetMzRange(mz_range_pair.first,
                                               mz_range_pair.second);

  // And now run the shaper.
  mass_spectral_trace = isotopic_cluster_shaper.run();

  const ProcessingFlow *processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p);
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow_p->getMsRunDataSetCstSPtr();

  const ProcessingFlow *temp_local_processing_flow_p =
    processing_flow_p->clone(nullptr);

  // Store the range to reinstate it later.
  bool got_range_ok;
  QCPRange range = source_plottable_p->getKeyRange(got_range_ok);

  // qDebug() << "Creating new trace.";

  QString sample_name =
    application_preferences_wnd_p->syntheticMassSpectrumSampleName();

  QString purified_sample_name =
    libXpertMassCore::Utils::sanitizeFileName(sample_name);

  // qDebug() << "Purified sample name:" << purified_sample_name;

  static_cast<Application *>(qApp)
    ->getProgramWindow()
    ->displayMassSpectralTrace(
      mass_spectral_trace, *temp_local_processing_flow_p, purified_sample_name);

  delete temp_local_processing_flow_p;

  if(got_range_ok)
    {
      source_plottable_p->keyAxis()->setRange(range);
    }

  return mass_spectral_trace;
}

void
MassSpecTracePlotCompositeWidget::synthesizeMassSpectrum(
  QList<libXpertMassCore::Processing::DeconvolutedFeature>
    &deconvoluted_features,
  QCPAbstractPlottable *source_plottable_p,
  const QString &sample_name,
  const QString &sample_color_name,
  const QString &mass_spectrum_title,
  bool should_identify)
{
  // qDebug() << "Should synthesize mass spectrum !";

  pappso::Trace mass_spectral_trace;

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  // qDebug() << "Got ProgramWindow pointer:" << program_wnd_p;

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  libXpertMassCore::MassPeakShaperConfig mass_peak_shaper_config;

  if(!application_preferences_wnd_p->getMassPeakShaperConfigPreferences(
       mass_peak_shaper_config))
    return;

  libXpertMassCore::ErrorList *error_list_p = new libXpertMassCore::ErrorList;

  if(!mass_peak_shaper_config.resolve(*error_list_p))
    {
      QMessageBox msgBox;
      QString msg =
        QString("Mass peak shaper configuration errors:\n%1")
          .arg(libXpertMassCore::Utils::joinErrorList(*error_list_p, "\n"));
      msgBox.setText(msg);
      msgBox.exec();

      return;
    }

  libXpertMassCore::MassSpectrumSynthesizer::Data data;

  // No need because we are only going to shape already computed
  // isotopic clusters.
  data.isotopicDataSPtr = nullptr;
  data.massPeakShaperConfig.initialize(mass_peak_shaper_config);
  data.massSpectrumTitle     = mass_spectrum_title;
  data.massSpectrumColorName = sample_color_name;
  data.errorList             = error_list_p;

  // Has the user set a fixed m/z range for the final mass spectrum ?

  QPair<double, double> fixed_mz_range_pair =
    application_preferences_wnd_p->getMassPeakShaperConfigWidget()
      ->getMzRangeValuePair();

  bool fixed_mz_range = fixed_mz_range_pair.second ? true : false;

  if(fixed_mz_range)
    {
      data.fixedMzRangeStart = fixed_mz_range_pair.first;
      data.fixedMzRangeStop  = fixed_mz_range_pair.second;
    }

  std::vector<libXpertMassCore::IsotopicClusterChargePair>
    isotopic_cluster_charge_pairs;

  //  At this point we can actually perform the mass spectrum synthesis.

  for(libXpertMassCore::Processing::DeconvolutedFeature &deconvoluted_feature :
      deconvoluted_features)
    {
      libXpertMassCore::IsotopicClusterChargePair isotopic_cluster_charge_pair;

      for(libXpertMassCore::Processing::SupportingIon &supporting_ion :
          deconvoluted_feature.supportingIons)
        {
          isotopic_cluster_charge_pairs.push_back(
            {std::make_shared<pappso::Trace>(
               *(supporting_ion.matchingIsotopicClusterQSPtr)),
             1});
        }
    }

  UserData *user_data_p = new UserData();

  user_data_p->initialSourcePlottable = source_plottable_p;
  user_data_p->shouldIdentify         = should_identify;

  QFont *label_font_rp = application_preferences_wnd_p->getLabelFont();

  if(label_font_rp != nullptr)
    user_data_p->labelFontString = label_font_rp->toString();
  else
    user_data_p->labelFontString =
      source_plottable_p->keyAxis()->labelFont().toString();

  user_data_p->labelListName =
    application_preferences_wnd_p->getLabelListName();

  user_data_p->shouldWriteLabels =
    application_preferences_wnd_p->shouldLabelPeaksWithAnalyteId();

  user_data_p->shouldAddLabelToList =
    application_preferences_wnd_p->shouldAddNewLabels();


  // Change cursor
  setCursor(Qt::WaitCursor);

  // Show that mass spectrum synthesis is in progress
  m_spectrumSynthesisInProgress = true;

  // Create and start the thread
  mqp_spectrumSynthesizerThread = new QThread(this);
  mqp_massSpectrumSynthesizer =
    new libXpertMassCore::MassSpectrumSynthesizer(data, nullptr);

  mqp_massSpectrumSynthesizer->moveToThread(mqp_spectrumSynthesizerThread);

  mqp_massSpectrumSynthesizer->setIsotopicClusterChargePairs(
    std::move(isotopic_cluster_charge_pairs));

  mqp_massSpectrumSynthesizer->setUserData(static_cast<void *>(user_data_p));

  // Connect signals

  connect(mqp_spectrumSynthesizerThread,
          &QThread::started,
          mqp_massSpectrumSynthesizer,
          &libXpertMassCore::MassSpectrumSynthesizer::run);


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

  static_cast<Application *>(qApp)->getProgramWindow()->showTaskMonitorWnd();

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    static_cast<Application *>(qApp)
      ->getProgramWindow()
      ->getTaskMonitorWnd()
      ->addTaskMonitorWidget(Qt::red);

  task_monitor_composite_widget_p->taskSetMainIdentifierText(sample_name);

  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Sythesizing mass spectrum");

  task_monitor_composite_widget_p->taskSetMinValue(0);

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mqp_massSpectrumSynthesizer.get());

  connect(mqp_massSpectrumSynthesizer,
          &libXpertMassCore::MassSpectrumSynthesizer::finishedSignal,
          this,
          &MassSpecTracePlotCompositeWidget::onMassSpectrumSynthesizerFinished);

  connect(mqp_massSpectrumSynthesizer,
          &libXpertMassCore::MassSpectrumSynthesizer::taskErrorSignal,
          this,
          &MassSpecTracePlotCompositeWidget::
            onMassSpectrumSynthesizerTaskErrorSignal);

  // Clean up threads when done
  connect(mqp_massSpectrumSynthesizer,
          &libXpertMassCore::MassSpectrumSynthesizer::finishedSignal,
          mqp_spectrumSynthesizerThread,
          &QThread::quit);

  connect(mqp_spectrumSynthesizerThread,
          &QThread::finished,
          mqp_spectrumSynthesizerThread,
          &QThread::deleteLater);

  // Start the thread
  mqp_spectrumSynthesizerThread->start();
}

void
MassSpecTracePlotCompositeWidget::onMassSpectrumSynthesizerFinished(
  pappso::Trace synthetic_mass_spectrum,
  QString mass_spectum_title,
  QString mass_spectrum_color,
  void *user_data_vp)
{
  Q_UNUSED(mass_spectrum_color)

  setCursor(Qt::ArrowCursor);

  UserData *user_data_p = reinterpret_cast<UserData *>(user_data_vp);

  if(user_data_p == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  QCPAbstractPlottable *source_plottable_p =
    reinterpret_cast<QCPAbstractPlottable *>(
      user_data_p->initialSourcePlottable);

  if(source_plottable_p == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  // Now we have to display the mass spectrum.

  const ProcessingFlow *processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p);

  if(processing_flow_p == nullptr)
    {
      qDebug() << "Could no get the processing flow for the plottable. Maybe "
                  "the plottable is no more?";
      return;
    }

  const ProcessingFlow *temp_local_processing_flow_p =
    processing_flow_p->clone(nullptr);

  static_cast<Application *>(qApp)
    ->getProgramWindow()
    ->displayMassSpectralTrace(synthetic_mass_spectrum,
                               *temp_local_processing_flow_p,
                               mass_spectum_title);

  delete temp_local_processing_flow_p;
}

void
MassSpecTracePlotCompositeWidget::onMassSpectrumSynthesizerTaskErrorSignal(
  MsXpS::libXpertMassCore::ErrorList *error_list_p)
{
  qDebug() << "There were errors:";
  qDebug() << libXpertMassCore::Utils::joinErrorList(*error_list_p);
}

void
MassSpecTracePlotCompositeWidget::identifyAnalytes(
  QList<libXpertMassCore::Processing::DeconvolutedFeature>
    &deconvoluted_features,
  QCPAbstractPlottable *source_plottable_p)
{
  if(!deconvoluted_features.size())
    {
      QMessageBox::warning(
        this,
        "Deconvolved mass-vs-id analyte identification",
        "There were no deconvoluted features. Cannot assign peaks.",
        QMessageBox::Ok);

      return;
    }

  // At this point, try to make the matching with the dictionary.

  ProgramWindow *program_wnd_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  // qDebug() << "Got ProgramWindow pointer:" << program_wnd_p;

  const ApplicationPreferencesWnd *application_preferences_wnd_p =
    program_wnd_p->getApplicationPreferencesWnd();

  libXpertMassCore::Processing::DeconvolutedMassIdMatcherParameters
    mass_id_matcher_params;

  bool ok = application_preferences_wnd_p->getAnalyteIdentificationPreferences(
    mass_id_matcher_params);

  if(!ok)
    {
      QMessageBox::warning(this,
                           "Deconvolved mass-vs-id analyte identification",
                           "Failed to get the parameters",
                           QMessageBox::Ok);

      return;
    }

  // qDebug() << "The isotopic data file path:"
  //          << mass_id_matcher_params.isotopicDataFilePath;


  // One important consideration: if the log details file name has been
  // set and the file already exists, we do not want to truncate it (this is
  // what happens in the library), so we first do a name change and then
  // we set it back to the params.

  if(!mass_id_matcher_params.processingDetailsLogFilePath.isEmpty())
    {
      QFileInfo file_info(mass_id_matcher_params.processingDetailsLogFilePath);

      if(file_info.exists())
        {
          QDateTime date_time      = QDateTime::currentDateTime();
          QString date_time_string = date_time.toString("yyyyMMdd@hh_mm_ss");
          mass_id_matcher_params.processingDetailsLogFilePath.append(
            QString("-%1").arg(date_time_string));
        }
    }

  // qDebug().noquote() << "Deconvolved mass-vs-id analyte identifier
  // parameters:"
  //                    << mass_id_matcher_params.toString();


  // QString isotopic_data_file_path =
  //   "/home/rusconi/labo/isospec-isotopic-files/light-c12-n14.dat";

  // QString dictionary_file_path =
  //   "/home/rusconi/devel/minexpert/development/plugins/common/dictionaries/"
  //   "Mass_combinations_Ecoli_v21_results.csv";

  // QString dictionary_file_path =
  //   "/home/rusconi/devel/minexpert/development/plugins/common/dictionaries/"
  //   "Mass_combinations_PG_Staph_aureus_v22D_trimers.csv";


  // The identification is computing-intensive, we want to put that
  // in another thread, like we did for the low mass deconvolution.

  QSharedPointer<QList<libXpertMassCore::Processing::DeconvolutedFeature>>
    deconvoluted_features_qsp =
      QSharedPointer<QList<libXpertMassCore::Processing::DeconvolutedFeature>>::
        create(deconvoluted_features);

  // Configure the worker thread

  if(!source_plottable_p)
    {
      qCritical() << "The source plottable is no more available. Returning.";
      return;
    }

  const ProcessingFlow *processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p);
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow_p->getMsRunDataSetCstSPtr();

  QColor label_color = application_preferences_wnd_p->getLabelColor();
  QFont label_font;
  QFont *label_font_rp = application_preferences_wnd_p->getLabelFont();

  if(label_font_rp != nullptr)
    label_font = *label_font_rp;
  else
    label_font = source_plottable_p->keyAxis()->labelFont();

  QString label_list_name = application_preferences_wnd_p->getLabelListName();
  bool should_add_labels_to_list =
    application_preferences_wnd_p->shouldAddNewLabels();
  bool should_write_labels =
    application_preferences_wnd_p->shouldLabelPeaksWithAnalyteId();

  libXpertMassCore::Processing::DeconvolutedMassIdMatcher::Data context_data;
  context_data.deconvolutedFeaturesQSPtr = deconvoluted_features_qsp;
  context_data.initialSourcePlottable    = source_plottable_p;
  context_data.sampleName = ms_run_data_set_csp->getMsRunId()->getSampleName();
  context_data.labelColorString     = label_color.name(QColor::HexRgb);
  context_data.labelFontString      = label_font.toString();
  context_data.labelListName        = label_list_name;
  context_data.shouldWriteLabels    = should_write_labels;
  context_data.shouldAddLabelToList = should_add_labels_to_list;

  // Change cursor
  setCursor(Qt::WaitCursor);

  // Show that a deconvolution is in progress
  m_analyteIdentificationInProgress = true;

  // Create and start the thread
  mqp_analyteIdentificationThread = new QThread(this);
  mqp_deconvolutedMassIdMatcher =
    new libXpertMassCore::Processing::DeconvolutedMassIdMatcher(
      mass_id_matcher_params, deconvoluted_features_qsp);

  mqp_deconvolutedMassIdMatcher->setData(context_data);
  mqp_deconvolutedMassIdMatcher->moveToThread(mqp_analyteIdentificationThread);

  // Connect signals
  connect(mqp_analyteIdentificationThread,
          &QThread::started,
          mqp_deconvolutedMassIdMatcher,
          &libXpertMassCore::Processing::DeconvolutedMassIdMatcher::run);


  // Allocate a new TaskMonitorCompositeWidgetNeww 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 =
    program_wnd_p->getTaskMonitorWnd()->addTaskMonitorWidget(Qt::red);

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Identifying analytes using a Mr vs Id dictionary file.");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mqp_deconvolutedMassIdMatcher.get());

  connect(
    mqp_deconvolutedMassIdMatcher,
    &libXpertMassCore::Processing::DeconvolutedMassIdMatcher::finishedSignal,
    this,
    &MassSpecTracePlotCompositeWidget::onAnalyteIdentificationFinished);

  // Clean up threads when done
  connect(
    mqp_deconvolutedMassIdMatcher,
    &libXpertMassCore::Processing::DeconvolutedMassIdMatcher::finishedSignal,
    mqp_analyteIdentificationThread,
    &QThread::quit);

  // We cannot destroy mqp_deconvolutedMassIdMatcher because it
  // belongs to this and also because we use it in
  // onAnalyteIdentificationFinished().
  // connect(mqp_deconvolutedMassIdMatcher,
  //         &libXpertMassCore::Processing::DeconvolutedMassIdMatcher::finishedSignal,
  //         mqp_deconvolutedMassIdMatcher,
  //         &libXpertMassCore::Processing::DeconvolutedMassIdMatcher::deleteLater);

  connect(mqp_analyteIdentificationThread,
          &QThread::finished,
          mqp_analyteIdentificationThread,
          &QThread::deleteLater);

  // Start the thread
  mqp_analyteIdentificationThread->start();

  return;
}

void
MassSpecTracePlotCompositeWidget::onAnalyteIdentificationFinished(
  void *initial_source_plottable,
  const QString &sample_name,
  const QString &label_color_string,
  const QString &label_font_string,
  const QString &label_list_name,
  bool should_write_labels,
  bool should_add_label_to_list)
{
  // qDebug() << "Finished the analyte identification with"
  //          << mqp_deconvolutedMassIdMatcher->getMassIdMatches().size()
  //          << "matches.";

  setCursor(Qt::ArrowCursor);

  QCPAbstractPlottable *source_plottable_p =
    reinterpret_cast<QCPAbstractPlottable *>(initial_source_plottable);

  if(source_plottable_p == nullptr)
    {
      qCritical() << "The source plottable is no more available. Return.";
    }

  const ProcessingFlow *processing_flow_p =
    getCstProcessingFlowPtrForPlottable(source_plottable_p);
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow_p->getMsRunDataSetCstSPtr();

  // qDebug() << "Now outputting the" << mass_id_matches.size()
  //          << "matches.";

  QString matches_text =
    QString("Analyte identification results for sample %1:\n").arg(sample_name);

  const QList<QSharedPointer<
    libXpertMassCore::Processing::DeconvolutedMassIdMatcher::MassIdMatch>>
    &mass_id_matches = mqp_deconvolutedMassIdMatcher->getMassIdMatches();

  for(int iter = 0; iter < mass_id_matches.size(); ++iter)
    {
      const QSharedPointer<
        libXpertMassCore::Processing::DeconvolutedMassIdMatcher::MassIdMatch>
        &mass_id_match_qsp = mass_id_matches.at(iter);

      matches_text += mass_id_match_qsp->toString() + "\n";
    }

  // qDebug().noquote() << matches_text;
  mp_parentWnd->getProgramWindow()->logColoredTextToConsole(
    matches_text, source_plottable_p->pen().color());

  // int assignment_count =
  //   mqp_deconvolutedMassIdMatcher->getMassIdMatches().size();

  // qDebug() << "Done handling the mass vs id matches, with " <<
  // assignment_count
  //          << "assignments.";

  if(should_write_labels)
    {
      // What we could do here is label the peaks that have
      // allowed identifying an analyte.

      // Let's check if the user has set a label list name.

      QString final_label_list_name = label_list_name;

      // qDebug() << "The requested label list name:" <<
      // final_label_list_name;

      TracePlotLabelListQSPtr trace_plot_label_list_qsp = nullptr;

      QFont label_font;
      label_font.fromString(label_font_string);

      if(!m_nameLabelListMap.contains(final_label_list_name))
        {
          if(final_label_list_name.isEmpty())
            final_label_list_name = "UntitledLabelList";

          // Create the list of trace plot labels
          trace_plot_label_list_qsp =
            QSharedPointer<TracePlotLabelList>::create(
              final_label_list_name,
              "labelsLayer",
              mp_plotWidget,
              /*should_manage_layout*/ true,
              /*x_axis_delta_trigger*/ 0.1,
              /*label_vertical_shift_count_factor*/ 2,
              label_font,
              QColor(label_color_string));

          m_nameLabelListMap[final_label_list_name] = trace_plot_label_list_qsp;
        }
      else
        {
          // The map contains a key by the name, so we also have the
          // pointer to the list map.
          trace_plot_label_list_qsp =
            m_nameLabelListMap.value(final_label_list_name);
          Q_ASSERT(trace_plot_label_list_qsp != nullptr);
        }

      if(!should_add_label_to_list)
        {
          // qDebug() << "Asking that old labels be cleared.";

          trace_plot_label_list_qsp->eraseLabels();
        }

      int label_count = 0;

      for(int iter = 0; iter < mass_id_matches.size(); ++iter)
        {
          const QSharedPointer<libXpertMassCore::Processing::
                                 DeconvolutedMassIdMatcher::MassIdMatch>
            &mass_id_match_qsp = mass_id_matches.at(iter);

          // We need to get the supporting ions.

          QSharedPointer<libXpertMassCore::Processing::DeconvolutedFeature>
            deconvoluted_feature_qsp =
              mass_id_match_qsp->deconvolutedFeatureQsp;

          std::vector<libXpertMassCore::Processing::SupportingIon>
            supporting_ions = deconvoluted_feature_qsp->supportingIons;


          for(const libXpertMassCore::Processing::SupportingIon
                &supporting_ion : supporting_ions)
            {
              QString label_text =
                QString("%1 - %2")
                  .arg(mass_id_match_qsp->id)
                  .arg(mass_id_match_qsp->massError, 0, 'f', 4);

              QPointF data_pointf(supporting_ion.monoisotopicMz,
                                  supporting_ion.intensity);

              // Do not replot each time a label is added, we'll
              // ask for replot at the end.
              trace_plot_label_list_qsp->newLabel(
                label_text, data_pointf, false /*should_replot*/);

              ++label_count;
            }
        }
      // End of
      // for(int iter = 0; iter < mass_id_matches.size(); ++iter)

      // qDebug() << label_count << "labels have been written.";

      // Now that we have finished writing all the labels, we can replot.
      mp_plotWidget->replot();
    }
  // End of
  // if(application_preferences_wnd_p->shouldLabelPeaksWithAnalyteId())

  // Do not do this, makes the program crash when the
  // composite widget is closed.
  // delete mqp_deconvolutedMassIdMatcher;
  // mqp_deconvolutedMassIdMatcher = 0;
}

void
MassSpecTracePlotCompositeWidget::plotWidgetKeyPressEvent(
  QKeyEvent *event, const pappso::MassSpecTracePlotContext &context)
{
  qDebug().noquote() << "ENTER with MassSpecTracePlotContext:"
                     << context.toString();

  // First call the base class that will fill-in the generic fields if the
  // space char was pressed.
  BaseTracePlotCompositeWidget::plotWidgetKeyPressEvent(event, context);

  // qDebug() << "After filling-in by base class, the stanza is:"
  //<< m_analysisStanza;

  m_plotWidgetPressedKeyCode = context.m_pressedKeyCode;

  // Now handle here the mass specific data, like z, Mr, m/z.
  if(context.m_pressedKeyCode == Qt::Key_Space)
    {
      QCPAbstractPlottable *plottable_p =
        plottableToBeUsedAsIntegrationSource();

      if(plottable_p == nullptr)
        {
          // qDebug()
          //   << "Returned plottable is nullptr (there might be more than
          //   one).";

#if 0
            // We actually do not do this because that part is handled by the base
            // class.

            // We should inform the user if there are more than one plottable.

            int plottable_count = mp_plotWidget->plottableCount();

            if(plottable_count > 1)
            {
                //qDebug();
                QMessageBox::information(
                    this,
                    "Crafting an analysis stanza for a graph",
                    "Please select a single trace and try again.");
            }
#endif

          // No plottable available, nothing to do.
          return;
        }

      QString stanza = craftAnalysisStanza(context);

      // qDebug() << "analysis stanza:" << stanza;

      // At this point, the analysis stanza can be effectively recorded.

      mp_parentWnd->recordAnalysisStanza(stanza, plottable_p->pen().color());
    }
  // End of
  // if(context.m_pressedKeyCode == Qt::Key_Space)
  else if(context.m_pressedKeyCode == Qt::Key_M)
    {
      // The M (lowercase) keyboard key triggers copying the Mr value to
      // the clipboard when a deconvolution was performed.

      QString mr_value_as_string =
        QString("%1").arg(context.m_lastMr, 0, 'f', 6);

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

      QClipboard *clipboard_p = QApplication::clipboard();
      clipboard_p->setText(mr_value_as_string, QClipboard::Clipboard);
    }
  // End of
  // else if(context.m_pressedKeyCode == Qt::Key_M)
  else if(context.m_pressedKeyCode == Qt::Key_Z)
    {
      // The Z (lowercase) keyboard key triggers copying the z value to
      // the clipboard when a deconvolution was performed.

      QString z_value_as_string = QString("%1").arg(context.m_lastZ);

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

      QClipboard *clipboard_p = QApplication::clipboard();
      clipboard_p->setText(z_value_as_string, QClipboard::Clipboard);
    }
  // End of
  // else if(context.m_pressedKeyCode == Qt::Key_Z)
  else if(context.m_pressedKeyCode == Qt::Key_X)
    {
      // The X (lowercase) keyboard key triggers copying the m/z value
      // exactly as the last cursor position (no deconvolution needs to be
      // performed to get to the x value (in a mass spectrum trace, the x
      // value is the m/z value).

      QPointF point = context.m_lastCursorHoveredPoint;

      QString x_value_as_string = QString("%1").arg(point.x(), 0, 'f', 6);

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

      QClipboard *clipboard_p = QApplication::clipboard();
      clipboard_p->setText(x_value_as_string, QClipboard::Clipboard);
    }
  // End of
  // else if(context.m_pressedKeyCode == Qt::Key_X)
  else if(context.m_pressedKeyCode == Qt::Key_Y)
    {
      // The Y (lowercase) keyboard key triggers copying the intensity value
      // exactly as the last cursor position (no deconvolution needs to be
      // performed to get to the x value (in a mass spectrum trace, the x
      // value is the m/z value).

      QPointF point = context.m_lastCursorHoveredPoint;

      QString y_value_as_string = QString("%1").arg(point.y(), 0, 'f', 6);

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

      QClipboard *clipboard_p = QApplication::clipboard();
      clipboard_p->setText(y_value_as_string, QClipboard::Clipboard);
    }
  // End of
  // else if(context.m_pressedKeyCode == Qt::Key_Y)
  // End of
  // else if(context.m_pressedKeyCode == Qt::Key_X)
  else if(context.m_pressedKeyCode == Qt::Key_B)
    {
      // The B (lowercase) keyboard key triggers copying both the
      // m/z and the intensity values exactly as the last cursor position (no
      // deconvolution needs to be performed to get to the x value (in a mass
      // spectrum trace, the x value is the m/z value).

      QPointF point = context.m_lastCursorHoveredPoint;

      QString x_y_value_as_string =
        QString("(%1,%2)").arg(point.x(), 0, 'f', 6).arg(point.y(), 0, 'f', 6);

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

      QClipboard *clipboard_p = QApplication::clipboard();
      clipboard_p->setText(x_y_value_as_string, QClipboard::Clipboard);
    }
  // End of
  // else if(context.m_pressedKeyCode == Qt::Key_B)

  // See the plotWidgetKeyReleaseEvent() for the Qt::Key_C handling.
}

void
MassSpecTracePlotCompositeWidget::plotWidgetKeyReleaseEvent(
  QKeyEvent *event, const pappso::MassSpecTracePlotContext &context)
{
  BaseTracePlotCompositeWidget::plotWidgetKeyReleaseEvent(event, context);

  // The release event sets the local key code to 0 so that we know that
  // no keyboard key is being pressed *now*.
  m_plotWidgetPressedKeyCode = context.m_pressedKeyCode;

  if(m_plotWidgetPressedKeyCode == Qt::Key_Delete)
    {
      // qDebug() << "Key: Key_Delete";

      QList<QCPAbstractPlottable *> plottables =
        plottablesToBeUsedAsIntegrationDestination();

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

      if(!plottables.size())
        return;

      if(plottables.size() == 1)
        dynamic_cast<MassSpecTracePlotWnd *>(parent())
          ->requestPlottableDestruction(
            getPlotWidget(), plottables.first(), getPlotWidget()->getContext());
      else
        {
          // qDebug() << "More than one plottable";
        }
    }
  else if(m_plotWidgetReleasedKeyCode == Qt::Key_C)
    {
      // This one acts like a switch on/of
      m_massModellingMethodSelectorWidgetShouldBeVisible =
        !m_massModellingMethodSelectorWidgetShouldBeVisible;

      if(m_massModellingMethodSelectorWidgetShouldBeVisible)
        {
          if(mp_massModellingMethodSelectorWidget == nullptr)
            {
              mp_massModellingMethodSelectorWidget =
                new MassModellingMethodSelectorWidget(this);
              updateMassModellingMethodSelectorWidgetPosition();
            }
        }

      if(mp_massModellingMethodSelectorWidget != nullptr)
        {
          mp_massModellingMethodSelectorWidget->setVisible(
            m_massModellingMethodSelectorWidgetShouldBeVisible);
        }
    }

  // If the text details widget exists, make sure we handle the fact
  // that when the shift key is pressed, the widget is pinned
  // and that when the shift key is release the widget is unpinned
  if(mp_textDetailsWidget != nullptr &&
     !(context.m_keyboardModifiers & Qt::ShiftModifier))
    {
      mp_textDetailsWidget->setPinned(false);
    }
}

void
MassSpecTracePlotCompositeWidget::plotWidgetMousePressEvent(
  QMouseEvent *event, const pappso::MassSpecTracePlotContext &context)
{
  // qDebug() << "The MassSpecTracePlotContext:" << context.toString();

  BaseTracePlotCompositeWidget::plotWidgetMousePressEvent(event, context);
}

void
MassSpecTracePlotCompositeWidget::plotWidgetMouseReleaseEvent(
  QMouseEvent *event, const pappso::BasePlotContext &context)
{
  Q_UNUSED(event)
  Q_UNUSED(context)

  // No more needed, the widget will hide automatically with timer.
  // if(mp_textDetailsWidget)
  //   mp_textDetailsWidget->hide();

  // There is a specific situation here: when the user is trying to
  // determine the charge of an isotopic cluster that they cover
  // by dragging the mouse cursor with the left button-pressed
  // while simultaneously pressing the Z keyboard key. In this situtation
  // the cluster centroids are automatically extracted and displaye as
  // a QCPBars plot in mp_centroidsBars.

  if(mp_centroidsBars != nullptr)
    {
#if 0
      MassSpecTracePlotWnd *parent_wnd = dynamic_cast<MassSpecTracePlotWnd *>(
        static_cast<BaseTracePlotCompositeWidget *>(this)->getParentWnd());

      if(parent_wnd == nullptr)
        qFatal() << "Programming error. The pointer cannot be nullptr.";

      parent_wnd->requestPlottableDestruction(
        getPlotWidget(), mp_centroidsBars, getPlotWidget()->getContext());

      // Absolutely needed because we rely on this pointer being nullptr or not
      // to know if we need to reallocate one or not.
      mp_centroidsBars = nullptr;
#endif
      // The user might have set the centroid bars plot to be visible
      // using the main menu. In that case, we do not hide that
      // plot.
      //
      // See togglePlotVisibilityFromMenuComboBox().

      if(!m_forceCentroidBarsVisibility)
        mp_centroidsBars->setVisible(false);
    }

  if(mp_gaussianFitGraph != nullptr)
    {
      if(!m_forceGaussianFitGraphVisibility)
        mp_gaussianFitGraph->setVisible(false);
    }
}

QCPGraph *
MassSpecTracePlotCompositeWidget::graphPlotToBeUsedAsIntegrationSource() const
{
  // qDebug() << "";

  // We need to override the base class method because we have multiple plots in
  // the mass spectral plot widget that we need to account for in our logic:
  //
  // - the centroid bars plot.
  // - the Gaussian fit graph plot.

  // First off, try the base class.

  QCPGraph *trace_graph_p =
    BasePlotCompositeWidget::graphPlotToBeUsedAsIntegrationSource();

  if(trace_graph_p != nullptr)
    return trace_graph_p;


  // We have to start from scratch: the situation is more complex than
  // expected in the base class function.


  int plottable_count = mp_plotWidget->plottableCount();

  if(!plottable_count)
    {
      // qDebug() << "Not a single graph/bars plot";

      return nullptr;
    }

  if(plottable_count == 1)
    {
      // qDebug() << "Only one graph/bars plot.";

      QCPAbstractPlottable *plottable_p = mp_plotWidget->plottable();

      // Returns nullptr if not of type QCPGraph.
      return static_cast<QCPGraph *>(plottable_p);
    }
  else if(plottable_count == 2)
    {
      // qDebug() << "There are two graph/bars plots.";

      // We do not care of selection only if of the two plottables, one
      // and one only is QCPGraph, which can only be true if that graph
      // is the mass spectral trace we need, because without that trace,
      // how can any centroid bar graph be created ?

      if(static_cast<QCPGraph *>(mp_plotWidget->plottable(0)) &&
         static_cast<QCPBars *>(mp_plotWidget->plottable(1)))
        {
          // One is graph (the first) and one is bars (the second),
          // so return the first. That cannot be the Gaussian fit graph plot
          // because then that would mean the centroids have been created out of
          // no mass spectral trace, which is not possible.

          // qDebug() << "Returning the plottable at index 0.";

          return static_cast<QCPGraph *>(mp_plotWidget->plottable(0));
        }
      else if(static_cast<QCPBars *>(mp_plotWidget->plottable(0)) &&
              static_cast<QCPGraph *>(mp_plotWidget->plottable(1)))
        {
          // One is bars (the first) and one is graph (the second),
          // so return the second. That cannot be the Gaussian fit graph plot
          // because then that would mean the centroids have been created out of
          // no mass spectral trace, which is not possible.

          // qDebug() << "Returning the plottable at index 1.";

          return static_cast<QCPGraph *>(mp_plotWidget->plottable(1));
        }

      // At this point, there are two plots, either both QCPBars or both
      // QCPGraph
      if(static_cast<QCPBars *>(mp_plotWidget->plottable(0)) &&
         static_cast<QCPBars *>(mp_plotWidget->plottable(1)))
        {
          // qDebug() << "Both plots are bars plots.";

          // In reality, at the moment this cannot be: how can there be
          // a bars plot (for the centroids) if not mass spectral trace
          // graph is there in the first place?
          Q_ASSERT(false);
          return nullptr;
        }

      // Now we know there are two QCPGraph plots, we need to check if
      // one of them is the Gaussian fit graph plot (if the Gaussian fit trace
      // actually exists).

      // qDebug() << "Both plots are graph plots.";

      if(mp_gaussianFitGraph != nullptr &&
         static_cast<QCPGraph *>(mp_plotWidget->plottable(0)) ==
           mp_gaussianFitGraph)
        {
          // qDebug() << "Returning the plottable at index 1 because index 0 is
          // "
          //             "the Gaussian fit trace plot.";

          // Then, return the other plot!
          return static_cast<QCPGraph *>(mp_plotWidget->plottable(1));
        }
      if(mp_gaussianFitGraph != nullptr &&
         static_cast<QCPGraph *>(mp_plotWidget->plottable(1)) ==
           mp_gaussianFitGraph)
        {
          // qDebug() << "Returning the plottable at index 0 because index 1 is
          // "
          //             "the Gaussian fit trace plot.";

          // Then, return the other plot!
          return static_cast<QCPGraph *>(mp_plotWidget->plottable(0));
        }

      // There are two plots, and none of them is the Gaussian fit trace graph.
      // So we need to check if one of these is selected.

      // qDebug()
      //   << "There are two plots, none of them is the Gaussian fit trace
      //   plot.";

      QList<QCPAbstractPlottable *> selected_plottable_list;
      selected_plottable_list      = mp_plotWidget->selectedPlottables();
      int selected_plottable_count = selected_plottable_list.size();

      if(!selected_plottable_count || selected_plottable_count > 1)
        {
          // qDebug() << "There are either no or more than one selected
          // plot(s).";

          return nullptr;
        }

      // There is no more any ambiguity: of the two QCPGraph plots, return
      // the only one that is selected.
      // qDebug()
      //   << "There is a single selected plot, returning it cast to QCPGraph.";

      return static_cast<QCPGraph *>(selected_plottable_list.first());
    }
  else if(plottable_count == 3)
    {
      qDebug() << "There are exactly 3 plots.";

      // The possible combination is:
      // The single mass spectral trace graph plot
      // The centroids bars plot
      // The Gaussian fit trace graph plot.

      // We know the Gaussian fit graph plot has objectName
      // ("GaussiantFitGraphPlot") and that the centroid bars plot has
      // objectName ("CentroidsBarsPlot").

      QCPGraph *test_graph_plot_p = nullptr;
      QCPBars *test_bars_plot_p   = nullptr;

      QCPGraph *mass_spectral_graph_plot_p = nullptr;
      QCPGraph *gaussian_fit_graph_plot_p  = nullptr;
      QCPBars *centroids_bars_plot_p       = nullptr;

      for(int iter = 0; iter < mp_plotWidget->plottableCount(); ++iter)
        {
          QCPAbstractPlottable *plottable_p = mp_plotWidget->plottable(iter);

          test_graph_plot_p = dynamic_cast<QCPGraph *>(plottable_p);

          if(test_graph_plot_p != nullptr)
            {
              // qDebug() << "The test graph plot has objectName:"
              //          << test_graph_plot_p->objectName();

              if(test_graph_plot_p->objectName() == "GaussianFitGraphPlot")
                {
                  // qDebug() << "At index" << iter
                  //          << "the plot is the gaussian fit graph plot.";

                  gaussian_fit_graph_plot_p = test_graph_plot_p;
                }
              else
                {
                  // qDebug() << "At index" << iter
                  //          << "the plot has to be the mass spectracl graph "
                  //             "plot, with name:"
                  //          << test_graph_plot_p->objectName();

                  mass_spectral_graph_plot_p = test_graph_plot_p;
                }
            }
          else
            {

              // qDebug() << "At index" << iter << "the plot is not a graph.";

              test_bars_plot_p = dynamic_cast<QCPBars *>(plottable_p);

              Q_ASSERT(test_bars_plot_p != nullptr);

              // qDebug() << "Cast to QCPBars, its objectName is:"
              //          << test_bars_plot_p->objectName();


              if(test_bars_plot_p->objectName() == "CentroidsBarsPlot")
                {
                  centroids_bars_plot_p = test_bars_plot_p;

                  // qDebug() << "At index" << iter
                  //          << "the plot is the centroids bars plot.";
                }
            }
        }

      if(mass_spectral_graph_plot_p != nullptr &&
         gaussian_fit_graph_plot_p != nullptr &&
         centroids_bars_plot_p != nullptr)
        {
          // qDebug() << "The three plots could be assigned.";

          return mass_spectral_graph_plot_p;
        }
    }
  else if(plottable_count >= 3)
    {
      // qDebug() << "There are more than 3 plots.";

      // Most complex situation. Just check if there is a selected plot,
      // and return it cast to QCPGraph. But ! do not return any graph plot
      // if the selected is the Gaussian fit trace graph.

      QList<QCPAbstractPlottable *> selected_plottable_list;
      selected_plottable_list      = mp_plotWidget->selectedPlottables();
      int selected_plottable_count = selected_plottable_list.size();

      if(!selected_plottable_count || selected_plottable_count > 1)
        {
          // qDebug() << "There are either no or more than one selected
          // plot(s).";
          return nullptr;
        }

      // There is still one ambiguity: of the many QCPGraph|QCPBars plots,
      // there is only one plot that is selected, but that might be the Gaussian
      // fit trace graph, in which case we do return nullptr.

      // qDebug() << "There is a single selected plot.";

      if(mp_gaussianFitGraph != nullptr &&
         static_cast<QCPGraph *>(selected_plottable_list.first()) ==
           mp_gaussianFitGraph)
        {
          // qDebug() << "That single selected plot is the Gaussian plot.";

          return nullptr;
        }

      // qDebug() << "That single selected plot is returned cast to QCPGraph.";

      // We finally cleared the path: the only selected plot is a QCPGraph plot
      // and it is not the Gaussian fit trace plot. Return it!
      return static_cast<QCPGraph *>(selected_plottable_list.first());
    }

  return nullptr;
}


void
MassSpecTracePlotCompositeWidget::plotWidgetMassDeconvolution(
  const pappso::MassSpecTracePlotContext &context)
{
  // qDebug();

  // The point with the append strategy below is that sometimes they get
  // chained ad infinitum if the mouse movements allow calculating
  // spurious deconvolutions... We want to append the deconvolution
  // results to the status text. QString new_text =
  // m_ui->statusLineEdit->text() + QString("-- z: %1 - m/z: %2 - Mr: %3")
  //.arg(context.m_lastZ)
  //.arg(context.m_lastMz)
  //.arg(context.m_lastMr);

  QString new_text =
    QString("[%1--%2] - delta: %3 -- z: %4 - m/z: %5 - Mr: %6")
      .arg(std::min<double>(context.m_xRegionRangeStart,
                            context.m_xRegionRangeStop),
           0,
           'f',
           libXpertMassCore::MZ_DEC_PLACES)
      .arg(std::max<double>(context.m_xRegionRangeStart,
                            context.m_xRegionRangeStop),
           0,
           'f',
           libXpertMassCore::MZ_DEC_PLACES)
      .arg(
        fabs(context.m_xDelta), 0, 'f', libXpertMassCore::DELTA_MZ_DEC_PLACES)
      .arg(context.m_lastZ)
      .arg(context.m_lastMz, 0, 'f', libXpertMassCore::MZ_DEC_PLACES)
      .arg(context.m_lastMr, 0, 'f', libXpertMassCore::MZ_DEC_PLACES);

  m_ui->statusLineEdit->setText(new_text);
}

void
MassSpecTracePlotCompositeWidget::plotWidgetResolvingPowerComputation(
  const pappso::MassSpecTracePlotContext &context)
{
  // We want to append the resolving power results to the status text.
  QString new_text = m_ui->statusLineEdit->text() +
                     QString("-- Rp: %1").arg(context.m_lastResolvingPower);

  m_ui->statusLineEdit->setText(new_text);
}

void
MassSpecTracePlotCompositeWidget::lastCursorHoveredPoint(const QPointF &pointf)

{
  BaseTracePlotCompositeWidget::lastCursorHoveredPoint(pointf);

  // qDebug() << "pointf:" << pointf;

  // At this point we can start doing mass spectrum-specific calculations.
}

void
MassSpecTracePlotCompositeWidget::moveMouseCursorToNextGraphPoint(
  const pappso::BasePlotContext &context)
{
  BaseTracePlotCompositeWidget::moveMouseCursorToNextGraphPoint(context);

  // Modify the context to conform to the integration range that was
  // computed in the base class function.

  pappso::BasePlotContext local_context;
  local_context.initialize(context);
  local_context.m_xRegionRangeStart = m_integrationRange.lower;
  local_context.m_xRegionRangeStop  = m_integrationRange.upper;

  if(m_isSinglePointIntegrationMode)
    integrationRequested(local_context);
}

void
MassSpecTracePlotCompositeWidget::resizeEvent(QResizeEvent *event)
{
  BasePlotCompositeWidget::resizeEvent(event);

  if(mp_massModellingMethodSelectorWidget != nullptr)
    updateMassModellingMethodSelectorWidgetPosition();
}

void
MassSpecTracePlotCompositeWidget::
  updateMassModellingMethodSelectorWidgetPosition()
{
  if(mp_massModellingMethodSelectorWidget == nullptr)
    return;

  qDebug() << "Is the widget visible?"
           << mp_massModellingMethodSelectorWidget->isVisible();

  // Adjust size to fit the new text, then pin to top-right with a 10px margin
  mp_massModellingMethodSelectorWidget->adjustSize();
  QCPAxisRect *axis_rect_p = mp_plotWidget->axisRect();
  QPoint axis_top_right    = axis_rect_p->topRight();
  mp_massModellingMethodSelectorWidget->move(
    axis_top_right.x() - mp_massModellingMethodSelectorWidget->width(),
    axis_top_right.y() + mp_massModellingMethodSelectorWidget->height());
}

void
MassSpecTracePlotCompositeWidget::leaveEvent(QEvent *event)
{
  // qDebug() << "";

  Q_UNUSED(event)

#if 0
This logic unfortunately does not work.
  // If we leave this composite widget to enter the text details
  // widget (that is, to go and click on the copy-to-clipboard button)
  // then, do not hide the widget !
  // But if we leave this composite widget to go anywhere else,
  // then do hide the text details widget to avoid having it
  // in the next screen if we move screens before the timeout
  // of the hide timer.
  if(mp_textDetailsWidget != nullptr)
    {
      if(!mp_textDetailsWidget->underMouse())
        {
          qDebug() << "";

          mp_textDetailsWidget->forcefullyHide();
          event->ignore();
          return;
        }
      else
        {
          qDebug() << "";
        }
    }
  QWidget::leaveEvent(event);
#endif
}

void
MassSpecTracePlotCompositeWidget::integrationRequested(
  const pappso::BasePlotContext &context)
{
  // qDebug() << "";

  // This function is called when an integration is performed
  // *starting* from this widget, not ending in this widget.
  // For integrations that are directed to a mass spectrum,
  // check the MassSpectracePlotWnd class !!!

  // qDebug().noquote() << "context:" << context.toString();

  // The reference implementation is the
  // TicXicChromTracePlotCompositeWidget one. Look there for all the
  // explanatory comments.

  // Make a local copy of the context because we may need to modify it.
  pappso::BasePlotContext local_context;
  local_context.initialize(context);

  // qDebug().noquote() << "local context:" << local_context.toString();

  // Check if the integration scope contains meaningful data. We are in trace
  // plot widget, so the scope might be in the form of a
  // mono-dimensional scope, although this is not mandatory.

  if(local_context.msp_integrationScope->is2D())
    {
      // qDebug()
      //   << "Odd situation where the selection polygon is two-dimensional "
      //      "and the trace plot selection is mono-dimensional. Converting "
      //      "it to a 1D selection polygon.";

      //**local_context.msp_integrationScope->convertTo1D();
      double width;
      if(!local_context.msp_integrationScope->getWidth(width))
        qFatal() << "Failed to get width.";

      QPointF point;
      if(!local_context.msp_integrationScope->getPoint(point))
        qFatal() << "Failed to get point.";

      local_context.msp_integrationScope =
        std::make_shared<const pappso::IntegrationScopeBase>(
          pappso::IntegrationScope(point, width));
    }

  std::const_pointer_cast<pappso::IntegrationScopeBase>(
    local_context.msp_integrationScope)
    ->setDataKindX(pappso::Enums::DataKind::mz);

  // Sanity check.

  // It might happen that the user inadventently starts a selection
  // left of x = 0 (that is, at negative m/z values), so we cope with
  // this, but it makes absolutely no sense that the whole selected
  // area be in the negative region of the m/z scale.
  double range_start;
  double range_end;

  local_context.msp_integrationScope->range(
    pappso::Enums::Axis::x, range_start, range_end);

  // qDebug().noquote() << "local context extracted X axis range:"
  //                    << range_start << "-" << range_end;

  // qDebug() << "Pressed key code:" << Qt::hex <<
  // local_context.m_pressedKeyCode; qDebug() << "Pressed key text:" <<
  // local_context.m_pressedKeyText;

  // Having negative masses does not make any sense.
  if(range_start < 0 && range_end < 0)
    return;

  QCPAbstractPlottable *plottable_p = plottableToBeUsedAsIntegrationSource();

  if(plottable_p == nullptr)
    {
      QMessageBox::information(this,
                               "Please select *one* trace",
                               "In order to perform an integration, the source "
                               "graph needs to be selected.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return;
    }

  // Create a temporary copy 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.

  ProcessingFlow *temp_processing_flow_p =
    getProcessingFlowPtrForPlottable(plottable_p)->clone(nullptr);

  // qDebug().noquote() << "Got processing flow from plottable:\n"
  //                    << temp_processing_flow_p->toString(0, "  ");

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getMsRunDataSetCstSPtrForPlottable(plottable_p);

  // qDebug().noquote() << "The ms run data statistics:"
  //                    <<
  //                    ms_run_data_set_csp->getMsRunDataSetStats().toString();

  // Create a processing step to document the specifics of this new
  // integration.

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

  MsFragmentationSpec ms_fragmentation_spec =
    temp_processing_flow_p->getDefaultMsFragmentationSpec();

  // Only set the ms frag spec to the processing spec if it is valid.
  if(ms_fragmentation_spec.isValid())
    {
      // qDebug().noquote() << "The default ms frag spec from the
      // processing flow " "is valid, using it : "
      //<< ms_fragmentation_spec.toString();

      processing_step_sp->setMsFragmentationSpec(ms_fragmentation_spec);
    }
  else
    {
      // qDebug() << "The default ms frag spec from the processing flow is
      // not
      // "
      //"valid. Not using it.";
    }

  // Now make sure we document the integration scope.
  // Now define the graph range for the integration operation.
  processing_step_sp->setIntegrationScope(local_context.msp_integrationScope);

  // qDebug() << "Set this integration scope to the processing step:"
  //          << processing_step_sp->getIntegrationScope()->toString();

  // We must say what is the data source processing type.
  processing_step_sp->setSrcProcessingType(pappso::Enums::Axis::x, "MZ");

  // It is essential that we document the relation between the axes and
  // the corresponding data type.
  processing_step_sp->setDataKind(pappso::Enums::Axis::x,
                                  pappso::Enums::DataKind::mz);
  processing_step_sp->setDataKind(pappso::Enums::Axis::y,
                                  pappso::Enums::DataKind::unset);

  // qDebug() << "Pressed key text:" << local_context.m_pressedKeyText;
  // qDebug() << "Pressed key code:" << Qt::hex <<
  // local_context.m_pressedKeyCode;

  // The user may either click onto one of the integration destination checkable
  // button the right hand side vertical strip of buttons or use a keyboard key
  // to select the destination.

  if(local_context.m_pressedKeyCode == Qt::Key_R)
    m_ui->integrateToRtPushButton->setChecked(true);
  else if(local_context.m_pressedKeyCode == Qt::Key_M)
    m_ui->integrateToMzPushButton->setChecked(true);
  else if(local_context.m_pressedKeyCode == Qt::Key_D)
    m_ui->integrateToDtPushButton->setChecked(true);
  else if(local_context.m_pressedKeyCode == Qt::Key_I)
    m_ui->integrateToIntPushButton->setChecked(true);

  // Now that we have updated the button check state depending on any
  // meaningful keyboard key code, we can check the buttons.

  if(m_ui->integrateToRtPushButton->isChecked())
    {
      // qDebug() << "Integrating to XIC chromatogram.";

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

      static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
        ->integrateToRt(plottable_p, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToMzPushButton->isChecked() ||
          m_ui->integrateToDtMzPushButton->isChecked() ||
          m_ui->integrateToMzRtPushButton->isChecked())
    {
      if(m_ui->integrateToMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to mass spectrum.";
          processing_step_sp->setDestProcessingType("MZ");
        }
      else if(m_ui->integrateToMzRtPushButton->isChecked())
        {
          // qDebug() << "Integrating to rt|m/z color map.";
          processing_step_sp->setDestProcessingType("MZ_RT");
        }
      else if(m_ui->integrateToDtMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to dt|m/z color map.";
          processing_step_sp->setDestProcessingType("DT_MZ");
        }

      // At this point we need to define how the mass data integrator will
      // combine spectra, since we are integrating to a mass spectrum.

      // The function below will first get the potential settings that might
      // have been stored by the user when "Set as default" is checked in the
      // MzIntegrationParamsDlg window. If no integration settings were found,
      // then other possibilities are tested (in particular by looking into
      // the ProcessingFlow).

      // Remember, however, that the MzIntegrationParams that are returned
      // might have been stored as settings for a totally different
      // data set. So we need to adjust the m/z data in them:

      // temp_processing_flow_p was obtained above
      // for the plottable we are working on.
      // Heap-allocated !!
      pappso::MzIntegrationParams *mz_integration_params_p =
        temp_processing_flow_p->getSensibleMzIntegrationParams();

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

      // Note that we cannot integrate mass spectra with a high resolution
      // here. We need to reduce the resolution to integer resolution.
      if(m_ui->integrateToDtMzPushButton->isChecked() ||
         m_ui->integrateToMzRtPushButton->isChecked())
        {
          mz_integration_params_p->setBinningType(
            pappso::MzIntegrationParams::BinningType::NONE);
          mz_integration_params_p->setDecimalPlaces(0);
        }

      // We finally have mz integ params that we can set to the processing
      // step that we are creating to document this current integration.
      // Takes ownership
      processing_step_sp->setMzIntegrationParams(mz_integration_params_p);

      // And nwo add the new processing step to the local copy of the
      // processing flow, so that we document on top of all the previous
      // steps, also this last one.

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

      // qDebug().noquote() << "Pushed back new processing step:\n"
      //                    << processing_step_sp->toString();

      // The local processing flow contains all the previous steps and the
      // last one that documents this current integration.

      if(m_ui->integrateToMzPushButton->isChecked())
        {
          static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToMz(plottable_p, nullptr, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToDtMzPushButton->isChecked())
        {
          static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToDtMz(plottable_p, *temp_processing_flow_p);
        }
      else if(m_ui->integrateToMzRtPushButton->isChecked())
        {
          static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToMzRt(plottable_p, *temp_processing_flow_p);
        }
    }
  else if(m_ui->integrateToDtPushButton->isChecked())
    {
      // qDebug() << "Integrating to drift spectrum.";

      processing_step_sp->setDestProcessingType("DT");

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

      static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
        ->integrateToDt(plottable_p, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToDtRtPushButton->isChecked())
    {
      // qDebug() << "Integrating to TIC|XIC chromatogram / drift spectrum
      // colormap.";

      processing_step_sp->setDestProcessingType("DT_RT");

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

      static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
        ->integrateToDtRt(plottable_p, *temp_processing_flow_p);
    }
  else if(m_ui->integrateToIntPushButton->isChecked())
    {
      // QString message = "Integrating to TIC intensity.";

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

      if(context.m_keyboardModifiers & Qt::ShiftModifier)
        {
          // In this specific case we want to integrate to intensity for all the
          // pinned-down widgets.

          // message += " With SHIFT modifier.";

          const pappso::IntegrationScope *integration_scope_p =
            dynamic_cast<const pappso::IntegrationScope *>(
              local_context.msp_integrationScope.get());

          static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToTicIntensityPinnedDownWidgets(*integration_scope_p);
        }
      else
        {
          processing_step_sp->setDestProcessingType("INT");

          static_cast<BasePlotWnd *>(mp_parentWnd)
            ->integrateToTicIntensity(
              plottable_p, nullptr, *temp_processing_flow_p);
        }
      // qDebug() << message;
    }
  else
    {
      QMessageBox::information(
        this,
        "Please select a destination for the integration",
        "In order to perform an integration, select a destination "
        "like a mass spectrum or a drift spectrum by clicking one "
        "of the buttons on the right margin of the plot widget.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);
    }

  delete temp_processing_flow_p;

  return;
}


QCPGraph *
MassSpecTracePlotCompositeWidget::addTrace(
  const pappso::Trace &trace,
  QCPAbstractPlottable *parent_plottable_p,
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const QString &sample_name,
  const ProcessingFlow &processing_flow,
  const QColor &color)
{

  // qDebug();

  // If color is not valid, we need to create one.

  QColor local_color(color);

  // Get color from the available colors, or if none is available, create
  // one randomly without requesting the user to select one from
  // QColorDialog.
  if(!local_color.isValid())
    local_color = libXpertMassGui::ColorSelector::getColor(true);

  // qDebug().noquote() << "The processing flow:" <<
  // processing_flow.toString();

  // The QCPGraph that is going to be created will have the proper color
  // defined in it. Also, we grab the graph that has been created because
  // we need to associate it with the matching processing flow in the map.

  // We have to verify if there is a destination policy for this composite
  // plot widget. There are different situations.

  // 1. The erase trace and create new push button is checked: the user
  // want to remove all the selected traces and replace them with the
  // trace passed as parameter.

  // 2. The keep traces and combine push button is checked: the user want
  // to combine all the selected traces individually to the new trace
  // passed as parameters.

  // 3. If no destination policy is defined, simply add the new trace
  // without touching any other trace that might be already plotted there.

  // We'll need this to factorize code.
  bool must_create_new_graph    = false;
  QCPAbstractPlottable *graph_p = nullptr;
  QList<QCPAbstractPlottable *> dest_plottable_list =
    plottablesToBeUsedAsIntegrationDestination();

  // qDebug() << "The destination plottable size has "
  //<< dest_plottable_list.size() << "graphs.";

  if(m_newTraceHandlingMethod == NewTraceHandlingMethod::OVERLAY)
    {
      // Keep the traces there and simply add a new one.
      must_create_new_graph = true;
    }
  else if(m_newTraceHandlingMethod == NewTraceHandlingMethod::MINUS_COMBINE ||
          m_newTraceHandlingMethod == NewTraceHandlingMethod::PLUS_COMBINE)

    {
      // qDebug();

      // Combination is requested. There are some situations:

      // 1. If there is no single trace in the widget, then simply add a
      // new trace.
      //
      // 2. If there is a single trace, then combine with that, selected
      // or not.
      //
      // 3. If there are multiple traces, then only combine with those.


      // Since we are asked that a combination to either a trace or a mass
      // spectrum be performed, there must be a non-nullptr mz integration
      // pointer in the step.
      // Returns the pointer, not a newly allocated instance for us.

      // If the user has configured that no binning is to be performed, we
      // should get that config now (or any other, of course).
      const pappso::MzIntegrationParams *mz_integration_params_p =
        processing_flow.getSensibleMzIntegrationParams();

      // qDebug() << "The sensible MzIntegrationParams: "
      //          << mz_integration_params_p->toString();

      if(mz_integration_params_p == nullptr)
        qFatal(
          "Combining to a mass spectrum, cannot be that the mz "
          "integration params are not available in the processing flow.");

      if(!dest_plottable_list.size())
        {
          // qDebug() << "Adding trace but there is not a single trace
          // available, " "creating a new one.";

          must_create_new_graph = true;
        }
      else
        {
          // qDebug() << "Adding new trace with combination requested.";

          // qDebug() << "The most recent mz integration parameters:"
          //          << mz_integration_params_p->toString(0, " ");

          if(mz_integration_params_p->getBinningType() ==
             pappso::MzIntegrationParams::BinningType::NONE)
            {
              // qDebug() << "Binning type none.";

              pappso::TraceMinusCombiner trace_minus_combiner;
              pappso::TracePlusCombiner trace_plus_combiner;

              // No binning is required, so most simple situation.

              for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
                {
                  QCPAbstractPlottable *iter_plottable_p =
                    dest_plottable_list.at(iter);
                  pappso::Trace iter_trace =
                    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                      ->toTrace(dynamic_cast<QCPGraph *>(iter_plottable_p));

                  // Initialize the receiving map trace with the trace we
                  // are iterating into.
                  pappso::MapTrace map_trace(iter_trace);

                  // And now combine the trace we got as parameter.

                  if(m_newTraceHandlingMethod ==
                     NewTraceHandlingMethod::MINUS_COMBINE)
                    {
                      // qDebug() << "Now minus combining without "
                      //             "binning.";
                      trace_minus_combiner.combine(map_trace, trace);
                    }
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    {
                      // qDebug() << "Now minus combining without "
                      //             "binning.";
                      trace_plus_combiner.combine(map_trace, trace);
                    }

                  QVector<double> key_vector;
                  QVector<double> value_vector;

                  // for(const auto &value : map_trace.xValues())
                  //   key_vector.push_back(value);
                  std::vector<double> x_values = map_trace.xValues();
                  std::copy(x_values.begin(),
                            x_values.end(),
                            std::back_inserter(key_vector));

                  // for(auto &value : map_trace.yValues())
                  //   value_vector.push_back(value);
                  std::vector<double> y_values = map_trace.yValues();
                  std::copy(y_values.begin(),
                            y_values.end(),
                            std::back_inserter(value_vector));

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }

              // Note how we do not change neither the sample name text nor its
              // color. He we just increment/decrement a trace. We do not set a
              // new trace.
            }
          else if(mz_integration_params_p->getBinningType() ==
                  pappso::MzIntegrationParams::BinningType::ARBITRARY)
            {
              // qDebug() << "Binning type arbitrary.";
              pappso::MassSpectrumMinusCombiner mass_spectrum_minus_combiner;
              pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner;

              // There is a requirement to perform a binning. We thus need
              // to create the bins. The smallest mz step needs to be the
              // smallest of the trace we get as a parameter and of the
              // trace we got for the receiving graph.

              // We'll need the mz integration parameters from the
              // processing flow we  get as parameters. Make a copy of
              // them as we'll need to set the mz smallest and greatest
              // values to them.

              pappso::MzIntegrationParams *copy_mz_integration_params_p =
                mz_integration_params_p->clone(nullptr);

              // qDebug() << "Combinining new trace into a graph trace.";

              for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
                {
                  QCPAbstractPlottable *iter_plottable_p =
                    dest_plottable_list.at(iter);
                  pappso::Trace iter_dest_trace =
                    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                      ->toTrace(static_cast<QCPGraph *>(iter_plottable_p));

                  // Initialize the receiving map trace with the trace we
                  // are iterating into.
                  pappso::MapTrace map_trace(iter_dest_trace);

                  // In order to maintain the phasing of bins,
                  // set the smallest m/z value to the min value between
                  // the current trace and the destination trace.

                  double src_smallest_mz  = trace.front().x;
                  double dest_smallest_mz = iter_dest_trace.front().x;

                  copy_mz_integration_params_p->setSmallestMz(
                    std::min(src_smallest_mz, dest_smallest_mz));

                  double src_greatest_mz  = trace.back().x;
                  double dest_greatest_mz = iter_dest_trace.back().x;

                  copy_mz_integration_params_p->setGreatestMz(
                    std::max(src_greatest_mz, dest_greatest_mz));

                  // qDebug()
                  //   << "At this point, the mz integration params:"
                  //   << copy_mz_integration_params_p->toString(0, " ");

                  // At this point we can ask the bins to be computed.
                  std::vector<double> bins =
                    copy_mz_integration_params_p->createBins();

                  // qDebug() << "Prepared bins with " << bins.size() <<
                  // "elements."
                  // << "starting with mz" << bins.front() << "ending with mz"
                  // << bins.back();

                  // Now set the bins to the combiner.

                  if(m_newTraceHandlingMethod ==
                     NewTraceHandlingMethod::MINUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::MINUS_COMBINE";
                      mass_spectrum_minus_combiner.setBins(bins);
                      mass_spectrum_minus_combiner.combine(map_trace, trace);
                    }
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::PLUS_COMBINE";
                      mass_spectrum_plus_combiner.setBins(bins);
                      mass_spectrum_plus_combiner.combine(map_trace, trace);
                    }


                  // Since the combination is terminated, set the new data
                  // to the graph (the data are sorted, thus the true
                  // below).
                  QVector<double> key_vector;
                  QVector<double> value_vector;

                  // for(const auto &value : map_trace.xValues())
                  //   key_vector.push_back(value);
                  std::vector<double> x_values = map_trace.xValues();
                  std::copy(x_values.begin(),
                            x_values.end(),
                            std::back_inserter(key_vector));

                  // for(auto &value : map_trace.yValues())
                  //   value_vector.push_back(value);
                  std::vector<double> y_values = map_trace.yValues();
                  std::copy(y_values.begin(),
                            y_values.end(),
                            std::back_inserter(value_vector));

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }
              // End of
              // for(int iter = 0; iter < dest_plottable_list.size(); ++iter)

              // Note how we do not change neither the sample name text nor its
              // color. He we just increment/decrement a trace. We do not set a
              // new trace.
            }
          else if(mz_integration_params_p->getBinningType() ==
                  pappso::MzIntegrationParams::BinningType::DATA_BASED)
            {
              // The existing trace should serve as a data template to construct
              // bins for our new trace to be combined to the existing one.

              // qDebug() << "Binning type data-based.";

              pappso::MassSpectrumMinusCombiner mass_spectrum_minus_combiner;
              pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner;

              // There is a requirement to perform a binning. We thus need
              // to create the bins. The smallest mz step needs to be the
              // smallest of the trace we get as a parameter and of the
              // trace we got for the receiving graph.

              // We'll need the mz integration parameters from the
              // processing flow we  get as parameters. Make a copy of
              // them as we'll need to set the mz smallest and greatest
              // values to them.

              pappso::MzIntegrationParams *copy_mz_integration_params_p =
                mz_integration_params_p->clone(nullptr);

              // qDebug() << "Combinining new trace into a graph trace.";

              for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
                {
                  QCPAbstractPlottable *iter_plottable_p =
                    dest_plottable_list.at(iter);
                  pappso::Trace iter_dest_trace =
                    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                      ->toTrace(static_cast<QCPGraph *>(iter_plottable_p));

                  // Initialize the receiving map trace with the trace we
                  // are iterating into.
                  pappso::MapTrace map_trace(iter_dest_trace);

                  // In order to maintain the phasing of bins, we should get the
                  // first m/z value of the destination trace and use it as an
                  // anchor point to the creation of the bins.

                  double src_smallest_mz = trace.front().x;
                  copy_mz_integration_params_p->setSmallestMz(src_smallest_mz);

                  double src_greatest_mz = trace.back().x;
                  copy_mz_integration_params_p->setGreatestMz(src_greatest_mz);

                  // qDebug() << qSetRealNumberPrecision(6)
                  //          << "The source plot trace has left m/z:"
                  //          << src_smallest_mz
                  //          << "and right m/z:" << src_greatest_mz;

                  pappso::MassSpectrumCstSPtr dest_mass_spectrum_csp =
                    std::make_shared<const pappso::MassSpectrum>(
                      iter_dest_trace);

                  // qDebug() << qSetRealNumberPrecision(6)
                  //          << "The destination plot trace has left m/z:"
                  //          << dest_mass_spectrum_csp->minX() << "and right
                  //          m/z:"
                  //          << dest_mass_spectrum_csp->maxX();

                  // qDebug()
                  //   << "At this point, the mz integration params:"
                  //   << copy_mz_integration_params_p->toString(0, " ");

                  // At this point we can ask the bins to be computed.
                  // These bins should be phased with the bins in
                  // iter_dest_trace.
                  std::vector<double> bins =
                    copy_mz_integration_params_p->createBins(
                      dest_mass_spectrum_csp);

                  // qDebug() << "Prepared bins with " << bins.size() <<
                  // "elements."
                  // << "starting with mz" << bins.front() << "ending with mz"
                  // << bins.back();

                  // Now set the bins to the combiner.

                  if(m_newTraceHandlingMethod ==
                     NewTraceHandlingMethod::MINUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::MINUS_COMBINE";
                      mass_spectrum_minus_combiner.setBins(bins);
                      mass_spectrum_minus_combiner.combine(map_trace, trace);
                    }
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::PLUS_COMBINE";
                      mass_spectrum_plus_combiner.setBins(bins);
                      mass_spectrum_plus_combiner.combine(map_trace, trace);
                    }


                  // Since the combination is terminated, set the new data
                  // to the graph (the data are sorted, thus the true
                  // below).
                  QVector<double> key_vector;
                  QVector<double> value_vector;

                  // for(const auto &value : map_trace.xValues())
                  //   key_vector.push_back(value);
                  std::vector<double> x_values = map_trace.xValues();
                  std::copy(x_values.begin(),
                            x_values.end(),
                            std::back_inserter(key_vector));

                  // for(auto &value : map_trace.yValues())
                  //   value_vector.push_back(value);
                  std::vector<double> y_values = map_trace.yValues();
                  std::copy(y_values.begin(),
                            y_values.end(),
                            std::back_inserter(value_vector));

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }
              // End of
              // for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
            }
          else
            {
              qFatal()
                << "Programming error. This line should never be reached.";
            }
        }
      // End of
      // else of if(!graph_count), that if there are graphs in the plot
      // widget.
    }
  // End of if(m_ui->keepTraceCombineNewPushButton->isChecked())
  else if(m_newTraceHandlingMethod == NewTraceHandlingMethod::REPLACE)
    {
      // qDebug() << "Adding new trace by the replace method: removing the
      // "
      //"current one(s).";

      // Start by erasing all the traces that are selected.

      for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
        {
          QCPAbstractPlottable *iter_plottable_p = dest_plottable_list.at(iter);

          // Destroy the graph , but not recursively (false).
          mp_parentWnd->getProgramWindow()->plottableDestructionRequested(
            this, iter_plottable_p, false);
        }

      must_create_new_graph = true;
    }
  else
    {
      // qDebug();

      // None of the button were checked, we simply created a new graph.
      must_create_new_graph = true;
    }

  if(must_create_new_graph)
    {
      // At this point we know we need to create a new trace.

      // qDebug()
      //<< "Adding a new trace: in fact we need to create one anew as
      // is.";

      graph_p = static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                  ->addTrace(trace, local_color);

      // Each time a new trace is added anywhere, it needs to be
      // documented in the main program window's graph nodes tree! This is
      // what we call the ms run data plot graph filiation documentation.

      mp_parentWnd->getProgramWindow()->documentMsRunDataPlottableFiliation(
        ms_run_data_set_csp, graph_p, parent_plottable_p, this);

#if 0
        //////// CAUTION ///////
        //////// CAUTION ///////
        // Erroneous: if we make the connection below each time a new graph is added
        // to a plot widget, the sigma function will be called as many times for each
        // graph that is being destroyed ! Now there is a single connection for the
        // pappso::BaseTracePlotWidget in the addTracePlot() function in each
        // XxxPlotWnd class.

        // The single graph destruction signal.

        //qDebug().noquote()
        //<< "Making the connection:"
        //<< "pappso::BaseTracePlotWidget::graphDestructionRequestedSignal to "
        //"mp_parentWnd->getProgramWindow()->graphDestructionRequested.";

        //connect(getPlotWidget(),
        //&pappso::BaseTracePlotWidget::graphDestructionRequestedSignal,
        //[this](
        //const pappso::BaseTracePlotWidget *base_trace_plot_widget_p,
        //QCPGraph *graph_p,
        //const pappso::BasePlotContext &context) {
        //mp_parentWnd->getProgramWindow()->graphDestructionRequested(this, graph_p, context);
        //});
        //////// CAUTION ///////
        //////// CAUTION ///////
#endif

      // We need to allocate a new ProcessingFlow instance with the graph
      // as its parent.
      ProcessingFlow *processing_flow_p = processing_flow.clone(graph_p);

      // Map the graph_p to the processing flow that we got to document
      // how the trace was obtained in the first place.
      m_plottableProcessingFlowMap[graph_p] = processing_flow_p;

      // Note how we do  change the sample name text and its color.
      // He we do set a new trace.
      m_ui->sampleNameLabel->setText(sample_name);
      setSampleNameColor(color);
    }
  // End of
  // if(must_create_new_graph)

  // We need to rescale the axes so that the trace is entirely shown!
  // mp_plotWidget->rescaleAxes();

  mp_plotWidget->replot();

  // graph_p might be nullptr.
  return static_cast<QCPGraph *>(graph_p);
}

QCPBars *
MassSpecTracePlotCompositeWidget::addBars(
  const pappso::Trace &trace,
  QCPAbstractPlottable *parent_plottable_p,
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const QString &sample_name,
  const ProcessingFlow &processing_flow,
  const QColor &color)
{
  // qDebug();

  // If color is not valid, we need to create one.

  QColor local_color(color);

  // Get color from the available colors, or if none is available, create
  // one randomly without requesting the user to select one from
  // QColorDialog.
  if(!local_color.isValid())
    local_color = libXpertMassGui::ColorSelector::getColor(true);

  // qDebug().noquote() << "The processing flow:" <<
  // processing_flow.toString();

  // The QCPGraph that is going to be created will have the proper color
  // defined in it. Also, we grab the graph that has been created because
  // we need to associate it with the matching processing flow in the map.

  // We have to verify if there is a destination policy for this composite
  // plot widget. There are different situations.

  // 1. The erase trace and create new push button is checked: the user
  // want to remove all the selected traces and replace them with the
  // trace passed as parameter.

  // 2. The keep traces and combine push button is checked: the user want
  // to combine all the selected traces individually to the new trace
  // passed as parameters.

  // 3. If no destination policy is defined, simply add the new trace
  // without touching any other trace that might be already plotted there.

  // We'll need this to factorize code.
  bool must_create_new_graph   = false;
  QCPAbstractPlottable *bars_p = nullptr;
  QList<QCPAbstractPlottable *> dest_plottable_list =
    plottablesToBeUsedAsIntegrationDestination();

  // qDebug() << "The list of plottables has " << dest_plottable_list.size()
  //          << "plots.";

  if(m_newTraceHandlingMethod == NewTraceHandlingMethod::OVERLAY)
    {
      // qDebug() << "Overlaying new bar plot in the plot widget.";

      // Keep the traces there and simply add a new one.
      must_create_new_graph = true;
    }
  else if(m_newTraceHandlingMethod == NewTraceHandlingMethod::MINUS_COMBINE ||
          m_newTraceHandlingMethod == NewTraceHandlingMethod::PLUS_COMBINE)

    {
      // qDebug() << "Requested combination to one or more existing plots.";

      // Combination is requested. There are some situations:

      // 1. If there is no single trace in the widget, then simply add a
      // new trace.
      //
      // 2. If there is a single trace, then combine with that, selected
      // or not.
      //
      // 3. If there are multiple traces, then only combine with those.


      // Since we are asked that a combination to either a trace or a mass
      // spectrum be performed, there must be a non-nullptr mz integration
      // pointer in the step.
      // Returns the pointer, not a newly allocated instance for us.

      // If the user has configured that no binning is to be performed, we
      // should get that config now (or any other, of course).
      const pappso::MzIntegrationParams *mz_integration_params_p =
        processing_flow.getSensibleMzIntegrationParams();

      // qDebug() << "The sensible MzIntegrationParams: "
      //          << mz_integration_params_p->toString();

      if(mz_integration_params_p == nullptr)
        qFatal(
          "Combining to a mass spectrum, cannot be that the mz "
          "integration params are not available in the processing flow.");

      if(!dest_plottable_list.size())
        {
          // qDebug() << "Absence of existing plots. Creating a new one.";

          must_create_new_graph = true;
        }
      else
        {
          // qDebug() << "The most recent mz integration parameters:"
          //          << mz_integration_params_p->toString(0, " ");

          if(mz_integration_params_p->getBinningType() ==
             pappso::MzIntegrationParams::BinningType::NONE)
            {
              // qDebug() << "Binning type none.";

              pappso::TraceMinusCombiner trace_minus_combiner;
              pappso::TracePlusCombiner trace_plus_combiner;

              // No binning is required, so most simple situation.

              for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
                {
                  QCPAbstractPlottable *iter_plottable_p =
                    dest_plottable_list.at(iter);
                  pappso::Trace iter_trace =
                    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                      ->toTrace(dynamic_cast<QCPGraph *>(iter_plottable_p));

                  // Initialize the receiving map trace with the trace we
                  // are iterating into.
                  pappso::MapTrace map_trace(iter_trace);

                  // And now combine the trace we got as parameter.

                  if(m_newTraceHandlingMethod ==
                     NewTraceHandlingMethod::MINUS_COMBINE)
                    {
                      // qDebug() << "Now minus combining without "
                      //             "binning.";
                      trace_minus_combiner.combine(map_trace, trace);
                    }
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    {
                      // qDebug() << "Now minus combining without "
                      //             "binning.";
                      trace_plus_combiner.combine(map_trace, trace);
                    }

                  QVector<double> key_vector;
                  QVector<double> value_vector;

                  // for(const auto &value : map_trace.xValues())
                  //   key_vector.push_back(value);
                  std::vector<double> x_values = map_trace.xValues();
                  std::copy(x_values.begin(),
                            x_values.end(),
                            std::back_inserter(key_vector));

                  // for(auto &value : map_trace.yValues())
                  //   value_vector.push_back(value);
                  std::vector<double> y_values = map_trace.yValues();
                  std::copy(y_values.begin(),
                            y_values.end(),
                            std::back_inserter(value_vector));

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }

              // Note how we do not change neither the sample name text nor its
              // color. He we just increment/decrement a trace. We do not set a
              // new trace.
            }
          else if(mz_integration_params_p->getBinningType() ==
                  pappso::MzIntegrationParams::BinningType::ARBITRARY)
            {
              // qDebug() << "Binning type arbitrary.";

              pappso::MassSpectrumMinusCombiner mass_spectrum_minus_combiner;
              pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner;

              // There is a requirement to perform a binning. We thus need
              // to create the bins. The smallest mz step needs to be the
              // smallest of the trace we get as a parameter and of the
              // trace we got for the receiving graph.

              // We'll need the mz integration parameters from the
              // processing flow we  get as parameters. Make a copy of
              // them as we'll need to set the mz smallest and greatest
              // values to them.

              pappso::MzIntegrationParams *copy_mz_integration_params_p =
                mz_integration_params_p->clone(nullptr);

              // qDebug() << "Combinining new trace into a graph trace.";

              for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
                {
                  QCPAbstractPlottable *iter_plottable_p =
                    dest_plottable_list.at(iter);
                  pappso::Trace iter_dest_trace =
                    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                      ->toTrace(static_cast<QCPGraph *>(iter_plottable_p));

                  // Initialize the receiving map trace with the trace we
                  // are iterating into.
                  pappso::MapTrace map_trace(iter_dest_trace);

                  // In order to maintain the phasing of bins,
                  // set the smallest m/z value to the min value between
                  // the current trace and the destination trace.

                  double src_smallest_mz  = trace.front().x;
                  double dest_smallest_mz = iter_dest_trace.front().x;

                  copy_mz_integration_params_p->setSmallestMz(
                    std::min(src_smallest_mz, dest_smallest_mz));

                  double src_greatest_mz  = trace.back().x;
                  double dest_greatest_mz = iter_dest_trace.back().x;

                  copy_mz_integration_params_p->setGreatestMz(
                    std::max(src_greatest_mz, dest_greatest_mz));

                  // qDebug()
                  //   << "At this point, the mz integration params:"
                  //   << copy_mz_integration_params_p->toString(0, " ");

                  // At this point we can ask the bins to be computed.
                  std::vector<double> bins =
                    copy_mz_integration_params_p->createBins();

                  // qDebug() << "Prepared bins with " << bins.size() <<
                  // "elements."
                  // << "starting with mz" << bins.front() << "ending with mz"
                  // << bins.back();

                  // Now set the bins to the combiner.

                  if(m_newTraceHandlingMethod ==
                     NewTraceHandlingMethod::MINUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::MINUS_COMBINE";
                      mass_spectrum_minus_combiner.setBins(bins);
                      mass_spectrum_minus_combiner.combine(map_trace, trace);
                    }
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::PLUS_COMBINE";
                      mass_spectrum_plus_combiner.setBins(bins);
                      mass_spectrum_plus_combiner.combine(map_trace, trace);
                    }


                  // Since the combination is terminated, set the new data
                  // to the graph (the data are sorted, thus the true
                  // below).

                  QVector<double> key_vector;
                  QVector<double> value_vector;

                  // for(const auto &value : map_trace.xValues())
                  //   key_vector.push_back(value);
                  std::vector<double> x_values = map_trace.xValues();
                  std::copy(x_values.begin(),
                            x_values.end(),
                            std::back_inserter(key_vector));

                  // for(auto &value : map_trace.yValues())
                  //   value_vector.push_back(value);
                  std::vector<double> y_values = map_trace.yValues();
                  std::copy(y_values.begin(),
                            y_values.end(),
                            std::back_inserter(value_vector));

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }
              // End of
              // for(int iter = 0; iter < dest_plottable_list.size(); ++iter)

              // Note how we do not change neither the sample name text nor its
              // color. He we just increment/decrement a trace. We do not set a
              // new trace.
            }
          else if(mz_integration_params_p->getBinningType() ==
                  pappso::MzIntegrationParams::BinningType::DATA_BASED)
            {
              // The existing trace should serve as a data template to construct
              // bins for our new trace to be combined to the existing one.

              // qDebug() << "Binning type data-based.";

              pappso::MassSpectrumMinusCombiner mass_spectrum_minus_combiner;
              pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner;

              // There is a requirement to perform a binning. We thus need
              // to create the bins. The smallest mz step needs to be the
              // smallest of the trace we get as a parameter and of the
              // trace we got for the receiving graph.

              // We'll need the mz integration parameters from the
              // processing flow we  get as parameters. Make a copy of
              // them as we'll need to set the mz smallest and greatest
              // values to them.

              pappso::MzIntegrationParams *copy_mz_integration_params_p =
                mz_integration_params_p->clone(nullptr);

              // qDebug() << "Combinining new trace into a graph trace.";

              for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
                {
                  QCPAbstractPlottable *iter_plottable_p =
                    dest_plottable_list.at(iter);
                  pappso::Trace iter_dest_trace =
                    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                      ->toTrace(static_cast<QCPGraph *>(iter_plottable_p));

                  // Initialize the receiving map trace with the trace we
                  // are iterating into.
                  pappso::MapTrace map_trace(iter_dest_trace);

                  // In order to maintain the phasing of bins, we should get the
                  // first m/z value of the destination trace and use it as an
                  // anchor point to the creation of the bins.

                  double src_smallest_mz = trace.front().x;
                  copy_mz_integration_params_p->setSmallestMz(src_smallest_mz);

                  double src_greatest_mz = trace.back().x;
                  copy_mz_integration_params_p->setGreatestMz(src_greatest_mz);

                  // qDebug() << qSetRealNumberPrecision(6)
                  //          << "The source plot trace has left m/z:"
                  //          << src_smallest_mz
                  //          << "and right m/z:" << src_greatest_mz;

                  pappso::MassSpectrumCstSPtr dest_mass_spectrum_csp =
                    std::make_shared<const pappso::MassSpectrum>(
                      iter_dest_trace);

                  // qDebug() << qSetRealNumberPrecision(6)
                  //          << "The destination plot trace has left m/z:"
                  //          << dest_mass_spectrum_csp->minX() << "and right
                  //          m/z:"
                  //          << dest_mass_spectrum_csp->maxX();

                  // qDebug()
                  //   << "At this point, the mz integration params:"
                  //   << copy_mz_integration_params_p->toString(0, " ");

                  // At this point we can ask the bins to be computed.
                  // These bins should be phased with the bins in
                  // iter_dest_trace.
                  std::vector<double> bins =
                    copy_mz_integration_params_p->createBins(
                      dest_mass_spectrum_csp);

                  // qDebug() << "Prepared bins with " << bins.size() <<
                  // "elements."
                  // << "starting with mz" << bins.front() << "ending with mz"
                  // << bins.back();

                  // Now set the bins to the combiner.

                  if(m_newTraceHandlingMethod ==
                     NewTraceHandlingMethod::MINUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::MINUS_COMBINE";
                      mass_spectrum_minus_combiner.setBins(bins);
                      mass_spectrum_minus_combiner.combine(map_trace, trace);
                    }
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    {
                      // qDebug() <<
                      // "NewTraceHandlingMethod::PLUS_COMBINE";
                      mass_spectrum_plus_combiner.setBins(bins);
                      mass_spectrum_plus_combiner.combine(map_trace, trace);
                    }

                  QVector<double> key_vector;
                  QVector<double> value_vector;

                  // for(const auto &value : map_trace.xValues())
                  //   key_vector.push_back(value);
                  std::vector<double> x_values = map_trace.xValues();
                  std::copy(x_values.begin(),
                            x_values.end(),
                            std::back_inserter(key_vector));

                  // for(auto &value : map_trace.yValues())
                  //   value_vector.push_back(value);
                  std::vector<double> y_values = map_trace.yValues();
                  std::copy(y_values.begin(),
                            y_values.end(),
                            std::back_inserter(value_vector));

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }
              // End of
              // for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
            }
          else
            {
              qFatal()
                << "Programming error. This line should never be reached.";
            }
        }
      // End of
      // else of if(!graph_count), that if there are graphs in the plot
      // widget.
    }
  // End of if(m_ui->keepTraceCombineNewPushButton->isChecked())
  else if(m_newTraceHandlingMethod == NewTraceHandlingMethod::REPLACE)
    {
      // qDebug() << "Replacing plots with new one.";

      // Start by erasing all the traces that are selected.

      for(int iter = 0; iter < dest_plottable_list.size(); ++iter)
        {
          QCPAbstractPlottable *iter_plottable_p = dest_plottable_list.at(iter);

          // Destroy the graph , but not recursively (false).
          mp_parentWnd->getProgramWindow()->plottableDestructionRequested(
            this, iter_plottable_p, false);
        }

      must_create_new_graph = true;
    }
  else
    {
      // qDebug();

      // None of the button were checked, we simply created a new graph.
      must_create_new_graph = true;
    }

  if(must_create_new_graph)
    {
      // At this point we know we need to create a new trace.

      // qDebug() << "Adding newly created plot";

      bars_p = static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                 ->addBars(trace, local_color);

      // Each time a new trace is added anywhere, it needs to be
      // documented in the main program window's graph nodes tree! This is
      // what we call the ms run data plot graph filiation documentation.

      mp_parentWnd->getProgramWindow()->documentMsRunDataPlottableFiliation(
        ms_run_data_set_csp, bars_p, parent_plottable_p, this);

#if 0
      //////// CAUTION ///////
      //////// CAUTION ///////
      // Erroneous: if we make the connection below each time a new graph is
      // added to a plot widget, the sigma function will be called as many times
      // for each graph that is being destroyed ! Now there is a single
      // connection for the pappso::BaseTracePlotWidget in the addTracePlot()
      // function in each XxxPlotWnd class.

      // The single graph destruction signal.

      // qDebug().noquote()
      //<< "Making the connection:"
      //<< "pappso::BaseTracePlotWidget::graphDestructionRequestedSignal to "
      //"mp_parentWnd->getProgramWindow()->graphDestructionRequested.";

      // connect(getPlotWidget(),
      //&pappso::BaseTracePlotWidget::graphDestructionRequestedSignal,
      //[this](
      // const pappso::BaseTracePlotWidget *base_trace_plot_widget_p,
      // QCPGraph *graph_p,
      // const pappso::BasePlotContext &context) {
      // mp_parentWnd->getProgramWindow()->graphDestructionRequested(this,
      // graph_p, context);
      // });
      //////// CAUTION ///////
      //////// CAUTION ///////
#endif

      // We need to allocate a new ProcessingFlow instance with the graph
      // as its parent.
      ProcessingFlow *processing_flow_p = processing_flow.clone(bars_p);

      // Map the graph_p to the processing flow that we got to document
      // how the trace was obtained in the first place.
      m_plottableProcessingFlowMap[bars_p] = processing_flow_p;

      // Note how we do  change the sample name text and its color.
      // He we do set a new trace.
      m_ui->sampleNameLabel->setText(sample_name);
      setSampleNameColor(color);
    }
  // End of
  // if(must_create_new_graph)

  // We need to rescale the axes so that the trace is entirely shown!
  // mp_plotWidget->rescaleAxes();

  mp_plotWidget->replot();

  // graph_p might be nullptr.
  return static_cast<QCPBars *>(bars_p);
}

QString
MassSpecTracePlotCompositeWidget::craftAnalysisStanza(
  const pappso::MassSpecTracePlotContext &context)
{
  // qDebug().noquote() << "With context:" << context.toString();
  // qDebug().noquote()
  //   << "We get from the base class stanza work the following text:"
  //   << m_analysisStanza;

  QCPAbstractPlottable *plottable_p = plottableToBeUsedAsIntegrationSource();

  if(plottable_p == nullptr)
    {
      // qDebug()
      //   << "Returned plottable is nullptr (there might be more than one).";

      // We should inform the user if there are more than one plottable.

      int plottable_count = mp_plotWidget->plottableCount();

      if(plottable_count > 1)
        {
          // qDebug();
          QMessageBox::information(
            this,
            "Crafting an analysis stanza for a graph",
            "Please select a single trace and try again.");
        }

      m_analysisStanza = "";
      return QString();
    }

  // qDebug() << "Plottable:" << plottable_p;

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getCstProcessingFlowPtrForPlottable(plottable_p)->getMsRunDataSetCstSPtr();

  // We do only handle here the specialty format elements: %z and %M.
  // Note that %M is a double value that may accept a precision format string
  // like this:
  // %M.3 to indicate that the molecular mass should be printed out with three
  // decimals.

  // The m_analysisStanza member has been filled-in by the base class and
  // the specialty format strings %z and %M have been let untouched, so we can
  // now work on them.

  // So, in fact, the m_analysisStanza behaves like a format string itself.

  QString formatString = m_analysisStanza;

  static const QRegularExpression re("%([zM])(\\.(\\d+))?");

  // Not all the format specifications are for number, specify which are
  static const QSet<QChar> floatKeys   = {'M'};
  static const QSet<QChar> integerKeys = {'z'};
  static const QSet<QChar> stringKeys  = {};

  QHash<QChar, QVariant> replacements;

  /////////////////// THE z charge value //////////////////////

  if(!qIsNaN(context.m_lastMz))
    replacements['M'] = context.m_lastMr;

  if(context.m_lastZ != std::numeric_limits<quint16>::max())
    replacements['z'] = context.m_lastZ;

  QString stanza;
  stanza.reserve(formatString.size());

  int lastIndex = 0;
  auto it       = re.globalMatch(formatString);

  while(it.hasNext())
    {
      QRegularExpressionMatch m = it.next();

      int start = m.capturedStart();
      int end   = m.capturedEnd();

      // Copy text up to this match
      stanza                  += formatString.mid(lastIndex, start - lastIndex);
      QString captured_string  = m.captured(1);
      const QChar key          = captured_string[0];

      // qDebug() << "Captured key: " << key;

      const QString precision_spec_string = m.captured(3); // just the digits
      bool ok                             = false;
      int precision_spec                  = precision_spec_string.toInt(&ok);

      if(!ok)
        precision_spec = 0;

      if(!replacements.contains(key))
        {
          QString untouched = m.captured(0);

          // qDebug() << "Adding new untouched string element:" << untouched;

          stanza    += untouched;
          lastIndex  = end;
          continue;
        }

      const QVariant &v = replacements[key];

      if(floatKeys.contains(key))
        {
          // ---- FLOAT TOKENS ----
          double d = 0.0;

          if(v.canConvert<double>())
            d = v.toDouble(); // integer or float → OK
          else
            {
              stanza    += "<NaN>"; // or keep literal, your choice
              lastIndex  = end;
              continue;
            }

          stanza += QString::number(d, 'f', precision_spec);
        }
      else if(integerKeys.contains(key))
        {
          // ---- INTEGER TOKENS ----
          qlonglong i = 0;

          // INTEGER tokens should accept:
          // int, uint, qint64, quint64        (ideal case)
          // double or float                    (we can convert by
          // truncation) string with digits                 (allowed at your
          // discretion)

          if(v.canConvert<qlonglong>())
            i = v.toLongLong();
          else
            {
              stanza    += "<NaI>"; // Not-an-Integer marker (or use literal)
              lastIndex  = end;
              continue;
            }

          stanza += QString::number(i);
        }

      else if(stringKeys.contains(key))
        {
          // ---- STRING TOKENS ----
          if(v.canConvert<QString>())
            stanza += v.toString();
          else
            stanza += "<NaS>"; // Not-a-String
        }

      lastIndex = end;
    }

  // trailing literal data
  stanza += formatString.mid(lastIndex);

  // Finally, set the stanza to the member datum and return it.
  // We want to keep it as a member datum, because derived classes
  // can build upon it with specialized data elements.
  m_analysisStanza = stanza;

  // qDebug() << "Returning m_analysisStanza:" << m_analysisStanza;

  return m_analysisStanza;
}

void
MassSpecTracePlotCompositeWidget::
  setMinimalChargeFractionalPartPinnedDownWidgets(double value)
{
  static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget)
    ->setChargeMinimalFractionalPart(value);
}

void
MassSpecTracePlotCompositeWidget::
  setChargeStateEnvelopePeakSpanPinnedDownWidgets(std::size_t value)
{
  static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget)
    ->setChargeStateEnvelopePeakSpan(value);
}


} // namespace MineXpert

} // namespace MsXpS
