/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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
 */


#include <qmath.h>
#include <omp.h>


/////////////////////// Qt includes
#include <QApplication>
#include <QClipboard>

/////////////////////// Local includes
#include <minexpert/gui/DriftSpecWnd.hpp>
#include <minexpert/gui/MainWindow.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderSqlite3.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderPwiz.hpp>
#include <minexpert/nongui/MassDataIntegrator.hpp>


namespace msXpSmineXpert
{

//! Initialize the widget count to 0.
int DriftSpecWnd::widgetCounter = 0;

//! Construct an initialized DriftSpecWnd instance.
/*!

  This function calls initialize() to perform the initialization of the
  widgets.

  \param parent parent widget.

*/
DriftSpecWnd::DriftSpecWnd(QWidget *parent)
  : AbstractMultiPlotWnd{
      parent, "mineXpert" /*m_name*/, "Drift-spectrum" /*m_desc*/}
{
  initialize();
}

//! Destruct \c this DriftSpecWnd instance.
DriftSpecWnd::~DriftSpecWnd()
{
  writeSettings();
}


//! Initialize the various widgets. Calls initializePlotRegion().
void
DriftSpecWnd::initialize()
{
  initializePlotRegion();

  readSettings();
}


//! Perform the initialization of the plot region of the window.
void
DriftSpecWnd::initializePlotRegion()
{
  // There are two regions in the window. The upper part of the mp_splitter
  // is gonna receive the multi-graph plotwidget. The lower part of the
  // splitter will receive all the individual plot widgets.

  // We will need later to know that this plot widget is actually for
  // displaying many different graphs, contrary to the normal situation (see
  // true parameter in the function call below).

  mp_multiGraphPlotWidget =
    new DriftSpecPlotWidget(this,
                            m_name,
                            m_desc,
                            Q_NULLPTR /* MassSpecDataSet * */,
                            QString() /* fileName */,
                            true /* isMultiGraph */);

  mp_multiGraphPlotWidget->setObjectName("multiGraphPlot");

  // We need to remove the single graph that was created upon creation of
  // the QCustomPlot widget because we'll later add graphs, and we want to
  // have a proper correlation between the index of the graphs and their
  // pointer value. If we did not remove that graph "by default", then there
  // would be a gap of 1 between the index of the first added mass graph and
  // its expected value.
  mp_multiGraphPlotWidget->clearGraphs();

  // Ensure that there is a connection between this plot widget and this
  // containing window, such that if there is a requirement to replot, the
  // widget gets the signal.
  connect(this,
          &AbstractMultiPlotWnd::replotWithAxisRange,
          mp_multiGraphPlotWidget,
          &AbstractPlotWidget::replotWithAxisRange);

  // This window is responsible for the writing of the analysis stanzas to
  // the file. We need to catch the signals.
  connect(mp_multiGraphPlotWidget,
          &DriftSpecPlotWidget::recordAnalysisStanza,
          this,
          &DriftSpecWnd::recordAnalysisStanza);

  // This window is responsible for displaying the key/value pairs of the
  // graphs onto which the mouse is moved. We need to catch the signal.
  connect(mp_multiGraphPlotWidget,
          &AbstractPlotWidget::updateStatusBarSignal,
          this,
          &AbstractMultiPlotWnd::updateStatusBar);


  // We want to create a scroll area in which the multigraph plot widget
  // will be sitting.
  QScrollArea *scrollArea = new QScrollArea();
  scrollArea->setObjectName(QStringLiteral("scrollArea"));
  scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  // We accept that the plots are resized, but we set a minimum size when we
  // create them.
  scrollArea->setWidgetResizable(true);
  scrollArea->setAlignment(Qt::AlignCenter);
  scrollArea->setWidget(mp_multiGraphPlotWidget);
  // And now prepend to the mp_splitter that new scroll area. Remember that
  // the mp_splitter had already a widget set to it, that is, the scroll
  // area that is now located below the mp_multiGraphPlotWidget, that is
  // responsible for the hosting of all the single-graph plot widgets.
  mp_splitter->insertWidget(0, scrollArea);
}


//! Add a new plot widget.
/*!

  \param keyVector vector of double values to be used as the x-axis data.

  \param valVector vector of double values to be used as the y-axis data.

  \param desc string containg the description of the plot.

  \param massSpecDataSet the MassSpecDataSet instance in which the mass data
  have been stored in the first place.

  \param isMultiGraph tell if the plot widget will be plot widget into which
  multiple graph will be plotted.

  \return the allocated plot widget.

*/
DriftSpecPlotWidget *
DriftSpecWnd::addPlotWidget(const QVector<double> &keyVector,
                            const QVector<double> &valVector,
                            const QString &desc,
                            const QColor &color,
                            const MassSpecDataSet *massSpecDataSet,
                            bool isMultiGraph)
{
  MainWindow *mainWindow = static_cast<MainWindow *>(parent());

  // Beware that massSpecDataSet might be Q_NULLPTR.
  QString fileName;

  if(massSpecDataSet != Q_NULLPTR)
    fileName = massSpecDataSet->fileName();
  else
    fileName = "NOT_SET";

  DriftSpecPlotWidget *widget = new DriftSpecPlotWidget(
    this, m_name, desc, massSpecDataSet, fileName, isMultiGraph);

  widget->setObjectName("monoGraphPlot");

  ///////////////// BEGIN Script availability of the new widget
  /////////////////////

  // Craft a name that differentiates this widget from the previous and next
  // ones. We use the static counter number to affix to the string
  // "driftSpecPlot".

  QString propName =
    QString("driftSpecPlotWidget%1").arg(DriftSpecWnd::widgetCounter);
  QString alias = "lastDriftSpecPlotWidget";

  QString explanation =
    QString(
      "Make available a drift spectrum plot widget under name \"%1\" "
      "with alias \"%2\".\n")
      .arg(propName)
      .arg(alias);

  // Make the new plot widget globally available under the crafted property
  // name and alias. Also, put out the explanation text. Note that by
  // providing 'this', we make the new object appear in the scripting objet
  // tree widget as a child item of this.

  QScriptValue scriptValue;
  mainWindow->mp_scriptingWnd->publishQObject(widget,
                                              this /* parent object */,
                                              propName,
                                              alias,
                                              explanation,
                                              QString() /* help string */,
                                              scriptValue);

#if 0
			// Old way of doing, we now handle the alias in one go above

			// Now make the new plot available under the name "lastDriftSpecPlot"
			// for the user to catch it with own variable, because that object name
			// will be overwritten later.

			mainWindow->mp_scriptingWnd->publishQObject(widget, this /* parent object */,
					"lastDriftSpecPlot", QString() /* alias */,
					"The last (in time) drift spectrum plot created. Use this handle at "
					"creation time to store the object under a name of your choice",
					"Make available the last mass spectrum plot under name \"lastDriftSpectrumPlot\"",
					scriptValue);
#endif

  ++DriftSpecWnd::widgetCounter;

  ///////////////// END Script availability of the new widget //////////////////


  widget->setMinimumSize(400, 150);
  mp_scrollAreaVBoxLayout->addWidget(widget);

  widget->setGraphData(keyVector, valVector);

  widget->setPlottingColor(color);

  widget->rescaleAxes(true /*only enlarge*/);
  widget->replot();

  // Store in the history the new ranges of both x and y axes.
  widget->updateAxisRangeHistory();

  // At this point, make sure that the same data are represented on the
  // multi-graph plot widget.

  QCPGraph *newGraph = mp_multiGraphPlotWidget->addGraph();
  newGraph->setData(keyVector, valVector);

  // Duplicate the same widget pen for the multigraph graph.
  newGraph->setPen(widget->graph()->pen());
  mp_multiGraphPlotWidget->rescaleAxes(true /*only enlarge*/);
  mp_multiGraphPlotWidget->replot();

  widget->setMultiGraph(newGraph);

  // Maintain a hash connection between any given plot widget and the
  // corresponding graph in the multigraph widget.
  m_plotWidgetGraphMap.insert(widget, newGraph);

  // Finally append the new widget to the list of widgets that belong to
  // this window.
  m_plotWidgetList.append(widget);

  // Signal/slot connections.

  connect(widget,
          &DriftSpecPlotWidget::recordAnalysisStanza,
          this,
          &DriftSpecWnd::recordAnalysisStanza);

  connect(widget,
          &AbstractPlotWidget::updateStatusBarSignal,
          this,
          &AbstractMultiPlotWnd::updateStatusBar);

  connect(this,
          &AbstractMultiPlotWnd::showHelpSummarySignal,
          widget,
          &AbstractPlotWidget::showHelpSummary);

  return widget;
}


//! Integrate data to a drift spectrum starting from TIC chromatogram.
/*!

  \param massSpecDataSet pointer to the MassSpecDataSet where the mass data
  are stored.

  \param integrationType IntegrationType of the integration to be performed.

  \param history History to account for the integration.

  \param color to use to plot the graph of the new drift spectrum.

*/
void
DriftSpecWnd::newDriftSpectrum(const MassSpecDataSet *massSpecDataSet,
                               const QString &msg,
                               History history,
                               QColor color)
{
  if(massSpecDataSet == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Plot widget relation between the caller widget and the newly created
  // widget later on.
  AbstractPlotWidget *senderPlotWidget =
    static_cast<AbstractPlotWidget *>(QObject::sender());

  // Make sure the cancel button is visible.
  mp_statusBarCancelPushButton->setVisible(true);

  // QThread will start the thread in which the integrator will work.
  QThread *thread = new QThread;

  // The integrator will perform the integration itself.
  MassDataIntegrator *integrator =
    new MassDataIntegrator(massSpecDataSet, history);

  integrator->moveToThread(thread);

  // Calling thread->start (QThread::start()) will automatically call the
  // integrateToDt() function of the integrator.
  connect(thread, SIGNAL(started()), integrator, SLOT(integrateToDt()));

  // When the integration is finished it emits resultsReadySignal and thus
  // we can stop the *real* thread in the QThread thread instance, using the
  // signal/slot mechanism. That is not like calling QThead::quit() !!!
  connect(integrator, SIGNAL(resultsReadySignal()), thread, SLOT(quit()));

  // This will be needed later, when we'll create a data plot relation
  // object relating the sender plot widget with the newly created one (in
  // newMassSpectrumDone).
  connect(integrator,
          &MassDataIntegrator::resultsReadySignal,
          this,
          [integrator, senderPlotWidget, color, this]() {
            newDriftSpectrumDone(integrator, senderPlotWidget, color);
          });

  connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

  connect(integrator,
          &MassDataIntegrator::updateFeedbackSignal,
          this,
          &AbstractMultiPlotWnd::updateFeedback);

  connect(this,
          &AbstractMultiPlotWnd::cancelOperationSignal,
          integrator,
          &MassDataIntegrator::cancelOperation);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "current thread id:" << QThread::currentThreadId();

  thread->start();
}


void
DriftSpecWnd::newDriftSpectrumDone(MassDataIntegrator *integrator,
                                   AbstractPlotWidget *senderPlotWidget,
                                   QColor color)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // De-setup the feedback.
  QTimer::singleShot(2000, this, [=]() {
    updateFeedback("", -1, false /* setVisible */, LogType::LOG_TO_STATUS_BAR);
  });

  // Make sure the cancel button is hidden.
  mp_statusBarCancelPushButton->setVisible(false);

  // Create a text item to indicate the last history item.

  QString historyItemText =
    integrator->history().newestHistoryItem()->asText(true /* brief */);

  QString plotText = QString("%1\n%2 - %3 ")
                       .arg(integrator->massSpecDataSet()->fileName())
                       .arg(m_desc)
                       .arg(historyItemText);

  // Allocate the new plot widget in which the data are to be plot.

  DriftSpecPlotWidget *widget = static_cast<DriftSpecPlotWidget *>(
    addPlotWidget(integrator->keyVector(),
                  integrator->valVector(),
                  plotText,
                  color,
                  integrator->massSpecDataSet()));

  // The history that we get as a parameter needs to be appended to the new
  // widget's history so that we have a full account of the history.

  widget->rhistory().copyHistory(integrator->history(),
                                 true /*remove duplicates*/);

  MainWindow *mainWindow = static_cast<MainWindow *>(parent());
  mainWindow->logConsoleMessage(widget->history().asText(
    "Plot widget history: \n", "End plot widget history:\n"));
  // And now only the innermost ranges of the whole history.
  mainWindow->logConsoleMessage(widget->history().innermostRangesAsText());

  // The plot widget reacts to the Qt::Key_Delete key by calling a function
  // that emits a set of signals, of which the destroyPlotWidget signal
  // actually indirectly triggers the destruction of the plot widget. This
  // signal is now connected to the main window where a function will call
  // for the destruction of all the target plot widgets, that is all the
  // plot widgets that were created as the result of an integration
  // computation of the plot widget currently being destroyed.

  connect(widget,
          &AbstractPlotWidget::destroyPlotWidget,
          mainWindow,
          &MainWindow::destroyAlsoAllTargetPlotWidgets);

  connect(widget,
          &AbstractPlotWidget::toggleMultiGraphSignal,
          mainWindow,
          &MainWindow::toggleMultiGraph);

  // Ensure that there is a connection between this plot widget and this
  // containing window, such that if there is a requirement to replot, the
  // widget gets the signal.

  connect(this,
          &AbstractMultiPlotWnd::replotWithAxisRange,
          widget,
          &AbstractPlotWidget::replotWithAxisRange);

  // Make sure we get the signal requiring to redraw the background of the
  // widget, depending on it being focused or not.

  connect(this,
          &AbstractMultiPlotWnd::redrawPlotBackground,
          widget,
          &AbstractPlotWidget::redrawPlotBackground);

  connect(widget,
          &DriftSpecPlotWidget::newMassSpectrum,
          mainWindow->mp_massSpecWnd,
          &MassSpecWnd::newMassSpectrum);

  connect(widget,
          &DriftSpecPlotWidget::newTicChromatogram,
          mainWindow->mp_ticChromWnd,
          &TicChromWnd::newTicChromatogram);

  connect(widget,
          &DriftSpecPlotWidget::ticIntensitySignal,
          this,
          &DriftSpecWnd::ticIntensity);

  mainWindow->m_dataPlotWidgetRelationer.newRelation(
    integrator->massSpecDataSet(),
    senderPlotWidget,
    widget,
    Q_NULLPTR /* open spectra dialog list widget item */);

  // Finally we can destroy the integrator.
  delete integrator;
}


//! Integrate data to a drift spectrum starting from TIC chromatogram.
/*!

  \param caller pointer to the caller widget.

  \param massSpecDataSet pointer to the MassSpecDataSet where the mass data
  are stored.

  \param integrationType IntegrationType of the integration to be performed.

  \param history History to account for the integration.

  \param color to use to plot the graph of the new drift spectrum.

*/
void
DriftSpecWnd::jsNewDriftSpectrum(AbstractPlotWidget *caller,
                                 const MassSpecDataSet *massSpecDataSet,
                                 const QString &msg,
                                 History history,
                                 QColor color)
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  if(massSpecDataSet == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Make sure the cancel button is visible.
  mp_statusBarCancelPushButton->setVisible(true);

  MassDataIntegrator integrator(massSpecDataSet, history);

  connect(&integrator,
          &MassDataIntegrator::updateFeedbackSignal,
          this,
          &AbstractMultiPlotWnd::updateFeedback);

  connect(this,
          &AbstractMultiPlotWnd::cancelOperationSignal,
          &integrator,
          &MassDataIntegrator::cancelOperation);

  bool res = integrator.integrate();

  // qDebug() << "At this point the integration is finished. Desetup the
  // feedback.";

  // De-setup the feedback.
  QTimer::singleShot(2000, this, [=]() {
    updateFeedback("", -1, false /* setVisible */, LogType::LOG_TO_STATUS_BAR);
  });

  // Make sure the cancel button is hidden.
  mp_statusBarCancelPushButton->setVisible(false);

  if(!res)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "The integration to Dt has created no drift spectrum.";

      return;
    }

