/* 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>
#include <iostream>
#include <iomanip>


/////////////////////// 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/processing/detection/localsignaltonoiseestimator.h>
#include <pappsomspp/core/processing/detection/highrespeakpicker.h>
#include <pappsomspp/core/utils.h>


/////////////////////// libXpertMassCore
#include <MsXpS/libXpertMassCore/MassDataCborMassSpectrumHandler.hpp>
#include <MsXpS/libXpertMassCore/MassPeakShaperConfig.hpp>
#include <MsXpS/libXpertMassCore/MassPeakShaper.hpp>
#include <MsXpS/libXpertMassCore/AbstractLowMassDeconvolver.hpp>
#include <MsXpS/libXpertMassCore/LowMassDeconvolverNoLabelling.hpp>
#include <MsXpS/libXpertMassCore/LowMassDeconvolverFullLabelling.hpp>
#include <MsXpS/libXpertMassCore/IsotopicClusterShaper.hpp>
#include <MsXpS/libXpertMassCore/DeconvolutedMassIdMatcher.hpp>
#include <MsXpS/libXpertMassCore/Utils.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();

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

  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),
          static_cast<void (pappso::MassSpecTracePlotWidget::*)(
            const pappso::MassSpecTracePlotContext &)>(
            &pappso::MassSpecTracePlotWidget::keyPressEventSignal),
          this,
          static_cast<void (MassSpecTracePlotCompositeWidget::*)(
            const pappso::MassSpecTracePlotContext &)>(
            &MassSpecTracePlotCompositeWidget::plotWidgetKeyPressEvent));

  connect(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::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";

  // At this point we can fit a Gaussian on the trace.

  int i, j, k, n, N;

  std::cout.precision(4); // set precision
  std::cout.setf(std::ios::fixed);

  N = local_maxima_trace.size();

  // qDebug() << "The number of local maxima: " << N;

  double *x     = new double[N];
  double *y     = new double[N];
  double *log_y = new double[N];

  pappso::DataPoint most_intense_datapoint;

  for(size_t iter = 0; iter < local_maxima_trace.size(); ++iter)
    {
      pappso::DataPoint data_point = local_maxima_trace.at(iter);

      if(data_point.y > most_intense_datapoint.y)
        most_intense_datapoint = data_point;

      x[iter] = data_point.x;
      y[iter] = data_point.y;

      // Transform the y values into ln(y) so that we transform something that
      // might be exponential to something that is polynomial (Caruana stuff).

      // std::log is the natural log (ln) in the stdlib, contrary to std::log10.

      log_y[iter] = std::log(data_point.y);
    }

  // Set the polynomial degree to 2 because we want a Gaussian fit and that is
  // what will provide the parameters for the Gaussian fit.
  n = 2;

  double *X = new double[2 * n + 1]; // Array that will store the values of
  // sigma(xi),sigma(xi^2),sigma(xi^3)....sigma(xi^2n)
  for(i = 0; i < 2 * n + 1; i++)
    {
      X[i] = 0;
      for(j = 0; j < N; j++)
        X[i] =
          X[i] + pow(x[j], i); // consecutive positions of the array will store
      // N,sigma(xi),sigma(xi^2),sigma(xi^3)....sigma(xi^2n)
    }

  // B is the Normal matrix(augmented) that will store the
  // equations. So it is an array of arrays.
  double **B = new double *[n + 1];

  for(int iter = 0; iter < n + 1; ++iter)
    B[iter] = new double[n + 2];

  // a is for value of the final coefficient
  double *a = new double[n + 1];

  // Build the Normal matrix by storing the
  // corresponding coefficients at the right positions
  // except the last column of the matrix
  for(i = 0; i <= n; i++)
    for(j = 0; j <= n; j++)
      B[i][j] = X[i + j];

  // Array to store the values of
  // sigma(yi),sigma(xi*yi),sigma(xi^2*yi)...sigma(xi^n*yi)
  double *Y = new double[n + 1];
  for(i = 0; i < n + 1; i++)
    {
      Y[i] = 0;
      for(j = 0; j < N; j++)
        // Y[i]=Y[i]+pow(x[j],i)*y[j];        //consecutive positions will store
        // sigma(yi),sigma(xi*yi),sigma(xi^2*yi)...sigma(xi^n*yi)
        Y[i] =
          Y[i] + pow(x[j], i) * log_y[j]; // consecutive positions will store
      // sigma(yi),sigma(xi*yi),sigma(xi^2*yi)...sigma(xi^n*yi)
    }
  for(i = 0; i <= n; i++)
    B[i][n + 1] = Y[i]; // load the values of Y as the last column of B(Normal
  // Matrix but augmented)
  n = n + 1; // n is made n+1 because the Gaussian Elimination part below was
  // for n equations, but here n is the degree of polynomial and for
  // n degree we get n+1 equations

  std::cout << "\nThe Normal(Augmented Matrix) is as follows:\n";
  for(i = 0; i < n; i++) // print the Normal-augmented matrix
    {
      for(j = 0; j <= n; j++)
        std::cout << B[i][j] << std::setw(16);
      std::cout << "\n";
    }

  for(i = 0; i < n;
      i++) // From now Gaussian Elimination starts(can be ignored) to solve
    // the set of linear equations (Pivotisation)
    for(k = i + 1; k < n; k++)
      if(B[i][i] < B[k][i])
        for(j = 0; j <= n; j++)
          {
            double temp = B[i][j];
            B[i][j]     = B[k][j];
            B[k][j]     = temp;
          }

  for(i = 0; i < n - 1; i++) // loop to perform the gauss elimination
    for(k = i + 1; k < n; k++)
      {
        double t = B[k][i] / B[i][i];
        for(j = 0; j <= n; j++)
          B[k][j] =
            B[k][j] - t * B[i][j]; // make the elements below the pivot elements
                                   // equal to zero or elimnate the variables
      }
  for(i = n - 1; i >= 0; i--) // back-substitution
    { // x is an array whose values correspond to the values of x,y,z..
      a[i] = B[i][n]; // make the variable to be calculated equal to the rhs of
      // the last equation
      for(j = 0; j < n; j++)
        if(j != i) // then subtract all the lhs values except the coefficient of
          // the variable whose value is being calculated
          a[i] = a[i] - B[i][j] * a[j];
      a[i] = a[i] / B[i][i]; // now finally divide the rhs by the coefficient of
                             // the variable to be calculated
    }

  // std::cout << "\nThe values of the coefficients are as follows:\n";
  // for(i = 0; i < n; i++)
  // std::cout << "x^" << i << "=" << a[i]
  //<< std::endl; // Print the values of x^0,x^1,x^2,x^3,....
  // std::cout << "\nHence the fitted Polynomial is given by:\ny=";
  // for(i = 0; i < n; i++)
  // std::cout << " + (" << a[i] << ")"
  //<< "x^" << i;
  // std::cout << "\n";

  double a_coeff        = a[0];
  double b_coeff        = a[1];
  double b_coeff_square = b_coeff * b_coeff;
  double c_coeff        = a[2];

  // std::cout << "a_coeff: " << a_coeff << "\nb_coeff: " << b_coeff
  //<< " b_coeff_square: " << b_coeff_square << "\nc_coeff: " << c_coeff
  //<< std::endl;

  double gaussian_mu = -b_coeff / (2 * c_coeff);
  // double gaussian_mu_square = gaussian_mu * gaussian_mu;

  double gaussian_sigma = sqrt(-1 / (2 * c_coeff));
  double fwhm           = 2 * gaussian_sigma;

  // std::cout << "gaussian_mu: " << gaussian_mu
  //<< "\ngaussian_sigma: " << gaussian_sigma
  //<< " gaussian_sigma_square: " << gaussian_sigma_square << std::endl;

  // Now perform the computation over the x values that were entered
  // initially.

  // From the Hongwei Guo article

  double A = std::exp(a_coeff - (b_coeff_square / (4 * c_coeff)));

  // std::cout
  //<< "Calculating A according to: A = exp(a_coeff - (b_coeff_square / (4 "
  //"* c_coeff)))"
  //<< std::endl;

  // std::cout << "A is now: " << A << std::endl;

  // At this point use our facility to compute the Gaussian trace.

  // This was the old version.
  // libXpertMassCore::MassPeakShaperConfig mass_peak_shaper_config(
  // 0, fwhm, "H", 1, 500, 0, 1,
  // libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN);

  libXpertMassCore::MassPeakShaperConfig mass_peak_shaper_config;
  mass_peak_shaper_config.setResolution(0);
  mass_peak_shaper_config.setFwhm(fwhm);
  mass_peak_shaper_config.setPointCount(200);
  mass_peak_shaper_config.setReferencePeakMz(most_intense_datapoint.x);
  mass_peak_shaper_config.setMzStep(0);
  mass_peak_shaper_config.setMassPeakShapeType(
    libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN);

  libXpertMassCore::MassPeakShaper mass_peak_shaper(
    gaussian_mu, A, mass_peak_shaper_config);

  mass_peak_shaper.computePeakShape();

  pappso::Trace fit_trace = mass_peak_shaper.getTrace();

#if 0

  // Testbed for the CBOR classes and the QTcpSocket communications.

  // qDebug() << "Going to write this trace:" << fit_trace.toString();

  libXpertMassCore::MassDataCborMassSpectrumHandler cbor_handler(this, fit_trace);

  QByteArray byte_array;

  cbor_handler.writeByteArray(byte_array);

  qDebug()
    << "Written the Gaussian mass spectrum to a byte array that is now of size:"
    << byte_array.size();

  qDebug() << "Going to signal that the byte array has to be served.";
  getParentWnd()->massDataToBeServedSignal(byte_array);
  qDebug() << "Signalled that the byte array has to be served.";


  // And now read it back from file.
  // cbor_trace_handler.clearTrace();
  // cbor_trace_handler.setInputFileName("/tmp/prova.cbor");
  // cbor_trace_handler.readFile();

  // pappso::Trace read_trace = cbor_trace_handler.getTrace();

  // qDebug() << "Read back this trace:" << read_trace.toString()
  //<< "of mass data type:"
  //<< static_cast<quint64>(cbor_trace_handler.getMassDataType())
  //<< "with title:" << cbor_trace_handler.getTitle();

  // QClipboard *clipboard = QApplication::clipboard();
  // clipboard->setText(read_trace.toString());

#endif

#if 0

    // Original way of doing where we only created points matching the local
    // maxima that were found above.

    for(int iter = 0; iter < N; ++iter)
    {
        double x_minus_mu_square =
            (x[iter] - gaussian_mu) * (x[iter] - gaussian_mu);

        // cout << "x_minus_mu_square: " << x_minus_mu_square << endl;

        double y = A * exp(-x_minus_mu_square / (2 * gaussian_sigma_square));

        std::cout << x[iter] << " " << y << "\n";

        fit_trace.push_back(pappso::DataPoint(x[iter], y));
    }
#endif


#if 0

    pappso::Trace other_fit_trace;

    // All this works well, but we want to use our own code.

    // But we want a much smoother Gaussian curve and larger also, so we manage to
    // compute more points.

    size_t local_maxima_trace_size       = local_maxima_trace.size();
    double local_maxima_x_interval_start = local_maxima_trace.front().x;
    double local_maxima_x_interval_end   = local_maxima_trace.back().x;

    double initial_mz_step =
        (local_maxima_x_interval_end - local_maxima_x_interval_start) /
        local_maxima_trace_size;

    // We want to have a much smaller step:

    double smaller_mz_step = initial_mz_step / 5;

    // We also want more points on the left and on the right of the initial maxima
    // trace x span. For example we want to add a fifth of the initial maxima
    // trace points on the left and the same on the right.

    size_t additional_side_points = local_maxima_trace_size / 2;

    // Now, we want that the Gaussian fit  be symmetrical even if the user did
    // not zoom-in on a symmetric set of isotopic cluster peaks. The symmetry is
    // performed with respect to the center of the Gaussian fit curve:
    // gaussian_mu.

    double left_half_x_interval = gaussian_mu - local_maxima_x_interval_start;
    double right_half_x_interval = local_maxima_x_interval_end - gaussian_mu;

    double half_interval = std::max<double>(left_half_x_interval, right_half_x_interval);

    double wider_x_interval_start =
        gaussian_mu - half_interval - (additional_side_points * smaller_mz_step);

    double wider_x_interval_end =
        gaussian_mu + half_interval + (additional_side_points * smaller_mz_step);

    size_t total_point_count =
        (wider_x_interval_end - wider_x_interval_start) / smaller_mz_step;

    //qDebug().noquote() << "local_maxima_x_interval_start:"
    //<< local_maxima_x_interval_start
    //<< "local_maxima_x_interval_end:"
    //<< local_maxima_x_interval_end << "\n"
    //<< "initial_mz_step:" << initial_mz_step << "\n"
    //<< "smaller_mz_step:" << smaller_mz_step << "\n"
    //<< "additional_side_points:" << additional_side_points
    //<< "\n"
    //<< "wider_x_interval_start:" << wider_x_interval_start
    //<< "\n"
    //<< "wider_x_interval_end:" << wider_x_interval_end << "\n"
    //<< "total_point_count:" << total_point_count << "\n";


    // At this point we can compute all the points of the Gaussian fit curve.

    for(size_t iter = 0; iter < total_point_count; ++iter)
    {
        double mz_value = wider_x_interval_start + (iter * smaller_mz_step);

        //qDebug() << "iter:" << iter << "with mz_value:" << mz_value;

        double mz_value_minus_mu_square =
            (mz_value - gaussian_mu) * (mz_value - gaussian_mu);

        double y =
            A * exp(-mz_value_minus_mu_square / (2 * gaussian_sigma_square));

        //qDebug().noquote() << QString("%1 %2").arg(mz_value, 0, 'f', 6).arg(y)
        //<< "\n";

        other_fit_trace.push_back(pappso::DataPoint(mz_value, y));
    }

#endif

  delete[] x;
  delete[] y;
  delete[] X;
  delete[] Y;

  delete[] a;

  // qDebug() << "n has value:" << n;

  for(int iter = 0; iter < n; ++iter)
    {
      // qDebug() << "Iterating at iter:" << iter;
      delete[] B[iter];
      // qDebug() << "deleted B at iter:" << iter;
    }

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

  if(!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(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(gaussian_mu, 0, 'f', 6)
                  .arg(A, 0, 'f', 2)
                  .arg(fwhm, 0, 'f', 3);

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

  delete temp_local_processing_flow_p;

  return;
}

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

  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)
        {
          QString msg = QString(
                          "There are currently %1 trace/bar plots\n"
                          "Please select a single trace plot and try again.")
                          .arg(plottable_count);
          // qDebug();
          QMessageBox::information(this, "Centroid extraction", msg);
        }

      return std::make_tuple(nullptr, pappso::Trace());
    }

  qDebug() << "Plottable:" << plottable_p;

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

  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<pappso::LocalSignalToNoiseEstimator::Parameters,
             pappso::HighResPeakPicker::Parameters>
    param_tuple =
      application_preferences_wnd_p->getPeakPickingPreferences(params_message);

  qDebug() << "Now allocating peak picker with the params:" << params_message;

  pappso::HighResPeakPicker peak_picker(std::get<1>(param_tuple));

  qDebug() << "Done allocating peak picker.";

  QList<pappso::HighResPeakPicker::PeakRange> peak_ranges;
  pappso::Trace trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(0, m_isPeakPickDeconvOnlyForVisibleRange);

  qDebug().noquote() << "Now picking peaks from starting trace:" << trace.size()
                     << "data points.";

  // qDebug() << "Starting trace:" << trace.toString();

  pappso::Trace centroids;
  peak_picker.pick(trace, centroids, peak_ranges);

  qDebug().noquote() << "Done peak picking, size of the new trace:"
                     << centroids.size() << "data points:\n"
                     << centroids.toString();

  // qDebug() << "The plottable:" << plottable_p->name();

  const ProcessingFlow *temp_local_processing_flow_p =
    getCstProcessingFlowPtrForPlottable(plottable_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.
  bool got_range_ok;
  QCPRange range = plottable_p->getKeyRange(got_range_ok);

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

  delete temp_local_processing_flow_p;

  if(got_range_ok)
    {
      plottable_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(),
           centroids.toString());

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

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

QList<libXpertMassCore::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::DeconvolutedFeature>();
    }

  QList<libXpertMassCore::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::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::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::LowMassDeconvolverWorker();

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

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

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

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

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

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

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

  connect(mqp_deconvolutionWorker,
          &libXpertMassCore::LowMassDeconvolverWorker::finishedSignal,
          mqp_deconvolutionWorker,
          &libXpertMassCore::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::DeconvolutedFeature>();
}

void
MassSpecTracePlotCompositeWidget::onDeconvolutionFinished(
  QList<libXpertMassCore::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 we no deconvoluted features.",
                               QMessageBox::Ok);
      return;
    }

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

  for(const libXpertMassCore::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)
{
  qDebug() << "Error:" << message;
}

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

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

pappso::Trace
MassSpecTracePlotCompositeWidget::synthesizeMassSpectrumOld(
  QList<libXpertMassCore::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::DeconvolutedFeature &deconvoluted_feature :
      deconvoluted_features)
    {
      libXpertMassCore::IsotopicClusterChargePair isotopic_cluster_charge_pair;

      for(libXpertMassCore::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::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::DeconvolutedFeature &deconvoluted_feature :
      deconvoluted_features)
    {
      libXpertMassCore::IsotopicClusterChargePair isotopic_cluster_charge_pair;

      for(libXpertMassCore::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::DeconvolutedFeature> &deconvoluted_features,
  QCPAbstractPlottable *source_plottable_p)
{
  if(!deconvoluted_features.size())
    {
      QMessageBox::warning(
        this,
        "Deconvolved mass-vs-id analyte identification",
        "There we 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::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::DeconvolutedFeature>>
    deconvoluted_features_qsp =
      QSharedPointer<QList<libXpertMassCore::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::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::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::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::DeconvolutedMassIdMatcher::finishedSignal,
          this,
          &MassSpecTracePlotCompositeWidget::onAnalyteIdentificationFinished);

  // Clean up threads when done
  connect(mqp_deconvolutedMassIdMatcher,
          &libXpertMassCore::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::DeconvolutedMassIdMatcher::finishedSignal,
  //         mqp_deconvolutedMassIdMatcher,
  //         &libXpertMassCore::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::DeconvolutedMassIdMatcher::MassIdMatch>>
    &mass_id_matches = mqp_deconvolutedMassIdMatcher->getMassIdMatches();

  for(int iter = 0; iter < mass_id_matches.size(); ++iter)
    {
      const QSharedPointer<
        libXpertMassCore::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::DeconvolutedMassIdMatcher::MassIdMatch>
            &mass_id_match_qsp = mass_id_matches.at(iter);

          // We need to get the supporting ions.

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

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


          for(const libXpertMassCore::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(
  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(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 mz_value_as_string =
        QString("%1").arg(context.m_lastMr, 0, 'f', 6);

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

      QClipboard *clipboard_p = QApplication::clipboard();
      clipboard_p->setText(mz_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)
}

void
MassSpecTracePlotCompositeWidget::plotWidgetKeyReleaseEvent(
  const pappso::BasePlotContext &context)
{
  BaseTracePlotCompositeWidget::plotWidgetKeyReleaseEvent(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";
    }
}

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::MassSpecTracePlotContext &context)
{
  // qDebug() << "The MassSpecTracePlotContext:" << context.toString();

  BaseTracePlotCompositeWidget::plotWidgetMouseReleaseEvent(event, context);
}

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_xRegionRangeEnd),
           0,
           'f',
           libXpertMassCore::MZ_DEC_PLACES)
      .arg(std::max<double>(context.m_xRegionRangeStart,
                            context.m_xRegionRangeEnd),
           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_xRegionRangeEnd   = m_integrationRange.upper;

  if(m_isSinglePointIntegrationMode)
    integrationRequested(local_context);
}

void
MassSpecTracePlotCompositeWidget::integrationRequested(
  const pappso::BasePlotContext &context)
{
  // 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;

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

  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())
    {
      // qDebug() << "Integrating to TIC intensity.";

      processing_step_sp->setDestProcessingType("INT");

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

      static_cast<BasePlotWnd *>(mp_parentWnd)
        ->integrateToTicIntensity(
          plottable_p, nullptr, *temp_processing_flow_p);
    }
  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
