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


/////////////////////// Qt includes
#include <QMessageBox>
#include <QSettings>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/IsotopicData.hpp>
#include <MsXpS/libXpertMassCore/Sequence.hpp>


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/IsotopicClusterGeneratorDlg.hpp>
#include <MsXpS/libXpertMassGui/DecimalPlacesOptionsDlg.hpp>


/////////////////////// Local includes
#include "CalculatorWnd.hpp"
#include "ProgramWindow.hpp"
#include "Application.hpp"
#include "CalculatorRecorderDlg.hpp"
#include "MzCalculationDlg.hpp"
#include "../nongui/ConfigSetting.hpp"

namespace MsXpS
{
namespace MassXpert
{

CalculatorWnd::CalculatorWnd(ProgramWindow *parent,
                             const QString &polChemDefFilePath,
                             const QString &application_name,
                             const QString &description,
                             const QString &mono,
                             const QString &avg)
  : AbstractMainTaskWindow(
      parent, "CalculatorWnd", application_name, description)
{

  // qDebug() << "Seeding with mono:" << mono << "and avg:" << avg
  //<< "Polymer chemistry definition file:" << polChemDefFilePath;

  // Start by creating the polymer chemistry definition.

  msp_polChemDef = std::make_shared<libXpertMassCore::PolChemDef>();

  if(polChemDefFilePath.isEmpty())
    {
      QMessageBox::warning(0,
                           tr("MassXpert3 - Calculator"),
                           tr("Polymer chemistry definition filepath empty."),
                           QMessageBox::Ok);

      return;
    }

  msp_polChemDef->setXmlDataFilePath(polChemDefFilePath);

  if(!initialize())
    {
      QMessageBox::warning(0,
                           QString("MassXpert - %1").arg(m_wndTypeName),
                           tr("Failed to initialize the calculator window."),
                           QMessageBox::Ok);
    }


  QString formula_string = "-H2O";
  libXpertMassCore::Formula formula(formula_string);

  libXpertMassCore::ErrorList error_list;

  bool ok =
    formula.validate(msp_polChemDef->getIsotopicDataCstSPtr(), &error_list);

  if(!ok)
    {
      QString msg =
        QString(
          "The formula: %1 failed to validate successfully with errors: \n%2")
          .arg(formula_string)
          .arg(libXpertMassCore::Utils::joinErrorList(error_list));

      QMessageBox::warning(
        0, tr("MassXpert3 - Calculator"), msg, QMessageBox::Ok);
    }


  // If the strings mono and avg are non null, then set their values
  // to the seed masses.

  if(!mono.isNull() && !mono.isEmpty())
    {
      m_ui.seedMonoMassLineEdit->setText(mono);

      // qDebug() << "Set the mono mass to" << mono;
    }

  if(!avg.isNull() && !avg.isEmpty())
    {
      m_ui.seedAvgMassLineEdit->setText(avg);

      // qDebug() << "Set the avg mass to" << avg;
    }
}

CalculatorWnd::~CalculatorWnd()
{
}

void
CalculatorWnd::closeEvent(QCloseEvent *event)
{
  writeSettings();

  event->accept();
}

void
CalculatorWnd::readSettings()
{
  QSettings settings(
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    QSettings::IniFormat);

  QString section =
    QString("%1/%2").arg(m_wndTypeName).arg(msp_polChemDef->getName());

  settings.beginGroup(section);

  restoreGeometry(settings.value("geometry").toByteArray());
  m_ui.hSplitter->restoreState(settings.value("hSplitterSize").toByteArray());

  int count = settings.beginReadArray("formulas");

  for(int iter = 0; iter < count; ++iter)
    {
      settings.setArrayIndex(iter);

      QString index;
      index.setNum(iter);

      QString value = settings.value(index).toString();

      m_ui.formulaComboBox->insertItem(iter, value);

      // 	qDebug () << __FILE__ << __LINE__
      // 		  << "Read line:" << value << "and"
      // 		  << m_ui.formulaComboBox->itemText(iter);
    }

  settings.endArray();

  settings.endGroup();
}

void
CalculatorWnd::writeSettings()
{
  QSettings settings(
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    QSettings::IniFormat);

  QString section =
    QString("%1/%2").arg(m_wndTypeName).arg(msp_polChemDef->getName());

  settings.beginGroup(section);

  settings.setValue("geometry", saveGeometry());
  settings.setValue("hSplitterSize", m_ui.hSplitter->saveState());

  // Write all the formulas that sit in the combo box
  settings.beginWriteArray("formulas");

  int count = m_ui.formulaComboBox->count();

  for(int iter = 0; iter < count; ++iter)
    {
      settings.setArrayIndex(iter);

      QString index;
      index.setNum(iter);

      settings.setValue(index, m_ui.formulaComboBox->itemText(iter));

      //      qDebug () << __FILE__ << __LINE__
      //                << "Set line:" <<
      //                m_ui.formulaComboBox->itemText(iter);
    }

  settings.endArray();
  // Done writing all the formulas.

  settings.endGroup();

  // Force the writing of the geometry settings of the chemical pad
  // (the settings are specific for each polymer chemistry
  // definition). Look at the function to get it.

  showChemPad(Qt::Checked);
  showChemPad(Qt::Unchecked);
}

bool
CalculatorWnd::initialize()
{
  m_ui.setupUi(this);

  setWindowIcon(qApp->windowIcon());

  // Now we need to actually show the window title (that element is empty in
  // m_ui)
  setWindowTitle(
    QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription));