  // Create a text item to indicate the last history item.

  QString historyItemText =
    history.newestHistoryItem()->asText(true /* brief */);

  QString plotText = QString("%1\n%2 - %3 ")
                       .arg(massSpecDataSet->fileName())
                       .arg(m_desc)
                       .arg(historyItemText);

  // Allocate the new plot widget in which the data are to be plot.

  DriftSpecPlotWidget *widget =
    static_cast<DriftSpecPlotWidget *>(addPlotWidget(integrator.keyVector(),
                                                     integrator.valVector(),
                                                     plotText,
                                                     color,
                                                     massSpecDataSet));

  // The history that we get as a parameter needs to be appended to the new
  // widget's history so that we have a full account of the history.

  widget->rhistory().copyHistory(history, true /*remove duplicates*/);

  MainWindow *mainWindow = static_cast<MainWindow *>(parent());
  mainWindow->logConsoleMessage(widget->history().asText(
    "Plot widget history: \n", "End plot widget history:\n"));
  // And now only the innermost ranges of the whole history.
  mainWindow->logConsoleMessage(widget->history().innermostRangesAsText());

  // The plot widget reacts to the Qt::Key_Delete key by calling a function
  // that emits a set of signals, of which the destroyPlotWidget signal
  // actually indirectly triggers the destruction of the plot widget. This
  // signal is now connected to the main window where a function will call
  // for the destruction of all the target plot widgets, that is all the
  // plot widgets that were created as the result of an integration
  // computation of the plot widget currently being destroyed.

