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


/////////////////////// StdLib includes
#include <cmath>
#include <algorithm>
#include <limits> // for std::numeric_limits


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

/////////////////////// pappsomspp includes
#include <pappsomspp/core/massspectrum/massspectrum.h>
#include <pappsomspp/core/processing/combiners/massspectrumpluscombiner.h>
#include <pappsomspp/core/processing/combiners/tracepluscombiner.h>
#include <pappsomspp/core/trace/trace.h>
#include <pappsomspp/core/processing/filters/filternormalizeintensities.h>


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/Utils.hpp"
#include "MsXpS/libXpertMassCore/MassDataCborMassSpectrumHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicClusterGenerator.hpp"
#include "MsXpS/libXpertMassCore/MassPeakShaper.hpp"
#include "MsXpS/libXpertMassCore/MassPeakShaperConfig.hpp"

#include "MsXpS/libXpertMassGui/IsotopicClusterShaperDlg.hpp"
#include "MsXpS/libXpertMassGui/ColorSelector.hpp"

#include "ui_IsotopicClusterShaperDlg.h"

namespace MsXpS
{

namespace libXpertMassGui
{


/*!
\class MsXpS::libXpertMassGui::IsotopicClusterShaperDlg
\inmodule libXpertMassGui
\ingroup XpertMassGuiMassCalculations
\inheaderfile IsotopicClusterShaperDlg.hpp

\brief The IsotopicClusterShaperDlg class provides a graphical user interface
for the full peak shaping process.

Each peak centroid in the isotopic cluster is shaped independently and all the
obtained shapes are combined into a single mass spectrum.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::mp_ui

\brief The graphical user interface definition.
*/

/*!
\variable \
MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::mp_massPeakShaperConfigWidget

\brief The widget where the mass peak shaping process configuration is done..
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::mp_programWindow

\brief The main program window.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_applicationName

\brief The name of the application.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_description

\brief The description of this IsotopicClusterShaperDlg dialog window instance.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_fileName

\brief The name of the file to which results might be written.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_mapTrace

\brief The map trace that is used to combine each individual peak shape into a
single mass spectrum.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_finalTrace

\brief The finale pappso::Trace in which to store the final mass spectrum.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_colorByteArray

\brief The byte array representation of the color with which the spectrum might
be traced.
*/

/*!
\variable \
MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_mzIntegrationParams

\brief The mass spectrum combination parameters (size of the bins, for example).
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_referencePeakMz

\brief The m/z value that is used as the reference peak when convertring full
with at half maximum values to and from resolving power values.
*/

/*!
\variable \
MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_normalizingIntensity

\brief The intensity value that the most intense peak in the cluster should
have.

All the other peaks intensities are normalized against this value.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_isotopicCluster

\brief The isotopic cluster in the form of a pappso::Trace instance.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_peakShapers

\brief The list of peak shapers used to shape the peak around each isotopic
cluster peak centroid.

Each peak centroid of the isotopic cluster will be handled by its own peak
shaper instance.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_config

\brief The peak shaping process configuration.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::msp_msgTimer

\brief The timer used to erase messages output to the user.
*/


/*!
\brief Constructs a IsotopicClusterShaperDlg instance.

\list
\li \a program_window_p: the program's main window.
\li \a applicationName: the name of the application, typically massXpert2 or
mineXpert2, for example.
\li \a description: the string describing what this dialog window is for (used
for the window title).
\endlist
*/
IsotopicClusterShaperDlg::IsotopicClusterShaperDlg(
  QWidget *program_window_p,
  const QString &applicationName,
  const QString &description)
  : QDialog(static_cast<QWidget *>(program_window_p)),
    mp_ui(new Ui::IsotopicClusterShaperDlg),
    mp_programWindow(program_window_p),
    m_applicationName(applicationName),
    m_description(description)
{

  if(!program_window_p)
    qFatal("Programming error. Program aborted.");

  setupDialog();

  // Update the window title because the window title element in m_ui might be
  // either erroneous or empty.
  setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description));
}

