/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2019 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QSettings>
#include <QMenuBar>
#include <QMenu>
#include <QDebug>


/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "BasePlotWnd.hpp"
#include "ProgramWindow.hpp"
#include "ColorSelector.hpp"
#include "MzIntegrationParamsDlg.hpp"
#include "MsFragmentationSpecDlg.hpp"
#include "Application.hpp"


namespace msxps
{
namespace minexpert
{


//! Construct an BasePlotWnd instance.
BasePlotWnd::BasePlotWnd(QWidget *parent,
                         const QString &title,
                         const QString &settingsTitle,
                         const QString &description)
  : QMainWindow(parent),
    mp_programWindow(static_cast<ProgramWindow *>(parent)),
    m_title(title),
    m_settingsTitle(settingsTitle),
    m_description(description)
{
  if(parent == Q_NULLPTR)
    qFatal("Programming error.");

  m_ui.setupUi(this);

  if(!initialize())
    qFatal("Programming error.");
}


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


//! Write the settings to as to restore the window geometry later.
void
BasePlotWnd::writeSettings()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup(m_settingsTitle);

  settings.setValue("geometry", saveGeometry());
  settings.setValue("isVisible", isVisible());

  settings.endGroup();
}


//! Read the settings to as to restore the window geometry.
void
BasePlotWnd::readSettings()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup(m_settingsTitle);

  restoreGeometry(settings.value("geometry").toByteArray());
  setVisible(settings.value("isVisible").toBool());

  settings.endGroup();
}


//! Handle the close event.
void
BasePlotWnd::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  // qDebug();
  writeSettings();
  hide();
}


ProgramWindow *
BasePlotWnd::getProgramWindow()
{
  return mp_programWindow;
}


//! Initialize the window.
bool
BasePlotWnd::initialize()
{
  setWindowTitle(QString("mineXpert2 - %1").arg(m_title));

  // This will take care of the main menu that sits in the first button of the
  // tool bar.
  setupToolBar();

  /****************** The widgetry layouting ******************/

  mp_centralWidget = new QWidget(this);
  mp_centralWidget->setObjectName(QStringLiteral("mp_centralWidget"));

  this->setCentralWidget(mp_centralWidget);

  // Passing the widget as parent to the layout constructor is the same as
  // calling widget->setLayout(layout).
  mp_centralWidgetGridLayout = new QGridLayout(mp_centralWidget);
  mp_centralWidgetGridLayout->setObjectName(
    QStringLiteral("mp_centralWidgetGridLayout"));

  // Now that the main layout is set to the central widget, let's pack the
  // scroll area in it. Upon adding the area to the layout, that area is
  // reparented to the layout that is responsible for its destruction.
  mp_scrollArea = new QScrollArea(mp_centralWidget);
  mp_scrollArea->setObjectName(QStringLiteral("mp_scrollArea"));
  mp_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  mp_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

  // We accept that the plots are resized, but we set a minimum size when we
  // create them.
  mp_scrollArea->setWidgetResizable(true);
  mp_scrollArea->setAlignment(Qt::AlignCenter);

  mp_scrollAreaWidgetContents = new QWidget();
  mp_scrollAreaWidgetContents->setObjectName(
    QStringLiteral("mp_scrollAreaWidgetContents"));
  mp_scrollAreaWidgetContents->setGeometry(QRect(0, 0, 766, 525));

  mp_scrollAreaVBoxLayout = new QVBoxLayout(mp_scrollAreaWidgetContents);
  mp_scrollAreaVBoxLayout->setObjectName(
    QStringLiteral("mp_scrollAreaVBoxLayout"));

  mp_splitter = new QSplitter(Qt::Vertical, mp_centralWidget);
  mp_splitter->setHandleWidth(3);

  // We want the host of all the plot widget to be this QSplitter instance
  // because this way, the user has some leeway to resize the plot widgets
  // inside the scroll area.
  mp_scrollAreaVBoxLayout->addWidget(mp_splitter);

  mp_scrollArea->setWidget(mp_scrollAreaWidgetContents);

  mp_centralWidgetGridLayout->addWidget(mp_scrollArea);

  readSettings();

  return true;
}