  connect(widget,
          &AbstractPlotWidget::destroyPlotWidget,
          mainWindow,
          &MainWindow::destroyAlsoAllTargetPlotWidgets);

  connect(widget,
          &AbstractPlotWidget::toggleMultiGraphSignal,
          mainWindow,
          &MainWindow::toggleMultiGraph);

  // Ensure that there is a connection between this plot widget and this
  // containing window, such that if there is a requirement to replot, the
  // widget gets the signal.

  connect(this,
          &AbstractMultiPlotWnd::replotWithAxisRange,
          widget,
          &AbstractPlotWidget::replotWithAxisRange);

  // Make sure we get the signal requiring to redraw the background of the
  // widget, depending on it being focused or not.

  connect(this,
          &AbstractMultiPlotWnd::redrawPlotBackground,
          widget,
          &AbstractPlotWidget::redrawPlotBackground);

  connect(widget,
          &DriftSpecPlotWidget::newMassSpectrum,
          mainWindow->mp_massSpecWnd,
          &MassSpecWnd::newMassSpectrum);

  connect(widget,
          &DriftSpecPlotWidget::newTicChromatogram,
          mainWindow->mp_ticChromWnd,
          &TicChromWnd::newTicChromatogram);

  connect(widget,
          &DriftSpecPlotWidget::ticIntensitySignal,
          this,
          &DriftSpecWnd::ticIntensity);