/*!
\brief Constructs a IsotopicClusterShaperDlg instance.

\list
\li \a program_window_p: the program's main window.
\li \a applicationName: the name of the application, typically massXpert2 or
mineXpert2, for example.
\li \a description: the string describing what this dialog window is for (used
for the window title).
\li \a isotopic_cluster_sp: the isotopic cluster that is to be processed in
order to shape each one of its peak centroids.
\endlist
*/
IsotopicClusterShaperDlg::IsotopicClusterShaperDlg(
  QWidget *program_window_p,
  const QString &applicationName,
  const QString &description,
  pappso::TraceCstSPtr isotopic_cluster_sp,
  int normalizing_intensity)
  : QDialog(static_cast<QWidget *>(program_window_p)),
    mp_ui(new Ui::IsotopicClusterShaperDlg),
    mp_programWindow(program_window_p),
    m_applicationName(applicationName),
    m_description(description)
{
  if(!program_window_p)
    qFatal("Programming error. Program aborted.");

  setupDialog();

  // Update the window title because the window title element in m_ui might be
  // either erroneous or empty.
  setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description));

  m_normalizingIntensity = normalizing_intensity;
  mp_ui->normalizingIntensitySpinBox->setValue(normalizing_intensity);

  // Logically if setting the normalizing intensity, then activate the group
  // box.
  mp_ui->normalizationGroupBox->setChecked(true);

  setIsotopiCluster(isotopic_cluster_sp);
}

/*!
\fn IsotopicClusterShaperDlg::closeEvent(QCloseEvent *event)
Upon closing of the dialog window (unused \a event), writes the settings
to the application configuration file.
*/
void
IsotopicClusterShaperDlg::closeEvent(QCloseEvent *event)
{
  Q_UNUSED(event)
  // qDebug();
  writeSettings(
    MsXpS::libXpertMassCore::Utils::configSettingsFilePath);
}

/*!
\brief Destructs this IsotopicClusterShaperDlg instance.
*/
IsotopicClusterShaperDlg::~IsotopicClusterShaperDlg()
{
  writeSettings(
    MsXpS::libXpertMassCore::Utils::configSettingsFilePath);
  delete mp_ui;
}

/*!
\brief Saves the settings of this dialog window to the application
configuration file (\a config_settings_file_path).

The saved configuration is read from the file to set back the dialog window in
the same status.
*/
void
IsotopicClusterShaperDlg::writeSettings(
  const QString &config_settings_file_path)
{
  // qDebug();

  // First save the config widget state.
  mp_massPeakShaperConfigWidget->writeSettings(config_settings_file_path);

  QSettings settings(config_settings_file_path, QSettings::IniFormat);

  settings.beginGroup("IsotopicClusterShaperDlg");

  settings.setValue("geometry", saveGeometry());
  settings.setValue("charge", mp_ui->ionChargeSpinBox->value());
  settings.setValue("splitter", mp_ui->splitter->saveState());

  settings.endGroup();
}

/*!
\brief Reads the settings of this dialog window from the application
configuration file (\a config_settings_file_path).

The configuration is read from the file to set back the dialog window in
the same status.
*/
void
IsotopicClusterShaperDlg::readSettings(const QString &config_settings_file_path)
{
  // qDebug();

  // First read the config widget state.
  mp_massPeakShaperConfigWidget->readSettings(config_settings_file_path);

  QSettings settings(config_settings_file_path, QSettings::IniFormat);

  settings.beginGroup("IsotopicClusterShaperDlg");

  restoreGeometry(settings.value("geometry").toByteArray());
  mp_ui->ionChargeSpinBox->setValue(settings.value("charge", 1).toInt());

  mp_ui->splitter->restoreState(settings.value("splitter").toByteArray());

  settings.endGroup();
}

