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


///////////////////////////// Qt include


///////////////////////////// Local includes
#include <minexpert/gui/DriftSpecPlotWidget.hpp>
#include <minexpert/gui/DriftSpecWnd.hpp>
#include <minexpert/nongui/AnalysisPreferences.hpp>
#include <minexpert/gui/MainWindow.hpp>
#include <minexpert/nongui/History.hpp>
#include <minexpert/nongui/MassDataIntegrator.hpp>


namespace msXpSmineXpert
{


/*/js/ Class: DriftSpecPlotWidget
 * <comment>This class cannot be instantiated with the new operator. Objects
 * of this class are made available to the scripting environment under the
 * form of object variable names driftSpecPlotWidget[index], with [index]
 * being 0 for the first object created and being incremented each time a new
 * DriftSpecPlotWidget is created.</comment>
 */


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

  This function registers the key codes that, in conjunction to mouse button
  clicks trigger specific actions, like data integrations.

  \param parent parent widget.

  \param name name of the module.

  \param desc description of the wiget type.

  \param massSpecDataSet MassSpecDataSet holding the mass data.

  \param fileName name of the file from which the data were loaded.

  \param isMultiGraph tells if the widget to be instanciated is for displaying
  multiple graphs.

*/
DriftSpecPlotWidget::DriftSpecPlotWidget(QWidget *parent,
                                         const QString &name,
                                         const QString &desc,
                                         const MassSpecDataSet *massSpecDataSet,
                                         const QString &fileName,
                                         bool isMultiGraph)
  : AbstractPlotWidget{
      parent, name, desc, massSpecDataSet, fileName, isMultiGraph}
{
  AbstractPlotWidget::registerQtKeyCode(
    Qt::Key_R,
    "Press R while right mouse click-drag to integrate to a XIC chromatogram");
  AbstractPlotWidget::registerQtKeyCode(
    Qt::Key_S,
    "Press S while right mouse click-drag to integrate to a mass spectrum");
  AbstractPlotWidget::registerQtKeyCode(Qt::Key_I,
                                        "Press I while right mouse click-drag "
                                        "to integrate to a TIC intensity "
                                        "value");

  // Give the axes some labels:
  xAxis->setLabel("drift time (ms)");
  yAxis->setLabel("counts");
}


//! Destruct \c this DriftSpecPlotWidget instance.
DriftSpecPlotWidget::~DriftSpecPlotWidget()
{
}


//! Return the arbitrary integration type that this plot widget can handle
int
DriftSpecPlotWidget::arbitraryIntegrationType()
{
  return IntegrationType::DT_TO_XXX;
}


//! Craft a string representing the data of the peak that was analysed.
/*!

  To do so, this functions get the data format string from the parent window's
  AnalysisPreferences instance. The format string is interpreted and the
  relevant values are substituted to craft a stanza (a paragraph) that matches
  the peak data at hand.

  \return a string containing the stanza.
  */
QString
DriftSpecPlotWidget::craftAnalysisStanza(QCPGraph *theGraph,
                                         const QString &fileName)
{

  // qDebug() << __FILE__ << __LINE__
  //<< "Entering" << __FUNCTION__";

  // This function might be called for a graph that sits in a single-graph
  // plot widget or for a graph that sits in a multi-graph plot widget.
  // Depending on the parameters, we'll know what is the case. If the
  // parameters are not Q_NULLPTR or empty, then the plot widget is a
  // multi-graph plot widget and we need to set the data in the analysis
  // record only for the graph passed as parameter.

  QCPGraph *origGraph = Q_NULLPTR;

  if(theGraph == Q_NULLPTR)
    origGraph = graph();
  else
    origGraph = theGraph;

  QString destFileName;

  if(fileName.isEmpty())
    destFileName = m_fileName;
  else
    destFileName = fileName;


  // When the mouse moves over the plot widget, its position is recorded
  // real time. That position is both a m/z value and an i (intensity)
  // value. We cannot rely on the i value, because it is valid only the for
  // the last added graph. But the m/z value is the same whatever the graph
  // we are interested in.
  // Craft the analysis stanza:
  QString stanza;

  DriftSpecWnd *wnd = dynamic_cast<DriftSpecWnd *>(mp_parentWnd);
  if(wnd == Q_NULLPTR)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  const AnalysisPreferences *analPrefs = wnd->analysisPreferences();
  if(analPrefs == Q_NULLPTR)
    return stanza;

  // We must have the name of the mass data file:
  if(m_fileName.isEmpty())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // The way we work here is that we get a format string that specifies how
  // the user wants to have the data formatted in the stanza. That format
  // string is located in a DataFormatStringSpecif object as the m_format
  // member. There is also a m_formatType member that indicates what is the
  // format that we request (mass spec, tic chrom or drift spec). That
  // format type member is an int that also is the key of the hash that is
  // located in the analPrefs: QHash<int, DataFormatStringSpecif *>
  // m_dataFormatStringSpecifHash.

  DataFormatStringSpecif *specif =
    analPrefs->m_dataFormatStringSpecifHash.value(FormatType::DRIFT_SPEC);

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

  // Handy local copy.
  QString formatString = specif->m_format;

  QChar prevChar = ' ';

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

      // qDebug() << __FILE__ << __LINE__
      // << "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 == formatString.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(destFileName);
              if(fileInfo.exists())
                stanza += fileInfo.fileName();
              else
                stanza += "Untitled";
              prevChar = ' ';
              continue;
            }
          if(curChar == 'X')
            {
              double startDt = m_startDragPoint.x();

              stanza += QString("%1").arg(startDt, 0, 'g', 6);
              prevChar = ' ';
              continue;
            }
          if(curChar == 'Y')
            {
              // We need to get the value of the intensity for the graph.
              double startDt   = m_startDragPoint.x();
              double intensity = getYatX(startDt, origGraph);

              if(!intensity)
                qDebug() << __FILE__ << __LINE__ << "Warning, intensity for dt "
                         << startDt << "is zero.";
              else
                stanza += QString("%1").arg(intensity, 0, 'g', 6);

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

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

              prevChar = ' ';
              continue;
            }
          if(curChar == 's')
            {
              stanza += QString("%1").arg(m_keyRangeStart, 0, 'f', 3);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'e')
            {
              stanza += QString("%1").arg(m_keyRangeEnd, 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.

          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)

  return stanza;
}


//! Handler for the mouse release event.
/*!

  This function checks for the pressed keyboard key code and, depending on
  that key code, triggers specific actions, like integrating data to mass
  spectrum or to retention time (XIC) or to single TIC intensity value.

*/
void
DriftSpecPlotWidget::mouseReleaseHandledEvent(QMouseEvent *event)
{
  if(m_isMultiGraph)
    // All the actions below can only be performed if the plot widget has a
    // knowledge of the mass spec data set. Thus, if *this plot widget is a
    // multi-graph plot widget we need to return immediately.
    return;

  if(m_pressedKeyCode == Qt::Key_S && event->button() == Qt::RightButton)
    {
      integrateToMz(xRangeMin(), xRangeMax());
    }

  if(m_pressedKeyCode == Qt::Key_R && event->button() == Qt::RightButton)
    {
      integrateToRt(xRangeMin(), xRangeMax());
    }

  if(m_pressedKeyCode == Qt::Key_I && event->button() == Qt::RightButton)
    {
      integrateToTicIntensity(xRangeMin(), xRangeMax());
    }
}


//! Integrate data to a mass spectrum with drift time range [\p lower -- \p
//! upper ].
void
DriftSpecPlotWidget::integrateToMz(double lower, double upper)
{
  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = lower;
  if(qIsNaN(rangeStart))
    {
      rangeStart = xAxis->range().lower;
    }

  double rangeEnd = upper;
  if(qIsNaN(rangeEnd))
    {
      rangeEnd = xAxis->range().upper;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::DT_TO_MZ, rangeStart, rangeEnd);

  // Aggreate the new item to the history we got as a parameter and that
  // we copied into localHistory.
  localHistory.appendHistoryItem(histItem);

  emit newMassSpectrum(mp_massSpecDataSet,
                       "Calculating mass spectrum.",
                       localHistory,
                       graph()->pen().color());
}


/*/js/
 * DriftSpecPlotWidget.jsIntegrateToMz(lower, upper)
 *
 * Starts an integration of mass spectra data to a mass spectrum limiting the
 * data range specified with the numerical arguments.
 *
 * lower, upper: retention time values limiting the integration. If no values
 * are provided, there is no limitation to the integration.
 *
 * This function creates a new plot widget to display the obtained results.
 */
void
DriftSpecPlotWidget::jsIntegrateToMz(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return;

  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = lower;
  if(qIsNaN(rangeStart))
    {
      rangeStart = xAxis->range().lower;
    }

  double rangeEnd = upper;
  if(qIsNaN(rangeEnd))
    {
      rangeEnd = xAxis->range().upper;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::DT_TO_MZ, rangeStart, rangeEnd);

  // Aggreate the new item to the history we got as a parameter and that
  // we copied into localHistory.
  localHistory.appendHistoryItem(histItem);

  DriftSpecWnd *wnd = dynamic_cast<DriftSpecWnd *>(mp_parentWnd);

  MainWindow *mainWindow = static_cast<MainWindow *>(wnd->parent());

  AbstractMultiPlotWnd *targetWnd = mainWindow->mp_massSpecWnd;

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  static_cast<MassSpecWnd *>(targetWnd)->jsNewMassSpectrum(
    this,
    mp_massSpecDataSet,
    "Calculating drift spectrum.",
    localHistory,
    m_plottingColor);
}


//! Integrate data to a TIC chromatogram (XIC) with drift time range [\p lower
//! -- \p upper ].
void
DriftSpecPlotWidget::integrateToRt(double lower, double upper)
{
  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = lower;
  if(qIsNaN(rangeStart))
    {
      rangeStart = xAxis->range().lower;
    }

  double rangeEnd = upper;
  if(qIsNaN(rangeEnd))
    {
      rangeEnd = xAxis->range().upper;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::DT_TO_RT, rangeStart, rangeEnd);

  // Aggreate the new item to the history we got as a parameter and that
  // we copied into localHistory.
  localHistory.appendHistoryItem(histItem);

  emit newTicChromatogram(mp_massSpecDataSet,
                          "Calculating extracted ion chromatogram.",
                          localHistory,
                          graph()->pen().color());
}


/*/js/
 * DriftSpecPlotWidget.jsIntegrateToRt(lower, upper)
 *
 * Starts an integration of mass spectra data to a XIC chromatogram limiting the
 * data range specified with the numerical arguments.
 *
 * lower, upper: retention time values limiting the integration. If no values
 * are provided, there is no limitation to the integration.
 *
 * This function creates a new plot widget to display the obtained results.
 */
void
DriftSpecPlotWidget::jsIntegrateToRt(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return;

  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = lower;
  if(qIsNaN(rangeStart))
    {
      rangeStart = xAxis->range().lower;
    }

  double rangeEnd = upper;
  if(qIsNaN(rangeEnd))
    {
      rangeEnd = xAxis->range().upper;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::DT_TO_RT, rangeStart, rangeEnd);

  // Aggreate the new item to the history we got as a parameter and that
  // we copied into localHistory.
  localHistory.appendHistoryItem(histItem);

  DriftSpecWnd *wnd = dynamic_cast<DriftSpecWnd *>(mp_parentWnd);

  MainWindow *mainWindow = static_cast<MainWindow *>(wnd->parent());

  AbstractMultiPlotWnd *targetWnd = mainWindow->mp_ticChromWnd;

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  static_cast<TicChromWnd *>(targetWnd)->jsNewTicChromatogram(
    this,
    mp_massSpecDataSet,
    "Calculating drift spectrum.",
    localHistory,
    m_plottingColor);
}


//! Integrate data to a single TIC intensity value with drift time range [\p
//! lower -- \p upper ].
void
DriftSpecPlotWidget::integrateToTicIntensity(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return;

  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = lower;
  if(qIsNaN(rangeStart))
    {
      rangeStart = xAxis->range().lower;
    }

  double rangeEnd = upper;
  if(qIsNaN(rangeEnd))
    {
      rangeEnd = xAxis->range().upper;
    }

  // Reset the axis range data because we may return prematurely and we want
  // to craft a stanza with proper values (even if they are qINan()).
  m_keyRangeStart = qSNaN();
  m_keyRangeEnd   = qSNaN();

  // Also reset the other datum that might hold invalid values.
  m_lastTicIntensity = qSNaN();

  if(rangeStart == rangeEnd)
    {
      return;
    }

  // Slot for sorting stuff.
  double tempVal;

  // Set the values in sorted order for later stanza crafting.
  if(rangeStart > rangeEnd)
    {
      tempVal    = rangeStart;
      rangeStart = rangeEnd;
      rangeStart = tempVal;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::DT_TO_TIC_INT, rangeStart, rangeEnd);

  // At this point store the range data so that the user may use it in
  // analysis reporting.
  m_keyRangeStart = rangeStart;
  m_keyRangeEnd   = rangeEnd;

  // Aggreate the new item to the local history copy we made.
  localHistory.appendHistoryItem(histItem);

  // Sanity check. It is not possible that this plot widget is not
  // multi-graph and that at the same time, the mass spec data set pointer
  // is nullptr.

  if(!m_isMultiGraph && mp_massSpecDataSet == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Capture immediately the text that is located in the status bar
  // because if the computation is long that text will have disappeared
  // and we won't be able to show it in the TIC int text.

  QMainWindow *parentWnd = static_cast<QMainWindow *>(mp_parentWnd);
  QString currentMessage = parentWnd->statusBar()->currentMessage();

  if(currentMessage.isEmpty())
    currentMessage = QString("range [%1-%2]")
                       .arg(m_keyRangeStart, 0, 'f', 3)
                       .arg(m_keyRangeEnd, 0, 'f', 3);
  else
    currentMessage += QString(" ; range [%1-%2]")
                        .arg(m_keyRangeStart, 0, 'f', 3)
                        .arg(m_keyRangeEnd, 0, 'f', 3);

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "Emitting ticIntensitySignal";

  emit ticIntensitySignal(mp_massSpecDataSet, currentMessage, localHistory);
}


/*/js/
 * DriftSpecPlotWidget.jsIntegrateToTicIntensity(lower, upper)
 *
 * Starts an integration of mass spectra data to a TIC intensity single value
 * limiting the data range specified with the numerical arguments.
 *
 * lower, upper: retention time values limiting the integration. If no values
 * are provided, there is no limitation to the integration.
 *
 * The TIC intensity value is returned
 */
double
DriftSpecPlotWidget::jsIntegrateToTicIntensity(double lower, double upper)
{
  if(m_isMultiGraph)
    // We cannot perform the action, because this plot widget has not mass
    // spec data set pointer, it is a graph-only plot widget to display all
    // the graphs overlaid.
    return 0;

  // Get the pointer to the x-axis (key axis).

  QCPAxis *xAxis = graph()->keyAxis();

  double rangeStart = lower;
  if(qIsNaN(rangeStart))
    {
      rangeStart = xAxis->range().lower;
    }

  double rangeEnd = upper;
  if(qIsNaN(rangeEnd))
    {
      rangeEnd = xAxis->range().upper;
    }

  // Reset the axis range data because we may return prematurely and we want
  // to craft a stanza with proper values (even if they are qINan()).
  m_keyRangeStart = qSNaN();
  m_keyRangeEnd   = qSNaN();

  // Also reset the other datum that might hold invalid values.
  m_lastTicIntensity = qSNaN();

  if(rangeStart == rangeEnd)
    {
      return false;
    }

  // Slot for sorting stuff.
  double tempVal;

  // Set the values in sorted order for later stanza crafting.
  if(rangeStart > rangeEnd)
    {
      tempVal    = rangeStart;
      rangeStart = rangeEnd;
      rangeStart = tempVal;
    }

  History localHistory  = m_history;
  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::DT_TO_TIC_INT, rangeStart, rangeEnd);

  // At this point store the range data so that the user may use it in
  // analysis reporting.
  m_keyRangeStart = rangeStart;
  m_keyRangeEnd   = rangeEnd;

  // Aggreate the new item to the history.
  localHistory.appendHistoryItem(histItem);

  DriftSpecWnd *parentWnd = static_cast<DriftSpecWnd *>(mp_parentWnd);

  // Capture immediately the text that is located in the status bar
  // because if the computation is long that text will have disappeared
  // and we won't be able to show it in the TIC int text.

  QString currentMessage = parentWnd->statusBar()->currentMessage();

  QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));

  // Sanity check. It is not possible that this plot widget is not
  // multi-graph and that at the same time, the mass spec data set pointer
  // is nullptr.

  if(!m_isMultiGraph && mp_massSpecDataSet == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  MassDataIntegrator integrator(mp_massSpecDataSet, localHistory);

  integrator.integrateToTicIntensity();

  m_lastTicIntensity = integrator.ticIntensity();

  QApplication::restoreOverrideCursor();

  if(currentMessage.isEmpty())
    parentWnd->statusBar()->showMessage(QString("range [%1-%2]: TIC I = %3")
                                          .arg(m_keyRangeStart, 0, 'f', 3)
                                          .arg(m_keyRangeEnd, 0, 'f', 3)
                                          .arg(m_lastTicIntensity, 0, 'g', 3));
  else
    parentWnd->statusBar()->showMessage(currentMessage +
                                        QString(" ; range [%1-%2]: TIC I = %3")
                                          .arg(m_keyRangeStart, 0, 'g', 3)
                                          .arg(m_keyRangeEnd, 0, 'g', 3)
                                          .arg(m_lastTicIntensity, 0, 'g', 3));

  return m_lastTicIntensity;
}


