/* 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/TicChromPlotWidget.hpp>
#include <minexpert/gui/TicChromWnd.hpp>
#include <minexpert/gui/AbstractMultiPlotWnd.hpp>
#include <minexpert/nongui/MassSpecSqlite3Handler.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: TicChromPlotWidget
 * <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 ticChromPlotWidget[index], with [index]
 * being 0 for the first object created and being incremented each time a new
 * TicChromPlotWidget is created.</comment>
 */


//! Construct an initialized TicChromPlotWidget 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.

*/
TicChromPlotWidget::TicChromPlotWidget(QWidget *parent,
                                       const QString &name,
                                       const QString &desc,
                                       const MassSpecDataSet *massSpecDataSet,
                                       const QString &fileName,
                                       bool isMultiGraph)
  : AbstractPlotWidget{
      parent, name, desc, massSpecDataSet, fileName, isMultiGraph}
{
  registerQtKeyCode(
    Qt::Key_S,
    "Press S while right mouse click-drag to integrate to a mass spectrum");
  registerQtKeyCode(
    Qt::Key_D,
    "Press D while right mouse click-drag to integrate to a drift spectrum");
  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("time (min)");
  yAxis->setLabel("tic");
}


//! Destruct \c this TicChromPlotWidget instance.
TicChromPlotWidget::~TicChromPlotWidget()
{
  // No need to delete the mp_massSpecDataSet
}


//! Create the contextual menus.
QMenu *
TicChromPlotWidget::createContextMenu()
{
  AbstractPlotWidget::createContextMenu();

  // We can only compute statistics on the basis of the current plot range
  // when the whole mass data set is in memory. Otherwise that would be too
  // lengthy.
  if(!mp_massSpecDataSet->isStreamed())
    {
      mpa_contextMenu->addSeparator();
      QAction *action = mpa_contextMenu->addAction("&Statistics");
      connect(
        action, &QAction::triggered, this, &TicChromPlotWidget::statistics);
    }

  return mpa_contextMenu;
}


//! 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
TicChromPlotWidget::craftAnalysisStanza(QCPGraph *theGraph,
                                        const QString &fileName)
{

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

  TicChromWnd *wnd = dynamic_cast<TicChromWnd *>(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;

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

  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 TIC chrom 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)

  // qDebug() << __FILE__ << __LINE__ << "Returning stanza: " << stanza;

  return stanza;
}


//! Set focus to \c this widget.
void
TicChromPlotWidget::setFocus()
{
  AbstractPlotWidget::setFocus();

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

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "params:" << m_history.mzIntegrationParams().asText();
}


//! 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 drift
  spectrum or to retention time (XIC) or to single TIC intensity value.
  */
void
TicChromPlotWidget::mouseReleaseHandledEvent(QMouseEvent *event)
{

  // This function is called by
  // AbstractPlotWidget::mouseReleaseHandler(QMouseEvent *event) when
  // the key that is pressed is listed in the m_regQtKeyCodeList.

  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)
    {
      if(!m_isDragging)
        {
          // The user simply clicked on the plot with the right mouse button
          // while maintaining the S key pressed. Integrate to mz only the
          // current single retention time. But we need to make sure that we get
          // into the pseudo range the read data point left of the clicked
          // location.

          QCPRange keyRange;

          bool res = findIntegrationLowerRangeForKey(m_lastMousedPlotPoint.x(),
                                                     &keyRange);

          if(!res)
            qFatal(
              "Fatal error at %s@%d -- %s(). "
              "Programming error."
              "Program aborted.",
              __FILE__,
              __LINE__,
              __FUNCTION__);

          integrateToMz(keyRange.lower, keyRange.upper);
        }
      else
        integrateToMz(xRangeMin(), xRangeMax());
    }

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

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


//! Perform a ranged [\p lower -- \p upper] data integration to a mass spectrum.
void
TicChromPlotWidget::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;
    }

  // The history has by definition the bin specifications, which we'll use
  // to perform the integrations. This is only true when integrating to
  // MZ, because the result will be a mass spectrum plot. When integrating
  // to DT or TICintensity, then no problem: we are working all along with
  // TIC values and TIC values do not need bins to be properly displayed
  // into a mass spectrum.

  History localHistory = m_history;

  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::RT_TO_MZ, rangeStart, rangeEnd);

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

  emit newMassSpectrum(mp_massSpecDataSet,
                       "Calculating mass spectrum.",
                       localHistory,
                       m_plottingColor);
}


/*/js/
 * TicChromPlotWidget.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.
 */

//! Perform a ranged [\p lower -- \p upper] data integration to a mass spectrum.
void
TicChromPlotWidget::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;
    }

  // The history has by definition the bin specifications, which we'll use
  // to perform the integrations. This is only true when integrating to
  // MZ, because the result will be a mass spectrum plot. When integrating
  // to DT or TICintensity, then no problem: we are working all along with
  // TIC values and TIC values do not need bins to be properly displayed
  // into a mass spectrum.

  History localHistory = m_history;

  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::RT_TO_MZ, rangeStart, rangeEnd);

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

  TicChromWnd *wnd = dynamic_cast<TicChromWnd *>(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);
}


//! Perform a ranged [\p lower -- \p upper] data integration to a drift
//! spectrum.
void
TicChromPlotWidget::integrateToDt(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::RT_TO_DT, rangeStart, rangeEnd);

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

  emit newDriftSpectrum(mp_massSpecDataSet,
                        "Calculating drift spectrum.",
                        localHistory,
                        m_plottingColor);
}


