/* 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 <QPushButton>
#include <QTimer>
#include <QSettings>
#include <QMessageBox>


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


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

#include "MsXpS/libXpertMassGui/MassPeakShaperConfigWidget.hpp"

#include "ui_MassPeakShaperConfigWidget.h"

namespace MsXpS
{

namespace libXpertMassGui
{


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

\brief The MassPeakShaperConfigWidget class provides a widget for the
configuration of the mass peak shaping process.

This composite widget contains all the widgets and all the logic required to
fully configure the mass peak shaping process.
*/

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

\brief The graphical user interface definition.
*/

/*!
\variable MsXpS::libXpertMassGui::MassPeakShaperConfigWidget::mp_parent

\brief The parent widget.
*/

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

\brief The reference m/z value used for peak width calculations.

This reference m/z value is used to compute the full width at half maximum of
the shaped peak on the basis of the resolution and backwards.
*/

/*!
\variable MsXpS::libXpertMassGui::MassPeakShaperConfigWidget::mp_config

\brief The configuration of the pass peak shaping process.
*/

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

\brief The intensity value that is used to normalize the peak height..
*/

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

\brief The timer object used to clear messages to the messages line edit box.
*/


/*!
\brief Constructs a MassPeakShaperConfigWidget instance.

\list
\li \a parent_p: the parent widget.
\li \a config: the configuration of the mass peak shaping process
\endlist
*/
MassPeakShaperConfigWidget::MassPeakShaperConfigWidget(
  const libXpertMassCore::MassPeakShaperConfig &config, QWidget *parent_p)
  : QWidget(parent_p),
    mp_ui(new Ui::MassPeakShaperConfigWidget),
    mp_config(new libXpertMassCore::MassPeakShaperConfig(config, this))
{
  // qDebug() << "Creating MassPeakShaperConfigWidget";

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

  setupWidget();
}

/*!
\brief Destructs this MassPeakShaperConfigWidget instance.
*/
MassPeakShaperConfigWidget::~MassPeakShaperConfigWidget()
{
  // The timer should not try to access a deleted widget!
  qDebug() << "Stopping the timer.";
  if(msp_msgTimer.get())
    msp_msgTimer.reset();

  writeSettings(MsXpS::libXpertMassCore::Utils::configSettingsFilePath);

  delete mp_ui;
}

/*!
\brief Writes the settings of this widget to \a config_settings_file_path for
later restoration.
*/
void
MassPeakShaperConfigWidget::writeSettings(
  const QString &config_settings_file_path)
{
  QSettings settings(config_settings_file_path, QSettings::IniFormat);

  settings.beginGroup("MassPeakShaperConfigWidget");

  settings.setValue(
    "massPeakShapeType",
    static_cast<int>(libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN));

  settings.setValue("pointCount", mp_ui->pointCountSpinBox->value());

  settings.setValue("resolution", mp_ui->resolutionSpinBox->value());

  settings.setValue("fwhm", mp_ui->fwhmDoubleSpinBox->value());

  settings.setValue("binSize", mp_ui->binSizeDoubleSpinBox->value());

  settings.setValue("binSizeGroupBoxState",
                    mp_ui->binSizeGroupBox->isChecked());

  settings.setValue("fmwhToBinSizeDivisor",
                    mp_ui->fmwhToBinSizeDivisorSpinBox->value());

  settings.endGroup();
}