void
BasePlotWnd::setupToolBar()
{
  /****************** The button tool bar ******************/
  /****************** The button tool bar ******************/

  mp_toolBar = addToolBar("");
  mp_toolBar->setObjectName(QStringLiteral("mp_toolBar"));

  // Make sure a right mouse button click does not elicit the context menu that
  // when clicked made the toolbar disappear totally.
  mp_toolBar->setContextMenuPolicy(Qt::PreventContextMenu);

  // Create the main menu that sits into the first button of the toolbar.
  createMainMenu();

  // The tool bar non-menu buttons

  const QIcon xRangeIcon = QIcon(":/images/lock-x-range.png");
  mp_lockXRangeAct       = new QAction(xRangeIcon, tr("Lock X range (Ctrl+L, X)"), this);
  mp_lockXRangeAct->setCheckable(true);
  mp_lockXRangeAct->setShortcut(QKeySequence("Ctrl+L, X"));
  mp_lockXRangeAct->setStatusTip(tr("Lock X range"));

  connect(
    mp_lockXRangeAct, &QAction::toggled, this, &BasePlotWnd::lockXRangeToggle);

  mp_toolBar->addAction(mp_lockXRangeAct);

  const QIcon yRangeIcon = QIcon(":/images/lock-y-range.png");
  mp_lockYRangeAct       = new QAction(yRangeIcon, tr("Lock Y range (Ctrl+L, Y)"), this);
  mp_lockYRangeAct->setCheckable(true);
  mp_lockYRangeAct->setShortcut(QKeySequence("Ctrl+L, Y"));
  mp_lockYRangeAct->setStatusTip(tr("Lock Y range"));

  connect(
    mp_lockYRangeAct, &QAction::toggled, this, &BasePlotWnd::lockYRangeToggle);

  mp_toolBar->addAction(mp_lockYRangeAct);
}


