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


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

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


/////////////////////// pappsomspp includes
#include <pappsomspp/processing/combiners/traceminuscombiner.h>
#include <pappsomspp/processing/combiners/tracepluscombiner.h>

#include <pappsomspp/processing/filters/savgolfilter.h>
#include <pappsomspp/processing/filters/filterlowintensitysignalremoval.h>

/////////////////////// Local includes
#include "BaseTracePlotCompositeWidget.hpp"
#include "BaseTracePlotWnd.hpp"
#include "ColorSelector.hpp"
#include "ProgramWindow.hpp"
#include "LabelledNumberLineEditAction.hpp"


namespace msxps
{
namespace minexpert
{


BaseTracePlotCompositeWidget::BaseTracePlotCompositeWidget(
  QWidget *parent, const QString &x_axis_label, const QString &y_axis_label)
  : BasePlotCompositeWidget(parent, x_axis_label, y_axis_label)
{
  // We need to add some menus / menu items.

  createMainMenu();

  // The trace combined plot widget receives traces from integrations. It is
  // possible to perform various actions with the traces inside a composite plot
  // widget and the selection of the action to perform is done using check
  // buttons or one combobox.

  // The replace trace(s) with new one action.

  m_ui.eraseTraceCreateNewPushButton->setShortcut(QKeySequence("Ctrl+T, E"));
  connect(m_ui.eraseTraceCreateNewPushButton, &QPushButton::clicked, [this]() {
    m_newTraceHandlingMethod = NewTraceHandlingMethod::REPLACE;
  });

  // The overlay action.

  m_ui.keepTraceCreateNewPushButton->setShortcut(QKeySequence("Ctrl+T, O"));
  connect(m_ui.keepTraceCreateNewPushButton, &QPushButton::clicked, [this]() {
    m_newTraceHandlingMethod = NewTraceHandlingMethod::OVERLAY;
  });

  // The combine (plus or minus) action.

  // The input logic, that is based on the set of icons in the left column. Some
  // icons are checkable buttons and one is a combobox. This is the one that is
  // tricky to handle because it is not possible to know if it is checed or not.

  const QIcon plus_combine_icon =
    QIcon(":/images/svg/keep-trace-and-plus-combine-new-one.svg");

  const QIcon minus_combine_icon =
    QIcon(":/images/svg/keep-trace-and-minus-combine-new-one.svg");

  m_ui.combinationsComboBox->insertItem(
    -1, minus_combine_icon, "", static_cast<int>(TraceCombinationSign::MINUS));
  m_ui.combinationsComboBox->insertItem(
    10, plus_combine_icon, "", static_cast<int>(TraceCombinationSign::PLUS));

  connect(m_ui.combinationsComboBox,
          QOverload<int>::of(&QComboBox::activated),
          [=]([[maybe_unused]] int index) {
            bool ok = false;

            int item_data = m_ui.combinationsComboBox->currentData().toInt(&ok);

            if(static_cast<TraceCombinationSign>(item_data) ==
               TraceCombinationSign::PLUS)
              {
                // qDebug() << "TraceCombinationSign::PLUS";

                m_newTraceHandlingMethod = NewTraceHandlingMethod::PLUS_COMBINE;
              }
            else if(static_cast<TraceCombinationSign>(item_data) ==
                    TraceCombinationSign::MINUS)
              {
                // qDebug() << "TraceCombinationSign::MINUS";

                m_newTraceHandlingMethod =
                  NewTraceHandlingMethod::MINUS_COMBINE;
              }
            else
              qFatal("Programming error.");
          });
}


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


void
BaseTracePlotCompositeWidget::createMainMenu()
{
  BasePlotCompositeWidget::createMainMenu();

  // Now append new Trace-specific menus

  // Complete the export/save menu item list with the trace specific menu item.

  QAction *save_peak_list_to_clipboard_p =
    new QAction("Save peak list to clipboard", this);
  save_peak_list_to_clipboard_p->setStatusTip(
    tr("Save peak list to clipboard"));
  save_peak_list_to_clipboard_p->setShortcut(QKeySequence("Ctrl+P, C"));

  connect(save_peak_list_to_clipboard_p, &QAction::triggered, [this]() {
    savePeakListToClipboard();
  });

  mp_mainMenu->addAction(save_peak_list_to_clipboard_p);

  mp_mainMenu->addSeparator();

  QMenu *integrations_menu_p = mp_mainMenu->addMenu("&Integrations");

  QAction *single_point_integration_mode_action_p = new QAction(
    "Perform single graph &point integrations", dynamic_cast<QObject *>(this));
  single_point_integration_mode_action_p->setStatusTip(
    "Perform single graph point integrations");
  single_point_integration_mode_action_p->setCheckable(true);
  single_point_integration_mode_action_p->setShortcut(
    QKeySequence("Ctrl+P, I"));

  connect(single_point_integration_mode_action_p,
          &QAction::triggered,
          [this, single_point_integration_mode_action_p]() {
            m_isSinglePointIntegrationMode =
              single_point_integration_mode_action_p->isChecked();
          });

  integrations_menu_p->addAction(single_point_integration_mode_action_p);

  mp_mainMenu->addSeparator();


  QMenu *filters_menu_p = mp_mainMenu->addMenu("&Filters");

  // We want to provide the user with the ability to define a noise sample. They
  // would create a new graph by integrating to that new graph with combination
  // the various regions they feel are noise. They would get a noise sample
  // trace. Then they'd call the menu below to store that trace for further use.

  QMenu *noise_handling_menu_p = filters_menu_p->addMenu("Noise handling");

  QAction *store_noise_sample_trace_action_p =
    new QAction("Store trace as noise sample trace", dynamic_cast<QObject *>(this));
  store_noise_sample_trace_action_p->setStatusTip(
    "Set the (selected) trace as a noise sample trace");
  connect(store_noise_sample_trace_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::setNoiseSampleTrace);
  noise_handling_menu_p->addAction(store_noise_sample_trace_action_p);

  QAction *reset_noise_sample_trace_action_p =
    new QAction("Reset the noise sample trace", dynamic_cast<QObject *>(this));
  reset_noise_sample_trace_action_p->setStatusTip(
    "Reset the noise sample trace");
  connect(reset_noise_sample_trace_action_p,
          &QAction::triggered,
          this,
          &BaseTracePlotCompositeWidget::resetNoiseSampleTrace);
  noise_handling_menu_p->addAction(reset_noise_sample_trace_action_p);

  filters_menu_p->addSeparator();

  // Savitzky-Golay filtering: two submenus: one to parameterize and the other
  // to actually run the filter.

  QMenu *savgol_filter_menu_p = filters_menu_p->addMenu("&Savitzky-Golay");

  QAction *savitzky_golay_config_filter_action_p = new QAction(
    "Configure Savitzky-Golay smoothing filter", dynamic_cast<QObject *>(this));
  savitzky_golay_config_filter_action_p->setStatusTip(
    "Configure Savitzky-Golay smoothing filter");
  savitzky_golay_config_filter_action_p->setShortcut(QKeySequence("Ctrl+S, C"));

  connect(savitzky_golay_config_filter_action_p, &QAction::triggered, [this]() {
    showSavitzkyGolayFilterParamsDlg();
  });

  savgol_filter_menu_p->addAction(savitzky_golay_config_filter_action_p);

  QAction *savitzky_golay_run_filter_action_p = new QAction(
    "Run Savitzky-Golay smoothing filter", dynamic_cast<QObject *>(this));
  savitzky_golay_run_filter_action_p->setStatusTip(
    "Run Savitzky-Golay smoothing filter");
  savitzky_golay_run_filter_action_p->setShortcut(QKeySequence("Ctrl+S, R"));

  connect(savitzky_golay_run_filter_action_p, &QAction::triggered, [this]() {
    runSavitzkyGolayFilter();
  });

  savgol_filter_menu_p->addAction(savitzky_golay_run_filter_action_p);

  // Now add a new menu for the Low intensity signal removel filter.

  QWidgetAction *widget_action_p =
    new LabelledNumberLineEditAction("Low intensity threshold removal",
                                     10000,
                                     0,
                                     10e100,
                                     2,
                                     true,
                                     dynamic_cast<QObject *>(this));
  widget_action_p->setStatusTip("Remove signal below the provided threshold");
  filters_menu_p->addAction(widget_action_p);

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

    double threshold =
      static_cast<LabelledNumberLineEditAction *>(widget_action_p)
        ->getValue()
        .toDouble(&ok);

    if(!ok)
      qFatal("Failed to convert QVariant to double");

    //qDebug() << "triggered with threshold value:" << threshold;

    // Get the source plottable that might be of interest to the user.
    QCPAbstractPlottable *source_plottable_p =
      plottableToBeUsedAsIntegrationSource();

    // qDebug() << "The source plottable:" << source_plottable_p;

    if(source_plottable_p == nullptr)
      {
        QMessageBox::information(
          this,
          "Please select *one* trace",
          "In order to set the Savitzky-Golay filter parameters, the source "
          "graph needs to be selected.",
          QMessageBox::StandardButton::Ok,
          QMessageBox::StandardButton::NoButton);

        return;
      }

    std::size_t point_count;
    double mean    = 0;
    double std_dev = 0;

    // If there is no noise sample trace, then provide 0-valued variables. The
    // user will see nothing.
    bool res = static_cast<BaseTracePlotWnd *>(mp_parentWnd)
                 ->getNoiseSampleStats(point_count, mean, std_dev);

    if(!res)
      {
        mean    = 0;
        std_dev = 0;
      }

    // Create the new filter.
    pappso::FilterLowIntensitySignalRemovalCstSPtr new_filter_csp =
      std::make_shared<pappso::FilterLowIntensitySignalRemoval>(
        pappso::FilterLowIntensitySignalRemoval(mean, std_dev, threshold));

    // Get the processing flow for that specific graph.
    ProcessingFlow *processing_flow_p =
      getProcessingFlowPtrForPlottable(source_plottable_p);

    // Add the filter to the processing flow for the proper plottable.
    processing_flow_p->addFilter(new_filter_csp);

    // Finally, since the action was triggered, run the filter.

    runFilter(new_filter_csp->name());
  });
}