/*!
\brief Reads the settings of this widget from \a config_settings_file_path.
*/
void
MassPeakShaperConfigWidget::readSettings(
  const QString &config_settings_file_path)
{
  // qDebug();

  QSettings settings(config_settings_file_path, QSettings::IniFormat);
  settings.beginGroup("MassPeakShaperConfigWidget");

  // By default we want a gaussian-type shape.
  int default_mass_peak_shape_type =
    static_cast<int>(libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN);
  int stored_mass_peak_shape_type =
    settings.value("massPeakShapeType", default_mass_peak_shape_type).toInt();

  mp_config->setMassPeakShapeType(
    static_cast<libXpertMassCore::Enums::MassPeakShapeType>(
      stored_mass_peak_shape_type));
  if(mp_config->getMassPeakShapeType() ==
     libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN)
    mp_ui->gaussianRadioButton->setChecked(true);
  else
    mp_ui->lorentzianRadioButton->setChecked(true);

  // Get the number of points used to craft the peak curve.
  mp_ui->pointCountSpinBox->setValue(settings.value("pointCount", 200).toInt());
  mp_config->setPointCount(mp_ui->pointCountSpinBox->value());

  mp_ui->resolutionSpinBox->setValue(
    settings.value("resolution", 40000).toInt());

  mp_ui->fwhmDoubleSpinBox->setValue(settings.value("fwhm", 0).toDouble());

  mp_ui->binSizeDoubleSpinBox->setValue(
    settings.value("binSize", 0).toDouble());

  mp_ui->binSizeGroupBox->setChecked(
    settings.value("binSizeGroupBoxState", 0).toInt());

  mp_ui->fmwhToBinSizeDivisorSpinBox->setValue(
    settings.value("fwhmToBinSizeDivisor", 6).toInt());

  settings.endGroup();
}

/*!
\brief Sets the reference m/z value to \a mz.
*/
void
MassPeakShaperConfigWidget::setReferencePeakMz(double mz)
{
  m_referencePeakMz = mz;

  mp_config->setReferencePeakMz(mz);
  mp_ui->referenceMzDoubleSpinBox->setValue(mz);
}

/*!
\brief Returns the reference m/z value.
*/
double
MassPeakShaperConfigWidget::getReferencePeakMz()
{
  m_referencePeakMz = mp_ui->referenceMzDoubleSpinBox->value();
  return m_referencePeakMz;
}

libXpertMassCore::MassPeakShaperConfig *
MassPeakShaperConfigWidget::getConfig()
{
  return mp_config;
}

