/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   

   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


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

/////////////////////// Local includes
#include "calculatorWnd.hpp"
#include "application.hpp"
#include "calculatorRecorderDlg.hpp"
#include "polChemDefEntity.hpp"
#include "sequence.hpp"
#include "mzCalculationDlg.hpp"
#include "spectrumCalculationDlg.hpp"


namespace massXpert
{

  enum formulaActions
    {
      ADD_FORMULA,
      REMOVE_FORMULA,
      CLEAR_WHOLE_MEMORY,
      SIMPLIFY_FORMULA
    };
  
  CalculatorWnd::CalculatorWnd(QString &filePath, 
                               const QString &mono, 
                               const QString &avg)
  {
    m_forciblyClose = false;
    m_noDelistWnd = false;

    m_formulaHandling = MXP_FORMULA_HANDLING_IMMEDIATE;
    
    if (filePath.isEmpty())
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     tr("Polymer chemistry definition filepath empty."),
			     QMessageBox::Ok);
      
	return;
      }
  
    m_polChemDef.setFilePath(filePath);
  
    if (!initialize())
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     tr("Failed to initialize the calculator window."),
			     QMessageBox::Ok);
      }

    QSettings settings 
      (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
    
    QString section = QString ("calculator_wnd/%2")
      .arg(m_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();

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

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

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

        // qDebug() << __FILE__ << __LINE__ 
        //          << "Set the mono mass to" << mono;
      }
    
    if(!avg.isNull())
      {
        m_ui.seedAvgMassLineEdit->setText(avg);

        // qDebug() << __FILE__ << __LINE__ 
        //          << "Set the avg mass to" << avg;
      }
    
    show();
  }
  
  
  CalculatorWnd::~CalculatorWnd()
  {
    
  }
  
  
  void
  CalculatorWnd::closeEvent(QCloseEvent *event)
  {
    QSettings settings 
      (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
    
    QString section = QString ("calculator_wnd/%2")
      .arg(m_polChemDef.name());

    settings.beginGroup(section);
    
    settings.setValue("geometry", saveGeometry());
    
    settings.setValue("hSplitterSize", m_ui.hSplitter->saveState());
    
    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();
    
    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);
    

    // We are asked not to remove this window from the list of all the
    // calculator windows. This occurs when all the windows are closed
    // in the application object.
    
    if (m_noDelistWnd)
      {
	m_noDelistWnd = false;
	
	event->accept();
	
	return;
      }
      
    // We must remove this window from the application's list of such
    // windows:
  
    Application *application = static_cast<Application *>(qApp);

    int index = application->calculatorWndList()->indexOf(this);
  
    application->calculatorWndList()->takeAt(index);
    
    event->accept();
  }
  

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

    QPixmap pixmap(":/images/massxpert-icon-32.png");
    QIcon icon(pixmap);
    setWindowIcon(icon);

    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,
	    SIGNAL(clicked()),
	    this,
	    SLOT(mzCalculation()));

    connect(m_ui.isotopicPatternCalculationPushButton,
	    SIGNAL(clicked()),
	    this,
	    SLOT(isotopicPatternCalculation()));

    connect(m_ui.formulaActionsComboBox,
	    SIGNAL(activated(int)),
	    this,
	    SLOT(formulaActionsComboBoxActivated(int)));
    

    m_ui.formulaSpinBox->setRange(-1000000000, 1000000000);
    m_ui.monomerSpinBox->setRange(-1000000000, 1000000000);
    m_ui.modifSpinBox->setRange(-1000000000, 1000000000);
    m_ui.sequenceSpinBox->setRange(-1000000000, 1000000000);
  
    m_recorderDlg.setParent(this, Qt::Dialog);
    m_chemPadDlg.setParent(this, Qt::Dialog);

    if (!m_polChemDef.renderXmlPolChemDefFile())
      return false;

    if (!populatePolChemDefComboBoxes())
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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();

    return true;
  }


  const PolChemDef &
  CalculatorWnd::polChemDef()
  {
    return m_polChemDef;
  }


  QString
  CalculatorWnd::polChemDefName()
  {
    return m_polChemDef.name();
  }
  

  void
  CalculatorWnd::updateWindowTitle()
  {
    setWindowTitle(tr("%1 %2[*]")
		   .arg(tr("massXpert - Calculator:"))
		   .arg(m_polChemDef.name()));
  }


  bool
  CalculatorWnd::populatePolChemDefComboBoxes()
  {
    int iter = 0;
    QStringList stringList;
  
    for (iter = 0; iter < m_polChemDef.monomerList().size(); ++iter)
      {
	Monomer *monomer = m_polChemDef.monomerList().at(iter);
      
	stringList << monomer->name();
      }
  
    m_ui.monomerComboBox->addItems(stringList);
    stringList.clear();
  
    for (iter = 0; iter < m_polChemDef.modifList().size(); ++iter)
      {
	Modif *modif = m_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(m_polChemDef.name());
  
    updateWindowTitle();
  
    return true;
  }
  

  bool
  CalculatorWnd::setupChemicalPad()
  {
    QString filePath;
    Application *application = static_cast<Application *>(qApp);

    // 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 chem_pad.conf file(which
    // might not exist, as it is not compulsory to have one).
    //
    // Otherwise, look in the user's config dir.

    if (m_polChemDef.name().isEmpty())
      {
	// See if the user has one chem_pad.conf file in her
	// configuration directory.

	filePath = application->configSettings()->userDataDir() + 
	  "/chem_pad.conf";
      }
    else
      {
	// Get the directory path of the polymer chemistry definition
	// and construct a full pathname to the potentially existing
	// config file.
      
	filePath = m_polChemDef.dirPath() 
	  + QDir::separator() + "chem_pad.conf";
      }
  
    QFile file(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, 
				 tr("massXpert - Calculator"),
				 tr("Chemical pad config file not found.\n"
				    "The chemical pad will not work properly."),
				 QMessageBox::Ok);
	return true;
      }
    
    bool ret = m_chemPadDlg.setup(filePath, m_polChemDef.name());
    
    return ret;
  }


  void 
  CalculatorWnd::recordResult()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    QString recorder = tr("Done: mono: %2 -- avg: %3\n")
      .arg(m_tempPonderable.mono(locale))
      .arg(m_tempPonderable.avg(locale));
  
    m_recorderDlg.record(recorder);
  }


  void
  CalculatorWnd::updateSeedResultLineEdits()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    m_ui.seedMonoMassLineEdit->setText(m_seedPonderable.mono(locale));
    m_ui.seedAvgMassLineEdit->setText(m_seedPonderable.avg(locale));
  
    // 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.mono(locale));
    m_ui.resultAvgMassLineEdit->setText(m_resultPonderable.avg(locale));
  }


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



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



  ////////////////////////////// SLOTS ///////////////////////////////
  void 
  CalculatorWnd::addToResult()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();
    QString mass;
    bool ok = true;

    // 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();
    m_seedPonderable.setMono(locale.toDouble(mass, &ok));
  
    if (m_seedPonderable.mono() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     tr("Error with seed mono mass"),
			     QMessageBox::Ok);
	return;
      }
  
    mass = m_ui.seedAvgMassLineEdit->text();
    m_seedPonderable.setAvg(locale.toDouble(mass, &ok));
  
    if (m_seedPonderable.avg() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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.mono(locale))
      .arg(m_seedPonderable.avg(locale))
      .arg(m_resultPonderable.mono(locale))
      .arg(m_resultPonderable.avg(locale));
  
    // Make the calculations.
    m_resultPonderable.incrementMono(m_seedPonderable.mono());
    m_resultPonderable.incrementAvg(m_seedPonderable.avg());

    // Update the lineEdits.

    m_ui.resultMonoMassLineEdit->setText(m_resultPonderable.mono(locale));
    m_ui.resultAvgMassLineEdit->setText(m_resultPonderable.avg(locale));
  
    // And now append the new recorder string to the recorder textEdit.
    m_recorderDlg.record(recorder);
  }

  void 
  CalculatorWnd::sendToResult()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();
    QString mass;
    bool ok = true;
  
    // 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();
    m_seedPonderable.setMono(locale.toDouble(mass, &ok));

    if (m_seedPonderable.mono() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     tr("Error with seed mono mass"),
			     QMessageBox::Ok);
	return;
      }
  
    // avg
    mass = m_ui.seedAvgMassLineEdit->text();
    m_seedPonderable.setAvg(locale.toDouble(mass, &ok));
  
    if (m_seedPonderable.avg() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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.mono(locale))
      .arg(m_seedPonderable.avg(locale))
      .arg(m_resultPonderable.mono(locale))
      .arg(m_resultPonderable.avg(locale));
    
    m_resultPonderable.setMono(m_seedPonderable.mono());
    m_resultPonderable.setAvg(m_seedPonderable.avg());
  
    // Update the lineEdits.

    m_ui.resultMonoMassLineEdit->setText(m_resultPonderable.mono(locale));
    m_ui.resultAvgMassLineEdit->setText(m_resultPonderable.avg(locale));
  }

  void 
  CalculatorWnd::removeFromResult()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();
    QString mass;
    bool ok = true;
  
    // 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();
    m_seedPonderable.setMono(locale.toDouble(mass, &ok));

    if (m_seedPonderable.mono() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     tr("Error with seed mono mass"),
			     QMessageBox::Ok);
	return;
      }
  
    // avg
    mass = m_ui.seedAvgMassLineEdit->text();
    m_seedPonderable.setAvg(locale.toDouble(mass, &ok));
  
    if (m_seedPonderable.avg() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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.mono(locale))
      .arg(m_seedPonderable.avg(locale))
      .arg(m_resultPonderable.mono(locale))
      .arg(m_resultPonderable.avg(locale));
  
    // Make the calculations.
    m_resultPonderable.decrementMono(m_seedPonderable.mono());
    m_resultPonderable.decrementAvg(m_seedPonderable.avg());

    // Update the lineEdits.

    m_ui.resultMonoMassLineEdit->setText(m_resultPonderable.mono(locale));
    m_ui.resultAvgMassLineEdit->setText(m_resultPonderable.avg(locale));
  
    // And now append the new recorder string to the recorder textEdit.
    m_recorderDlg.record(recorder);
  }

  void 
  CalculatorWnd::clearSeed()
  {
    m_seedPonderable.clearMasses();
  
    m_ui.seedMonoMassLineEdit->setText("");
    m_ui.seedAvgMassLineEdit->setText("");
  }

  void 
  CalculatorWnd::addToSeed()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();
    QString mass;
    bool ok = true;
  
    // 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();
    m_seedPonderable.setMono(locale.toDouble(mass, &ok));

    if (m_seedPonderable.mono() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     tr("Error with seed mono mass"),
			     QMessageBox::Ok);
	return;
      }
  
    // avg
    mass = m_ui.seedAvgMassLineEdit->text();
    m_seedPonderable.setAvg(locale.toDouble(mass, &ok));
  
    if (m_seedPonderable.avg() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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.mono(locale))
      .arg(m_resultPonderable.avg(locale))
      .arg(m_seedPonderable.mono(locale))
      .arg(m_seedPonderable.avg(locale));
  
    // Make the calculations.
    m_seedPonderable.incrementMono(m_resultPonderable.mono());
    m_seedPonderable.incrementAvg(m_resultPonderable.avg());

    // Update the lineEdits.

    m_ui.seedMonoMassLineEdit->setText(m_seedPonderable.mono(locale));
    m_ui.seedAvgMassLineEdit->setText(m_seedPonderable.avg(locale));
  
    // And now append the new recorder string to the recorder textEdit.
    m_recorderDlg.record(recorder);
  }

  void 
  CalculatorWnd::sendToSeed()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();
  
    // 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.mono(locale))
      .arg(m_resultPonderable.avg(locale))
      .arg(m_seedPonderable.mono(locale))
      .arg(m_seedPonderable.avg(locale));
  
    m_seedPonderable.setMono(m_resultPonderable.mono());
    m_seedPonderable.setAvg(m_resultPonderable.avg());
  
    // Update the lineEdits.

    m_ui.seedMonoMassLineEdit->setText(m_seedPonderable.mono(locale));
    m_ui.seedAvgMassLineEdit->setText(m_seedPonderable.avg(locale));
  }

  void 
  CalculatorWnd::removeFromSeed()
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();
    QString mass;
    bool ok = true;
  
    // 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();
    m_seedPonderable.setMono(locale.toDouble(mass, &ok));

    if (m_seedPonderable.mono() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     tr("Error with seed mono mass"),
			     QMessageBox::Ok);
	return;
      }
  
    // avg
    mass = m_ui.seedAvgMassLineEdit->text();
    m_seedPonderable.setAvg(locale.toDouble(mass, &ok));
  
    if (m_seedPonderable.avg() == 0 && !ok)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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.mono(locale))
      .arg(m_resultPonderable.avg(locale))
      .arg(m_seedPonderable.mono(locale))
      .arg(m_seedPonderable.avg(locale));
  
    // Make the calculations.
    m_seedPonderable.decrementMono(m_resultPonderable.mono());
    m_seedPonderable.decrementAvg(m_resultPonderable.avg());

    // Update the lineEdits.

    m_ui.seedMonoMassLineEdit->setText(m_seedPonderable.mono(locale));
    m_ui.seedAvgMassLineEdit->setText(m_seedPonderable.avg(locale));
  
    // And now append the new recorder string to the recorder textEdit.
    m_recorderDlg.record(recorder);
  }

  void 
  CalculatorWnd::clearResult()
  {
    m_resultPonderable.clearMasses();
  
    m_ui.resultMonoMassLineEdit->setText("");
    m_ui.resultAvgMassLineEdit->setText("");
  }




  void 
  CalculatorWnd::apply(const QString &formula)
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();
    QString mass;
    bool ok = true;
    bool calculationPerformed = false;
    int res = -1;
    
    if (m_polChemDef.atomList().size() == 0)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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();
	    m_tempPonderable.setMono(locale.toDouble(mass, &ok));
	  
	    if (m_tempPonderable.avg() == 0 && !ok)
	      {
		QMessageBox::warning(0, 
				     tr("massXpert - Calculator"),
				     tr("Error with seed avg mass"),
				     QMessageBox::Ok);
		return;
	      }
      
	    mass = m_ui.seedAvgMassLineEdit->text();
	    m_tempPonderable.setAvg(locale.toDouble(mass, &ok));
      
	    if (m_tempPonderable.avg() == 0 && !ok)
	      {
		QMessageBox::warning(0, 
				     tr("massXpert - Calculator"),
				     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.
    m_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 MXP_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 MXP_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 MXP_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 & MXP_FORMULA_HANDLING_PRINT)
	  {
	    // Get the line edit widget of the comboBox.
	    QLineEdit* lineEdit = m_ui.formulaComboBox->lineEdit();
		
	    if (m_formulaHandling & MXP_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, 
				   tr("massXpert - Calculator"),
				   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, 
			       tr("massXpert - Calculator"),
			       tr("Error accounting formula"),
			       QMessageBox::Ok);
	else if (res == 1)
	  {
	    recordResult();
	    
	    calculationPerformed = true;
	  }
      }
      

    if (!m_polChemDef.name().isEmpty())
      {
	// A polymer chemistry definition was loaded, account for its
	// potential chemical entities.

	res = accountMonomer();
      
	if (res == -1)
	  QMessageBox::warning(0, 
			       tr("massXpert - Calculator"),
			       tr("Error accounting monomer"),
			       QMessageBox::Ok);
	else if (res == 1)
	  {
	    recordResult();
	  
	    calculationPerformed = true;
	  }
      
      
	res = accountModif ();
      
	if (res == -1)
	  QMessageBox::warning(0, 
			       tr("massXpert - Calculator"),
			       tr("Error accounting modif"),
			       QMessageBox::Ok);
	else if (res == 1)
	  {
	    recordResult();

	    calculationPerformed = true;
	  }
      

	res = accountSequence();
      
	if (res == -1)
	  QMessageBox::warning(0, 
			       tr("massXpert - Calculator"),
			       tr("Error accounting sequence"),
			       QMessageBox::Ok);
	else if (res == 1)
	  {
	    recordResult();

	    calculationPerformed = true;
	  }
      }
    // End of
    // if (!m_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,
			   &m_polChemDef,
			   &m_polChemDef.ionizeRule(),
			   m_resultPonderable.mono(),
			   m_resultPonderable.avg());
  
    dlg->show();
  }


  void 
  CalculatorWnd::isotopicPatternCalculation()
  {
    SpectrumCalculationDlg *dlg = 
      new SpectrumCalculationDlg(this, 
                                 m_polChemDef.atomList(),
                                 MXP_SPECTRUM_CALCULATION_MODE_CLUSTER);
  
    // We really need that the dialog be set modal because we want to
    // be able to access all the atom and oligomer data freely and
    // durably.
    dlg->setModal(true);
    
    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();
      }
  }
  

  int
  CalculatorWnd::accountFormula(const QString &formula, int times) 
  {
    QString text;
    int 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.

    if (m_polChemDef.atomList().size() == 0)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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.
	text =  m_ui.formulaComboBox->currentText();
	      
	if (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).
	text.remove(QRegExp("\\s+"));
    
	// Note that we might also have a title for the formula, which
	// is enclosed between `"' at the beginning of the line.

	text.remove(QRegExp("\".*\""));
	
	// How many times should the formula be accounted for?
	count = m_ui.formulaSpinBox->value();
      
	if (count == 0)
	  return 0;
      }
    else
      {
	text = formula;

	// The times param tells us how many times should the formula be
	// accounted for.
	count = times;
      }
  
    Modif fake(&m_polChemDef, "NOT_SET", text);
    if (!fake.calculateMasses())
      return -1;
  
    // Prepare the recorder string.  
    QString recorder = tr("Accounting formula: %1(%2 times)... ")
      .arg(text)
      .arg(count);
  
    // And now append the new recorder string to the recorder textEdit.
    m_recorderDlg.record(recorder);
  
    if (!fake.accountMasses(&m_tempPonderable.rmono(),
			    &m_tempPonderable.ravg(),
			    count))
      return -1;
  
    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..
    Monomer monomer(&m_polChemDef, "NOT_SET");
  
    int index = -1;
    index = Monomer::isNameInList(text, 
				  m_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.
    m_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.
    Modif modif (&m_polChemDef, "NOT_SET");
    int index = -1;

    index = Modif::isNameInList(text, 
				m_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.
    m_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;
  

    Sequence sequence(text);
  
    if (sequence.makeMonomerList(&m_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.
    m_recorderDlg.record(recorder);
  
    for (int iter = 0; iter < sequence.size(); ++iter)
      {
	const 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 
	  (static_cast<Application *>(qApp)->configSettingsFilePath(), 
	   QSettings::IniFormat);
	m_recorderDlg.restoreGeometry 
	  (settings.value("calculator_recorder_dlg/geometry").toByteArray());

	m_recorderDlg.show();
      }
    else
      {
	QSettings settings 
	  (static_cast<Application *>(qApp)->configSettingsFilePath(), 
	   QSettings::IniFormat);
	settings.setValue("calculator_recorder_dlg/geometry", 
			  m_recorderDlg.saveGeometry());
      
	m_recorderDlg.hide();
      }
  }

  void 
  CalculatorWnd::showChemPad(int state)
  {
    if (state == Qt::Checked)
      {
	QSettings settings 
	  (static_cast<Application *>(qApp)->configSettingsFilePath(), 
	   QSettings::IniFormat);
	
	settings.beginGroup("calculator_chem_pad_dlg");
	
	QString setting = QString("%1-%2")
	  .arg(polChemDefName())
	  .arg("geometry");
	
	m_chemPadDlg.restoreGeometry(settings.value(setting).toByteArray());
	
	settings.endGroup();
	
	m_chemPadDlg.show();
      }
    else
      {
	QSettings settings 
	  (static_cast<Application *>(qApp)->configSettingsFilePath(), 
	   QSettings::IniFormat);
	
	settings.beginGroup("calculator_chem_pad_dlg");
	
	QString setting = QString("%1-%2")
	  .arg(polChemDefName())
	  .arg("geometry");
	
	settings.setValue(setting, m_chemPadDlg.saveGeometry());
	
	settings.endGroup();
	      
	m_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.

    if (m_polChemDef.atomList().size() == 0)
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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(QRegExp("\\s+"));
    
    // Note that we might also have a title for the formula, which is
    // enclosed between `"' at the beginning of the line.

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

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

    Modif fake(&m_polChemDef, "NOT_SET", text);
    if (!fake.calculateMasses())
      {
	QMessageBox::warning(0, 
			     tr("massXpert - Calculator"),
			     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