/*!
\brief Sets up the dialog window.
*/
void
IsotopicClusterShaperDlg::setupDialog()
{
  mp_ui->setupUi(this);

  // We want to destroy the dialog when it is closed.
  setAttribute(Qt::WA_DeleteOnClose);

  // Setup the peak shaper configuration widget inside of the frame

  QVBoxLayout *v_box_layout_p = new QVBoxLayout(this);

  // Trasmit the config member object reference so that it can be modified by
  // the configuration widget.
  mp_massPeakShaperConfigWidget =
    new MassPeakShaperConfigWidget(m_config, this);
  v_box_layout_p->addWidget(mp_massPeakShaperConfigWidget);

  mp_ui->peakShaperConfigFrame->setLayout(v_box_layout_p);

  // Set the message timer to be singleShot. This time is used to erase the text
  // shown in the message line edit widget after 3 seconds.
  msp_msgTimer->setInterval(3000);
  msp_msgTimer->setSingleShot(true);
  connect(msp_msgTimer.get(), &QTimer::timeout, [this]() {
    mp_ui->messageLineEdit->setText("");
  });

  connect(mp_ui->normalizationGroupBox,
          &QGroupBox::toggled,
          this,
          &IsotopicClusterShaperDlg::normalizingGrouBoxToggled);

  connect(mp_ui->normalizingIntensitySpinBox,
          &QSpinBox::valueChanged,
          this,
          &IsotopicClusterShaperDlg::normalizingIntensityValueChanged);


  // Set the default color of the mass spectra trace upon mass spectrum
  // synthesis
  QColor color("black");
  // Now prepare the color in the form of a QByteArray
  QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly);
  stream << color;


  // The color button
  connect(mp_ui->colorSelectorPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterShaperDlg::traceColorPushButtonClicked);

  // The values above are actually set in readSettings (with default values if
  // missing in the config settings.)
  readSettings(
    MsXpS::libXpertMassCore::Utils::configSettingsFilePath);


  // Always start the dialog with the first page of the tab widget,
  // the input data page.
  mp_ui->tabWidget->setCurrentIndex(
    static_cast<int>(TabWidgetPage::INPUT_DATA));

  // Throughout of *this* dialog window, the normalization factor
  // for the peak shapes (Gaussian, specifically) is going to be 1.

  connect(mp_ui->importPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterShaperDlg::importFromText);

  connect(mp_ui->checkParametersPushButton,
          &QPushButton::clicked,
          this,
          qOverload<>(&IsotopicClusterShaperDlg::checkParameters));

  connect(mp_ui->runPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterShaperDlg::run);

  connect(mp_ui->outputFilePushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterShaperDlg::outputFileName);

  connect(mp_ui->displayMassSpectrumPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterShaperDlg::displayMassSpectrum);

  connect(mp_ui->copyMassSpectrumToClipboardPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterShaperDlg::copyMassSpectrumToClipboard);
}

/*!
\brief Returns a string with the mass spectrum name.

The mass spectrum is the result of the isotopic cluster shaping process.
*/
QString
IsotopicClusterShaperDlg::craftMassSpectrumName()
{
  // The user might have forgotten to insert a preferred name in the mass
  // spectrum file name edit widget. We thus craft one on the basis of the
  // centroid peaks and the current time.

  QString name = mp_ui->massSpectrumNameResultsLineEdit->text();

  if(name.isEmpty())
    {
      name =
        QString("%1-%2 @ %3")
          .arg(m_peakShapers.at(0)->getPeakCentroid().x)
          .arg(m_peakShapers.at(m_peakShapers.size() - 1)->getPeakCentroid().x)
          .arg(QTime::currentTime().toString("hh:mm:ss.zzz"));
    }
  else
    {
      name.append(" @ ");
      name.append(QTime::currentTime().toString("hh:mm:ss.zzz"));
    }

  // qDebug() << "Returning mass spectrum name:" << name;
  return name;
}

/*!
\brief Creates all the peak shapers required to process all the peak
centroids in the isotopic cluster.
*/
std::size_t
IsotopicClusterShaperDlg::fillInThePeakShapers()
{
  // qDebug();

  // Clear the peak shapers that might have been created in a previous run.
  m_peakShapers.clear();

  int charge = mp_ui->ionChargeSpinBox->value();
  // We compute the m/z value below, so z cannot be 0.
  if(charge <= 0)
    qFatal(
      "Programming error. By this time the charge should have been validated > "
      "0");

  for(auto data_point : m_isotopicCluster)
    {
      // We need to account for the charge. Note the division below.

      m_peakShapers.append(std::make_shared<libXpertMassCore::MassPeakShaper>(
        pappso::DataPoint(data_point.x / charge, data_point.y), m_config));
    }

  // qDebug() << "m_config:" << m_config.asText(800);

  message(
    QString("The number of peak centroids is: %1").arg(m_peakShapers.size()));

  return m_peakShapers.size();
}