void
BasePlotWnd::createMainMenu()
{
  // The toolbar menu button

  // The main menu in the form of a button with an icon like thoses found for
  // the menus in mobile phone applications.

  mp_mainMenu = new QMenu(this);

  const QIcon main_menu_icon =
    QIcon(":/images/mobile-phone-like-menu-button.svg");
  mp_mainMenuPushButton = new QPushButton(main_menu_icon, QString(""), this);
  mp_mainMenuPushButton->setToolTip("Plot widget menu (Ctrl+W, M)");
  mp_mainMenuPushButton->setShortcut(QKeySequence("Ctrl+W, M"));

  // When the main menu push button is clicked, the menu show up.
  connect(mp_mainMenuPushButton, &QPushButton::clicked, [this]() {
    mp_mainMenu->show();
  });


  ////////////// MZ INTEG PARAMS and MS FRAG SPEC ////////////////////
  ////////////// MZ INTEG PARAMS and MS FRAG SPEC ////////////////////

  // We want to be able to set the mz integ params for all the plot widgets in
  // the window in one go.

  QAction *new_act_p =
    new QAction("Set m/z &integration params for all pinned-down widgets", this);
  new_act_p->setShortcut(QKeySequence("Ctrl+I, P"));

  connect(new_act_p, &QAction::triggered, [this]() {
    setMzIntegrationParamsForAllPlotWidgets();
  });

  mp_mainMenu->addAction(new_act_p);

  // We want to be able to set the ms fragmentation spec for all the plot
  // widgets in the window in one go.

  new_act_p =
    new QAction("Set MS &fragmentation spec for all pinned-down widgets", this);
  new_act_p->setShortcut(QKeySequence("Ctrl+F, S"));

  connect(new_act_p, &QAction::triggered, [this]() {
    setMsFragmentationSpecForAllPlotWidgets();
  });

  mp_mainMenu->addAction(new_act_p);

  ////////////// MZ INTEG PARAMS and MS FRAG SPEC ////////////////////
  ////////////// MZ INTEG PARAMS and MS FRAG SPEC ////////////////////


  // Separate the last added item from the rest.
  mp_beforePlotWidgetManagementSeparatorAction = mp_mainMenu->addSeparator();


  ////////////// PLOT WIDGET MANAGEMENT ////////////////////
  ////////////// PLOT WIDGET MANAGEMENT ////////////////////

  mp_plotWidgetManagementMenu = new QMenu("Plot &widget management");
  mp_mainMenu->addMenu(mp_plotWidgetManagementMenu);

  // An action to allow resizing all the plot widget to the mean size. Useful
  // when the user has resized to naught some plot widget and they want to get
  // back to their initial size.
  mp_resetPlotSizesAct =
    new QAction(tr("&Reset all the plot widget sizes"), this);
  mp_resetPlotSizesAct->setStatusTip(tr("Reset all the plot widget sizes"));
  mp_resetPlotSizesAct->setShortcut(QKeySequence("Ctrl+R, S"));

  // Lambda function to compute the right size for each plot widget in the
  // splitter.
  connect(mp_resetPlotSizesAct, &QAction::triggered, [this]() {
    // First off, if there are not widgets, do nothing, division by zero !!!

    if(!mp_splitter->count())
      return;
    QList<int> sizes = mp_splitter->sizes();
    int size_sum     = 0;
    foreach(int size, sizes)
      size_sum += size;

    int mean_size = size_sum / sizes.size();

    QList<int> new_sizes;

    for(int iter = 0; iter < sizes.size(); ++iter)
      new_sizes.append(mean_size);

    mp_splitter->setSizes(new_sizes);
  });

  mp_plotWidgetManagementMenu->addAction(mp_resetPlotSizesAct);

  QMenu *pin_unpin_plot_widgets_menu_p =
    new QMenu("&Pin-down/Unpin plot widgets...");
  mp_plotWidgetManagementMenu->addMenu(pin_unpin_plot_widgets_menu_p);


  // An action to push-pin all the widgets.

  new_act_p = new QAction(tr("&Pin-down all the widgets"), this);
  new_act_p->setStatusTip(tr("Pin-down all the widgets"));
  new_act_p->setShortcut(QKeySequence("Ctrl+P, A"));

  connect(new_act_p, &QAction::triggered, [this]() {
    std::vector<BasePlotCompositeWidget *> widget_list = allWidgets();

    for(auto &&widget_p : widget_list)
      widget_p->pinDown(true);
  });

  pin_unpin_plot_widgets_menu_p->addAction(new_act_p);

  // An action to unpush-pin all the widgets.

  new_act_p = new QAction(tr("&Un-pin all the widgets"), this);
  new_act_p->setStatusTip(tr("Un-pin all the widgets"));
  new_act_p->setShortcut(QKeySequence("Ctrl+U, A"));

  connect(new_act_p, &QAction::triggered, [this]() {
    std::vector<BasePlotCompositeWidget *> widget_list = allWidgets();

    for(auto &&widget_p : widget_list)
      widget_p->pinDown(false);
  });

  pin_unpin_plot_widgets_menu_p->addAction(new_act_p);

  mp_plotWidgetManagementMenu->addSeparator();

  QMenu *remove_plot_widgets_menu_p = new QMenu("Remove plot &widgets...");
  mp_plotWidgetManagementMenu->addMenu(remove_plot_widgets_menu_p);

  QAction *remove_pinned_plot_widgets_p = new QAction(tr("&Remove pinned-down plot widgets"), this);
  remove_pinned_plot_widgets_p->setStatusTip(tr("Remove pinned-down plot widgets"));
  remove_pinned_plot_widgets_p->setShortcut(QKeySequence("Ctrl+R, P"));

  connect(remove_pinned_plot_widgets_p, &QAction::triggered, [this]() {
    std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

    for(auto &&widget_p : widget_list)
      plotCompositeWidgetDestructionRequested(widget_p);
  });

  remove_plot_widgets_menu_p->addAction(remove_pinned_plot_widgets_p);

  QAction *remove_pinned_plot_widgets_descendants_p =
    new QAction(tr("Remove pinned-down plot widgets and &descendants"), this);
  remove_pinned_plot_widgets_descendants_p->setStatusTip(
    tr("Remove pinned-down plot widgets and descendants"));
  remove_pinned_plot_widgets_descendants_p->setShortcut(QKeySequence("Ctrl+R, Ctrl+P, D"));

  connect(remove_pinned_plot_widgets_descendants_p, &QAction::triggered, [this]() {
    std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

    for(auto &&widget_p : widget_list)
      plotCompositeWidgetDestructionRequested(widget_p);
  });

  remove_plot_widgets_menu_p->addAction(remove_pinned_plot_widgets_descendants_p);

  remove_plot_widgets_menu_p->addSeparator();

  mp_removeEmptyPlotWidgetsAct =
    new QAction(tr("Remove &empty plot widgets"), this);
  mp_removeEmptyPlotWidgetsAct->setStatusTip(tr("Remove empty plot widgets"));
  mp_removeEmptyPlotWidgetsAct->setShortcut(QKeySequence("Ctrl+R, E"));

  connect(mp_removeEmptyPlotWidgetsAct,
          &QAction::triggered,
          this,
          &BasePlotWnd::removeEmptyPlotWidgets);

  remove_plot_widgets_menu_p->addAction(mp_removeEmptyPlotWidgetsAct);


  mp_removeAllPlotWidgetsAct = new QAction(tr("Remove &all plot widgets"), this);
  mp_removeAllPlotWidgetsAct->setStatusTip(tr("Remove all plot widgets"));
  mp_removeAllPlotWidgetsAct->setShortcut(QKeySequence("Ctrl+R, A"));

  connect(mp_removeAllPlotWidgetsAct, &QAction::triggered, [this]() {
    QList<BasePlotCompositeWidget *> widget_list =
      findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

    while(widget_list.size())
      plotCompositeWidgetDestructionRequested(widget_list.takeLast());
  });

  remove_plot_widgets_menu_p->addAction(mp_removeAllPlotWidgetsAct);

  mp_removeAllPlotWidgetsAndDescendantsAct =
    new QAction(tr("Remove all plot widgets and d&escendants"), this);
  mp_removeAllPlotWidgetsAndDescendantsAct->setStatusTip(
    tr("Remove all plot widgets and descendants"));
  mp_removeAllPlotWidgetsAndDescendantsAct->setShortcut(QKeySequence("Ctrl+R, Ctrl+A, D"));

  connect(
    mp_removeAllPlotWidgetsAndDescendantsAct, &QAction::triggered, [this]() {
      QList<BasePlotCompositeWidget *> widget_list =
        findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

      while(widget_list.size())
        // We want the removal to recursively remove all descendants
        plotCompositeWidgetDestructionRequested(widget_list.takeLast());
    });

  remove_plot_widgets_menu_p->addAction(
    mp_removeAllPlotWidgetsAndDescendantsAct);

  ////////////// PLOT WIDGET MANAGEMENT ////////////////////
  ////////////// PLOT WIDGET MANAGEMENT ////////////////////

  mp_mainMenu->addSeparator();

  QAction *save_plot_to_graphics_file_p =
    new QAction("Save pinned plot(s) to &graphics file", this);
  save_plot_to_graphics_file_p->setStatusTip(
    tr("Save pinned plot(s) to graphics file"));
  save_plot_to_graphics_file_p->setShortcut(QKeySequence("Ctrl+S, P"));

  mp_mainMenu->addAction(save_plot_to_graphics_file_p);

  connect(save_plot_to_graphics_file_p,
          &QAction::triggered,
          this,
          &BasePlotWnd::savePinnedPlotsToGraphicsFile);

  // The main menu is under the control of a push button.
  mp_mainMenuPushButton->setMenu(mp_mainMenu);

  // Finally add the main menu push button to the toolbar!
  mp_toolBar->addWidget(mp_mainMenuPushButton);
}