/*/js/
 * DriftSpecPlotWidget.newPlot(trace)
 *
 * Create a new DriftSpecPlotWidget using <Trace> trace for the initialization
 * of the data.
 *
 * This function creates a new plot widget to display the <Trace> data.
 */

//! Create a new plot. This function is useful in the scripting environment.
void
DriftSpecPlotWidget::newPlot()
{

  // We only work on this if *this plot widget is *not* multigraph

  if(m_isMultiGraph)
    return;

  MainWindow *mainWindow = static_cast<MainWindow *>(mp_parentWnd->parent());
  QScriptContext *ctx =
    mainWindow->mp_scriptingWnd->m_scriptEngine.currentContext();

  int argCount = ctx->argumentCount();
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "argCount:" << argCount;

  if(argCount != 1)
    {
      ctx->throwError(
        "Syntax error: function newPlot() takes one Trace object as argument.");
      return;
    }

  QScriptValue arg = ctx->argument(0);
  QVariant variant = arg.data().toVariant();

  if(!variant.isValid())
    {
      ctx->throwError(
        "Syntax error: function newPlot() takes one Trace object as argument.");
      return;
    }

  if(variant.userType() == QMetaType::type("msXpSlibmass::Trace"))
    {
      msXpSlibmass::Trace trace(qscriptvalue_cast<msXpSlibmass::Trace>(arg));

      // Get the color of the graph, so that we can print the stanza in
      // the console window with the proper color.

      QPen pen     = graph()->pen();
      QColor color = pen.color();

      static_cast<TicChromWnd *>(mp_parentWnd)
        ->newPlot(this,
                  mp_massSpecDataSet,
                  QVector<double>::fromList(trace.keyList()),
                  QVector<double>::fromList(trace.valList()),
                  "Trace from scripting environment.",
                  m_history,
                  color);
    }
  else
    {
      ctx->throwError(
        "Syntax error: function newPlot() takes one Trace object as argument.");
      return;
    }
}