/*/js/
 * TicChromPlotWidget.jsIntegrateToDt(lower, upper)
 *
 * Starts an integration of mass spectra data to a drift 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
TicChromPlotWidget::jsIntegrateToDt(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::RT_TO_DT, rangeStart, rangeEnd);

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

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

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

  AbstractMultiPlotWnd *targetWnd = mainWindow->mp_driftSpecWnd;

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

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


//! Perform a ranged [\p lower -- \p upper] data integration to a single TIC
//! intensity value.
void
TicChromPlotWidget::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::RT_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);

  // 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/
 * TicChromPlotWidget.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
TicChromPlotWidget::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::RT_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);

  TicChromWnd *parentWnd = static_cast<TicChromWnd *>(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, 'f', 3)
                                          .arg(m_keyRangeEnd, 0, 'f', 3)
                                          .arg(m_lastTicIntensity, 0, 'g', 3));

  return m_lastTicIntensity;
}


//! Perform a ranged [\p lower -- \p upper] data integration to a XIC
//! chromatogram.
/*

   This process is not equivalent to an integration to RT because we do not take
   into account the History of the current plot widget. Indeed, the integration
   is performed without any filtering from the initial mass spectrometry data.

*/
void
TicChromPlotWidget::integrateToXic(double lower, double upper)
{

  // The history list needs to be empty because we do not want any filtering of
  // the data apart from the two parameters to this function. However, we
  // want to keep the mass spectral data integration parameters.

  History localHistory = m_history;
  localHistory.freeList();

  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(IntegrationType::FILE_TO_XIC, lower, upper);

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

  // The signal is emitted, but the connection to the receiver exists only
  // if this plot widget is NOT multi-graph. So, even if mp_massSpecDataSet
  // is nullptr, there is no risk.
  emit newTicChromatogram(mp_massSpecDataSet,
                          "Calculating extracted ion chromatogram.",
                          localHistory,
                          m_plottingColor);
}


/*/js/
 * TicChromPlotWidget.jsIntegrateToXic(lower, upper)
 *
 * Starts an integration of mass spectra data to a XIC chromatogram limiting
 * the data range specified with the numerical arguments.
 *
 * This process is not equivalent to an integration to RT because we do not
 * take into account the History of the current plot widget. Indeed, the
 * integration is performed without any filtering from the initial mass
 * spectrometry data.
 *
 * 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
TicChromPlotWidget::jsIntegrateToXic(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;

  // The history list needs to be empty because we do not want any filtering of
  // the data apart from the two parameters to this function. However, we
  // want to keep the mass spectral data integration parameters.

  History localHistory = m_history;
  localHistory.freeList();

  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(IntegrationType::FILE_TO_XIC, lower, upper);

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

  TicChromWnd *wnd = dynamic_cast<TicChromWnd *>(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);
}


/*/js/
 * TicChromPlotWidget.statistics()
 *
 * Compute the mass spectra statistics on the spectra currently in the range.
 */
//! Compute mass spectra statistics on the spectra in currently the range.
QString
TicChromPlotWidget::statistics()
{
  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 QString();

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

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

  double rangeStart = xAxis->range().lower;

  double rangeEnd = xAxis->range().upper;

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

  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(mp_massSpecDataSet == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Make sure we compute the statistics according to the parameters in
  // localHistory.
  MassSpecDataStats statistics =
    mp_massSpecDataSet->makeStatistics(localHistory);

  QString text = statistics.asText();

  QApplication::restoreOverrideCursor();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Computed statistics\n" << text;

  TicChromWnd *parentWnd = static_cast<TicChromWnd *>(mp_parentWnd);
  MainWindow *mainWindow = static_cast<MainWindow *>(parentWnd->parent());

  mainWindow->logConsoleMessage(
    "\n------------------------------------------------\n");
  mainWindow->logConsoleMessage(text, m_plottingColor);
  mainWindow->logConsoleMessage(
    "------------------------------------------------\n");

  return text;
}


/*/js/
 * TicChromPlotWidget.newPlot(trace)
 *
 * Create a new TicChromPlotWidget 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
TicChromPlotWidget::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.

      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,
                  m_plottingColor);
    }
  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
TicChromPlotWidget::keyReleaseEvent(QKeyEvent *event)
{
  TicChromWnd *parentWnd = static_cast<TicChromWnd *>(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 a handle to the conventional plot widget of which this
              // multi-graph plot widget's graph is a replica.

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

              if(ticChromPlotWidget == Q_NULLPTR)
                {
                  qFatal(
                    "Fatal error at %s@%d -- %s. "
                    "Cannot be that a multiGraph plot widget has not "
                    "corresponding monoGraphPlot."
                    "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(
                   ticChromPlotWidget))
                {
                  // 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 *>(ticChromPlotWidget)
                      ->mp_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, m_plottingColor);
                    }
                }
            }
          // 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.

              emit recordAnalysisStanza(stanza, m_plottingColor);
            }
        }

      event->accept();

      return;
    }
  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 a handle to the conventional plot widget of which this
              // multi-graph plot widget's graph is a replica.

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

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

                  return;
                }

              // 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(
                   ticChromPlotWidget))
                {

                  // We can craft the label text to send to the console window,
                  // and
                  // that, with the proper color.

                  label = QString("RT(%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, m_plottingColor);
                    }
                }
            }
          // 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("RT(%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, m_plottingColor);
            }
        }

      event->accept();

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

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


//! Mouse event handler.
void
TicChromPlotWidget::mousePressHandler(QMouseEvent *event)
{
  AbstractPlotWidget::mousePressHandler(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
TicChromPlotWidget::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
TicChromPlotWidget::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());
}


//! Return the arbitrary integration type that this plot widget can handle
int
TicChromPlotWidget::arbitraryIntegrationType()
{
  return IntegrationType::RT_TO_XXX;
}
} // namespace msXpSmineXpert
// namespace msXpSmineXpert