void
BasePlotWnd::setMzIntegrationParamsForAllPlotWidgets()
{
  // qDebug();

  pappso::MzIntegrationParams mz_integration_params;

  MzIntegrationParamsDlg *mz_integration_params_dlg_p =
    new MzIntegrationParamsDlg(this, mz_integration_params, Qt::black);

  connect(mz_integration_params_dlg_p,
          &MzIntegrationParamsDlg::mzIntegrationParamsChangedSignal,
          this,
          &BasePlotWnd::mzIntegrationParamsChanged);

  connect(
    mz_integration_params_dlg_p,
    &MzIntegrationParamsDlg::mzIntegrationParamsDlgShouldBeDestroyedSignal,
    [this]() {
      // qDebug();
      delete QObject::sender();
    });

  mz_integration_params_dlg_p->show();
}


void
BasePlotWnd::mzIntegrationParamsChanged(
  pappso::MzIntegrationParams mz_integration_params)
{
  // We now know what are the mz integ params. We need to propagate them to all
  // the pinned-down widgets.

  std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

  for(auto &&widget_p : widget_list)
    {
      QList<QCPAbstractPlottable *> destination_plottables =
        widget_p->plottablesToBeUsedAsIntegrationDestination();

      for(int iter = 0; iter < destination_plottables.size(); ++iter)
        {
          QCPAbstractPlottable *iter_plottable_p =
            destination_plottables.at(iter);

          // Now get the ProcessingFlow for the plottable:

          ProcessingFlow *processing_flow_p =
            widget_p->getProcessingFlowPtrForPlottable(iter_plottable_p);

          // Now get the mz integration params out of it, because we need to
          // know some params like the smallest/greatest m/z bin, for example.
          // Then, we'll update it with the new params set here.

          pappso::MzIntegrationParams iter_mz_integration_params =
            processing_flow_p->getDefaultMzIntegrationParams();

          // Now modify the values according to the new settings:

          iter_mz_integration_params.setBinningType(
            mz_integration_params.getBinningType());
          iter_mz_integration_params.setPrecision(
            mz_integration_params.getPrecision());
          iter_mz_integration_params.setDecimalPlaces(
            mz_integration_params.getDecimalPlaces());
          iter_mz_integration_params.setRemoveZeroValDataPoints(
            mz_integration_params.isRemoveZeroValDataPoints());

          // And finally set the default mz integration params to be our updated
          // mz integration params.
          processing_flow_p->setDefaultMzIntegrationParams(
            iter_mz_integration_params);

          pappso::MzIntegrationParams mz_integration_params =
            processing_flow_p->getDefaultMzIntegrationParams();

          // qDebug() << "Set the mz integration params to one plottable:"
          //<< mz_integration_params.toString();
        }
    }
}


void
BasePlotWnd::setMsFragmentationSpecForAllPlotWidgets()
{
  // qDebug();

  // Set the ms fragmentation spec for all the selected/single plottables in
  // each pinned-down plot widget (see analog function for mz integ. params).

  // Start with a fresh default spec.

  MsFragmentationSpec ms_fragmentation_spec;

  MsFragmentationSpecDlg *ms_fragmentation_spec_dlg_p =
    new MsFragmentationSpecDlg(this, ms_fragmentation_spec, Qt::black);

  connect(
    ms_fragmentation_spec_dlg_p,
    &MsFragmentationSpecDlg::msFragmentationSpecDlgShouldBeDestroyedSignal,
    [this]() {
      // qDebug();
      delete QObject::sender();
    });

  connect(ms_fragmentation_spec_dlg_p,
          &MsFragmentationSpecDlg::msFragmentationSpecChangedSignal,
          this,
          &BasePlotWnd::msFragmentationSpecChanged);

  ms_fragmentation_spec_dlg_p->show();
}