//! Key release event handler.
/*!

  Depending on the type of plot widget (multigraph or not), the actions
  triggered in this function will differ.

  For example, the space key will trigger the crafting of an analysis stanza
  (see craftAnalysisStanza()). The 'L' key triggers the printout of the
  currently pointed graph point data to the console window. The TAB key will
  trigger the cycling of the selected item in the open spectra dialog window
  (see OpenSpectraDlg).

*/
void
DriftSpecPlotWidget::keyReleaseEvent(QKeyEvent *event)
{
  DriftSpecWnd *parentWnd = static_cast<DriftSpecWnd *>(mp_parentWnd);
  MainWindow *mainWindow  = static_cast<MainWindow *>(parentWnd->parent());

  // First, check if the space bar was pressed.
  if(event->key() == Qt::Key_Space)
    {

      // The handling of the stanza creation is different in these two
      // situations:
      //
      // 1. the plot widget where this event occurred is a conventional
      // mono-graph plot widget that sits in the lower part of the window. In
      // this case, the filename of the spectrum data file is available.
      //
      // 2. the plot widget is a multi-graph plot widget that sits in the
      // upper part of the multi plot window. In that case, it is more
      // difficult to know what is the file that initially contained the data
      // plotted. We want to know what graph corresponds to what list widget
      // item that is selected (or not) in the open mass spectra dialog window
      // (see MainWindow).

      QString stanza;
      if(m_isMultiGraph)
        {

          // The event occurred in the multi-graph plot widget where many graphs
          // are displayed. We need to craft a stanza for each graph that
          // replicates the data of a plot that is proxied in the list widget of
          // the OpenSpectraDlg. Only handle the spectra of which the list
          // widget item is currently selected and for which the corresponding
          // mass multi-graph plot is visible.

          for(int iter = 0; iter < graphCount(); ++iter)
            {
              QCPGraph *iterGraph = graph(iter);

              // Only work on this graph if it is visible:
              if(!iterGraph->visible())
                continue;

              // Get the color of the graph, so that we can print the stanza
              // in
              // the console window with the proper color.
              QPen curPen     = iterGraph->pen();
              QColor curColor = curPen.color();

              // Get a handle to the conventional plot widget of which this
              // multi-graph plot widget's graph is a replica.

              AbstractPlotWidget *driftSpecPlotWidget =
                parentWnd->monoGraphPlot(iterGraph);

              // Now use that pointer to get a handle to the root tic
              // chromatogram plot
              // widget whence the data in this plot widget came:

              // // Old version:
              // AbstractPlotWidget *rootPlotWidget =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.key(massSpecPlotWidget);

              // New version:
              DataPlotWidgetRelation *p_rootRelation = Q_NULLPTR;
              AbstractPlotWidget *rootPlotWidget =
                mainWindow->m_dataPlotWidgetRelationer.rootPlotWidget(
                  driftSpecPlotWidget, &p_rootRelation);

              if(rootPlotWidget == Q_NULLPTR)
                {
                  qFatal(
                    "Fatal error at %s@%d -- %s. "
                    "Cannot be that a multiGraph plot widget has no root plot "
                    "widget."
                    "Program aborted.",
                    __FILE__,
                    __LINE__,
                    __FUNCTION__);
                }

              // At this point, we know what plot widget in the Tic chrom window
              // is at the origin of the graph currently iterated into. We need
              // to
              // know if that plot widget has a corresponding item in the open
              // spectrum dialog list widget that is currently selected.

              if(mainWindow->mp_openSpectraDlg->isPlotWidgetSelected(
                   rootPlotWidget))
                {

                  // This means that we have to get the filename out of this tic
                  // chrom plot widget's mass spec data set:

                  QString fileName =
                    static_cast<TicChromPlotWidget *>(rootPlotWidget)
                      ->massSpecDataSet()
                      ->fileName();

                  stanza = craftAnalysisStanza(iterGraph, fileName);

                  if(stanza.isEmpty())
                    {
                      parentWnd->statusBar()->showMessage(
                        "Failed to craft an analysis stanza. "
                        "Please set the analysis preferences.");

                      event->accept();

                      return;
                    }
                  else
                    {
                      emit recordAnalysisStanza(stanza, curColor);
                    }
                }
            }
          // End of
          // for(int iter = 0; iter < graphCount(); ++iter)
        }
      // End of
      // if(m_isMultiGraph)
      else
        {

          // We are in a plot widget that is a single-graph plot widget, the
          // situation is easier:

          stanza = craftAnalysisStanza();

          if(stanza.isEmpty())
            {
              parentWnd->statusBar()->showMessage(
                "Failed to craft an analysis stanza. "
                "Please set the analysis preferences.");

              event->accept();

              return;
            }
          else
            {
              // Send the stanza to the console window, so that we can make a
              // copy
              // paste.

              // Get the color of the graph, so that we can print the stanza
              // in
              // the console window with the proper color.
              QPen curPen     = graph()->pen();
              QColor curColor = curPen.color();

              emit recordAnalysisStanza(stanza, curColor);
            }
        }

      event->accept();

      return;
    }
  else if(event->key() == Qt::Key_Tab)
    {
      // qDebug() << __FILE__ << __LINE__
      // << "DriftSpecPlotWidget::keyReleaseEvent
      // AbstractPlotWidgetevent->key() == Qt::Key_Tab";

      if(!m_isMultiGraph)
        {
          // We do not want to handle the TAB key because it will try to cycle
          // into the
          // various plot widgets of the lower part of the window.

          // qDebug() << __FILE__ << __LINE__
          // << "Mass spec plot widget is not multi-graph";

          event->accept();

          return;
        }

      // When this key event occurs in a multigraph plot widget, that means
      // that the user wants to cycle in the graphs, most probably because she
      // is in an analysis procedure.
      //
      // The OpenSpectraDlg list widget's items reflect all the mass specra
      // opened. For each open spectrum, there is a corresponding list widget
      // item, colored the same as the corresponding tic chrom plot widget
      // that show the TIC chromatogram.
      //
      // Note that there might be list widget items that we are not going to
      // deal with because they have no mass spec plot widget (and thus not
      // multi-graph plot widget graph neither) because the user has opened
      // the mass file but not yet performed any mass spectrum combination.
      //
      // Also, the TAB key cycling only works when one list widget item is
      // selected. If there are more than one item selected, then deselect all
      // the item but the first of the selected ones, to seed the cycling
      // conditions.

      int iCount  = mainWindow->mp_openSpectraDlg->itemCount();
      int siCount = mainWindow->mp_openSpectraDlg->selectedItemCount();

      AbstractPlotWidget *originPlotWidget;

      if(!siCount)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Not a single list widget item is selected. There are"
          // << iCount << "items in the list widget";

          // Not a single list widget item is selected. We want to select the
          // first one that has a corresponding graph in this multi-graph plot
          // widget.
          for(int iter = 0; iter < iCount; ++iter)
            {
              originPlotWidget =
                mainWindow->mp_openSpectraDlg->plotWidgetAt(iter);

              // Get to the corresponding mass spec plot widget.
              // Because a single tic chrom plot widget might generated many
              // different mass spec plot widgets, we get a list:
              // QList<AbstractPlotWidget *>massSpecPlotWidgetList =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.values(originPlotWidget);

              QList<AbstractPlotWidget *> driftSpecPlotWidgetList;
              mainWindow->m_dataPlotWidgetRelationer.targets(
                originPlotWidget, &driftSpecPlotWidgetList);

              // As long as there is at least one item, we are ok.
              if(driftSpecPlotWidgetList.size())
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "List widget at index " << iter << "will be
                  // selected.";

                  // Select that item right away.
                  mainWindow->mp_openSpectraDlg->deselectAllPlotWidgetsButOne(
                    originPlotWidget);
                  break;
                }
            }
        }
      else if(siCount == 1)
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Only one item is selected in the list widget. There are"
          // << iCount << "items in the list widget";

          // There is only one item that is selected. Just get to the next
          // one,
          // if possible, or cycle back to the next possible starting from the
          // top.
          int siIndex;
          originPlotWidget =
            mainWindow->mp_openSpectraDlg->firstSelectedPlotWidget(&siIndex);


          // qDebug() << __FILE__ << __LINE__
          // << "The item that is selected is at index " << siIndex;

          // The item that is selected is at index siIndex. Now, iterate in
          // the
          // various items of the list widget, starting at index siIndex + 1,
          // or
          // 0 and check if the iterated item has a corresponding mass spec
          // window plot widget. If so, just select it.

          if(iCount == 1)
            {

              // qDebug() << __FILE__ << __LINE__
              // << "The is only one item in the list widget, thus nothing to
              // do.";

              // There is one item selected and only one item. Nothing to do.
              event->accept();
              return;
            }

          // There are multiple items, so iterate into each of them and see if
          // we can select it, if it has a corresponding plot widget here.
          // Start iterating at the next index, since we want to cycle through
          // the various items.

          // If the currently selected item is the last of the list widget,
          // then
          // loop over to the first.
          if(++siIndex > iCount - 1)
            siIndex = 0;

          // qDebug() << __FILE__ << __LINE__
          // << "Starting iteration in the list widget at index " << siIndex;

          int iter       = siIndex;
          bool hasLooped = false;

          while(true)
            {
              // We cannot use the for loop below, because we want to loop
              // over
              // the end of the widget list if siIndex is in the middle of it,
              // for example.
              // for(int iter = siIndex; iter < iCount; ++iter)

              // qDebug() << __FILE__ << __LINE__
              // << "Iterating with iter: " << iter;

              originPlotWidget =
                mainWindow->mp_openSpectraDlg->plotWidgetAt(iter);

              // Get to the corresponding mass spec plot widget.
              // Because a single tic chrom plot widget might generated many
              // different mass spec plot widgets, we get a list:
              // QList<AbstractPlotWidget *>massSpecPlotWidgetList =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.values(originPlotWidget);

              QList<AbstractPlotWidget *> driftSpecPlotWidgetList;
              mainWindow->m_dataPlotWidgetRelationer.targets(
                originPlotWidget, &driftSpecPlotWidgetList);

              // As long as there is at least one item, we are ok.
              if(driftSpecPlotWidgetList.size())
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "The item at index " << iter << "is going to be
                  // selected.";

                  // Select that item right away.
                  mainWindow->mp_openSpectraDlg->deselectAllPlotWidgetsButOne(
                    originPlotWidget);
                  break;
                }

              // qDebug() << __FILE__ << __LINE__
              // << "Iterated in list widget item index " << iter;

              if(++iter >= iCount)
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "iter :" << iter << "is >= iCount, reset to 0";

                  iter = 0;

                  // We will need to know that we looped over the last widget
                  // list
                  // item, such that we can test if we are going past the
                  // first
                  // index that we iterated over.
                  hasLooped = true;

                  continue;
                }

              // But we do not want to loop forever:
              if(hasLooped && iter >= siIndex)
                {
                  // qDebug() << __FILE__ << __LINE__
                  // << "We looped already and iter is:" << iter << "so we
                  // break the loop.";

                  break;
                }
            }
          // End of
          // while(true)

          // qDebug() << __FILE__ << __LINE__
          // << "Out of for loop iterating in the list widget items.";

          event->accept();
          return;
        }
      else
        {
          // The number of selected items is more than one. We want to only
          // have
          // one item selected, that has a corresponding mass spec plot
          // widget.
          // Then, next TAB key strokes will cycle through the list widget
          // items.
          int siIndex =
            mainWindow->mp_openSpectraDlg->firstSelectedPlotWidgetIndex();

          // If the currently selected item is the last of the list widget,
          // then
          // loop over to the first.
          if(++siIndex >= iCount - 1)
            siIndex = 0;

          for(int iter = siIndex; iter < iCount; ++iter)
            {
              originPlotWidget =
                mainWindow->mp_openSpectraDlg->plotWidgetAt(iter);

              // Get to the corresponding mass spec plot widget.
              // Because a single tic chrom plot widget might generated many
              // different mass spec plot widgets, we get a list:
              // QList<AbstractPlotWidget *>massSpecPlotWidgetList =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.values(originPlotWidget);

              QList<AbstractPlotWidget *> driftSpecPlotWidgetList;
              mainWindow->m_dataPlotWidgetRelationer.targets(
                originPlotWidget, &driftSpecPlotWidgetList);

              // As long as there is at least one item, we are ok.
              if(driftSpecPlotWidgetList.size())
                {
                  // Select that item right away.
                  mainWindow->mp_openSpectraDlg->deselectAllPlotWidgetsButOne(
                    originPlotWidget);
                  break;
                }
            }

          event->accept();
          return;
        }
      // End of else that is, there are more than one list widget item
      // selected.
    }
  // End of else if(event->key() == Qt::Key_Tab)
  else if(event->key() == Qt::Key_L)
    {
      // The user wants to copy the current cursor location to the console
      // window, such that it remains available after having moved the cursor.
      // The handling of the text creation is different in these two
      // situations:
      //
      // 1. the plot widget where this event occurred is a conventional
      // mono-graph plot widget that sits in the lower part of the window. In
      // this case, the color to be used to display the data label in the
      // console window is easily gotten from the graph's pen.
      //
      // 2. the plot widget is a multi-graph plot widget that sits in the
      // upper part of the multi plot window. In that case, it is more
      // difficult to know what is the proper color to use, because we need to
      // go to the open spectra dialog and get the list of selected files that
      // initially contained the data plotted. We want to know what graph
      // corresponds to what list widget item that is selected (or not) in the
      // open mass spectra dialog window (see MainWindow).

      QString label;
      if(m_isMultiGraph)
        {

          // The event occurred in the multi-graph plot widget where many graphs
          // are displayed. We need to craft a label for each graph that
          // replicates the data of a plot that is proxied in the list widget of
          // the OpenSpectraDlg. Only handle the spectra of which the list
          // widget item is currently selected and for which the corresponding
          // mass multi-graph plot is visible.

          for(int iter = 0; iter < graphCount(); ++iter)
            {
              QCPGraph *iterGraph = graph(iter);

              // Only work on this graph if it is visible:
              if(!iterGraph->visible())
                continue;

              // Get the color of the graph, so that we can print the label in
              // the console window with the proper color.
              QPen curPen     = iterGraph->pen();
              QColor curColor = curPen.color();

              // Get a handle to the conventional plot widget of which this
              // multi-graph plot widget's graph is a replica.
              AbstractPlotWidget *driftSpecPlotWidget =
                parentWnd->monoGraphPlot(iterGraph);

              // Now use that pointer to get a handle to the tic chromatogram
              // plot
              // widget when the data in this plot widget came:
              // AbstractPlotWidget *originPlotWidget =
              // mainWindow->m_ticChromVersusMassSpecMultiMap.key(massSpecPlotWidget);

              DataPlotWidgetRelation *p_rootRelation = Q_NULLPTR;

              AbstractPlotWidget *originPlotWidget =
                mainWindow->m_dataPlotWidgetRelationer.rootPlotWidget(
                  driftSpecPlotWidget, &p_rootRelation);

              if(originPlotWidget == Q_NULLPTR)
                {
                  qFatal(
                    "Fatal error at %s@%d -- %s. "
                    "Cannot be that a multiGraph plot widget has no root plot "
                    "widget."
                    "Program aborted.",
                    __FILE__,
                    __LINE__,
                    __FUNCTION__);
                }

              // At this point, we know what plot widget in the Tic chrom
              // window
              // is at the origin of the graph currently iterated into. We
              // need to
              // know if that plot widget has a corresponding item in the open
              // spectrum dialog list widget that is currently selected.

              if(mainWindow->mp_openSpectraDlg->isPlotWidgetSelected(
                   originPlotWidget))
                {
                  // We can craft the label text to send to the console
                  // window, and
                  // that, with the proper color.

                  label = QString("DT(%1,%2)\n")
                            .arg(m_lastMousedPlotPoint.x())
                            .arg(m_lastMousedPlotPoint.y());

                  if(label.isEmpty())
                    {
                      parentWnd->statusBar()->showMessage(
                        "Failed to craft a text label.");
                      event->accept();
                      return;
                    }
                  else
                    {
                      // Send the label to the console window, so that we can
                      // make a copy
                      // paste.
                      mainWindow->logConsoleMessage(label, curColor);
                    }
                }
            }
          // End of
          // for(int iter = 0; iter < graphCount(); ++iter)
        }
      // End of
      // if(m_isMultiGraph)
      else
        {
          // We are in a plot widget that is a single-graph plot widget, the
          // situation is easier:

          label = QString("DT(%1,%2)\n")
                    .arg(m_lastMousedPlotPoint.x())
                    .arg(m_lastMousedPlotPoint.y());

          if(label.isEmpty())
            {
              parentWnd->statusBar()->showMessage(
                "Failed to craft a text label. ");
              event->accept();
              return;
            }
          else
            {
              // Send the label to the console window, so that we can make a
              // copy
              // paste.

              // Get the color of the graph, so that we can print the stanza
              // in
              // the console window with the proper color.
              QPen curPen     = graph()->pen();
              QColor curColor = curPen.color();

              mainWindow->logConsoleMessage(label, curColor);
            }
        }

      event->accept();

      return;
    }
  // End of
  // else if(event->key() == Qt::Key_C)

  // Now let the base class do its work.
  AbstractPlotWidget::keyReleaseEvent(event);
}