/*!
\brief Sets up this widget.
*/
void
MassPeakShaperConfigWidget::setupWidget()
{
  mp_ui->setupUi(this);

  // Ranges for various numerical values (with defaults)
  readSettings(MsXpS::libXpertMassCore::Utils::configSettingsFilePath);

  mp_ui->resolutionSpinBox->setRange(0, 1000000);
  mp_ui->fwhmDoubleSpinBox->setRange(0, 10);
  mp_ui->fmwhToBinSizeDivisorSpinBox->setRange(1, 100);

  // 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("");
  });

  // All the tooltips

  QString tool_tip_string;

  tool_tip_string =
    "Number of points that will make the peak shape. {[\n]}"
    "Typically, an optimal value is in the 150 to 200 range";
  mp_ui->pointCountSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string =
    "This reference m/z value is used to compute the "
    "FWHM based on a resolving power value. And vice versa";
  mp_ui->referenceMzDoubleSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string = "Resolving power of the mass spectrometer";
  mp_ui->resolutionSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string =
    "Full width at half maximum (the width of the peak at 50 % height). This "
    "value relates to the resolving power of the instrument";
  mp_ui->fwhmDoubleSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string =
    "This divisor value is only used at the combination step. Each shaped "
    "isotopic cluster centroid is combined into the same final mass spectrum "
    "that represents the entire isotopic cluster. The highest this divisor "
    "setting, the greatest the number of bins in the final spectrum; and thus, "
    "the more the shape of the peak will resemble a pure Gaussian or "
    "Lorentzian curve. {[\n]}"

    "A good empirically determined value is 6 (when the number of points in "
    "the Gaussian | Lorentzian is 200). {[\n]}"

    "This somehow amounts to the idea that it takes at least 6 bins to make a "
    "nicely shaped peak of the configured FWHM.";
  mp_ui->fmwhToBinSizeDivisorSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string = "Uncheck this check box if no binning is required";
  mp_ui->binSizeLogicGroupBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string =
    "Phasing the bins to a given m/z range means that, when performing "
    "multiple simulations, that m/z range will be constant for each "
    "simulation.  This means that upon combination of the different simulated "
    "spectra, the binning will be optimal.";
  mp_ui->mzRangeGroupBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string = "Left-most m/z value in the simulated spectrum";
  mp_ui->mzRangeStartDoubleSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string = "Right-most m/z value in the simulated spectrum";
  mp_ui->mzRangeStopDoubleSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string = "Check the check box and enter the fixed bin size";
  mp_ui->binSizeGroupBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string =
    "Set this parameter to a larger value if the peaks seem to be spiky";
  mp_ui->binSizeDoubleSpinBox->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));

  tool_tip_string =
    "Check that all the entered parameters make a valid configuration";
  mp_ui->checkParametersPushButton->setToolTip(
    libXpertMassCore::Utils::stanzify(tool_tip_string, 50));


  connect(mp_ui->mzRangeStartDoubleSpinBox,
          &QDoubleSpinBox::valueChanged,
          this,
          &MassPeakShaperConfigWidget::computeReferencePeakMzValue);

  connect(mp_ui->mzRangeStopDoubleSpinBox,
          &QDoubleSpinBox::valueChanged,
          this,
          &MassPeakShaperConfigWidget::computeReferencePeakMzValue);

  connect(mp_ui->referenceMzDoubleSpinBox,
          &QDoubleSpinBox::valueChanged,
          this,
          &MassPeakShaperConfigWidget::referencePeakMzValueChanged);

  connect(mp_ui->fmwhToBinSizeDivisorSpinBox,
          &QSpinBox::valueChanged,
          this,
          &MassPeakShaperConfigWidget::fwhmBinSizeDivisorValueChanged);

  connect(mp_ui->resolutionSpinBox,
          &QSpinBox::editingFinished,
          this,
          &MassPeakShaperConfigWidget::resolutionEditingFinished);

  connect(mp_ui->fwhmDoubleSpinBox,
          &QSpinBox::editingFinished,
          this,
          &MassPeakShaperConfigWidget::fwhmEditingFinished);

  connect(mp_ui->pointCountSpinBox,
          &QSpinBox::editingFinished,
          this,
          &MassPeakShaperConfigWidget::pointCountEditingFinished);

  connect(mp_ui->gaussianRadioButton,
          &QRadioButton::toggled,
          this,
          &MassPeakShaperConfigWidget::gaussianRadioButtonToggled);

  connect(mp_ui->lorentzianRadioButton,
          &QRadioButton::toggled,
          this,
          &MassPeakShaperConfigWidget::lorentzianRadioButtonToggled);

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