/*!
\brief Checks that the configuration parameters are correct. Any encountered
error is appended to \a error_list.
*/
bool
IsotopicClusterShaperDlg::checkParameters(
  libXpertMassCore::ErrorList &error_list)
{
  qDebug();

  // The general idea is that a number of parameters are inter-dependent. We
  // need to check these parameters in a precise order because of these
  // inter-dependencies.

  // Remove all the text in the log text edit widget.
  mp_ui->logPlainTextEdit->clear();

  // Remove all text in the results text edit widget.
  mp_ui->resultPlainTextEdit->clear();

  QString msg;

  // Make sure we have some (mz,i) values to crunch.
  if(!m_isotopicCluster.size())
    {
      msg = "There are no centroid peaks to process.\n";
      message(msg);
      error_list.append(msg);
      qDebug() << msg;
      return false;
    }

  if(m_referencePeakMz <= 0)
    {
      msg =
        "The reference peak m/z value is not set. Please enter one m/z value.";
      message(msg);
      error_list.append(msg);
      qDebug() << msg;
      return false;
    }

  //////////////////// THE ION CHARGE /////////////////////

  int charge = mp_ui->ionChargeSpinBox->value();
  // m/z has charge at the denominator!
  if(charge <= 0)
    {
      msg = "The ion charge must be >= 1";
      message(msg, 6000);
      error_list.append(msg);
      qDebug() << msg;
      return false;
    }

  /////////////////// Ask the widget to perform the check ///////////////////

  libXpertMassCore::ErrorList widget_error_list;

  if(!mp_massPeakShaperConfigWidget->checkParameters(&error_list))
    {
      msg =
        QString("There were errors in the configuration:\n%1")
          .arg(libXpertMassCore::Utils::joinErrorList(widget_error_list, "\n"));
      message(msg, 100000);
      error_list.append(msg);
      qDebug() << msg;
      return false;
    }

  // No errors ? Then output the configuration.
  qDebug().noquote() << mp_massPeakShaperConfigWidget->getConfig();

  m_config.initialize(*mp_massPeakShaperConfigWidget->getConfig());

  QString text = m_config.toString();

  qDebug().noquote() << "The configuration:\n" << text;

  mp_ui->logPlainTextEdit->appendPlainText(text);

  // Finally do a resolve of all the config bits to something actually useful.
  libXpertMassCore::ErrorList resolve_error_list;

  if(!m_config.resolve(resolve_error_list))
    {
      msg = QString("Failed to finally resolve the configuration:\n%1")
              .arg(libXpertMassCore::Utils::joinErrorList(resolve_error_list,
                                                          "\n"));
      message(msg, 6000);
      error_list.append(msg);
      qDebug() << msg;
      return false;
    }

  // There is a crucial sanity check: if the user has asked that the mz range
  // be fixed, it is essential that the smallest/greatest m/z values encountered
  // in the generated shapes are contained in that range, otherwise crash in
  // the function that creates the bins.
  //
  // At the present time, we do not have the shapes already, but we have the
  // centroids, we can already check them.

  // Returns both first and second value = 0 if the
  // user did not set this setting.
  QPair<double, double> mz_range_value_pair =
    mp_massPeakShaperConfigWidget->getMzRangeValuePair();
  bool use_fixed_mz_range = (mz_range_value_pair.second != 0);

  if(use_fixed_mz_range)
    {
      if((m_isotopicCluster.front().x < mz_range_value_pair.first) ||
         (m_isotopicCluster.back().x > mz_range_value_pair.second))
        {
          QMessageBox::warning(
            0,
            tr("Peak shaper (Gaussian or Lorentzian)"),
            QString("\nThe generated shapes encompass an m/z range that is\n"
                    "outside of the configured fixed m/z range.\n\n"
                    "The suggested fixed m/z range values: [%1-%2].\n")
              .arg(m_isotopicCluster.front().x - 1, 0, 'f', 6)
              .arg(m_isotopicCluster.back().x + 1, 0, 'f', 6),
            QMessageBox::Ok);

          return false;
        }
    }

  // At this point, because we have set all the relevant values to the
  // m_config PeakShapeConfig, we can create the PeakShaper instances and
  // set to them the m_config.

  if(!fillInThePeakShapers())
    {
      QMessageBox::warning(
        0,
        tr("Peak shaper (Gaussian or Lorentzian)"),
        tr("Please, insert at least one (m/z i) pair in the following "
           "format:\n\n"
           "<number><separator><number>\n\n"
           "With <separator> being any non numerical character \n"
           "(space or non-'.' punctuation, for example).\n\n"
           "Once the data have been pasted in the edit widget, click the "
           "'Import from text' button above.\n"
           "Also, make sure that you fill-in the resolution or the FWHM "
           "value and the ion charge."),
        QMessageBox::Ok);

      return false;
    }

  mp_ui->logPlainTextEdit->appendPlainText(
    QString("Number of input peak centroids to process: %1.\n")
      .arg(m_peakShapers.size()));

  mp_ui->logPlainTextEdit->appendPlainText(m_config.toString());


  message("The configuration validated fine. Check the LOG tab for details.");

  // At this point we'll have some things to report to the LOG tab,
  // switch to it now.
  // mp_ui->tabWidget->setCurrentIndex(static_cast<int>(TabWidgetPage::LOG));

  // qDebug() << "Now returning true after having checked the parameters.";
  return true;
}