  setAttribute(Qt::WA_DeleteOnClose);
  statusBar()->setSizeGripEnabled(true);

  ////// Connection of the SIGNALS and SLOTS //////

  connect(
    m_ui.addToResultPushButton, SIGNAL(clicked()), this, SLOT(addToResult()));

  connect(
    m_ui.sendToResultPushButton, SIGNAL(clicked()), this, SLOT(sendToResult()));

  connect(m_ui.removeFromResultPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(removeFromResult()));

  connect(m_ui.clearSeedPushButton, SIGNAL(clicked()), this, SLOT(clearSeed()));

  connect(m_ui.addToSeedPushButton, SIGNAL(clicked()), this, SLOT(addToSeed()));

  connect(
    m_ui.sendToSeedPushButton, SIGNAL(clicked()), this, SLOT(sendToSeed()));

  connect(m_ui.removeFromSeedPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(removeFromSeed()));

  connect(
    m_ui.clearResultPushButton, SIGNAL(clicked()), this, SLOT(clearResult()));

  connect(m_ui.showRecorderCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(showRecorder(int)));

  connect(m_ui.showChemPadCheckBox,
          SIGNAL(stateChanged(int)),
          this,
          SLOT(showChemPad(int)));

  connect(m_ui.applyPushButton, SIGNAL(clicked()), this, SLOT(apply()));

  connect(m_ui.mzCalculationPushButton,
          &QPushButton::clicked,
          this,
          &CalculatorWnd::mzCalculation);

  connect(m_ui.formulaActionsComboBox,
          &QComboBox::activated,
          this,
          &CalculatorWnd::formulaActionsComboBoxActivated);

  connect(m_ui.isotopicPatternCalculationPushButton,
          &QPushButton::clicked,
          this,
          &CalculatorWnd::isotopicPatternCalculation);

  connect(m_ui.decimalPlacesOptionsPushButton,
          &QPushButton::clicked,
          this,
          &CalculatorWnd::decimalPlacesOptions);

  m_ui.formulaDoubleSpinBox->setRange(-1000000000, 1000000000);
  m_ui.monomerSpinBox->setRange(-1000000000, 1000000000);
  m_ui.modifSpinBox->setRange(-1000000000, 1000000000);
  m_ui.sequenceSpinBox->setRange(-1000000000, 1000000000);

  mpa_recorderDlg = new CalculatorRecorderDlg(
    static_cast<QWidget *>(this),
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath());

  mpa_chemPadDlg = new CalculatorChemPadDlg(this);

  if(!libXpertMassCore::PolChemDef::renderXmlPolChemDefFile(msp_polChemDef))
    return false;

  if(!populatePolChemDefComboBoxes())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to populate the polymer"
           " chemistry data comboboxes.\n"
           "The calculator will not work properly."),
        QMessageBox::Ok);

      return false;
    }

  // Populate the formulaActionsComboBox with the actions:


  m_ui.formulaActionsComboBox->insertItem(
    static_cast<std::underlying_type_t<FormulaComboAction>>(
      FormulaComboAction::ADD),
    tr("Add formula to memory"));

  m_ui.formulaActionsComboBox->insertItem(
    static_cast<std::underlying_type_t<FormulaComboAction>>(
      FormulaComboAction::REMOVE),
    tr("Remove formula from memory"));

  m_ui.formulaActionsComboBox->insertItem(
    static_cast<std::underlying_type_t<FormulaComboAction>>(
      FormulaComboAction::CLEAR),
    tr("Clear memory"));

  m_ui.formulaActionsComboBox->insertItem(
    static_cast<std::underlying_type_t<FormulaComboAction>>(
      FormulaComboAction::SIMPLIFY),
    tr("Simplify formula"));

  if(!setupChemicalPad())
    return false;

  updateWindowTitle();

  readSettings();

  // But we want that the formulaComboBox line edit widget be empty
  // even when formulas have been loaded. So clear the line edit
  // widget of the combo box.

  m_ui.formulaComboBox->lineEdit()->clear();

  show();

  return true;
}