void
BasePlotWnd::msFragmentationSpecChanged(
  MsFragmentationSpec ms_fragmentation_spec)
{
  // We now know what is the ms fragmentation spec. We need to propagate it to
  // all the pinned-down widgets.

  std::vector<BasePlotCompositeWidget *> widget_list = pinnedDownWidgets();

  for(auto &&widget_p : widget_list)
    {
      QList<QCPAbstractPlottable *> destination_plottables =
        widget_p->plottablesToBeUsedAsIntegrationDestination();

      for(int iter = 0; iter < destination_plottables.size(); ++iter)
        {
          QCPAbstractPlottable *iter_plottable_p =
            destination_plottables.at(iter);

          // Now get the ProcessingFlow for the plottable:

          ProcessingFlow *processing_flow_p =
            widget_p->getProcessingFlowPtrForPlottable(iter_plottable_p);

          // And finally set the default ms frag pec.
          processing_flow_p->setDefaultMsFragmentationSpec(
            ms_fragmentation_spec);
        }
    }
}


void
BasePlotWnd::show()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup(m_settingsTitle);

  restoreGeometry(settings.value(m_settingsTitle + "_geometry").toByteArray());

  settings.endGroup();

  QMainWindow::show();
}


void
BasePlotWnd::savePinnedPlotsToGraphicsFile()
{

  // Get a list of pinned down plot composite widgets.

  std::vector<BasePlotCompositeWidget *> pinned_down_widgets =
    pinnedDownWidgets();

  for(auto &&composite_widget_p : pinned_down_widgets)
    {

      QColor color = Qt::black;

      QCPAbstractPlottable *plottable_p = composite_widget_p->firstPlottable();

      if(plottable_p != nullptr)
        color = plottable_p->pen().color();

      QString sample_name = composite_widget_p->m_ui.sampleNameLabel->text();
      SaveToGraphicsFileDlg dlg(
        this, composite_widget_p, "mineXpert", sample_name, color);

      dlg.exec();
    }
}


#if 0

// Old version
void
BasePlotWnd::integrateToTicIntensity(QCPAbstractPlottable *parent_plottable_p,
                                     const ProcessingFlow &processing_flow)
{
  // qDebug().noquote() << "Integrating to rt with processing flow:"
  //<< processing_flow.toString();

  // Get the ms run data set that for the graph we are going to base the
  // integration on.
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();
  if(ms_run_data_set_csp == nullptr)
    qFatal("Cannot be that the pointer is nullptr.");

  // Pass the integrator the flow we got as param and that describes in its
  // most recent step the integration that it should perform.
  MsRunDataSetTreeMassDataIntegratorToTicInt *mass_data_integrator_p =
    new MsRunDataSetTreeMassDataIntegratorToTicInt(ms_run_data_set_csp,
                                                   processing_flow);

  // Ensure the mass data integrator messages are used.

  connect(mass_data_integrator_p,
          &MassDataIntegrator::logTextToConsoleSignal,
          mp_programWindow,
          &ProgramWindow::logTextToConsole);

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // Allocate a mass data integrator to integrate the data.

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(
    this,
    // SIGNAL(integrateToTicIntensitySignal(MsRunDataSetTreeMassDataIntegratorToTicInt
    // *)),
    static_cast<void (BasePlotWnd::*)(
      MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
      &BasePlotWnd::integrateToTicIntensitySignal),
    mass_data_integrator_task_p,
    // SLOT(integrateToTicIntensity(MsRunDataSetTreeMassDataIntegratorToTicInt
    // *)),
    static_cast<void (MassDataIntegratorTask::*)(
      MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
      &MassDataIntegratorTask::integrateToTicIntensity),
    // Fundamental for signals that travel across QThread instances...
    Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // When the read task finishes, it sends a signal that we trap to go on with
  // the plot widget creation stuff.

  // Since we allocated the QThread dynamically we need to be able to destroy
  // it later, so make the connection.
  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          this,
          [this,
           thread_p,
           mass_data_integrator_p,
           parent_plottable_p,
           mass_data_integrator_task_p]() {
            // Do not forget that we have to delete the MassDataIntegratorTask
            // allocated instance.
            mass_data_integrator_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
            this->finishedIntegratingToTicIntensity(mass_data_integrator_p,
                                                    parent_plottable_p);
          });


  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_programWindow->getTaskMonitorWnd()->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->setMsRunIdText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->setTaskDescriptionText(
    "Integrating to mass spectrum");
  task_monitor_composite_widget_p->setProgressBarMinValue(0);

  // Make the connections

  // When the integrator task instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some seconds,
  // the monitor widget.

  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          task_monitor_composite_widget_p,
          &TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &MassDataIntegrator::cancelOperation);

  // We need to register the meta type for std::size_t because otherwise it
  // cannot be shipped though signals.

  qRegisterMetaType<std::size_t>("std::size_t");

  // Now make all the connections that will allow the integrator to provide
  // dynamic feedback to the user via the task monitor widget.
  task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
    mass_data_integrator_p);

  // qDebug() << "going to emit integrateToTicIntensitySignal with mass "
  //"data integrator:"
  //<< mass_data_integrator_p;

  emit integrateToTicIntensitySignal(mass_data_integrator_p);

  // We do not want to make signal/slot calls more than once. This is because
  // one user might well trigger more than one integration from this window to
  // a mass spectrum. Thus we do not want that *this window be still connected
  // to the specific mass_data_integrator_task_p when a new integration is
  // triggered. We want the signal/slot pairs to be contained to specific
  // objects. Each MassSpecTracePlotWnd::integrateToMz() call must be
  // contained to a this/mass_data_integrator_task_p specific signal/slot
  // pair.
  disconnect(this,
             // SIGNAL(integrateToTicIntensitySignal(
             // MsRunDataSetTreeMassDataIntegratorToTicInt *)),
             static_cast<void (BasePlotWnd::*)(
               MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
               &BasePlotWnd::integrateToTicIntensitySignal),
             mass_data_integrator_task_p,
             // SLOT(integrateToTicIntensity(
             // MsRunDataSetTreeMassDataIntegratorToTicInt *)));
             static_cast<void (MassDataIntegratorTask::*)(
               MsRunDataSetTreeMassDataIntegratorToTicInt *)>(
               &MassDataIntegratorTask::integrateToTicIntensity));
}
#endif