/*!
\brief Checks that the configuration parameters are correct.
\sa checkParameters(libXpertMassCore::ErrorList &error_list)
*/
bool
IsotopicClusterShaperDlg::checkParameters()
{
  libXpertMassCore::ErrorList error_list;
  return checkParameters(error_list);
}

/*!
\brief Set \a message to the message line edit widget with \a timeout.

At the end of the time out the message is erased.
*/
void
IsotopicClusterShaperDlg::message(const QString &message, int timeout)
{
  mp_ui->messageLineEdit->setText(message);
  msp_msgTimer->stop();
  msp_msgTimer->setInterval(timeout);
  msp_msgTimer->start();
}

/*!
\brief Runs the computations.
*/
void
IsotopicClusterShaperDlg::run()
{
  qDebug();

  libXpertMassCore::ErrorList error_list;

  if(!checkParameters(error_list))
    {
      QMessageBox::warning(
        0,
        tr("Isotopic cluster shaper"),
        QString("The parameters check failed with errors:\n%1")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list, "\n")),
        QMessageBox::Ok);

      return;
    }


  // Clear the map trace that will receive the results of the combinations.
  m_mapTrace.clear();

  // At this point all the data are set, we can start the computation on all
  // the various peak shapers.

  // At this point we need to think hard: When the user runs the shaper once
  // on all the centroids of an isotopic cluster and then displays the
  // spectrum, that spectrum is beautiful, because the bins have been computed
  // on the leftmost / rightmost m/z values of the whole set of sub-spectra
  // obtained for each isotopologue.

  // Good.

  // Now, if the user first runs a simulation, displays a nice spectrum, then
  // computes another simulation and the user wants to combine this second
  // isotopic cluster simulation spectrum into the nice first spectrum, there
  // the problem arises: because it is almost never the case that the new
  // simulation spectrum starts with the same m/z range values, the bins
  // (if computed, again) are going to be shifted from the previous ones
  // computed for the first mass spectrum. This reality has the effect that
  // the combined mass spectrum will have horrendous spikes on to of the
  // beautifully-shaped peaks of the first simulation spectrum.

  // To avoid this effect, one should define the m/z range values and stick
  // to them through all the mass spectra simulated for a given session
  // of simulations that will need to be combined into a single spectrum.
  // This is what the "Phase the bins to the m/z range" groupbox is for
  // in the peak shaper configuration widget.

  // Check this right now:

  double mz_range_start = std::numeric_limits<double>::max();
  double mz_range_stop  = std::numeric_limits<double>::min();

  // Returns both first and second value = 0 if the
  // user did not set this setting.
  QPair<double, double> mz_range_value_pair =
    mp_massPeakShaperConfigWidget->getMzRangeValuePair();
  bool use_fixed_mz_range = (mz_range_value_pair.second != 0);

  if(use_fixed_mz_range)
    {
      mz_range_start = mz_range_value_pair.first;
      mz_range_stop  = mz_range_value_pair.second;
    }

  // Hold the extreme m/z values after having computed the set of
  // isotopic peak shapes.
  double smallest_mz_value = std::numeric_limits<double>::max();
  double greatest_mz_value = std::numeric_limits<double>::min();

  // Now start the actual processing of the centroid shapes.
  int processed = 0;

  // Shape the peak around each centroid value.
  for(auto peak_shaper_sp : m_peakShapers)
    {
      if(!peak_shaper_sp->computePeakShape())
        {
          QMessageBox::warning(
            0,
            tr("Peak shaper (Gaussian or Lorentzian)"),
            QString("Failed to compute a Trace for peak shape at m/z %1.")
              .arg(peak_shaper_sp->getPeakCentroid().x, QMessageBox::Ok));

          return;
        }

      // Now that we have computed the full shape around the centroid, we'll
      // be able to extract the smallest and greatest mz values of the whole
      // shape. We will need these two bounds to craft the bins in the
      // MzIntegrationParams object. Note that we do this only if the
      // user has not set a m/z range to be fixed already.

      double mz_value;

      if(peak_shaper_sp->getTrace().size())
        {
          mz_value          = peak_shaper_sp->getTrace().front().x;
          smallest_mz_value = std::min(mz_value, smallest_mz_value);

          mz_value          = peak_shaper_sp->getTrace().back().x;
          greatest_mz_value = std::max(mz_value, greatest_mz_value);
        }

      ++processed;
    }

  // There is a crucial sanity check: if the user has asked that the mz range
  // be fixed, it is essential that the smallest/greatest m/z values
  // encountered in the generated shapes are contained in that range,
  // otherwise crash in the function that creates the bins.

  if(use_fixed_mz_range)
    {
      if((smallest_mz_value < mz_range_start) ||
         (greatest_mz_value > mz_range_stop))
        {
          QMessageBox::warning(
            0,
            tr("Peak shaper (Gaussian or Lorentzian)"),
            QString("\nThe generated shapes encompass an m/z range that is\n"
                    "outside of the configured fixed m/z range.\n\n"
                    "The suggested fixed m/z range values: [%1-%2].\n")
              .arg(smallest_mz_value, 0, 'f', 6)
              .arg(greatest_mz_value, 0, 'f', 6),
            QMessageBox::Ok);

          return;
        }
    }
  else
    {
      // We do no use a fixed m/z range, so we need to set the values
      // for later consumption.
      mz_range_start = smallest_mz_value;
      mz_range_stop  = greatest_mz_value;
    }

  // Now create the actual spectrum by combining all the shapes into a cluster
  // shape.

  if(m_config.isWithBins())
    {
      // Bins were requested.

      // There are two situations:
      // 1. The bin size is fixed.
      // 2. The bin size is dynamically calculated on the basis of the
      // resolution and of the bin size divisor.

      double bin_size = m_config.getBinSize();

      if(m_config.getBinSizeFixed())
        {
          // The bin size is fixed, easy situation.

          qDebug() << "The bin size is FIXED.";

          m_mzIntegrationParams.initialize(
            mz_range_start,
            mz_range_stop,
            pappso::MzIntegrationParams::BinningType::ARBITRARY,
            pappso::PrecisionFactory::getDaltonInstance(bin_size),
            /*binSizeDivisor, 1 = no-op*/ 1,
            /*decimalPlacesr*/ -1,
            true);
        }
      else
        {
          // The bin size is dynamically calculated.

          if(m_config.getMassPeakWidthLogic() ==
             libXpertMassCore::Enums::MassPeakWidthLogic::FWHM)
            {
              qDebug() << "The mass peak width logic is FWHM.";

              m_mzIntegrationParams.initialize(
                mz_range_start,
                mz_range_stop,
                pappso::MzIntegrationParams::BinningType::ARBITRARY,
                pappso::PrecisionFactory::getDaltonInstance(bin_size),
                1,
                -1,
                true);
            }
          else if(m_config.getMassPeakWidthLogic() ==
                  libXpertMassCore::Enums::MassPeakWidthLogic::RESOLUTION)
            {
              qDebug() << "The mass peak width logic is RESOLUTION.";
              m_mzIntegrationParams.initialize(
                mz_range_start,
                mz_range_stop,
                pappso::MzIntegrationParams::BinningType::ARBITRARY,
                pappso::PrecisionFactory::getResInstance(
                  m_config.getResolution()),
                m_config.getBinSizeDivisor(),
                -1,
                true);
            }
          else
            qFatal(
              "Programming error. At this point, the peak width logic should "
              "have been set.");
        }


      // Now compute the bins.
      std::vector<double> bins = m_mzIntegrationParams.createBins();

      // We will need to perform combinations, positive combinations.
      pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner;

      mass_spectrum_plus_combiner.setBins(bins);

      for(auto mass_peak_shaper_sp : m_peakShapers)
        {
          mass_spectrum_plus_combiner.combine(m_mapTrace,
                                              mass_peak_shaper_sp->getTrace());
        }
    }
  else
    {
      // No bins were required. We can combine simply:

      qDebug() << "NO BINS requested.";

      pappso::TracePlusCombiner trace_plus_combiner(-1 /*decimal_places*/);

      for(auto mass_peak_shaper_sp : m_peakShapers)
        {
          // The combiner does not handle any data point for which y = 0.
          // So the result trace does not have a single such point;
          trace_plus_combiner.combine(m_mapTrace,
                                      mass_peak_shaper_sp->getTrace());
        }
    }

  message(QString("Successfully processed %1 peak centroids").arg(processed));

  // This is actually where the normalization needs to be performed. The
  // user might have asked that the apex of the shape be at a given
  // intensity. This is actually true when the IsoSpec++-based calculations
  // have been performed, that is the peak centroids have the expected
  // intensities normalized to the expected value. But then we have binned
  // all the numerous peak centroids into bins that are way larger than the
  // mz_step above. This binning is crucial to have a final spectrum that
  // actually looks like a real one. But it comes with a drawback: the
  // intensities of the m/z bins are no more the one of the initial
  // centroids, they are way larger. So we need to normalize the obtained
  // trace.

  m_finalTrace.clear();

  if(mp_ui->normalizationGroupBox->isChecked())
    {
      m_normalizingIntensity = mp_ui->normalizingIntensitySpinBox->value();

      // qDebug() << "Now normalizing to  intensity = " <<
      // m_normalizingIntensity;

      pappso::Trace trace = m_mapTrace.toTrace();
      m_finalTrace        = trace.filter(
        pappso::FilterNormalizeIntensities(m_normalizingIntensity));

      // double max_int = normalized_trace.maxYDataPoint().y;
      // qDebug() << "After normalization max int:" << max_int;
    }
  else
    m_finalTrace = m_mapTrace.toTrace();

  mp_ui->resultPlainTextEdit->appendPlainText(m_finalTrace.toString());

  // Now switch to the page that contains these results:

  // Provide the title of the mass spectrum there for the user to have a last
  // opportunity to change it.

  mp_ui->massSpectrumNameResultsLineEdit->setText(
    mp_ui->massSpectrumTitleLineEdit->text());

  mp_ui->tabWidget->setCurrentIndex(static_cast<int>(TabWidgetPage::RESULTS));
}