libXpertMassCore::PolChemDefCstSPtr
CalculatorWnd::polChemDef()
{
  return msp_polChemDef;
}

void
CalculatorWnd::decimalPlacesOptions()
{
  libXpertMassGui::DecimalPlacesOptionsDlg *dlg =
    new libXpertMassGui::DecimalPlacesOptionsDlg(
      dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(), this);

  dlg->show();
}

QString
CalculatorWnd::polChemDefName()
{
  return msp_polChemDef->getName();
}

void
CalculatorWnd::updateWindowTitle()
{
  setWindowTitle(QString("%1 - %2 - %3[*]")
                   .arg(m_applicationName)
                   .arg(m_windowDescription)
                   .arg(msp_polChemDef->getName()));
}

bool
CalculatorWnd::populatePolChemDefComboBoxes()
{
  QStringList string_list;

  for(const libXpertMassCore::MonomerCstSPtr monomer_csp :
      msp_polChemDef->getMonomersCstRef())
    string_list << monomer_csp->getName();
  m_ui.monomerComboBox->addItems(string_list);

  string_list.clear();

  for(const libXpertMassCore::ModifCstSPtr modif_csp :
      msp_polChemDef->getModifsCstRef())
    string_list << modif_csp->getName();
  m_ui.modifComboBox->addItems(string_list);

  // Set the polymer chemistry definition type to its lineEdit.
  m_ui.polChemDefTypeLineEdit->setText(msp_polChemDef->getName());

  updateWindowTitle();

  return true;
}

bool
CalculatorWnd::setupChemicalPad()
{
  QString filePath;

  // Each polymer chemistry definition might have a chemical pad
  // configuration file associated to it(simply by being stored in
  // the directory of the polymer chemistry definition files).
  //
  // If a polymer chemistry definition file is loaded in the
  // calculator, try to get its corresponding chemPad.conf file(which
  // might not exist, as it is not compulsory to have one).
  //
  // Otherwise, look in the user's config dir.

  if(msp_polChemDef->getName().isEmpty())
    {
      // See if the user has one chemPad.conf file in her
      // configuration directory.

      ConfigSettingsSPtr config_settings_sp = mp_parentWnd->getConfigSettings();
      const ConfigSettingSPtr config_setting_sp =
        config_settings_sp->getConfigSetting("dataDir", UserType::USER);

      filePath = config_setting_sp->m_value.toString() + "/chemPad.conf";
    }
  else
    {
      // Get the directory path of the polymer chemistry definition
      // and construct a full pathname to the potentially existing
      // config file.

      filePath = msp_polChemDef->getXmlDataDirPath() + "/chemPad.conf";
    }

  QFile file(filePath);

  // qDebug() << __FILE__ << __LINE__
  // << "The expected chemical pad configuration file is: "
  // << filePath;

  // Note that failing to setup the chemical pad is nothing so serious
  // that we return false.
  if(!file.open(QFile::ReadOnly))
    {
      QMessageBox::information(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Chemical pad config file not found.\n"
           "The chemical pad will not work properly."),
        QMessageBox::Ok);
      return true;
    }

  bool ret = mpa_chemPadDlg->setup(filePath, msp_polChemDef->getName());

  return ret;
}