/*!
\brief Determines the bin configuration from the data entered by the user.

Returns true if the calculations succeeded, false otherwise.
*/
bool
MassPeakShaperConfigWidget::processBinSizeConfig()
{
  // We want to understand what the user is expecting: create bins or not,
  // and do they want to have the bin size configured manually or do they
  // expect that the bin size be determined automatically.

  double bin_size = 0;
  bool no_bins    = !mp_ui->binSizeLogicGroupBox->isChecked();

  // First off, report the usage of bins. If no bins are to be calculated,
  // then it is not necessary to bother with the fwhm / bin_size factor (see
  // below).
  if(no_bins)
    {
      // qDebug() << "The user requests no bins.";

      mp_config->setWithBins(false);
      mp_config->setBinSize(0);

      return true;
    }
  else
    {
      mp_config->setWithBins(true);

      // qDebug() << "The user requests bins, either automatic of manual.";

      bool set_bin_size_manually = mp_ui->binSizeGroupBox->isChecked();

      if(set_bin_size_manually)
        {
          // We are checking the manual settings configuration.

          // qDebug() << "Configuring the bin size by direct setting.";

          mp_config->setBinSizeFixed(true);

          // Since the bin size is set manually, let the config object know that
          // it does not need to perform any computation.
          mp_config->setBinSizeDivisor(1);

          // Since the user checked the group box and they did not check the "no
          // bins" checkbox, then that means that they want to
          // actually set the bin size.

          bin_size = mp_ui->binSizeDoubleSpinBox->value();

          if(!bin_size)
            {
              qDebug() << "The bin size cannot be 0.";
              message("The bin size cannot be 0.");
              return false;
            }

          mp_config->setBinSize(bin_size);

          // qDebug() << "Bin size set manually:" << bin_size;
        }
      else
        {
          // qDebug() << "Configuring the bin size by calculation.";

          // The user did not elect to set the bin size manually, we can
          // compute it and update the value in its spin box.

          // The mass peak shaper will compute the bin size simply by dividing
          // FWHM by that factor. The idea is that it takes roughly that value
          // data points to craft a peak that has that FWHM.
          int divisor = mp_ui->fmwhToBinSizeDivisorSpinBox->value();

          if(!divisor)
            {
              qDebug() << "The FWHM / bin size divisor cannot be 0.";
              message(
                "The FWHM / bin size divisor cannot be 0. Please, fix it.");

              return false;
            }

          // That divisor will be accounted for in the config when
          // asked to compute the bin size = fwhm / divisor. An empirically good
          // value is 6.
          mp_config->setBinSizeDivisor(divisor);

          mp_config->setBinSizeFixed(false);

          bool ok = false;

          double bin_size = mp_config->binSize(&ok);

          if(!ok)
            {
              message("Could not compute the bin size.");
              qDebug() << "Could not compute the bin size.";
              return false;
            }

          mp_ui->binSizeDoubleSpinBox->setValue(bin_size);

          // qDebug() << "The configuration has computed bin size:" << bin_size;
        }
    }

  return true;
}

/*!
\brief Signals that the user has finished entering the resolving power value.
*/
void
MassPeakShaperConfigWidget::resolutionEditingFinished()
{
  double resolution = mp_ui->resolutionSpinBox->value();

  if(!resolution)
    {
      // Tell the user to set a valid FWHM value, then.
      message("Will use the FWHM. Please, set a valid FWHM value");

      return;
    }

  // At this point we know that resolution contains a proper value.

  mp_config->setResolution(resolution);

  // We can compute the FWHM using the resolution only if the reference peaks's
  // m/z value has been set.

  if(mp_config->getReferencePeakMz() <= 0)
    {
      message("Please fill-in the reference peak's m/z value.");

      return;
    }

  QString msg;

  msg += "Will try to use the resolution... ";

  message(msg);

  bool ok = false;

  double fwhm = mp_config->fwhm(&ok);

  if(!ok)
    {
      msg += "Failed. Check the parameters.";
      message(msg);
      return;
    }

  mp_ui->fwhmDoubleSpinBox->setValue(fwhm);

  // Check if we have enough of data to actually compute the bin size (or not
  // depending on the user's requirements).

  ok = processBinSizeConfig();

  if(ok && mp_config->isWithBins())
    {
      QString msg = QString("Could compute the bin size: %1")
                      .arg(mp_config->getBinSize(), 0, 'f', 10);

      message(msg);

      // Since we used the resolution as the starting point, show that fact.
      mp_ui->resolutionBasedRadioButton->setChecked(true);
    }

  return;
}