/*!
\brief Provides the user with a dialog for selecting a file name where to
output the results of the computation.
*/
void
IsotopicClusterShaperDlg::outputFileName()
{
  m_fileName = QFileDialog::getSaveFileName(
    this, tr("Export to text file"), QDir::homePath(), tr("Any file type(*)"));
}

/*!
\brief Crafts a mass spectrum name and emits a signal to display the mass
spectrum.
*/
void
IsotopicClusterShaperDlg::displayMassSpectrum()
{
  // We want that the process going on from now on has an unambiguous
  // file/sample name, so we craft the name here.

  // Finally, publish the mass spectrum.

  qDebug() << "Going to write this trace:" << m_finalTrace.toString();

  QString title = craftMassSpectrumName();
  emit displayMassSpectrumSignal(
    title,
    m_colorByteArray,
    std::make_shared<const pappso::Trace>(m_finalTrace));
}

/*!
\brief Copies the mass spectrum to the clipboard.

The mass spectrum is the result of the combination of all the peak shapes into
a single trace.
*/
void
IsotopicClusterShaperDlg::copyMassSpectrumToClipboard()
{

  // Simply copy the results shown in the text edit widget to the clipboard.
  // We cannot use m_mapTrace for that because if normalization was asked for,
  // then we would not see that in m_mapTrace (normalization is local).

  QString trace_text = mp_ui->resultPlainTextEdit->toPlainText();

  if(trace_text.isEmpty())
    {
      message("The mass spectrum is empty.");
      return;
    }

  QClipboard *clipboard = QApplication::clipboard();
  clipboard->setText(trace_text);
}