void
BasePlotWnd::integrateToTicIntensity(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  // qDebug().noquote() << "Integrating to mz with processing flow:"
  //<< processing_flow.toString();

  // Get the ms run data set that for the graph we are going to base the
  // integration on.
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();
  if(ms_run_data_set_csp == nullptr)
    qFatal("Cannot be that the pointer is nullptr.");

  // First prepare a vector of QualifiedMassSpectrumCstSPtr

  std::size_t qualified_mass_spectra_count = fillInQualifiedMassSpectraVector(
    ms_run_data_set_csp, qualified_mass_spectra_sp, processing_flow);

  if(!qualified_mass_spectra_count)
    return;

  // qDebug() << "The number of remaining mass spectra:"
  //<< qualified_mass_spectra_count;

  // Now start the actual integration work.

  // Allocate a mass data integrator to integrate the data.

  QualifiedMassSpectrumVectorMassDataIntegratorToTicInt
    *mass_data_integrator_p =
      new QualifiedMassSpectrumVectorMassDataIntegratorToTicInt(
        ms_run_data_set_csp, processing_flow, qualified_mass_spectra_sp);

  mass_data_integrator_p->setMaxThreadUseCount(mp_programWindow->getMaxThreadUseCount());

  // Ensure the mass data integrator messages are used.

  connect(
    mass_data_integrator_p,
    &QualifiedMassSpectrumVectorMassDataIntegrator::logTextToConsoleSignal,
    mp_programWindow,
    &ProgramWindow::logTextToConsole);

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(this,

          static_cast<void (BasePlotWnd::*)(
            QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
            &BasePlotWnd::integrateToTicIntensitySignal),

          mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
            &MassDataIntegratorTask::integrateToTicIntensity),

          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // Since we allocated the QThread dynamically we need to be able to
  // destroy it later, so make the connection.

  connect(mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),

          this,

          [this,
           thread_p,
           mass_data_integrator_p,
           parent_plottable_p,
           mass_data_integrator_task_p]() {
            // Do not forget that we have to delete the
            // MassDataIntegratorTask allocated instance.
            mass_data_integrator_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
            this->finishedIntegratingToTicIntensity(mass_data_integrator_p,
                                                    parent_plottable_p);
          });


  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_programWindow->mp_taskMonitorWnd->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->setMsRunIdText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->setTaskDescriptionText(
    "Integrating to TIC intensity from the table view");
  task_monitor_composite_widget_p->setProgressBarMinValue(0);

  // Make the connections

  // When the MsRunReadTask instance has finished working, it will send a signal
  // that we trap to finally destroy (after a time lag of some seconds, the
  // monitor widget.

  connect(mass_data_integrator_task_p,

          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),

          task_monitor_composite_widget_p,

          &TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::cancelOperation);

  task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
    mass_data_integrator_p);

  emit integrateToTicIntensitySignal(mass_data_integrator_p);

  // Because the emitter is the same ProgramWindow object, we need to disconnect
  // the signal so that at next opening of mass data file, it will not be called
  // twice.
  disconnect(this,

             static_cast<void (BasePlotWnd::*)(
               QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
               &BasePlotWnd::integrateToTicIntensitySignal),

             mass_data_integrator_task_p,

             static_cast<void (MassDataIntegratorTask::*)(
               QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
               &MassDataIntegratorTask::integrateToTicIntensity));
}