void
CalculatorWnd::recordResult()
{
  QString recorder =
    QString("Done: mono: %2 -- avg: %3\n")
      .arg(m_tempMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_tempAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  mpa_recorderDlg->record(recorder);
}

void
CalculatorWnd::updateSeedResultLineEdits()
{
  m_ui.seedMonoMassLineEdit->setText(
    QString::number(m_seedMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.seedAvgMassLineEdit->setText(
    QString::number(m_seedAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

  m_resultMono = m_tempMono;
  m_resultAvg  = m_tempAvg;

  m_ui.resultMonoMassLineEdit->setText(
    QString::number(m_resultMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    QString::number(m_resultAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
}

void
CalculatorWnd::recorderDlgClosed()
{
  m_ui.showRecorderCheckBox->setCheckState(Qt::Unchecked);
}

void
CalculatorWnd::chemPadDlgClosed()
{
  m_ui.showChemPadCheckBox->setCheckState(Qt::Unchecked);
}

////////////////////////////// SLOTS ///////////////////////////////
void
CalculatorWnd::addToResult()
{
  QString mass;

  // The data in the seed masses lineEdit widgets must be added to the
  // result masses.

  // Get the masses from the seed lineEdits and put these in their
  // respective ponderable.

  bool ok;

  // mono
  m_seedMono = m_ui.seedMonoMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  m_seedAvg = m_ui.seedAvgMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed avg mass"),
        QMessageBox::Ok);
      return;
    }

  // Prepare the recorder string.
  QString recorder =
    QString(
      "Added seed (mono: %1 ; avg: %2) \n"
      "to result (was mono:%3 ; avg: %4) \n")
      .arg(m_seedMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  // Make the calculations.
  m_resultMono += m_seedMono;
  m_resultAvg  += m_seedAvg;

  // Update the lineEdits.

  m_ui.resultMonoMassLineEdit->setText(
    QString::number(m_resultMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    QString::number(m_resultAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);
}

void
CalculatorWnd::sendToResult()
{
  QString mass;

  // The seed masses must be sent(no computation here) to the results
  // masses.

  // Get the masses from the seed lineEdits and put these in their
  // respective ponderable.

  bool ok;

  // mono
  m_seedMono = m_ui.seedMonoMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  m_seedAvg = m_ui.seedAvgMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed avg mass"),
        QMessageBox::Ok);
      return;
    }

  // Prepare the recorder string.
  QString recorder =
    QString(
      "Sent seed (mono: %1 ; avg: %2) \n"
      "to result (was mono:%3 ; avg: %4) \n")
      .arg(m_seedMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  // Make the calculations.
  m_resultMono = m_seedMono;
  m_resultAvg  = m_seedAvg;

  // Update the lineEdits.
  m_ui.resultMonoMassLineEdit->setText(
    QString::number(m_resultMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    QString::number(m_resultAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
}

void
CalculatorWnd::removeFromResult()
{
  QString mass;

  // The data in the seed masses lineEdit widgets must be removed from
  // the result masses.

  // Get the masses from the seed lineEdits and put these in their
  // respective ponderable.

  bool ok;

  // mono
  m_seedMono = m_ui.seedMonoMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  m_seedAvg = m_ui.seedAvgMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed avg mass"),
        QMessageBox::Ok);
      return;
    }

  // Prepare the recorder string.
  QString recorder =
    QString(
      "Removed seed (mono: %1 ; avg: %2) \n"
      "from result (was mono:%3 ; avg: %4) \n")
      .arg(m_seedMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  // Make the calculations.
  m_resultMono -= m_seedMono;
  m_resultAvg  -= m_seedAvg;

  // Update the lineEdits.
  m_ui.resultMonoMassLineEdit->setText(
    QString::number(m_resultMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    QString::number(m_resultAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);
}

void
CalculatorWnd::clearSeed()
{
  m_seedMono = 0.0;
  m_seedAvg  = 0.0;

  m_ui.seedMonoMassLineEdit->setText("");
  m_ui.seedAvgMassLineEdit->setText("");
}

void
CalculatorWnd::addToSeed()
{
  QString mass;

  // The data in the result masses lineEdit widgets must be added to
  // the seed masses.

  // Get the masses from the seed lineEdits and put these in their
  // respective ponderable.

  bool ok;

  // mono
  m_seedMono = m_ui.seedMonoMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  m_seedAvg = m_ui.seedAvgMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed avg mass"),
        QMessageBox::Ok);
      return;
    }

  // Prepare the recorder string.
  QString recorder =
    QString(
      "Added result(mono: %1 ; avg: %2) \n"
      "to seed(was mono:%3 ; avg: %4) \n")
      .arg(m_resultMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  // Make the calculations.
  m_seedMono += m_resultMono;
  m_seedAvg  += m_resultAvg;

  // Update the lineEdits.
  m_ui.seedMonoMassLineEdit->setText(
    QString::number(m_seedMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.seedAvgMassLineEdit->setText(
    QString::number(m_seedAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);
}

void
CalculatorWnd::sendToSeed()
{
  // The result masses must be sent(no computation here) to the seed
  // masses.

  // Prepare the recorder string.
  QString recorder =
    QString(
      "Sent result (mono: %1 ; avg: %2) \n"
      "to seed (was mono:%3 ; avg: %4) \n")
      .arg(m_resultMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  // Make the calculations.
  m_seedMono = m_resultMono;
  m_seedAvg  = m_resultAvg;

  // Update the lineEdits.
  m_ui.seedMonoMassLineEdit->setText(
    QString::number(m_seedMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.seedMonoMassLineEdit->setText(
    QString::number(m_seedAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
}

void
CalculatorWnd::removeFromSeed()
{
  QString mass;

  // The data in the result masses lineEdit widgets must be removed
  // from the seed masses.

  // Get the masses from the seed lineEdits and put these in their
  // respective ponderable.

  bool ok;

  // mono
  m_seedMono = m_ui.seedMonoMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  m_seedAvg = m_ui.seedAvgMassLineEdit->text().toDouble(&ok);

  if(!ok)
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed avg mass"),
        QMessageBox::Ok);
      return;
    }

  // Prepare the recorder string.
  QString recorder =
    QString(
      "Removed result (mono: %1 ; avg: %2) \n"
      "from seed (was mono:%3 ; avg: %4) \n")
      .arg(m_resultMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_resultAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  // Make the calculations.
  m_seedMono -= m_resultMono;
  m_seedAvg  -= m_resultAvg;

  // Update the lineEdits.

  m_ui.seedMonoMassLineEdit->setText(
    QString::number(m_seedMono, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));
  m_ui.seedAvgMassLineEdit->setText(
    QString::number(m_seedAvg, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES));

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);
}

void
CalculatorWnd::clearResult()
{
  m_resultMono = 0.0;
  m_resultAvg  = 0.0;

  m_ui.resultMonoMassLineEdit->setText("");
  m_ui.resultAvgMassLineEdit->setText("");
}

void
CalculatorWnd::apply(const QString &formula)
{
  // This function might be called from the chempad's buttons with the
  // corresponding formula.

  QString mass;

  bool calculationPerformed = false;
  int res                   = -1;
  bool ok;

  libXpertMassCore::IsotopicDataSPtr isotopic_data_sp =
    msp_polChemDef->getIsotopicDataSPtr();

  if(!isotopic_data_sp->size())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("No atom list is available"),
        QMessageBox::Ok);
      return;
    }

  // We have to iterate into all the relevant widgets and account
  // for the corresponding chemical entities.

  // The starting masses are either the seed masses or the result
  // masses. If the results masses lineEdits are empty, then we use
  // the seed masses. If not, then we prefer using the results masses
  // because this way the calculations are incremental, exactly as
  // when a desktop calculator is used.

  if(!m_ui.resultMonoMassLineEdit->text().isEmpty() ||
     !m_ui.resultAvgMassLineEdit->text().isEmpty())
    {
      // The result masses lineEdits contain something, so we use
      // these to perform the calculations.

      // We use the m_tempPonderable as a repository for the masses
      // actually used for the computation, be them seed or result:

      m_tempMono = m_resultMono;
      m_tempAvg  = m_resultAvg;

      // Store the result masses in the seed masses object, because
      // we'll need these values to put them in the seed lineEdits to
      // provide one level of undo.

      m_seedMono = m_resultMono;
      m_seedAvg  = m_resultAvg;
    }
  else
    {
      // The result masses are empty, so consider using the seed
      // masses instead.

      // We use the m_tempPonderable as a repository for the masses
      // actually used for the computation, be them seed or result:

      if(m_ui.seedMonoMassLineEdit->text().isEmpty() ||
         m_ui.seedAvgMassLineEdit->text().isEmpty())
        {
          // The seed masses are empty and the result masses
          // also. Thus we postulate the user just wants to start
          // fresh, that is seed masses are all 0.

          m_tempMono = 0.0;
          m_tempAvg  = 0.0;
        }
      else
        {
          m_tempMono = m_ui.seedMonoMassLineEdit->text().toDouble(&ok);

          if(!ok)
            {
              QMessageBox::warning(0,
                                   QString("%1 - %2")
                                     .arg(m_applicationName)
                                     .arg(m_windowDescription),
                                   tr("Error with seed mono mass"),
                                   QMessageBox::Ok);
              return;
            }

          m_tempAvg = m_ui.seedAvgMassLineEdit->text().toDouble(&ok);

          if(!ok)
            {
              QMessageBox::warning(0,
                                   QString("%1 - %2")
                                     .arg(m_applicationName)
                                     .arg(m_windowDescription),
                                   tr("Error with seed avg mass"),
                                   QMessageBox::Ok);
              return;
            }
        }

      // Store these same seed masses in the seed masses object,
      // because we'll need these values to put them in the seed
      // lineEdits to provide one level of undo.

      m_seedMono = m_tempMono;
      m_seedAvg  = m_tempAvg;
    }

  // Prepare the recorder string.
  QString recorder =
    tr(
      "===================================================================\n"
      "Seed masses: %1 -- %2\n")
      .arg(m_seedMono, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES)
      .arg(m_seedAvg, 0, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);


  // Finally we can start doing work with the chemical entities...

  // Formulae

  // If the 'formula' parameter contains a string, then two cases arise:

  // 1. If m_formulaHandling has the FormulaHandling::PRINT bit
  // set, then do not account the formula, but simply insert it in
  // the formula line edit widget.

  // 2. If m_formulaHandling has the FormulaHandling::WITH_SPACE
  // bit set, then also add a space to the formula (this is
  // interesting when sending a large number of forumulas, so that
  // each sent one is distinct from the next one.

  // 2. If m_formulaHandling has the FormulaHandling::IMMEDIATE
  // bit set, then account the formula immediately and return.

  // Both situations 1. and 2. are related to this function being
  //  called from one chemical pad button.

  if(!formula.isEmpty())
    {
      // There is a formula in the parameter... Check if we have to
      // evaluate it immediately or if we have to just insert it in
      // the current formula comboBox widget's item.

      if((bool)(m_formulaHandling & FormulaHandling::PRINT))
        {
          // Get the line edit widget of the comboBox.
          QLineEdit *lineEdit = m_ui.formulaComboBox->lineEdit();

          if((bool)(m_formulaHandling & FormulaHandling::WITH_SPACE))
            {
              lineEdit->insert(QString(" "));
              lineEdit->insert(formula);
              lineEdit->insert(QString(" "));
            }
          else
            {
              lineEdit->insert(formula);
            }

          return;
        }
      else
        {
          int res = accountFormula(formula);

          if(res == -1)
            QMessageBox::warning(
              0,
              QString("%1 - %2")
                .arg(m_applicationName)
                .arg(m_windowDescription),
              tr("Error accounting formula '%1'").arg(formula),
              QMessageBox::Ok);
          else if(res == 1)
            {
              recordResult();

              updateSeedResultLineEdits();
            }

          return;
        }
    }
  else
    {
      // No formula was put into the parameter in the call to this
      // function. Just do the whole business as usual.

      res = accountFormula();

      if(res == -1)
        QMessageBox::warning(
          0,
          QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
          tr("Error accounting formula"),
          QMessageBox::Ok);
      else if(res == 1)
        {
          recordResult();

          calculationPerformed = true;
        }
    }


  if(!msp_polChemDef->getName().isEmpty())
    {
      // A polymer chemistry definition was loaded, account for its
      // potential chemical entities.

      res = accountMonomer();

      if(res == -1)
        QMessageBox::warning(
          0,
          QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
          tr("Error accounting monomer"),
          QMessageBox::Ok);
      else if(res == 1)
        {
          recordResult();

          calculationPerformed = true;
        }


      res = accountModif();

      if(res == -1)
        QMessageBox::warning(
          0,
          QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
          tr("Error accounting modif"),
          QMessageBox::Ok);
      else if(res == 1)
        {
          recordResult();

          calculationPerformed = true;
        }


      res = accountSequence();

      if(res == -1)
        QMessageBox::warning(
          0,
          QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
          tr("Error accounting sequence"),
          QMessageBox::Ok);
      else if(res == 1)
        {
          recordResult();

          calculationPerformed = true;
        }
    }
  // End of
  // if (!msp_polChemDef->type().isEmpty())

  // Update the lineEdits only if at last one calculation was
  // performed.

  if(!calculationPerformed)
    return;

  updateSeedResultLineEdits();
}

void
CalculatorWnd::mzCalculation()
{
  // Call the constructor with mono/avg seeds.
  MzCalculationDlg *dlg = new MzCalculationDlg(
    this,
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    msp_polChemDef,
    m_applicationName,
    "m/z ratio calculator",
    msp_polChemDef->getIonizerCstRef(),
    m_resultMono,
    m_resultAvg);

  dlg->show();
}

void
CalculatorWnd::formulaActionsComboBoxActivated(int formula_combo_action)
{
  if(static_cast<FormulaComboAction>(formula_combo_action) ==
     FormulaComboAction::ADD)
    {
      //	qDebug() << __FILE__ << __LINE__ << "ADD_TO_MEMORY";
      addFormulaToMemory();
    }


  if(static_cast<FormulaComboAction>(formula_combo_action) ==
     FormulaComboAction::REMOVE)
    {
      //	qDebug() << __FILE__ << __LINE__ << "ADD_TO_MEMORY";
      removeFormulaFromMemory();
    }


  if(static_cast<FormulaComboAction>(formula_combo_action) ==
     FormulaComboAction::CLEAR)
    {
      //	qDebug() << __FILE__ << __LINE__ << "CLEAR_MEMORY";
      clearWholeMemory();
    }


  if(static_cast<FormulaComboAction>(formula_combo_action) ==
     FormulaComboAction::SIMPLIFY)
    {
      //	qDebug() << __FILE__ << __LINE__ << "SIMPLIFY_FORMULA";
      simplifyFormula();
    }
}

void
CalculatorWnd::isotopicPatternCalculation()
{
  libXpertMassGui::IsotopicClusterGeneratorDlg *dlg =
    new libXpertMassGui::IsotopicClusterGeneratorDlg(
      this, m_applicationName, "Isotopic cluster generator");

  dlg->show();
}

int
CalculatorWnd::accountFormula(const QString &formula, double times)
{
  QString formula_text;

  // Note how count is a double and not an int. This has
  // implications below.
  double count = 0;

  // This function might be called via the chemical pad, and thus has
  // to test existence of atom definitions. The 'apply()' function
  // does that test for when the calculation is triggered by clicking
  // on the apply button.

  libXpertMassCore::IsotopicDataSPtr isotopic_data_sp =
    msp_polChemDef->getIsotopicDataSPtr();

  if(!isotopic_data_sp->size())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("No atom list is available"),
        QMessageBox::Ok);
      return false;
    }

  if(formula.isEmpty())
    {
      // No string passed as parameter, get the formula from its
      // corresponding comboBox.
      formula_text = m_ui.formulaComboBox->currentText();

      if(formula_text.isEmpty())
        return 0;

      // We want to remove spaces from the text. This is useful so
      // the user might enter spaces in the formula to ease
      // re-reading the formula text (useful when entering complex
      // formula for saccharides, for example, in my own
      // experience).
      formula_text.remove(QRegularExpression("\\s+"));

      // Note that we might also have a title for the formula, which
      // is enclosed between `"' at the beginning of the line.

      formula_text.remove(QRegularExpression("\".*\""));

      // How many times should the formula be accounted for?
      count = m_ui.formulaDoubleSpinBox->value();

      if(count == 0)
        return 0;
    }
  else
    {
      formula_text = formula;

      // The times param tells us how many times should the formula be
      // accounted for.
      count = times;
    }

  libXpertMassCore::Modif fake(msp_polChemDef, "NOT_SET", formula_text);

  //  Upon construction,  the masses are calculated but there is not fatal.

  // So, calculate the masses for the formula so that we know everything is
  // correct. These masses become member data of the Modif. nullptr below will
  // simply trigger the search of the reference data in the polymer chemistry
  // definition member of the Modif instance.
  if(!fake.calculateMasses(/* Isotopic data*/ nullptr))
    return -1;

  // Prepare the recorder string.
  QString recorder =
    tr("Accounting formula: %1(%2 times)... ").arg(formula_text).arg(count);

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);

  // And now actually compute the compounding of the pre-calculated masses
  // with the count (which might be a double). Because it might be
  // a double, we have to first account the masses once, and then
  // multiply their result.

  double temp_mono = 0;
  double temp_avg  = 0;

  fake.accountMasses(&temp_mono, &temp_avg, 1);

  // Now that we know to what mass corresponds the formula_text,
  // we can multiply these by the factor that is actually the count.

  m_tempMono += temp_mono * count;
  m_tempAvg  += temp_avg * count;

  return 1;
}

int
CalculatorWnd::accountMonomer()
{
  QString text;

  // Get to see if there is a necessity to account for the displayed
  // monomer.

  int count = m_ui.monomerSpinBox->value();

  if(count == 0)
    return 0;

  text = m_ui.monomerComboBox->currentText();

  // Locate and make a copy of the reference monomer, that we may not
  // use itself, as the formula parsing operation do modify the
  // object..
  MsXpS::libXpertMassCore::MonomerCstSPtr monomer_csp =
    msp_polChemDef->getMonomerCstSPtrByName(text);

  // It cannot be that the text in the combobox does not correspond to
  // a known monomer.
  if(monomer_csp == nullptr)
    qFatal() << "Programming error. Monomer name not known to the polymer "
                "chemistry definition.";

  // Prepare the recorder string.
  QString recorder = tr("Accounting monomer: %1 (%2 times)... ")
                       .arg(monomer_csp->getName())
                       .arg(count);

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);

  monomer_csp->accountMasses(m_tempMono, m_tempAvg, count);

  return 1;
}

int
CalculatorWnd::accountModif()
{
  QString text;

  // Get to see if there is a necessity to account for the displayed
  // modif.

  int count = m_ui.modifSpinBox->value();

  if(count == 0)
    return 0;

  text = m_ui.modifComboBox->currentText();

  libXpertMassCore::ModifCstSPtr modif_csp =
    msp_polChemDef->getModifCstSPtrByName(text);

  // It cannot be that the text in the combobox does not correspond to
  // a known modif.
  if(modif_csp == nullptr)
    qFatal() << "Programming error. Modif name not known to the polymer "
                "chemistry definition.";

  // Prepare the recorder string.

  QString recorder = tr("Accounting modif: %1 (%2 times)... ")
                       .arg(modif_csp->getName())
                       .arg(count);

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);

  modif_csp->accountMasses(m_tempMono, m_tempAvg, count);

  return 1;
}

int
CalculatorWnd::accountSequence()
{
  QString text;

  // Get to see if there is a necessity to account for the displayed
  // sequence.

  int count = m_ui.sequenceSpinBox->value();

  if(count == 0)
    return 0;

  text = m_ui.sequenceLineEdit->text();

  if(text.isEmpty())
    return 0;

  libXpertMassCore::Sequence sequence(msp_polChemDef, text);

  if(!sequence.isValid())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        QString("%1 is an invalid sequence.").arg(text),
        QMessageBox::Ok);

      return -1;
    }

  // Prepare the recorder string.

  QString recorder =
    tr("Accounting sequence: %1(%2 times)... ").arg(text).arg(count);

  // And now append the new recorder string to the recorder textEdit.
  mpa_recorderDlg->record(recorder);

  for(const libXpertMassCore::MonomerSPtr &monomer_sp :
      sequence.getMonomersCstRef())
    monomer_sp->accountMasses(m_tempMono, m_tempAvg, count);

  return 1;
}

void
CalculatorWnd::showRecorder(int state)
{
  if(state == Qt::Checked)
    {
      QSettings settings(
        dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
        QSettings::IniFormat);
      mpa_recorderDlg->restoreGeometry(
        settings.value("CalculatorRecorderDlg/geometry").toByteArray());

      mpa_recorderDlg->show();
    }
  else
    {
      QSettings settings(
        dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
        QSettings::IniFormat);
      settings.setValue("CalculatorRecorderDlg/geometry",
                        mpa_recorderDlg->saveGeometry());

      mpa_recorderDlg->hide();
    }
}

void
CalculatorWnd::showChemPad(int state)
{
  if(state == Qt::Checked)
    {
      QSettings settings(
        dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
        QSettings::IniFormat);
      settings.beginGroup("CalculatorChemPadDlg");

      QString setting = QString("%1-%2").arg(polChemDefName()).arg("geometry");

      mpa_chemPadDlg->restoreGeometry(settings.value(setting).toByteArray());

      settings.endGroup();

      mpa_chemPadDlg->show();
    }
  else
    {
      QSettings settings(
        dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
        QSettings::IniFormat);
      settings.beginGroup("CalculatorChemPadDlg");

      QString setting = QString("%1-%2").arg(polChemDefName()).arg("geometry");

      settings.setValue(setting, mpa_chemPadDlg->saveGeometry());

      settings.endGroup();

      mpa_chemPadDlg->hide();
    }
}

void
CalculatorWnd::setFormulaHandling(FormulaHandling formula_handling)
{
  m_formulaHandling = formula_handling;
}

void
CalculatorWnd::addFormulaToMemory()
{
  // Get the current text from the comboBox's lineEdit widget.

  QString text = m_ui.formulaComboBox->currentText();

  //    qDebug() << __FILE__ << __LINE__ << "text:" << text;


  if(text.isEmpty())
    return;

  m_ui.formulaComboBox->addItem(text);
}

void
CalculatorWnd::removeFormulaFromMemory()
{
  int currentIndex = m_ui.formulaComboBox->currentIndex();

  if(currentIndex != -1)
    m_ui.formulaComboBox->removeItem(currentIndex);
}

void
CalculatorWnd::clearWholeMemory()
{
  m_ui.formulaComboBox->clear();
}

QString
CalculatorWnd::simplifyFormula()
{
  // Imagine we do lots of formula stuff, and we want to refactor a
  // given series of formulas into one single formula like this:

  // We have +CH3 -C +C2H6O4N5P

  // We want to have the result of this chemical "reaction": that is,
  // C2H8O4N5P

  // To do the simplification we need the container of Isotope instances of the
  // current polymer chemistry definition.

  libXpertMassCore::IsotopicDataSPtr isotopic_data_sp =
    msp_polChemDef->getIsotopicDataSPtr();

  if(!isotopic_data_sp->size())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("No atom list is available"),
        QMessageBox::Ok);
      return QString("");
    }

  QString text = m_ui.formulaComboBox->currentText();

  if(text.isEmpty())
    return QString("");

  // We want to remove spaces from the text. This is useful so the
  // user might enter spaces in the formula to ease re-reading the
  // formula text (useful when entering complex formula for
  // saccharides, for example, in my own experience).
  text.remove(QRegularExpression("\\s+"));

  libXpertMassCore::Formula formula(text);

  libXpertMassCore::ErrorList error_list;

  if(!formula.validate(msp_polChemDef->getIsotopicDataCstSPtr(),
                       /*store*/ true,
                       /*reset*/ true,
                       &error_list))
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        QString("Failed to parse the formula with errors:\n %1.")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list)),
        QMessageBox::Ok);

      return QString("");
    }

  QString simplified = formula.elementalComposition();

  //    qDebug() << "simplified:" << simplified;

  if(!simplified.isEmpty())
    {
      // Store the formula to the memory, so that the user can check
      // things.

      addFormulaToMemory();

      // Set the new simplified formula to the comboBox.
      m_ui.formulaComboBox->lineEdit()->setText(simplified);
    }

  return simplified;
}

} // namespace MassXpert
} // namespace MsXpS