/*!
\brief Selects a color for the mass spectrum trace to be plotted.
*/
void
IsotopicClusterShaperDlg::traceColorPushButtonClicked()
{

  // Allow (true) the user to select a color that has been chosen already.
  QColor color = ColorSelector::chooseColor(true);

  if(!color.isValid())
    return;

  // Store the color as a QByteArray in the member datum.
  QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly);
  stream << color;

  // Update the color of the button.
  QPushButton *colored_push_button = mp_ui->colorSelectorPushButton;
  colorizePushButton(colored_push_button, m_colorByteArray);
}

/*!
\brief Sets the \a isotopic_cluster_sp isotopic cluster that is the starting
material for the computations.
*/
void
IsotopicClusterShaperDlg::setIsotopiCluster(
  pappso::TraceCstSPtr isotopic_cluster_sp)
{
  // If this dialog window is reused, clear the data from a previous
  // calculation.
  m_isotopicCluster.clear();

  // Start by copying the centroids locally in the form of a Trace collecting
  // DataPoint objects.
  m_isotopicCluster.assign(isotopic_cluster_sp->begin(),
                           isotopic_cluster_sp->end());

  // Now create the text to display that in the text edit widget.
  QString result_as_text;

  // Convert the data into a string that can be displayed in the text edit
  // widget.

  // Reset the value to min so that we can test for new intensities below.
  double greatestIntensity = std::numeric_limits<double>::min();

  for(auto data_point : m_isotopicCluster)
    {
      result_as_text += QString("%1 %2\n")
                          .arg(data_point.x, 0, 'f', 30)
                          .arg(data_point.y, 0, 'f', 30);

      // Store the peak centroid that has the greatest intensity.
      // Used to compute the FWHM value starting from resolution. And
      // vice-versa.
      if(data_point.y > greatestIntensity)
        {
          m_referencePeakMz = data_point.x;
          greatestIntensity = data_point.y;
        }
    }

  // We did this verification, but if the user wants that this dialog window
  // be reused and has checked the mass peak shaper config widget's
  // keepReferenceMassPushButton (acts like a check button),
  // then, we are not going to modify neither the reference peak m/z value,
  // nor do we set the m/z range values.

  // qDebug() << "Reference (most intense) peak's m/z value:" <<
  // m_referencePeakMz;

  if(mp_massPeakShaperConfigWidget->isReferencePeakMzKept())
    {
      // The user does not want the reference peak m/z value to be updated.
    }
  else
    {
      m_config.setReferencePeakMz(m_referencePeakMz);
      mp_massPeakShaperConfigWidget->setReferencePeakMz(m_referencePeakMz);
    }

  mp_ui->inputDataPointsPlainTextEdit->setPlainText(result_as_text);
}