  // AbstractPlotWidget *senderPlotWidget =
  // static_cast<AbstractPlotWidget *>(QObject::sender());

  // if(senderPlotWidget == Q_NULLPTR)
  // qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  mainWindow->m_dataPlotWidgetRelationer.newRelation(
    massSpecDataSet,
    caller,
    widget,
    Q_NULLPTR /* open spectra dialog list widget item */);
}


void
DriftSpecWnd::ticIntensity(const MassSpecDataSet *massSpecDataSet,
                           const QString &msg,
                           History history)
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  if(massSpecDataSet == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  mp_statusBarCancelPushButton->setVisible(true);

  AbstractPlotWidget *senderPlotWidget =
    static_cast<AbstractPlotWidget *>(QObject::sender());

  // QThread will start the thread in which the integrator will work.
  QThread *thread = new QThread;

  // The integrator will perform the integration itself.
  MassDataIntegrator *integrator =
    new MassDataIntegrator(massSpecDataSet, history);

  integrator->moveToThread(thread);

  // Calling thread->start (QThread::start()) will automatically call the
  // integrateToTicIntensity() function of the integrator.
  connect(
    thread, SIGNAL(started()), integrator, SLOT(integrateToTicIntensity()));

  // When the integration is finished it emits resultsReadySignal and thus
  // we can stop the *real* thread in the QThread thread instance, using the
  // signal/slot mechanism. That is not like calling QThead::quit() !!!
  connect(integrator, SIGNAL(resultsReadySignal()), thread, SLOT(quit()));

  // This will be needed later, when we'll create a data plot relation
  // object relating the sender plot widget with the newly created one (in
  // ticIntensityDone).
  connect(integrator,
          &MassDataIntegrator::resultsReadySignal,
          this,
          [integrator, msg, senderPlotWidget, this]() {
            ticIntensityDone(integrator, msg, senderPlotWidget);
          });

  connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

  connect(integrator,
          &MassDataIntegrator::updateFeedbackSignal,
          this,
          &AbstractMultiPlotWnd::updateFeedback);

  connect(this,
          &AbstractMultiPlotWnd::cancelOperationSignal,
          integrator,
          &MassDataIntegrator::cancelOperation);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "current thread id:" << QThread::currentThreadId();

  thread->start();
}


