/* 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 <QThread>
#include <QInputDialog>


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


/////////////////////// pappsomspp includes
#include <pappsomspp/widget/plotwidget/massspectraceplotwidget.h>
#include <pappsomspp/processing/combiners/tracepluscombiner.h>
#include <pappsomspp/processing/combiners/traceminuscombiner.h>
#include <pappsomspp/processing/combiners/massspectrumpluscombiner.h>
#include <pappsomspp/processing/combiners/massspectrumminuscombiner.h>


/////////////////////// Local includes
#include "MassSpecTracePlotCompositeWidget.hpp"
#include "MassSpecTracePlotWidget.hpp"
#include "MassSpecTracePlotWnd.hpp"
#include "ProgramWindow.hpp"
#include "../nongui/MassPeakShaperConfig.hpp"
#include "../nongui/MassPeakShaper.hpp"
#include "ColorSelector.hpp"


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);
  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]() {
      BaseTracePlotCompositeWidget::mp_parentWnd->plotWidgetGotFocus(this);
    });

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

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

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

  // For this one we need to force the cast to the
  // pappso::MassSpecTracePlotWidget because the context that is passed as
  // argument is specific of that class.
  connect(static_cast<pappso::MassSpecTracePlotWidget *>(mp_plotWidget),
          &pappso::MassSpecTracePlotWidget::keyPressEventSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetKeyPressEvent);

  connect(mp_plotWidget,
          &pappso::MassSpecTracePlotWidget::keyReleaseEventSignal,
          this,
          &MassSpecTracePlotCompositeWidget::plotWidgetKeyReleaseEvent);

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

  // This connection might be required if the specialized mass spectrum plot
  // widget has some specific data to provide in the specialized context.
  //
  // 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]() {
    // qDebug();
    static_cast<BaseTracePlotWnd *>(getParentWnd())->duplicateTracePlot(this);
  });
}


void
MassSpecTracePlotCompositeWidget::createMainMenu()
{
  // qDebug() << "ENTER";

  // First make sure we do construct the base trace plot context menu.

  BaseTracePlotCompositeWidget::createMainMenu();

  mp_mainMenu->addSeparator();

  QMenu *deconvolutions_menu_p = mp_mainMenu->addMenu("&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]() {
      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);
    });

  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]() {
    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);
  });

  deconvolutions_menu_p->addAction(set_inter_peak_span_action_p);

  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");
  gaussian_fit_action_p->setShortcut(QKeySequence("Ctrl+G, F"));

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

  mp_mainMenu->addAction(gaussian_fit_action_p);

  // qDebug() << "EXIT";
}


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

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

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

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

  MassPeakShaperConfig mass_peak_shaper_config(
    0, fwhm, "H", 1, 500, 0, 1, MassPeakShapeType::GAUSSIAN);
  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

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

  for(int iter = 0; iter < n + 1; ++iter)
    delete[] B[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.");

      return;
    }

  // Create a local copy of the processing flow.

  ProcessingFlow local_processing_flow =
    getProcessingFlowForPlottable(source_plottable_p);

  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(),
           local_processing_flow,
           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());
}

void
MassSpecTracePlotCompositeWidget::mainMenuPushButtonClicked()
{
  // qDebug() << "ENTER";

  //// Create the contextual menu that will show when the button is clicked (see
  //// below).

  // The menu, when it is opened (shown) changes the size of the plot widget. We
  // want to restore the initial size.

  QByteArray composite_plot_widget_geometry = mp_plotWidget->saveGeometry();

  createMainMenu();
  m_ui.mainMenuPushButton->showMenu();

  mp_plotWidget->restoreGeometry(composite_plot_widget_geometry);

  // qDebug() << "EXIT";
}


void
MassSpecTracePlotCompositeWidget::plotWidgetKeyPressEvent(
  const pappso::MassSpecTracePlotContext &context)
{
  // qDebug().noquote() << "ENTER with massplotcontext:" << context.toString();

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

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

  m_plotWidgetPressedKeyCode = context.baseContext.pressedKeyCode;

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

      if(plottable_p == nullptr)
        {

#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.baseContext.pressedKeyCode == Qt::Key_Space)
}


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.pressedKeyCode;

  // qDebug() << "The key:" << m_plotWidgetPressedKeyCode
  //<< "the modifiers:" << context.keyboardModifiers;
}


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.lastZ)
  //.arg(context.lastMz)
  //.arg(context.lastMr);

  QString new_text =
    QString("[%1--%2] - delta: %3 -- z: %4 - m/z: %5 - Mr: %6")
      .arg(std::min<double>(context.baseContext.xRegionRangeStart,
                            context.baseContext.xRegionRangeEnd),
           0,
           'f',
           3)
      .arg(std::max<double>(context.baseContext.xRegionRangeStart,
                            context.baseContext.xRegionRangeEnd),
           0,
           'f',
           3)
      .arg(fabs(context.baseContext.xDelta), 0, 'f', 3)
      .arg(context.lastZ)
      .arg(context.lastMz, 0, 'f', 3)
      .arg(context.lastMr);

  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.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 = context;
  local_context.xRegionRangeStart       = m_integrationRange.lower;
  local_context.xRegionRangeEnd         = m_integrationRange.upper;

  if(m_isSinglePointIntegrationMode)
    integrationRequested(local_context);
}


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

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

  // Immediately check if the selection range makes sense. For example, having
  // both the start/end values in the negative values does not mean anything
  // with respect to mass spectrum data!

  // Remember that the xregion start and end are not sorted.
  double range_start =
    std::min<double>(context.xRegionRangeStart, context.xRegionRangeEnd);
  double range_end =
    std::max<double>(context.xRegionRangeStart, context.xRegionRangeEnd);

  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 local copy of the processing flow.

  ProcessingFlow local_processing_flow =
    getProcessingFlowForPlottable(plottable_p);

  // qDebug().noquote() << "Processing flow for graph:"
  //<< local_processing_flow.toString();

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    getMsRunDataSetCstSPtrForPlottable(plottable_p);

  // qDebug() << ms_run_data_set_csp->getMsRunDataSetStats().toString();

  // Now we need to add a new step in that processing flow so that we document
  // this new integration that we are performing.

  ProcessingStep *processing_step_p = new ProcessingStep();

  ProcessingSpec *processing_spec_p = new ProcessingSpec();

  MsFragmentationSpec ms_fragmentation_spec =
    local_processing_flow.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_spec_p->setMsFragmentationSpec(ms_fragmentation_spec);
    }
  else
    {
      // qDebug() << "The default ms frag spec from the processing flow is not "
      //"valid. Not using it.";
    }

  // Now define the graph range for the integration operation.

  processing_spec_p->setRange(range_start, range_end);

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

      processing_step_p->newSpec(ProcessingType("MZ_TO_RT"), processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

      static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
        ->integrateToRt(plottable_p, local_processing_flow);
    }
  else if(m_ui.integrateToMzPushButton->isChecked() ||
          m_ui.integrateToDtMzPushButton->isChecked() ||
          m_ui.integrateToRtMzPushButton->isChecked())
    {
      if(m_ui.integrateToMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to mass spectrum.";
          processing_step_p->newSpec(ProcessingType("MZ_TO_MZ"),
                                     processing_spec_p);
        }
      else if(m_ui.integrateToRtMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to rt|m/z color map.";
          processing_step_p->newSpec(ProcessingType("MZ_TO_RT_MZ"),
                                     processing_spec_p);
        }
      else if(m_ui.integrateToDtMzPushButton->isChecked())
        {
          // qDebug() << "Integrating to dt|m/z color map.";
          processing_step_p->newSpec(ProcessingType("MZ_TO_DT_MZ"),
                                     processing_spec_p);
        }

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

      // This is a multi-layer work: the graph has a ProcessingFlow associated
      // to it via the m_graphProcessingFlowMap. We have gotten it above.

      // Extract from it the default pappso::MzIntegrationParams:

      pappso::MzIntegrationParams mz_integration_params =
        local_processing_flow.getDefaultMzIntegrationParams();

      // If it is not valid, then try mz integration params from the various
      // steps of the flow. Preferentially the most recent ones.
      if(!mz_integration_params.isValid())
        {
          // qDebug()
          //<< "The processing flow's default mz integ params are not valid.";

          mz_integration_params =
            *(local_processing_flow.mostRecentMzIntegrationParams());
        }

      // If it is not valid, finally resort to the parameters that can be
      // crafted on the basis of the statistical analysis of the ms run data
      // set while it was loaded from file.
      if(!mz_integration_params.isValid())
        {
          // qDebug() << "The most recent mz integ param are not valid.";

          mz_integration_params =
            ms_run_data_set_csp->craftInitialMzIntegrationParams();
        }

      if(!mz_integration_params.isValid())
        qFatal(
          "The mz integ params from the ms run data set stats are not "
          "valid.");

      // 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.integrateToRtMzPushButton->isChecked())
        {
          mz_integration_params.setBinningType(pappso::BinningType::NONE);
          mz_integration_params.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.
      processing_step_p->setMzIntegrationParams(mz_integration_params);

      // 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.
      local_processing_flow.push_back(processing_step_p);

      // qDebug().noquote() << "Pushed back new processing step:"
      //<< processing_step_p->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, local_processing_flow);
        }
      else if(m_ui.integrateToDtMzPushButton->isChecked())
        {
          static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToDtMz(plottable_p, local_processing_flow);
        }
      else if(m_ui.integrateToRtMzPushButton->isChecked())
        {
          static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
            ->integrateToRtMz(plottable_p, local_processing_flow);
        }
    }
  else if(m_ui.integrateToDtPushButton->isChecked())
    {
      // qDebug() << "Integrating to drift spectrum.";

      processing_step_p->newSpec(ProcessingType("MZ_TO_DT"), processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

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


      ProcessingSpec *spec_p = new ProcessingSpec();

      processing_step_p->newSpec(ProcessingType("MZ_TO_RT_DT"), spec_p);

      // We now need to specify the MZ range.

      processing_step_p->newSpec(ProcessingType("MZ_TO_ANY"),
                                 processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

      static_cast<MassSpecTracePlotWnd *>(mp_parentWnd)
        ->integrateToRtDt(plottable_p, local_processing_flow);
    }
  else if(m_ui.integrateToIntPushButton->isChecked())
    {
      // qDebug() << "Integrating to TIC intensity.";

      processing_step_p->newSpec(ProcessingType("MZ_TO_INT"),
                                 processing_spec_p);

      local_processing_flow.push_back(processing_step_p);

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

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

  // 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 = 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 *plottable_p = nullptr;
  QList<QCPAbstractPlottable *> dest_plottable_list =
    plottablesToBeUsedAsIntegrationDestination();

  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.

      if(!dest_plottable_list.size())
        must_create_new_graph = true;
      else
        {
          // Since we are asked that an integration to a mass spectrum be
          // performed, there must be a non-nullptr mz integration pointer in
          // the step.
          const pappso::MzIntegrationParams *mz_integration_params_p =
            processing_flow.mostRecentMzIntegrationParams();

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

          if(mz_integration_params_p->getBinningType() == pappso::BinningType::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(static_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)
                    trace_minus_combiner.combine(map_trace, trace);
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    trace_plus_combiner.combine(map_trace, trace);

                  // Old version that was deprecated in 5.14.2
                  //
                  // static_cast<QCPGraph *>(iter_plottable_p)
                  //->setData(
                  // QVector<double>::fromStdVector(map_trace.firstToVector()),
                  // QVector<double>::fromStdVector(
                  // map_trace.secondToVector()),
                  // true);

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

#pragma GCC warning "Filippo Rusconi: Please check if the bug was fixed in Qt"

#if 0
                    // Now replace the graph's data. Note that the data are
                    // inherently sorted (true below).

                    // The begin() -- end() ranges constructor did not work as of
                    // Qt 5.14.2 this day: 20200721

                    key_vector =
                        QVector(map_trace.firstToVector().begin(),
                                map_trace.firstToVector().end());
                    value_vector =
                        QVector(map_trace.secondToVector().begin(),
                                map_trace.secondToVector().end());
#endif

                  for(auto &value : map_trace.xValues())
                    key_vector.push_back(value);

                  for(auto &value : map_trace.yValues())
                    value_vector.push_back(value);

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }
            }
          else if(mz_integration_params_p->getBinningType() ==
                  pappso::BinningType::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.

              const pappso::MzIntegrationParams *mz_integration_params_p =
                processing_flow.mostRecentMzIntegrationParams();

              if(mz_integration_params_p == nullptr)
                qFatal(
                  "Pointer to the returned mz integration parameters cannot be "
                  "nullptr.");

              pappso::MzIntegrationParams copy_mz_integration_params =
                *processing_flow.mostRecentMzIntegrationParams();

              // Because we are making an integration to a mass spectrum plot
              // widget, we must have that pointer to mz integration params!

              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(static_cast<QCPGraph *>(iter_plottable_p));

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

                  double smallest_mz_step =
                    std::min<double>(iter_trace.front().x, trace.front().x);
                  copy_mz_integration_params.setSmallestMz(smallest_mz_step);

                  double greatest_mz_step =
                    std::max<double>(iter_trace.back().x, trace.back().x);
                  copy_mz_integration_params.setGreatestMz(greatest_mz_step);

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

                  // Now set the bins to the combiner.

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


                  // At this point we can perform the combination.
                  if(m_newTraceHandlingMethod ==
                     NewTraceHandlingMethod::MINUS_COMBINE)
                    {
                      qDebug() << "NewTraceHandlingMethod::MINUS_COMBINE";
                      mass_spectrum_minus_combiner.combine(map_trace, trace);
                    }
                  else if(m_newTraceHandlingMethod ==
                          NewTraceHandlingMethod::PLUS_COMBINE)
                    {
                      qDebug() << "NewTraceHandlingMethod::PLUS_COMBINE";
                      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).

                  // Old version that was deprecated in 5.14.2
                  //
                  // static_cast<QCPGraph *>(iter_plottable_p)
                  //->setData(
                  // QVector<double>::fromStdVector(map_trace.firstToVector()),
                  // QVector<double>::fromStdVector(
                  // map_trace.secondToVector()),
                  // true);

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

#pragma GCC warning "Filippo Rusconi: Please check if the bug was fixed in Qt"

#if 0
                    // Now replace the graph's data. Note that the data are
                    // inherently sorted (true below).

                    // The begin() -- end() ranges constructor did not work as of
                    // Qt 5.14.2 this day: 20200721

                    key_vector =
                        QVector(map_trace.firstToVector().begin(),
                                map_trace.firstToVector().end());
                    value_vector =
                        QVector(map_trace.secondToVector().begin(),
                                map_trace.secondToVector().end());
#endif

                  for(auto &value : map_trace.xValues())
                    key_vector.push_back(value);

                  for(auto &value : map_trace.yValues())
                    value_vector.push_back(value);

                  static_cast<QCPGraph *>(iter_plottable_p)
                    ->setData(key_vector, value_vector, true);
                }
            }
        }
      // 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();

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

      plottable_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, plottable_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

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

#if 0

        // Test that the map works.
        ProcessingFlow processing_flow_test = getProcessingFlowForPlottable(graph_p);

        //qDebug() << processing_flow_test.getMsRunDataSetCstSPtr()
        //->getMsRunId()
        //->getFileName();

        //qDebug() << "Default mz integration parameters (test):"
        //<< processing_flow_test.getDefaultMzIntegrationParams().toString();

#endif

      // Now set the name of the sample to the trace label.

      m_ui.sampleNameLabel->setText(sample_name);
    }

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


QString
MassSpecTracePlotCompositeWidget::craftAnalysisStanza(
  const pappso::MassSpecTracePlotContext &context)
{
  // qDebug();

  QCPAbstractPlottable *plottable_p = plottableToBeUsedAsIntegrationSource();

  if(plottable_p == nullptr)
    {
      // 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();
    }

  ProcessingFlow processing_flow = getProcessingFlowForPlottable(plottable_p);

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();

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

  // Craft the analysis stanza by copying the m_analysisStanza that was
  // partially filled-in by the base classes and go on filling-in fields still
  // marked with "%<char>" format patterns.

  QString orig_stanza = m_analysisStanza;
  QString stanza;

  QChar prevChar = ' ';

  for(int iter = 0; iter < orig_stanza.size(); ++iter)
    {
      QChar curChar = orig_stanza.at(iter);

      // qDebug() << "Current char:" << curChar;

      if(curChar == '\\')
        {
          if(prevChar == '\\')
            {
              stanza += '\\';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '\\';
              continue;
            }
        }

      if(curChar == '%')
        {
          if(prevChar == '\\')
            {
              stanza += '%';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '%';
              continue;
            }
        }

      if(curChar == 'n')
        {
          if(prevChar == '\\')
            {
              stanza += QString("\n");

              // Because a newline only works if it is followed by something,
              // and
              // if the user wants a termination newline, then we need to
              // duplicate that new line char if we are at the end of the
              // string.

              if(iter == orig_stanza.size() - 1)
                {
                  // This is the last character of the line, then, duplicate
                  // the
                  // newline so that it actually creates a new line in the
                  // text.

                  stanza += QString("\n");
                }

              prevChar = ' ';
              continue;
            }
        }

      if(prevChar == '%')
        {
          // The current character might have a specific signification.
          if(curChar == 'f')
            {
              QFileInfo fileInfo(sample_name);
              if(fileInfo.exists())
                stanza += fileInfo.fileName();
              else
                stanza += "Untitled";
              prevChar = ' ';
              continue;
            }
          if(curChar == 's')
            {

              QFileInfo fileInfo(sample_name);
              if(fileInfo.exists())
                stanza += fileInfo.fileName();
              else
                stanza += "Untitled";

              prevChar = ' ';
              continue;
            }
          if(curChar == 'X')
            {
              // If the deconvolution was successful, than lastMz is the right
              // value. Otherwise it is the lastCursorHoveredPoint.

              if(context.lastMz != std::numeric_limits<double>::min())
                {
                  stanza += QString("%1").arg(context.lastMz, 0, 'g', 6);
                }
              else
                {
                  stanza += QString("%1").arg(
                    context.baseContext.lastCursorHoveredPoint.x(), 0, 'g', 6);
                }

              prevChar = ' ';
              continue;
            }
          if(curChar == 'Y')
            {
              // We need to get the value of the intensity for the graph, but
              // not as an integration to INT, but simply as the y coordinate
              // of the point at X.

              if(context.lastMz != std::numeric_limits<double>::min())
                {
                  stanza += QString("%1").arg(
                    static_cast<pappso::MassSpecTracePlotWidget *>(
                      mp_plotWidget)
                      ->getYatX(context.lastMz,
                                static_cast<QCPGraph *>(plottable_p)),
                    0,
                    'g',
                    6);
                }
              else
                {
                  stanza += QString("%1").arg(
                    static_cast<pappso::MassSpecTracePlotWidget *>(
                      mp_plotWidget)
                      ->getYatX(context.baseContext.lastCursorHoveredPoint.x(),
                                static_cast<QCPGraph *>(plottable_p)),
                    0,
                    'g',
                    6);
                }

              prevChar = ' ';
              continue;
            }
          if(curChar == 'x')
            {
              stanza +=
                QString("%1").arg(context.baseContext.xDelta, 0, 'g', 6);
              prevChar = ' ';
              continue;
            }
          if(curChar == 'y')
            {
              stanza += "this is the mass spec count delta y";

              prevChar = ' ';
              continue;
            }
          if(curChar == 'z')
            {
              stanza += QString("%1").arg(context.lastZ);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'M')
            {
              stanza += QString("%1").arg(context.lastMr, 0, 'f', 6);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'I')
            {
              stanza += QString("%1").arg(m_lastTicIntensity, 0, 'g', 3);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'b')
            {
              stanza += QString("%1").arg(
                context.baseContext.xRegionRangeStart, 0, 'f', 3);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'e')
            {
              stanza += QString("%1").arg(
                context.baseContext.xRegionRangeEnd, 0, 'f', 3);

              prevChar = ' ';
              continue;
            }
          // At this point the '%' is not followed by any special character
          // above, so we skip them both from the text. If the '%' is to be
          // printed, then it needs to be escaped. We do this because we do not
          // expect that derived classes will do the format-based field-filling
          // work later.

          continue;
        }
      // End of
      // if(prevChar == '%')

      // The character prior this current one was not '%' so we just append
      // the current character.
      stanza += curChar;
    }
  // End of
  // for (int iter = 0; iter < pattern.size(); ++iter)

  // Finally copy the result into the member datum.
  m_analysisStanza = stanza;
  return stanza;
}


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