/*/js/
 * TicChromPlotWidget.jsSetMzIntegrationParams(mzIntegrationParams)
 *
 * Set the m/z integration parameters object passed as parameter to this plot
 * widget so that they are taken into account for the next integrations to a
 * mass spectrum.
 */
void
DriftSpecPlotWidget::jsSetMzIntegrationParams()
{
  if(m_isMultiGraph)
    return;

  MainWindow *mainWindow = static_cast<MainWindow *>(mp_parentWnd->parent());
  QScriptContext *ctx =
    mainWindow->mp_scriptingWnd->m_scriptEngine.currentContext();

  int argCount = ctx->argumentCount();
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "argCount:" << argCount;

  if(argCount != 1)
    {
      ctx->throwError(
        "Syntax error: function setMzIntegrationParams takes one "
        "MzIntegrationParams object as argument.");
      return;
    }

  QScriptValue arg = ctx->argument(0);
  QVariant variant = arg.data().toVariant();

  if(!variant.isValid())
    {
      ctx->throwError(
        "Syntax error: function setMzIntegrationParams takes one "
        "MzIntegrationParams object as argument.");
      return;
    }

  if(variant.userType() ==
     QMetaType::type("msXpSmineXpert::MzIntegrationParams"))
    {
      MzIntegrationParams params(
        qscriptvalue_cast<msXpSmineXpert::MzIntegrationParams>(arg));
      AbstractPlotWidget::setMzIntegrationParams(params);

      // Now update the values in the MzIntegrationParamsWnd.

      AbstractMultiPlotWnd *parentWnd =
        dynamic_cast<AbstractMultiPlotWnd *>(mp_parentWnd);

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

      parentWnd->updateMzIntegrationParams(m_history.mzIntegrationParams(),
                                           m_plottingColor);
    }
  else
    {
      ctx->throwError(
        "Syntax error: function setMzIntegrationParams takes one "
        "MzIntegrationParams object as argument.");
      return;
    }
}


/*/js/
 * TicChromPlotWidget.jsMzIntegrationParams()
 *
 * Return the MzIntegrationParams object currently set for this color map
 * plot widget.
 *
 * These parameters are taken into account for the integrations to a mass
 * spectrum.
 */
QScriptValue
DriftSpecPlotWidget::jsMzIntegrationParams() const
{
  MainWindow *mainWindow = static_cast<MainWindow *>(mp_parentWnd->parent());

  if(m_isMultiGraph)
    return mainWindow->mp_scriptingWnd->m_scriptEngine.toScriptValue(
      MzIntegrationParams());

  return mainWindow->mp_scriptingWnd->m_scriptEngine.toScriptValue(
    m_history.mzIntegrationParams());
}


} // namespace msXpSmineXpert