void
DriftSpecWnd::ticIntensityDone(MassDataIntegrator *integrator,
                               QString msg,
                               AbstractPlotWidget *senderPlotWidget)
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // De-setup the feedback.
  QTimer::singleShot(2000, this, [=]() {
    updateFeedback("", -1, false /* setVisible */, LogType::LOG_TO_STATUS_BAR);
  });

  // Make sure the cancel button is hidden.
  mp_statusBarCancelPushButton->setVisible(false);

  // Set the proper value in the plot widget right away so that it can be
  // queried in another context.
  senderPlotWidget->setLastTicIntensity(integrator->ticIntensity());

  QString fullMsg =
    msg + QString(": TIC int.: %1").arg(integrator->ticIntensity());

  // Now we can dipslay the results in the status bar:
  statusBar()->showMessage(fullMsg);

  // And in the main console window, now , with the proper color.

  QColor color = senderPlotWidget->color();

  MainWindow *mainWindow = static_cast<MainWindow *>(parent());
  mainWindow->logConsoleMessage(fullMsg, color, false /*overwrite*/);

  // Finally we can destroy the integrator.
  delete integrator;
}


void
DriftSpecWnd::newPlot(AbstractPlotWidget *senderPlotWidget,
                      const MassSpecDataSet *massSpecDataSet,
                      QVector<double> keys,
                      QVector<double> values,
                      const QString &msg,
                      History history,
                      QColor color)
{
  if(senderPlotWidget == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(massSpecDataSet == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Create a text item to indicate the last history item.

  QString historyItemText =
    history.newestHistoryItem()->asText(true /* brief */);

  QString plotText = QString("%1\n%2 - %3\n%4")
                       .arg(massSpecDataSet->fileName())
                       .arg(m_desc)
                       .arg(historyItemText)
                       .arg(msg);

  // Allocate the new plot widget in which the data are to be plot.

  DriftSpecPlotWidget *widget = static_cast<DriftSpecPlotWidget *>(
    addPlotWidget(keys, values, plotText, color, massSpecDataSet));

  // The history that we get as a parameter needs to be appended to the new
  // widget's history so that we have a full account of the history.

  widget->rhistory().copyHistory(history, true /*remove duplicates*/);

  MainWindow *mainWindow = static_cast<MainWindow *>(parent());
  mainWindow->logConsoleMessage(widget->history().asText(
    "Plot widget history: \n", "End plot widget history:\n"));
  // And now only the innermost ranges of the whole history.
  mainWindow->logConsoleMessage(widget->history().innermostRangesAsText());

  // The plot widget reacts to the Qt::Key_Delete key by calling a function
  // that emits a set of signals, of which the destroyPlotWidget signal
  // actually indirectly triggers the destruction of the plot widget. This
  // signal is now connected to the main window where a function will call for
  // the destruction of all the target plot widgets, that is all the plot
  // widgets that were created as the result of an integration computation of
  // the plot widget currently being destroyed.

  connect(widget,
          &AbstractPlotWidget::destroyPlotWidget,
          mainWindow,
          &MainWindow::destroyAlsoAllTargetPlotWidgets);

  connect(widget,
          &AbstractPlotWidget::toggleMultiGraphSignal,
          mainWindow,
          &MainWindow::toggleMultiGraph);

  // Ensure that there is a connection between this plot widget and this
  // containing window, such that if there is a requirement to replot, the
  // widget gets the signal.

  connect(this,
          &AbstractMultiPlotWnd::replotWithAxisRange,
          widget,
          &AbstractPlotWidget::replotWithAxisRange);

  // Make sure we get the signal requiring to redraw the background of the
  // widget, depending on it being focused or not.

  connect(this,
          &AbstractMultiPlotWnd::redrawPlotBackground,
          widget,
          &AbstractPlotWidget::redrawPlotBackground);

  mainWindow->m_dataPlotWidgetRelationer.newRelation(
    massSpecDataSet,
    senderPlotWidget,
    widget,
    Q_NULLPTR /* open spectra dialog list widget item */);
}


//! Record an analysis stanza.
/*!

  \param stanza string to print out in the record.

  \param color color to use for the printout.

*/
void
DriftSpecWnd::recordAnalysisStanza(const QString &stanza, const QColor &color)
{
  // The record might go to a file or to the console or both.

  if((mp_analysisPreferences->m_recordTarget & RecordTarget::RECORD_TO_FILE) ==
     RecordTarget::RECORD_TO_FILE)
    {
      if(mp_analysisFile != Q_NULLPTR)
        {

          // Write the stanza, that was crafted by the calling plot widget to
          // the file.

          if(!mp_analysisFile->open(QIODevice::Append))
            {
              statusBar()->showMessage(
                QString("Could not record the step because "
                        "the file could not be opened."),
                4000);
            }
          else
            {
              mp_analysisFile->write(stanza.toLatin1());
              // Force writing because we may want to have tail -f work fine
              // on the
              // file, and see modifications live to change fiels in a text
              // editor.
              mp_analysisFile->flush();
              mp_analysisFile->close();
            }
        }
      else
        {
          qDebug() << __FILE__ << __LINE__
                   << "The mp_analysisFile pointer is Q_NULLPTR.";

          statusBar()->showMessage(
            QString("Could not record the analysis step to file. "
                    "Please define a file to write the data to."),
            4000);
        }
    }

  // Also, if recording to the console is asked for, then do that also.
  if((mp_analysisPreferences->m_recordTarget &
      RecordTarget::RECORD_TO_CONSOLE) == RecordTarget::RECORD_TO_CONSOLE)
    {
      MainWindow *mainWindow = static_cast<MainWindow *>(parent());
      mainWindow->logConsoleMessage(stanza, color);
    }

  // Also, if recording to the clipboard is asked for, then do that also.
  if((mp_analysisPreferences->m_recordTarget &
      RecordTarget::RECORD_TO_CLIPBOARD) == RecordTarget::RECORD_TO_CLIPBOARD)
    {
      QClipboard *clipboard = QApplication::clipboard();
      clipboard->setText(clipboard->text() + stanza, QClipboard::Clipboard);
    }

  return;
}


bool
DriftSpecWnd::focusNextPrevChild(bool next)
{

  // In this window, we do not want that the TAB key serves to move from
  // widget to widget.

  return false;
}


} // namespace msXpSmineXpert