void
BasePlotWnd::finishedIntegratingToTicIntensity(
  QualifiedMassSpectrumVectorMassDataIntegrator *mass_data_integrator_p,
  QCPAbstractPlottable *parent_plottable_p)
{
  // This function is actually a slot that is called when the integration
  // is terminated in a QThread.

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // We get back the integrator that was made to work in another thread. Now
  // get the obtained data and work with them.

  // Store the MsRunDataSetSPtr for later use before deleting the integrattor.

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    mass_data_integrator_p->getMsRunDataSet();

  double tic_intensity =
    static_cast<QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *>(
      mass_data_integrator_p)
      ->getTicIntensity();

  // Also, do not forget to copy the processing flow from the integrator. This
  // processing flow will be set into the plot widget !

  ProcessingFlow processing_flow = mass_data_integrator_p->getProcessingFlow();

  // Finally, do not forget that we need to delete the integrator now that the
  // calculation is finished.
  delete mass_data_integrator_p;
  mass_data_integrator_p = nullptr;

  // At this point, we need to get a color for the plot. That color needs to
  // be that of the parent.

  QColor plot_color;

  // There are two situations: either the integration started from a plot
  // widget and the color is certainly obtainable via the pen of the plot.
  if(parent_plottable_p != nullptr)
    plot_color = parent_plottable_p->pen().color();
  else
    // Or the integration started at the MsRunDataSetTableView and the color
    // can only be obtained via the MsRunDataSet in the OpenMsRunDataSetsDlg.
    plot_color = mp_programWindow->getColorForMsRunDataSet(ms_run_data_set_csp);

  // Now print the sample name and the tic intensity.

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

  QString msg =
    QString("%1: TIC intensity: %2\n").arg(sample_name).arg(tic_intensity);

  mp_programWindow->logColoredTextToConsole(msg, plot_color);

  mp_programWindow->getPlotWidget(ms_run_data_set_csp, parent_plottable_p)
    ->newTicIntensityCalculated(tic_intensity);
}


//! Set the x-axis range lock between all the plot widgets.
void
BasePlotWnd::lockXRangeToggle(bool checked)
{
  m_isLockedXRange = checked;
}


//! Set the y-axis range lock between all the plot widgets.
void
BasePlotWnd::lockYRangeToggle(bool checked)
{
  m_isLockedYRange = checked;
}


void
BasePlotWnd::plotWidgetGotFocus(BasePlotCompositeWidget *composite_widget_p)
{
  // qDebug() << "Plot widget:" << composite_widget_p << "got focus.";

  // Get a list of all the composite plot widgets.

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  // qDebug() << "Number of trace plot composite widgets:" <<
  // widget_list.size();

  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      BasePlotCompositeWidget *iter_composite_widget_p = widget_list.at(iter);

      if(composite_widget_p == iter_composite_widget_p)
        {
          iter_composite_widget_p->redrawBackground(true);

          // qDebug() << "Asking focused background redraw for plot widget:"
          //<< iter_composite_widget_p;
        }
      else
        {
          iter_composite_widget_p->redrawBackground(false);

          // qDebug() << "Asking unfocused background redraw for plot widget:"
          //<< iter_composite_widget_p;
        }
    }
}


void
BasePlotWnd::plotCompositeWidgetDestructionRequested(
  BasePlotCompositeWidget *composite_widget_p)
{
  // qDebug() << "Starting the destruction process of the composite widget:"
  //<< composite_widget_p << "recursively:" << recursively;

  // We want to remove the graphs that sit in this plot widget. But we only go
  // down their children if recursively is true.

  mp_programWindow->plotCompositeWidgetDestructionRequested(composite_widget_p);

  // qDebug() << "Done deleting the composite widget.";
}


std::vector<BasePlotCompositeWidget *>
BasePlotWnd::allWidgets()
{
  // qDebug();

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  std::vector<BasePlotCompositeWidget *> pinned_down_widgets;

  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      BasePlotCompositeWidget *widget_p = widget_list.at(iter);
      pinned_down_widgets.push_back(widget_p);
    }

  return pinned_down_widgets;
}


std::vector<BasePlotCompositeWidget *>
BasePlotWnd::pinnedDownWidgets()
{
  // qDebug();

  // Iterate in the list of composite widgets currently in this window, and
  // check if they are pinned-down. If so add them to the vector.

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  std::vector<BasePlotCompositeWidget *> pinned_down_widgets;

  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      BasePlotCompositeWidget *widget_p = widget_list.at(iter);

      if(widget_p->isPinnedDown())
        pinned_down_widgets.push_back(widget_p);
    }

  return pinned_down_widgets;
}


BasePlotCompositeWidget *
BasePlotWnd::plotCompositeWidgetForPlottable(QCPAbstractPlottable *plottable_p)
{

  // We need to find the QCustomPlot first:

  QCustomPlot *q_custom_plot_p = plottable_p->parentPlot();

  // Now iterate in all the children of this window and search for each
  // composite widget, which one has a member matching q_custom_plot_p.

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      BasePlotCompositeWidget *composite_widget_p = widget_list.at(iter);

      if(composite_widget_p->mp_plotWidget == q_custom_plot_p)
        return composite_widget_p;
    }

  return nullptr;
}