/*!
\brief Sets \a title as the mass spectrum title to the line edit widget.
*/
void
IsotopicClusterShaperDlg::setMassSpectrumTitle(const QString &title)
{
  mp_ui->massSpectrumTitleLineEdit->setText(title);
}

/*!
\brief Sets the \a color_byte_array color.

Colorizes the push button to provide a visual feedback of the color that was
selected.
*/
void
IsotopicClusterShaperDlg::setColorByteArray(QByteArray color_byte_array)
{
  m_colorByteArray = color_byte_array;

  // Update the color of the button.
  colorizePushButton(mp_ui->colorSelectorPushButton, color_byte_array);
}

/*!
\brief Set the input data tab the currently visible tab.
*/
void
IsotopicClusterShaperDlg::showTabWidgetInputDataPage()
{
  mp_ui->tabWidget->setCurrentIndex(
    static_cast<int>(TabWidgetPage::INPUT_DATA));
}

/*!
\brief Colorizes the \a push_button_p push button with the color encoded in
\a color_byte_array.
*/
void
IsotopicClusterShaperDlg::colorizePushButton(QPushButton *push_button_p,
                                             QByteArray color_byte_array)
{
  QColor color;
  QDataStream stream(&color_byte_array, QIODevice::ReadOnly);
  stream >> color;

  if(color.isValid())
    {
      QPalette palette = push_button_p->palette();
      palette.setColor(QPalette::Button, color);
      push_button_p->setAutoFillBackground(true);
      push_button_p->setPalette(palette);
      push_button_p->update();

      // Now prepare the color in the form of a QByteArray

      QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly);

      stream << color;
    }
}

/*!
\brief Imports the peak centrois from the text widget and make a
pappso::Trace out of them that is then set as the isotopic cluster.

\setIsotopiCluster()
*/
void
IsotopicClusterShaperDlg::importFromText()
{
  // We have peak centroids as in the text edit widget
  //
  // 59.032643588680002721957862377167 0.021811419157258506856811308694
  //
  // and we want to import them.

  QString peak_centroids_text =
    mp_ui->inputDataPointsPlainTextEdit->toPlainText();

  // Easily transfom that into a Trace
  pappso::Trace trace(peak_centroids_text);

  pappso::TraceSPtr temp_cluster_sp = std::make_shared<pappso::Trace>();

  for(auto datapoint : trace)
    {
      temp_cluster_sp->append(datapoint);
    }

  // Finally do the real import.
  setIsotopiCluster(temp_cluster_sp);

  message("The data were imported. Please check the import results.");
}

/*!
\brief Signals that the normalize intensity value has changed.

The new value is set to the member datum m_normalizingIntensity.
*/
void
IsotopicClusterShaperDlg::normalizingIntensityValueChanged()
{
  m_normalizingIntensity = mp_ui->normalizingIntensitySpinBox->value();
}

/*!
\brief Signals that the the normalization group box was toggled.

If the group box is checked, the normalizing intensity is read from the spin
box widget and set to the member datum m_normalizingIntensity. Otherwise, the
member datum m_normalizingIntensity is set to
std::numeric_limits<double>::min().
*/
void
IsotopicClusterShaperDlg::normalizingGrouBoxToggled(bool checked)
{
  if(!checked)
    m_normalizingIntensity = std::numeric_limits<double>::min();
  else
    m_normalizingIntensity = mp_ui->normalizingIntensitySpinBox->value();
}


} // namespace libXpertMassGui

} // namespace MsXpS