/*!
\brief Signals that the user has finished entering the FWHM value.

The FWHM is the full width at half maximum, that is the width of the peak at its
half-height. This value is a direct reflection of the resolving power of the
mass spectrometer.
*/
void
MassPeakShaperConfigWidget::fwhmEditingFinished()
{
  double fwhm = mp_ui->fwhmDoubleSpinBox->value();

  if(!fwhm)
    {
      // Tell the user to set a valid resolution value, then.
      message("Will use the resolution. Please, set a valid resolution value");
      return;
    }

  // At this point we know that fwhm contains a proper value.

  mp_config->setFwhm(fwhm);

  // We can compute the resolution using FWHM only if the reference peaks's m/z
  // value has been set.

  if(mp_config->getReferencePeakMz() <= 0)
    {
      message("Please fill-in the reference peak's m/z value.");

      return;
    }

  QString msg;

  msg += "Will use the FWHM... ";

  message(msg);

  bool ok = false;

  double resolution = mp_config->resolution(&ok);

  // This is a peculiar situation, because from here we MUST be able to
  // compute the resolution, because all that is required is the FWHM, that we
  // have, and the reference peak centroid that SHOULD be there also. So, make
  // the error fatal.

  if(!ok)
    qFatal(
      "Programming error. At this point we should be able to compute the "
      "resolution.");

  mp_ui->resolutionSpinBox->setValue(resolution);

  // Check if we have enough of data to actually compute the bin size (or not
  // depending on the user's requirements).

  ok = processBinSizeConfig();

  if(ok && mp_config->isWithBins())
    {
      QString msg = QString("Could compute the bin size: %1")
                      .arg(mp_config->getBinSize(), 0, 'f', 10);

      // Since we used the FWHM as the starting point, show that fact.
      mp_ui->fwhmBasedRadioButton->setChecked(true);

      message(msg);
    }

  return;
}

/*!
\brief Signals that the user has finished editing the count of points needed to
shape the peak.
*/
void
MassPeakShaperConfigWidget::pointCountEditingFinished()
{
  // The points have changed, we'll use that value to compute the m/z step
  // between two consecutive points in the shaped peak.

  // All we need is either FWHM or resolution to advance the configuration.
  // Just check what we have.

  int point_count = mp_ui->pointCountSpinBox->value();

  if(point_count < 5)
    {
      message("The number of points to craft the shape is too small.");
      return;
    }

  mp_config->setPointCount(point_count);

  // At this point try to perform some calculations.

  double fwhm    = mp_ui->fwhmDoubleSpinBox->value();
  int resolution = mp_ui->resolutionSpinBox->value();

  if(fwhm)
    fwhmEditingFinished();
  else if(resolution)
    resolutionEditingFinished();

  // The bin size configuration is handled by the functions above.

  // That's all we can do.
}

/*!
\brief Signals that the user edited the reference m/z value with text \a text.
*/
void
MassPeakShaperConfigWidget::referencePeakMzValueChanged(double value)
{
  if(value <= 0)
    {
      message("Please fix the reference peak's m/z value.");
      return;
    }

  mp_config->setReferencePeakMz(value);
}

void
MassPeakShaperConfigWidget::computeReferencePeakMzValue(double value)
{
  Q_UNUSED(value)

  // We update the referencePeakMzValue as the middle value of
  // the mz range.

  double mz_range_start = mp_ui->mzRangeStartDoubleSpinBox->value();
  double m_range_stop   = mp_ui->mzRangeStopDoubleSpinBox->value();

  mz_range_start = std::min(mz_range_start, m_range_stop);
  m_range_stop   = std::max(mz_range_start, m_range_stop);

  if(!m_range_stop)
    return;

  double middle_mz_range_value =
    mz_range_start + 0.5 * (m_range_stop - mz_range_start);

  mp_ui->referenceMzDoubleSpinBox->setValue(middle_mz_range_value);
}

/*!
\brief Signals that the value by which the FWHM value should be divided is now
\a value.

The \a value is the number by which FWHM should be divided to yield the actual
bin size.
*/
void
MassPeakShaperConfigWidget::fwhmBinSizeDivisorValueChanged(
  [[maybe_unused]] int value)
{
  // Recalculate all the bin size stuff.
  processBinSizeConfig();
}

/*!
\brief Signals that the peak shape type has changed.

If \a checked is true, then the type is
libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN, else it is
libXpertMassCore::Enums::MassPeakShapeType::LORENTZIAN.
*/
void
MassPeakShaperConfigWidget::gaussianRadioButtonToggled(bool checked)
{
  if(checked)
    mp_config->setMassPeakShapeType(
      libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN);
  else
    mp_config->setMassPeakShapeType(
      libXpertMassCore::Enums::MassPeakShapeType::LORENTZIAN);
}