QCPGraph *
BaseTracePlotCompositeWidget::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();

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

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

  // 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;
  QCPGraph *graph_p          = nullptr;
  QList<QCPAbstractPlottable *> dest_graph_list =
    plottablesToBeUsedAsIntegrationDestination();

  pappso::TracePlusCombiner trace_combiner;

  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:

      // Now check what is the text:

      // 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_graph_list.size())
        must_create_new_graph = true;
      else
        {
          // // The integration that is performed here is granted to be in
          // integration to either TIC or DT because for MZ there is an
          // overriden function in the MassSpecTracePlotCompositeWidget class.
          // So go for Trace combinations and not Mass Spectrum combinations.

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

          for(int iter = 0; iter < dest_graph_list.size(); ++iter)
            {
              QCPGraph *iter_graph_p =
                static_cast<QCPGraph *>(dest_graph_list.at(iter));

              pappso::Trace iter_trace =
                static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
                  ->toTrace(iter_graph_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);

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

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

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

              // Apparently, as of 20210928, that bug is fixed.
              //#pragma GCC warning "Filippo Rusconi: Please check if the bug
              // was fixed in Qt"

              // 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 -- and still does not work
              // 20211018.


              // key_vector =
              // QVector(map_trace.xValues().begin(),
              // map_trace.xValues().end());
              // value_vector =
              // QVector(map_trace.yValues().begin(),
              // map_trace.yValues().end());

              // This is the workaround fix.

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

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

              iter_graph_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)
    {
      // Start by erasing all the traces that are selected.

      for(int iter = 0; iter < dest_graph_list.size(); ++iter)
        {
          QCPGraph *iter_graph_p =
            static_cast<QCPGraph *>(dest_graph_list.at(iter));

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

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

  if(must_create_new_graph)
    {
      // qDebug();

      // At this point we know we need to create a new trace.

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

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

      // Now set the name of the sample to the trace label using the right
      // color.

      QPalette palette;
      palette.setColor(QPalette::WindowText, local_color);
      m_ui.sampleNameLabel->setPalette(palette);
      m_ui.sampleNameLabel->setText(sample_name);
    }

  // qDebug() << "Finished adding trace, asking for the plot widget to replot.";

  mp_plotWidget->replot();

  // The setFocus() call below will trigger the mp_plotWidget to send a signal
  // that will be caught by the parent window that will iterate in all the plot
  // widget and set their background to focused/unfocused color according to
  // their focus status.
  // mp_plotWidget->setFocus();

  // graph_p might be nullptr.
  return graph_p;
}


void
BaseTracePlotCompositeWidget::showSavitzkyGolayFilterParamsDlg()
{

  // First we get the source plottable 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 set the m/z integration parameters, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  // qDebug() << "The source plottable:" << source_plottable_p;

  // Set aside SavGolParams that we'll need later.
  pappso::SavGolParams sav_gol_params;

  // Now see if we can get the filter from the ProcessingFlow. If it is not
  // there, not a problem, since the idea is precisely that we are setting one
  // up.

  pappso::FilterNameInterfaceCstSPtr filter_csp =
    filter("Savitzky-Golay", source_plottable_p);

  if(filter_csp != nullptr)
    {
      const pappso::FilterSavitzkyGolay *filter_p =
        static_cast<const pappso::FilterSavitzkyGolay *>(filter_csp.get());

      if(filter_p != nullptr)
        {
          sav_gol_params = filter_p->getParameters();
          // qDebug() << "Got parameters from found filter:"
          //<< sav_gol_params.toString();
        }
    }

  // Else, we keep the default values set in the constructor.

  if(mp_savitzkyGolayFilterParamsDlg == nullptr)
    {
      // qDebug();

      mp_savitzkyGolayFilterParamsDlg = new SavitzkyGolayFilterParamsDlg(
        this, sav_gol_params, source_plottable_p->pen().color());

      connect(mp_savitzkyGolayFilterParamsDlg,
              &SavitzkyGolayFilterParamsDlg::
                savitzkyGolayFilterParamsDlgShouldBeDestroyedSignal,
              [this]() {
                // qDebug();
                delete QObject::sender();
                mp_savitzkyGolayFilterParamsDlg = nullptr;
              });

      connect(
        mp_savitzkyGolayFilterParamsDlg,
        &SavitzkyGolayFilterParamsDlg::savitzkyGolayFilterParamsChangedSignal,
        this,
        &BaseTracePlotCompositeWidget::savitzkyGolayFilterParamsChanged);
    }

  mp_savitzkyGolayFilterParamsDlg->show();
}


void
BaseTracePlotCompositeWidget::savitzkyGolayFilterParamsChanged(
  pappso::SavGolParams sav_gol_params)
{
  // qDebug();

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

  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  // qDebug() << "The source plottable:" << source_plottable_p;

  if(source_plottable_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to set the Savitzky-Golay filter parameters, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  // Create a new filter

  pappso::FilterSavitzkyGolayCstSPtr filter_csp =
    std::make_shared<const pappso::FilterSavitzkyGolay>(
      pappso::FilterSavitzkyGolay(sav_gol_params));

  // Next get the processing flow for that specific graph.

  ProcessingFlow *processing_flow_p =
    getProcessingFlowPtrForPlottable(source_plottable_p);

  // And finally set the parameters.
  processing_flow_p->addFilter(filter_csp);
}


void
BaseTracePlotCompositeWidget::runFilter(const QString &filter_name)
{
  // qDebug();

  // First get the plottable for which to apply the filter.

  QCPAbstractPlottable *source_plottable_p =
    plottableToBeUsedAsIntegrationSource();

  // qDebug() << "The source plottable:" << source_plottable_p;

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

      return;
    }

  // Now see if we can get a filter by that name and for that plottable.

  pappso::FilterNameInterfaceCstSPtr filter_csp =
    filter(filter_name, source_plottable_p);

  if(filter_csp == nullptr)
    {
      QMessageBox::information(this,
                               "Filter not found",
                               "The filter was not found. Please, check that "
                               "it was configured properly.",
                               QMessageBox::StandardButton::Ok,
                               QMessageBox::StandardButton::NoButton);

      return;
    }

  // qDebug() << "Running filter by name:" << filter_name
  //<< "with string representation : " << filter_csp->toString();

  // We can go on with the filtering task.

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

  pappso::Trace filtered_trace = filter_csp->filter(data_trace);

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

  if(!filtered_trace.size())
    {
      qDebug() << "The trace produced by the filter is empty.";

      m_ui.statusLineEdit->setText(
        QString("The filter %1 produced an empty trace. Plotting nothing.")
          .arg(filter_name));

      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(filtered_trace,
           source_plottable_p,
           ms_run_data_set_csp,
           ms_run_data_set_csp->getMsRunId()->getSampleName(),
           local_processing_flow,
           source_plottable_p->pen().color());
}


void
BaseTracePlotCompositeWidget::runSavitzkyGolayFilter()
{
  runFilter("Savitzky-Golay");
}


void
BaseTracePlotCompositeWidget::plotWidgetKeyPressEvent(
  const pappso::BasePlotContext &context)
{
   //qDebug() << "ENTER with base plot context:" << context.toString();

  // Call the base class that will fill-in various generic data, like the
  // file/sample name, the x and y ranges...

  BasePlotCompositeWidget::plotWidgetKeyPressEvent(context);

  m_plotWidgetPressedKeyCode = context.m_pressedKeyCode;

  // qDebug() << "The context" << context.toString();

  if(context.m_pressedKeyCode == Qt::Key_Space)
    {
      // At the moment, in this class we have nothing very specific to do. But
      // we could reparse the m_analysisStanza string and fill-in any
      // remaining
      // %<char> that might have been remained unfilled by the base class
      // function call.
    }

  // We do not call the stanza recording function because that will be
  // performed by the derived classes when all the chances of having filled-in
  // the fields have been met.
}


void
BaseTracePlotCompositeWidget::plotWidgetKeyReleaseEvent(
  const pappso::BasePlotContext &context)
{
  BasePlotCompositeWidget::plotWidgetKeyReleaseEvent(context);

  // The F3 key triggers the displacement of the mouse cursor to the next
  // graph point. If integrations are requested, then an integration for that
  // single point is performed (see m_isSinglePointIntegrationMode).

  if(m_plotWidgetReleasedKeyCode == Qt::Key_F3)
    {
      // qDebug() << "F3 key was released.";
      moveMouseCursorToNextGraphPoint(context);
    }

  if(m_plotWidgetPressedKeyCode == Qt::Key_Escape)
    {
      // The user wants to nullify any ongoing integration using the right
      // mouse button.

      // The simplest way is to simulate a left click mouse button.

      if(mp_plotWidget)
        {
          QPointF mouseCursorPos(
            mp_plotWidget->xAxis->coordToPixel(context.m_currentDragPoint.x()),
            mp_plotWidget->xAxis->coordToPixel(context.m_currentDragPoint.y()));

          QMouseEvent *mouse_event_p = new QMouseEvent(QEvent::MouseButtonPress,
                                                       mouseCursorPos,
                                                       mouseCursorPos,
                                                       Qt::LeftButton,
                                                       Qt::LeftButton,
                                                       Qt::NoModifier);

          QCoreApplication::sendEvent(QWidget::focusWidget(), mouse_event_p);

          mouse_event_p = new QMouseEvent(QEvent::MouseButtonRelease,
                                          mouseCursorPos,
                                          mouseCursorPos,
                                          Qt::LeftButton,
                                          Qt::LeftButton,
                                          Qt::NoModifier);

          QCoreApplication::sendEvent(QWidget::focusWidget(), mouse_event_p);
        }

      // qDebug() << "Escape key released.";
    }
}


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

  // We want to reach the next valid graph point and, if integration is
  // requested, perform an integration around that single point, without
  // including any other point.

  // The idea is that when we find the next data point, we try to find a range
  // that encloses it. If x is the found *next* data point, w is prev and y is
  // next, then this is the situation.
  // ------- w ----a--- x ----b---y ---------
  //
  // Ideally we want an integration range like [ a - b ], that is half w-x to
  // half x-y.

  // qDebug();

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());
  if(source_graph_p == nullptr)
    return;

  QSharedPointer<QCPGraphDataContainer> graph_data_container_sp =
    source_graph_p->data();

  if(!graph_data_container_sp->size())
    {
      // qDebug() << "Graph is empty.Returning.";
      return;
    }

  // This is the secret of the whole process. In order to be able to use the
  // findEnd() function from QCustomPlot, we need to virtually displace the
  // current cursor point one pixel to the right. In this manner findEnd()
  // will not return the same iterator position as for the previous function
  // call.
  //
  // From the docs:
  // findEnd() returns an iterator to the element after the data point with a
  // (sort-)key that is equal to, just above or just below sortKey. If
  // expandedRange is true, the data point just above sortKey will be
  // considered, otherwise the one just below.

  QPointF incremented_cursor_point =
    mp_plotWidget->horizontalGetGraphCoordNewPointCountPixels(1);

  // qDebug() << "incremented_incremented_cursor_point:"
  //<< incremented_cursor_point;

  QCPGraphDataContainer::const_iterator next_data_point_iterator =
    graph_data_container_sp->findEnd(incremented_cursor_point.x(),
                                     // allow for upper position
                                     false);

  QCPGraphDataContainer::const_iterator find_end_data_iterator =
    graph_data_container_sp->findEnd(incremented_cursor_point.x(),
                                     // allow for upper position
                                     false);

  // We need to check that it does not point to end().

  if(find_end_data_iterator == graph_data_container_sp->end())
    {
      // qDebug() << "find_end_data_iterator is equal to end(), setting "
      //"next_data_point_iterator to begin().";

      next_data_point_iterator = graph_data_container_sp->begin();
    }
  else
    {
      // qDebug() << "find_end_data_iterator:" << find_end_data_iterator->key
      //<< "-" << find_end_data_iterator->value;
    }

  // qDebug() << "Now next_data_point_iterator:" <<
  // next_data_point_iterator->key
  //<< "-" << next_data_point_iterator->value;

  // Finally, we know where we want to land.

  mp_plotWidget->moveMouseCursorGraphCoordToGlobal(
    QPointF(next_data_point_iterator->key, next_data_point_iterator->value));

  // qDebug() << "Moved cursor to next point.";

  if(m_isSinglePointIntegrationMode)
    {
      // qDebug();

      // Go on with the range integration determination and integration.
      // Now compute the range of interest

      // ------- w ----a--- x ----b---y ---------

      //
      // To recap:
      //
      // next_data_point_iterator points to x


      if(next_data_point_iterator == graph_data_container_sp->begin())
        {
          // qDebug() << "next_data_point_iterator is begin(). Subtracting 10
          // to " "the begin() key.";

          m_integrationRange.lower = graph_data_container_sp->begin()->key - 10;
        }
      else
        {
          // Get the previous point.

          QPointF previous_point(std::prev(next_data_point_iterator)->key,
                                 std::prev(next_data_point_iterator)->value);

          // qDebug() << "Previous point:" << previous_point;

          // Compute the midvalue a (see diagram above).

          m_integrationRange.lower =
            next_data_point_iterator->key -
            (next_data_point_iterator->key - previous_point.x()) / 2;
        }

      // At this point, we have the lower member of the m_integrationRange.

      // Now compute the b value.

      if(next_data_point_iterator == std::prev(graph_data_container_sp->end()))
        {
          // We are at the last graph point, simply go to the right of it.

          m_integrationRange.upper = next_data_point_iterator->key + 10;
        }
      else
        {
          // Get the next point.

          QPointF next_point(std::next(next_data_point_iterator)->key,
                             std::next(next_data_point_iterator)->value);

          // Compute the midvalue b (see diagram above).

          m_integrationRange.upper =
            (next_data_point_iterator->key +
             (next_point.x() - next_data_point_iterator->key) / 2);
        }

      // At this point, we should have reliable m_integrationRange.

      // qDebug() << "Integration range:" << m_integrationRange.lower << "-"
      //<< m_integrationRange.upper;

      // qDebug() << "The context from the plot widget, now that we have moved
      // "
      //"the cursor:"
      //<< mp_plotWidget->getContext().toString();
    }
}


void
BaseTracePlotCompositeWidget::setNoiseSampleTrace()
{

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

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());

  if(source_graph_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to set the noise sample trace, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  pappso::Trace trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(source_graph_p);

  static_cast<BaseTracePlotWnd *>(mp_parentWnd)->setNoiseSampleTrace(trace);
}


void
BaseTracePlotCompositeWidget::resetNoiseSampleTrace()
{

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

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());

  if(source_graph_p == nullptr)
    {
      QMessageBox::information(
        this,
        "Please select *one* trace",
        "In order to reset the noise sample trace, the source "
        "graph needs to be selected.",
        QMessageBox::StandardButton::Ok,
        QMessageBox::StandardButton::NoButton);

      return;
    }

  static_cast<BaseTracePlotWnd *>(mp_parentWnd)->resetNoiseSampleTrace();
}


bool
BaseTracePlotCompositeWidget::savePeakListToClipboard()
{
  // qDebug();

  // Check first how many plottables there are in the widget.

  QCPGraph *source_graph_p =
    static_cast<QCPGraph *>(plottableToBeUsedAsIntegrationSource());

  if(source_graph_p == nullptr)
    {
      // We should inform the user if there are more than one plottable.

      int plottable_count = mp_plotWidget->plottableCount();

      if(plottable_count > 1)
        QMessageBox::information(this,
                                 "Save peak list to clipboard",
                                 "Please select a single trace and try again.");

      return false;
    }

  pappso::Trace trace =
    static_cast<pappso::BaseTracePlotWidget *>(mp_plotWidget)
      ->toTrace(source_graph_p);

  QString trace_string = trace.toString();

  QClipboard *clipboard_p = QApplication::clipboard();

  clipboard_p->setText(trace_string, QClipboard::Clipboard);

  return true;
}


} // namespace minexpert

} // namespace msxps
