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


/////////////////////// Qt includes
#include <QtGui>
#include <QtXml>
#include <QtGlobal>
#include <QMessageBox>


/////////////////////// libmass includes
#include <libXpertMass/IsotopicData.hpp>


/////////////////////// libmassgui includes
#include <libXpertMass/PolChemDefEntity.hpp>
#include <libXpertMass/Sequence.hpp>
#include <libXpertMassGui/IsotopicClusterGeneratorDlg.hpp>


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


namespace MsXpS
{

namespace massxpert
{


enum formulaActions
{
  ADD_FORMULA,
  REMOVE_FORMULA,
  CLEAR_WHOLE_MEMORY,
  SIMPLIFY_FORMULA
};

CalculatorWnd::CalculatorWnd(ProgramWindow *parent,
                             const QString &polChemDefFilePath,
                             const QString &applicationName,
                             const QString &description,
                             const QString &mono,
                             const QString &avg)
  : AbstractMainTaskWindow(
      parent, "CalculatorWnd", applicationName, 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<libXpertMass::PolChemDef>();

  if(polChemDefFilePath.isEmpty())
    {
      QMessageBox::warning(0,
                           tr("massXpert - 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";
  libXpertMass::Formula formula(formula_string);

  formula.validate(msp_polChemDef->getIsotopicDataCstSPtr());




  // 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(mp_parentWnd->configSettingsFilePath(),
                     QSettings::IniFormat);

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

  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(mp_parentWnd->configSettingsFilePath(),
                     QSettings::IniFormat);

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

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

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

  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), mp_parentWnd->configSettingsFilePath());

  mpa_chemPadDlg = new CalculatorChemPadDlg(this);

  if(!libXpertMass::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(ADD_FORMULA,
                                          tr("Add formula to memory"));

  m_ui.formulaActionsComboBox->insertItem(REMOVE_FORMULA,
                                          tr("Remove formula from memory"));

  m_ui.formulaActionsComboBox->insertItem(CLEAR_WHOLE_MEMORY,
                                          tr("Clear whole memory"));

  m_ui.formulaActionsComboBox->insertItem(SIMPLIFY_FORMULA,
                                          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;
}


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


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


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


bool
CalculatorWnd::populatePolChemDefComboBoxes()
{
  int iter = 0;
  QStringList stringList;

  for(iter = 0; iter < msp_polChemDef->monomerList().size(); ++iter)
    {
      libXpertMass::Monomer *monomer = msp_polChemDef->monomerList().at(iter);

      stringList << monomer->name();
    }

  m_ui.monomerComboBox->addItems(stringList);
  stringList.clear();

  for(iter = 0; iter < msp_polChemDef->modifList().size(); ++iter)
    {
      libXpertMass::Modif *modif = msp_polChemDef->modifList().at(iter);

      stringList << modif->name();
    }

  m_ui.modifComboBox->addItems(stringList);
  stringList.clear();

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

  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->name().isEmpty())
    {
      // See if the user has one chemPad.conf file in her
      // configuration directory.

      const ConfigSettings *configSettings = mp_parentWnd->configSettings();
      const ConfigSetting *configSetting =
        configSettings->value("dataDir", UserType::USER_TYPE_USER);

      filePath = configSetting->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->name());

  return ret;
}


void
CalculatorWnd::recordResult()
{
  QString recorder =
    tr("Done: mono: %2 -- avg: %3\n")
      .arg(m_tempPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_tempPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  mpa_recorderDlg->record(recorder);
}


void
CalculatorWnd::updateSeedResultLineEdits()
{
  m_ui.seedMonoMassLineEdit->setText(
    m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.seedAvgMassLineEdit->setText(
    m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  // The m_resultPonderable must always be consistent with the data in
  // the corresponding lineEdits.

  m_resultPonderable.setMono(m_tempPonderable.mono());
  m_resultPonderable.setAvg(m_tempPonderable.avg());

  m_ui.resultMonoMassLineEdit->setText(
    m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    m_resultPonderable.avgString(libXpertMass::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.

  // mono
  mass = m_ui.seedMonoMassLineEdit->text();
  if(!m_seedPonderable.setMono(mass))
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  mass = m_ui.seedAvgMassLineEdit->text();
  if(!m_seedPonderable.setAvg(mass))
    {
      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 =
    tr(
      "Added seed(mono: %1 ; avg: %2) \n"
      "to result(was mono:%3 ; avg: %4) \n")
      .arg(m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  // Make the calculations.
  m_resultPonderable.incrementMono(m_seedPonderable.mono());
  m_resultPonderable.incrementAvg(m_seedPonderable.avg());

  // Update the lineEdits.

  m_ui.resultMonoMassLineEdit->setText(
    m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    m_resultPonderable.avgString(libXpertMass::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.

  // mono
  mass = m_ui.seedMonoMassLineEdit->text();
  if(!m_seedPonderable.setMono(mass))
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  mass = m_ui.seedAvgMassLineEdit->text();
  if(!m_seedPonderable.setAvg(mass))
    {
      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 =
    tr(
      "Sent seed(mono: %1 ; avg: %2) \n"
      "to result(was mono:%3 ; avg: %4) \n")
      .arg(m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  m_resultPonderable.setMono(m_seedPonderable.mono());
  m_resultPonderable.setAvg(m_seedPonderable.avg());

  // Update the lineEdits.

  m_ui.resultMonoMassLineEdit->setText(
    m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    m_resultPonderable.avgString(libXpertMass::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.

  // mono
  mass = m_ui.seedMonoMassLineEdit->text();
  if(!m_seedPonderable.setMono(mass))
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  mass = m_ui.seedAvgMassLineEdit->text();
  if(!m_seedPonderable.setAvg(mass))
    {
      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 =
    tr(
      "Removed seed(mono: %1 ; avg: %2) \n"
      "from result(was mono:%3 ; avg: %4) \n")
      .arg(m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  // Make the calculations.
  m_resultPonderable.decrementMono(m_seedPonderable.mono());
  m_resultPonderable.decrementAvg(m_seedPonderable.avg());

  // Update the lineEdits.

  m_ui.resultMonoMassLineEdit->setText(
    m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.resultAvgMassLineEdit->setText(
    m_resultPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

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

void
CalculatorWnd::clearSeed()
{
  m_seedPonderable.clearMasses();

  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.

  // mono
  mass = m_ui.seedMonoMassLineEdit->text();
  if(!m_seedPonderable.setMono(mass))
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  mass = m_ui.seedAvgMassLineEdit->text();
  if(!m_seedPonderable.setAvg(mass))
    {
      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 =
    tr(
      "Added result(mono: %1 ; avg: %2) \n"
      "to seed(was mono:%3 ; avg: %4) \n")
      .arg(m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  // Make the calculations.
  m_seedPonderable.incrementMono(m_resultPonderable.mono());
  m_seedPonderable.incrementAvg(m_resultPonderable.avg());

  // Update the lineEdits.

  m_ui.seedMonoMassLineEdit->setText(
    m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.seedAvgMassLineEdit->setText(
    m_seedPonderable.avgString(libXpertMass::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 =
    tr(
      "Sent result(mono: %1 ; avg: %2) \n"
      "to seed(was mono:%3 ; avg: %4) \n")
      .arg(m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  m_seedPonderable.setMono(m_resultPonderable.mono());
  m_seedPonderable.setAvg(m_resultPonderable.avg());

  // Update the lineEdits.

  m_ui.seedMonoMassLineEdit->setText(
    m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.seedAvgMassLineEdit->setText(
    m_seedPonderable.avgString(libXpertMass::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.

  // mono
  mass = m_ui.seedMonoMassLineEdit->text();
  if(!m_seedPonderable.setMono(mass))
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Error with seed mono mass"),
        QMessageBox::Ok);
      return;
    }

  // avg
  mass = m_ui.seedAvgMassLineEdit->text();
  if(!m_seedPonderable.setAvg(mass))
    {
      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 =
    tr(
      "Removed result(mono: %1 ; avg: %2) \n"
      "from seed(was mono:%3 ; avg: %4) \n")
      .arg(m_resultPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_resultPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES))
      .arg(m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

  // Make the calculations.
  m_seedPonderable.decrementMono(m_resultPonderable.mono());
  m_seedPonderable.decrementAvg(m_resultPonderable.avg());

  // Update the lineEdits.

  m_ui.seedMonoMassLineEdit->setText(
    m_seedPonderable.monoString(libXpertMass::OLIGOMER_DEC_PLACES));
  m_ui.seedAvgMassLineEdit->setText(
    m_seedPonderable.avgString(libXpertMass::OLIGOMER_DEC_PLACES));

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

void
CalculatorWnd::clearResult()
{
  m_resultPonderable.clearMasses();

  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;

  libXpertMass::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_tempPonderable.setMono(m_resultPonderable.mono());
      m_tempPonderable.setAvg(m_resultPonderable.avg());

      // 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_seedPonderable.setMono(m_resultPonderable.mono());
      m_seedPonderable.setAvg(m_resultPonderable.avg());
    }
  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_tempPonderable.setMono(0);
          m_tempPonderable.setAvg(0);
        }
      else
        {
          mass = m_ui.seedMonoMassLineEdit->text();
          if(!m_tempPonderable.setMono(mass))
            {
              QMessageBox::warning(0,
                                   QString("%1 - %2")
                                     .arg(m_applicationName)
                                     .arg(m_windowDescription),
                                   tr("Error with seed avg mass"),
                                   QMessageBox::Ok);
              return;
            }

          mass = m_ui.seedAvgMassLineEdit->text();
          if(!m_tempPonderable.setAvg(mass))
            {
              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_seedPonderable.setMono(m_tempPonderable.mono());
      m_seedPonderable.setAvg(m_tempPonderable.avg());
    }

  // Prepare the recorder string.
  QString recorder =
    tr(
      "===================================================================\n"
      "Seed masses: %1 -- %2\n")
      .arg(m_seedPonderable.mono())
      .arg(m_seedPonderable.avg());

  // 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 FORMULA_HANDLING_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 FORMULA_HANDLING_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 FORMULA_HANDLING_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(m_formulaHandling & FORMULA_HANDLING_PRINT)
        {
          // Get the line edit widget of the comboBox.
          QLineEdit *lineEdit = m_ui.formulaComboBox->lineEdit();

          if(m_formulaHandling & FORMULA_HANDLING_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->name().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,
                         mp_parentWnd->configSettingsFilePath(),
                         msp_polChemDef,
                         m_applicationName,
                         "m/z ratio calculator",
                         &msp_polChemDef->ionizeRule(),
                         m_resultPonderable.mono(),
                         m_resultPonderable.avg());

  dlg->show();
}


void
CalculatorWnd::formulaActionsComboBoxActivated(int index)
{
  if(index == ADD_FORMULA)
    {
      //	qDebug() << __FILE__ << __LINE__ << "ADD_TO_MEMORY";
      addFormulaToMemory();
    }


  if(index == REMOVE_FORMULA)
    {
      //	qDebug() << __FILE__ << __LINE__ << "ADD_TO_MEMORY";
      removeFormulaFromMemory();
    }


  if(index == CLEAR_WHOLE_MEMORY)
    {
      //	qDebug() << __FILE__ << __LINE__ << "CLEAR_MEMORY";
      clearWholeMemory();
    }


  if(index == SIMPLIFY_FORMULA)
    {
      //	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;

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

  libXpertMass::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;
    }

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

  // First calculate the masses for the formula. These masses
  // become member data of the Modif.
  if(!fake.calculateMasses())
    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;

  if(!fake.accountMasses(&temp_mono, &temp_avg, 1))
    return -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_tempPonderable.rmono() += temp_mono * count;
     m_tempPonderable.ravg() += 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..
  libXpertMass::Monomer monomer(msp_polChemDef, "NOT_SET");

  int index = -1;
  index     = libXpertMass::Monomer::isNameInList(
    text, msp_polChemDef->monomerList(), &monomer);

  // It cannot be that the text in the combobox does not correspond to
  // a known monomer.
  if(index == -1)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // Prepare the recorder string.

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

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

  if(!monomer.accountMasses(
       &m_tempPonderable.rmono(), &m_tempPonderable.ravg(), count))
    return -1;

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

  // Locate the reference modif and make a copy of it, as we may not
  // use itself, as the parsing operation do modify the object.
  libXpertMass::Modif modif(msp_polChemDef, "NOT_SET");
  int index = -1;

  index =
    libXpertMass::Modif::isNameInList(text, msp_polChemDef->modifList(), &modif);

  // It cannot be that the text in the combobox does not correspond to
  // a known modif.
  if(index == -1)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // Prepare the recorder string.

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

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

  if(!modif.accountMasses(
       &m_tempPonderable.rmono(), &m_tempPonderable.ravg(), count))
    return -1;

  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;


  libXpertMass::Sequence sequence(text);

  if(sequence.makeMonomerList(msp_polChemDef, false, 0) == -1)
    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(int iter = 0; iter < sequence.size(); ++iter)
    {
      const libXpertMass::Monomer *monomer = sequence.at(iter);
      Q_ASSERT(monomer);

      if(!monomer->accountMasses(
           &m_tempPonderable.rmono(), &m_tempPonderable.ravg(), count))
        return -1;
    }

  return 1;
}


void
CalculatorWnd::showRecorder(int state)
{
  if(state == Qt::Checked)
    {
      QSettings settings(mp_parentWnd->configSettingsFilePath(),
                         QSettings::IniFormat);
      mpa_recorderDlg->restoreGeometry(
        settings.value("CalculatorRecorderDlg/geometry").toByteArray());

      mpa_recorderDlg->show();
    }
  else
    {
      QSettings settings(mp_parentWnd->configSettingsFilePath(),
                         QSettings::IniFormat);
      settings.setValue("CalculatorRecorderDlg/geometry",
                        mpa_recorderDlg->saveGeometry());

      mpa_recorderDlg->hide();
    }
}

void
CalculatorWnd::showChemPad(int state)
{
  if(state == Qt::Checked)
    {
      QSettings settings(mp_parentWnd->configSettingsFilePath(),
                         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(mp_parentWnd->configSettingsFilePath(),
                         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(int val)
{
  m_formulaHandling = val;
}


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: that is C2H8O4N5P

  // To do the simplification we need the list of atoms of the
  // current polymer chemistry definition.

  libXpertMass::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+"));

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

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

  // Validate the formula by creating a fake modification object
  // with it and by accounting masses for that formula.

  libXpertMass::Modif fake(msp_polChemDef, "NOT_SET", text);
  if(!fake.calculateMasses())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to parse the formula."),
        QMessageBox::Ok);

      return QString("");
    }

  QString simplified = fake.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