/*!
\brief Signals that the peak shape type has changed.

If \a checked is true, then the type is
libXpertMassCore::Enums::MassPeakShapeType::LORENTZIAN, else it is
libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN.
*/
void
MassPeakShaperConfigWidget::lorentzianRadioButtonToggled(bool checked)
{
  if(checked)
    mp_config->setMassPeakShapeType(
      libXpertMassCore::Enums::MassPeakShapeType::LORENTZIAN);
  else
    mp_config->setMassPeakShapeType(
      libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN);
}

/*!
\brief Writes \a message to the message line edit widget.

The message is erase after \a timeout in milliseconds.
*/
void
MassPeakShaperConfigWidget::message(const QString &message, int timeout)
{
  mp_ui->messageLineEdit->setText(message);
  msp_msgTimer->stop();
  msp_msgTimer->setInterval(timeout);
  msp_msgTimer->start();
}

/*!
\brief Returns the resolving power spin box widget.
*/
QSpinBox *
MassPeakShaperConfigWidget::getResolutionSpinBox()
{
  return mp_ui->resolutionSpinBox;
}

/*!
\brief Returns the FWHM spin box widget.
*/
QDoubleSpinBox *
MassPeakShaperConfigWidget::getFwhmDoubleSpinBox()
{
  return mp_ui->fwhmDoubleSpinBox;
}

/*!
\brief Returns the FWHM to bin size divisor spin box widget.
*/
QSpinBox *
MassPeakShaperConfigWidget::getFmwhToBinSizeDivisorSpinBox()
{
  return mp_ui->fmwhToBinSizeDivisorSpinBox;
}

/*!
\brief Returns the shape point count spin box widget.
*/
QSpinBox *
MassPeakShaperConfigWidget::getPointCountSpinBox()
{
  return mp_ui->pointCountSpinBox;
}

/*!
\brief Returns the Gaussian shape type radio button widget.
*/
QRadioButton *
MassPeakShaperConfigWidget::getGaussianRadioButton()
{
  return mp_ui->gaussianRadioButton;
}

/*!
\brief Returns the Lorentzian shape type radio button widget.
*/
QRadioButton *
MassPeakShaperConfigWidget::getLorentzianRadioButton()
{
  return mp_ui->lorentzianRadioButton;
}

void
MassPeakShaperConfigWidget::setParameters(
  double reference_peak_mz,
  int point_count,
  libXpertMassCore::Enums::MassPeakShapeType mass_peak_shape_type,
  double resolution,
  double fwhm,
  int bin_size_divisor,
  bool should_create_bins)
{
  mp_ui->referenceMzDoubleSpinBox->setValue(reference_peak_mz);

  mp_ui->pointCountSpinBox->setValue(point_count);
  if(mass_peak_shape_type ==
     libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN)
    mp_ui->gaussianRadioButton->setChecked(true);
  else
    mp_ui->gaussianRadioButton->setChecked(false);

  if(resolution)
    {
      mp_ui->resolutionSpinBox->setValue(resolution);
      mp_ui->resolutionBasedRadioButton->setChecked(true);
    }
  else
    {
      if(fwhm)
        {
          mp_ui->fwhmDoubleSpinBox->setValue(fwhm);
          mp_ui->fwhmBasedRadioButton->setChecked(true);
        }
      else
        {
          // Default: 40000 resolution
          mp_ui->resolutionSpinBox->setValue(40000);
          mp_ui->resolutionBasedRadioButton->setChecked(true);
          message("Set resolution to default value 40000");
        }
    }

  if(!bin_size_divisor)
    bin_size_divisor = 1;

  mp_ui->fmwhToBinSizeDivisorSpinBox->setValue(bin_size_divisor);

  mp_ui->binSizeLogicGroupBox->setChecked(should_create_bins);
}

void
MassPeakShaperConfigWidget::setBinSizeManually(double bin_size,
                                               bool should_create_bins)
{
  mp_ui->binSizeLogicGroupBox->setChecked(should_create_bins);

  mp_ui->binSizeDoubleSpinBox->setValue(bin_size);
}