void
BasePlotWnd::plotRangesChanged(const pappso::BasePlotContext &context)
{
  // qDebug();

  // Iterate in the list of composite widgets currently in this window, and
  // ask them to replot using the range if necessary.

  int which_plot_axis = 0;

  if(m_isLockedXRange)
    which_plot_axis |= static_cast<int>(pappso::PlotAxis::x_axis);

  if(m_isLockedYRange)
    which_plot_axis |= static_cast<int>(pappso::PlotAxis::y_axis);

  if(!which_plot_axis)
    return;

  QCPRange x_axis_range(context.xRange.lower, context.xRange.upper);
  QCPRange y_axis_range(context.yRange.lower, context.yRange.upper);

  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");
  std::vector<BaseTracePlotCompositeWidget *> pinned_down_widgets;
  for(int iter = 0; iter < widget_list.size(); ++iter)
    {
      BasePlotCompositeWidget *widget_p = widget_list.at(iter);

      if(QObject::sender() == dynamic_cast<QObject *>(widget_p))
        {
          // We do not want to ask that the emitter replots itself, which
          // would be nonsense.

          // qDebug() << "This one is the sender.";
          continue;
        }

      widget_p->mp_plotWidget->replotWithAxesRanges(
        x_axis_range,
        y_axis_range,
        static_cast<pappso::PlotAxis>(which_plot_axis));
    }
}


QFile *
BasePlotWnd::getAnalysisFilePtr()
{
  return mp_programWindow->getAnalysisFilePtr();
}


AnalysisPreferences *
BasePlotWnd::getAnalysisPreferences()
{
  return mp_programWindow->getAnalysisPreferences();
}


void
BasePlotWnd::recordAnalysisStanza(QString stanza, const QColor &color)
{
  mp_programWindow->recordAnalysisStanza(stanza, color);
}


void
BasePlotWnd::showWindow()
{
  activateWindow();
  raise();
  show();
}


std::size_t
BasePlotWnd::fillInQualifiedMassSpectraVector(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    &qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{

  // If the pointer to the vector is nullptr, we need to create the vector.

  if(qualified_mass_spectra_sp == nullptr)
    {
      qualified_mass_spectra_sp =
        std::make_shared<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>();
    }

  // If the vector is not empty, then we return its size without modification.

  if(qualified_mass_spectra_sp->size())
    return qualified_mass_spectra_sp->size();

  // Now, we know we can fill in the vector.

  // We get a processing flow from which we extract the innermost rt and dt
  // ranges. We use those ranges for selecting the most restricted set of
  // qualified mass spectra so as to speed up the integrations.

  double start_rt = std::numeric_limits<double>::infinity();
  double end_rt   = std::numeric_limits<double>::infinity();

  bool integration_rt = processing_flow.innermostRtRange(start_rt, end_rt);

  if(!integration_rt)
    qFatal("Programming error.");

  // std::size_t added_rt_mass_spectra =
  ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
    ->addDataSetQualMassSpectraInsideDtRtRange(
      start_rt, end_rt, *qualified_mass_spectra_sp, pappso::DataKind::rt);

  // qDebug() << "Added RT mass spectra:" << added_rt_mass_spectra;

  // Now see if we can remove some mass spectra on the basis of DT values.

  double start_dt = std::numeric_limits<double>::infinity();
  double end_dt   = std::numeric_limits<double>::infinity();

  bool integration_dt = processing_flow.innermostDtRange(start_dt, end_dt);

  if(integration_dt)
    {
      // qDebug() << "Detected DT integration";

      // std::size_t removed_mass_spectra =
      ms_run_data_set_csp->getMsRunDataSetTreeCstSPtr()
        ->removeDataSetQualMassSpectraOutsideDtRtRange(
          start_dt, end_dt, *qualified_mass_spectra_sp, pappso::DataKind::dt);

      // qDebug() << "Removed mass spectra on basis of DT:"
      //<< removed_mass_spectra;
    }

  return qualified_mass_spectra_sp->size();
}


void
BasePlotWnd::removeEmptyPlotWidgets()
{
  QList<BasePlotCompositeWidget *> widget_list =
    findChildren<BasePlotCompositeWidget *>("plotCompositeWidget");

  int widget_list_size = widget_list.size();

  if(!widget_list_size)
    return;

  int index = widget_list_size - 1;

  while(index > -1)
    {
      BasePlotCompositeWidget *iter_widget_p = widget_list.at(index);

      int plottable_count = iter_widget_p->getPlotWidget()->plottableCount();

      if(!plottable_count)
        plotCompositeWidgetDestructionRequested(iter_widget_p);

      --index;
    }
}


} // namespace minexpert

} // namespace msxps