/*!
\brief Checks that all the parameters are consistent and correct.
Returns true is the check succeeded, false otherwise.

\sa checkParameters()
*/
bool
MassPeakShaperConfigWidget::checkParameters()
{
  libXpertMassCore::ErrorList error_list;
  return checkParameters(&error_list);
}

/*!
\overload
\brief Checks that all the parameters are consistent and correct.

If errors occurs, they are added to \a error_list_p.

Returns true is the check succeeded, false otherwise.

\sa checkParameters()
*/
bool
MassPeakShaperConfigWidget::checkParameters(
  libXpertMassCore::ErrorList *error_list_p)
{
  int initial_error_count = 0;
  if(error_list_p)
    initial_error_count = error_list_p->size();

  QString msg;

  mp_config->reset();

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

  /////// The reference peak m/z value (for FWHM calculations) ////////

  m_referencePeakMz = mp_ui->referenceMzDoubleSpinBox->value();

  if(m_referencePeakMz <= 0)
    {
      msg = "Please fill in the reference peak m/z value.";
      message(msg);
      if(error_list_p != nullptr)
        error_list_p->push_back(msg);
      qDebug() << msg;
    }

  mp_config->setReferencePeakMz(m_referencePeakMz);

  // We will need this value for computations below.
  int point_count = mp_ui->pointCountSpinBox->value();

  if(point_count < 3)
    {
      msg = "The number of points allowed to craft the shape is too small.";
      message(msg);
      if(error_list_p != nullptr)
        error_list_p->push_back(msg);
      qDebug() << msg;
    }
  else
    mp_config->setPointCount(point_count);
  // qDebug() << "The set point count:" << mp_config->getPointCount();

  // Get to know if we want gaussian or lorentzian shapes.
  if(mp_ui->gaussianRadioButton->isChecked())
    mp_config->setMassPeakShapeType(
      libXpertMassCore::Enums::MassPeakShapeType::GAUSSIAN);
  else
    mp_config->setMassPeakShapeType(
      libXpertMassCore::Enums::MassPeakShapeType::LORENTZIAN);

  // Now check the resolution/fwhm data that are competing one another.

  double fwhm    = mp_ui->fwhmDoubleSpinBox->value();
  int resolution = mp_ui->resolutionSpinBox->value();

  // Both values cannot be naught.
  if(!resolution && !fwhm)
    {
      msg = "Please, fix the Resolution / FWHM value (one or the other).";
      message(msg);
      if(error_list_p != nullptr)
        error_list_p->push_back(msg);
      qDebug() << msg;

      // By default we favor the resolving power-based calculation.
      mp_ui->resolutionBasedRadioButton->setChecked(true);
    }

  if(resolution)
    {
      // We will use the resolution
      mp_config->setResolution(resolution);

      // We have to compute the FWHM value because that is a fundamental
      // parameter for the peak shaping. We use the most intense peak
      // centroid's mz value for that.

      bool ok = false;

      // With all the data successfully tested above, we must be able to
      // compute the FWHM. The config will store the computed value.
      double fwhm = mp_config->fwhm(&ok);

      if(!ok)
        {
          msg = "Failed to compute FWHM starting from resolution.";
          message(msg);
          if(error_list_p != nullptr)
            error_list_p->push_back(msg);
          qDebug() << msg;

          // By default we favor the resolving power-based calculation.
          mp_ui->resolutionBasedRadioButton->setChecked(true);
        }

      mp_ui->fwhmDoubleSpinBox->setValue(fwhm);

      if(mp_config->getMassPeakWidthLogic() ==
         libXpertMassCore::Enums::MassPeakWidthLogic::RESOLUTION)
        mp_ui->resolutionBasedRadioButton->setChecked(true);
      else if(mp_config->getMassPeakWidthLogic() ==
              libXpertMassCore::Enums::MassPeakWidthLogic::FWHM)
        mp_ui->fwhmBasedRadioButton->setChecked(true);
    }
  // End of
  // if(resolution)
  else
    {
      // We will use the FWHM
      mp_config->setFwhm(fwhm);

      // We all the data above, we should be able to compute the resolution
      // and set it to the config.

      bool ok    = false;
      resolution = mp_config->resolution(&ok);

      if(!ok)
        {
          msg = "Failed to compute the resolution.";
          message(msg);
          if(error_list_p != nullptr)
            error_list_p->push_back(msg);
          qDebug() << msg;

          // By default we favor the resolving power-based calculation.
          mp_ui->resolutionBasedRadioButton->setChecked(true);
        }

      mp_ui->resolutionSpinBox->setValue(resolution);

      if(mp_config->getMassPeakWidthLogic() ==
         libXpertMassCore::Enums::MassPeakWidthLogic::RESOLUTION)
        mp_ui->resolutionBasedRadioButton->setChecked(true);
      else if(mp_config->getMassPeakWidthLogic() ==
              libXpertMassCore::Enums::MassPeakWidthLogic::FWHM)
        mp_ui->fwhmBasedRadioButton->setChecked(true);
    }
  // End of
  // ! if(resolution), that is use FWHM

  //////////////////// THE BIN SIZE /////////////////////

  // qDebug() << "Now processing the bin size configuration.";

  if(!processBinSizeConfig())
    {
      msg = "Failed to compute the bin size.";
      message(msg);
      if(error_list_p != nullptr)
        error_list_p->push_back(msg);
      qDebug() << msg;
    }

  // Craft a string describing the whole set of params as we have been
  // working on them in the configuration instance.

  QString mass_peak_shaper_config_text = mp_config->toString();

  qDebug().noquote() << "Configuration:" << mass_peak_shaper_config_text;

  libXpertMassCore::ErrorList error_list;

  if(!mp_config->resolve(error_list))
    {
      msg =
        QString("Failed to finally resolve the configuration with errors:\n%1")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list, "\n"));
      message(msg);
      if(error_list_p != nullptr)
        error_list_p->push_back(msg);
      qDebug() << msg;
    }

  if(error_list_p != nullptr && error_list_p->size() > initial_error_count)
    {
      QMessageBox::warning(
        this,
        "Mass peak shaper configuration - errors:",
        libXpertMassCore::Utils::joinErrorList(*error_list_p, "\n"),
        QMessageBox::Ok);

      return false;
    }
  // qDebug() << "Emitting signal with the configuration.";

  emit updatedMassPeakShaperConfigSignal(*mp_config);

  message("The parameters were validated successfully.");

  return true;
}

bool
MassPeakShaperConfigWidget::isReferencePeakMzKept() const
{
  return mp_ui->keepReferenceMassPushButton->isChecked();
}

QPair<double, double>
MassPeakShaperConfigWidget::getMzRangeValuePair() const
{
  if(mp_ui->mzRangeGroupBox->isChecked())
    {
      double mz_range_start = mp_ui->mzRangeStartDoubleSpinBox->value();
      double mz_range_stop  = mp_ui->mzRangeStopDoubleSpinBox->value();

      return QPair<double, double>(std::min(mz_range_start, mz_range_stop),
                                   std::max(mz_range_start, mz_range_stop));
    }

  return QPair<double, double>(0, 0);
}

const libXpertMassCore::MassPeakShaperConfig *
MassPeakShaperConfigWidget::getConfig() const
{
  return mp_config;
}

void
MassPeakShaperConfigWidget::registerJsConstructor(QJSEngine *engine)

{
  if(!engine)
    {
      qDebug()
        << "Cannot register MassPeakShaperConfigWidget class: engine is null";
      return;
    }

  // Register the meta object as a constructor

  QJSValue jsMetaObject =
    engine->newQMetaObject(&MassPeakShaperConfigWidget::staticMetaObject);
  engine->globalObject().setProperty("MassPeakShaperConfigWidget",
                                     jsMetaObject);
}


} // namespace libXpertMassGui

} // namespace MsXpS
