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

   http://www.filomace.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<QMessageBox>
#include<QFileDialog>

/////////////////////// Std includes
#include <math.h>
#include <algorithm>
#include <limits> // for std::numeric_limits

using namespace std;

/////////////////////// Local includes
#include "spectrumCalculationDlg.hpp"
#include "isotopicPatternCalculator.hpp"
#include "application.hpp"


namespace massXpert
{

  enum
    {
      TAB_WIDGET_INPUT_DATA,
      TAB_WIDGET_LOG,
      TAB_WIDGET_RESULTS,
    };
  
    
  SpectrumCalculationDlg::SpectrumCalculationDlg 
  (QWidget *parent, 
   const QList<Atom *> &atomList,
   SpectrumCalculationMode mode)
   : QDialog(parent), 
     m_mode(mode),
     m_atomList(atomList),
     m_polChemDef(static_cast<CalculatorWnd *>(parent)->polChemDef())
     {
    Q_ASSERT(parent);

    setWindowTitle("massXpert: Spectrum Calculator");
    
    m_aborted = false;
    
    // When the window is created the formula line edit is empty, so
    // at least that data has an error. The ionization data is most
    // probably not erroneous because it comes from the polymer
    // chemistry definition or from the caller.
    m_validationErrors = MXP_VALIDATION_FORMULA_ERRORS;
    
    m_filePath = "";

    m_ui.setupUi(this);

    // Set the oligomer list pointer to 0, so that we know if it was
    // set or not later.
    mp_oligomerList = 0;
    
    setupDialog();
    
    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
  
    settings.beginGroup("isotopic_pattern_calculation_dlg");

    restoreGeometry(settings.value("geometry").toByteArray());

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

    settings.endGroup();

    connect(m_ui.chargeSpinBox,
            SIGNAL(valueChanged(int)),
            this,
            SLOT(chargeChanged(int)));
    
    connect(m_ui.formulaLineEdit,
            SIGNAL(textEdited(const QString &)),
            this,
            SLOT(formulaEdited(const QString &)));

    connect(m_ui.pointNumberSpinBox,
            SIGNAL(valueChanged(int)),
            this,
            SLOT(pointsChanged(int)));

    connect(m_ui.resolutionSpinBox,
            SIGNAL(valueChanged(int)),
            this,
            SLOT(resolutionChanged(int)));

    connect(m_ui.fwhmLineEdit,
            SIGNAL(textEdited(const QString &)),
            this,
            SLOT(fwhmEdited(const QString &)));

    connect(m_ui.monoRadioButton,
            SIGNAL(toggled(bool)),
            this,
            SLOT(massTypeRadioButtonToggled(bool)));
    
    connect(m_ui.avgRadioButton,
            SIGNAL(toggled(bool)),
            this,
            SLOT(massTypeRadioButtonToggled(bool)));
    
    connect(m_ui.avgRadioButton,
            SIGNAL(toggled(bool)),
            this,
            SLOT(massTypeRadioButtonToggled(bool)));
    
    connect(m_ui.isotopicClusterCheckBox,
            SIGNAL(toggled(bool)),
            this,
            SLOT(isotopicClusterCheckBoxToggled(bool)));
    
    connect(m_ui.executePushButton,
	     SIGNAL(clicked()),
	     this,
	     SLOT(execute()));
  
    connect(m_ui.abortPushButton,
	     SIGNAL(clicked()),
	     this,
	     SLOT(abort()));

    connect(m_ui.outputFilePushButton,
	     SIGNAL(clicked()),
	     this,
	     SLOT(outputFile()));
  }


  void 
  SpectrumCalculationDlg::closeEvent(QCloseEvent *event)
  {
    if (event)
      printf("%s", "");
  
    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
  
    settings.beginGroup("isotopic_pattern_calculation_dlg");
  
    settings.setValue("geometry", saveGeometry());
  
    settings.setValue("splitter", m_ui.splitter->saveState());

    settings.endGroup();
  }


  SpectrumCalculationDlg::~SpectrumCalculationDlg()
  {
    while(m_patternPointList.size())
      delete m_patternPointList.takeFirst();
  }

  
  void 
  SpectrumCalculationDlg::setOligomerList(OligomerList *list)
  {
    if(!list)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    mp_oligomerList = list;
  }
  

  void 
  SpectrumCalculationDlg::setupDialog()
  {
    // Always start the dialog with the first page of the tab widget,
    // the input data page.
    m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_INPUT_DATA);

    // By default we want a gaussian-type shape.
    m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN);
    
    // Throughout of *this* dialog window, the normalization factor
    // for the peak shapes (Gaussian, specifically) is going to be 1.

    // Charge stuff.
    m_ui.chargeSpinBox->setRange(1, 10000000);
    m_ui.chargeSpinBox->setValue(1);
    m_charge = m_ui.chargeSpinBox->value();

    // Isotopic peak probability stuff.
    m_ui.minimumProbabilityLineEdit->setText("0.0000001");
    m_minimumProbability = 0.0000001;
  
    // Max number of peaks in the isotopic cluster.
    m_ui.maximumPeaksSpinBox->setRange(0, 1000);
    m_ui.maximumPeaksSpinBox->setValue(100);
    m_maximumPeaks = 100;
  
    // Resolution and FWHM stuff
    m_ui.resolutionSpinBox->setRange(0, 1000000);
    m_ui.resolutionSpinBox->setValue(0);
    m_resolution = 0;
    
    // Get the number of points used to craft the curve.
    m_config.setPointNumber(m_ui.pointNumberSpinBox->value());

    // Since both values above are not correct, set the corresponding
    // bits to the validation error int.
    m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS;
    m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;

    // Now, depending on the kind of operation required for the
    // dialog, adapt some behaviour.


    if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      {
        // In isotopic cluster calculation mode, the m/z calculated
        // starting from the formula and the charge is of course mono!
        // Thus, we set it checked and disable the enclosing group box.

        m_ui.monoRadioButton->setChecked(true);
        m_ui.massTypeGroupBox->setDisabled(true);

        // Of course, the computeIsotopicClusterCheckBox should be
        // checked and disabled.
        m_ui.isotopicClusterCheckBox->setChecked(true);
        m_ui.isotopicClusterCheckBox->setDisabled(true);

        // And all the related widgets should be active;

        m_ui.pointNumberSpinBox->setDisabled(false);
        m_ui.minimumProbabilityLineEdit->setDisabled(false);
        m_ui.maximumPeaksSpinBox->setDisabled(false);

      }
    else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM)
      {
        // In spectrum mode, the formula, z and m/z are not active.
        m_ui.mzGroupBox->setDisabled(true);

        // By default, we work on mono masses.
        m_withMonoMass= true;
        m_ui.monoRadioButton->setChecked(m_withMonoMass);
      
        // By default, we do not want isotopic cluster calculations.
        m_withCluster = false;
        m_ui.isotopicClusterCheckBox->setChecked(m_withCluster);

        // Not willing to compute isotopic clusters, thus all the
        // related widgets should be inactive.
        m_ui.pointNumberSpinBox->setDisabled(true);
        m_ui.minimumProbabilityLineEdit->setDisabled(true);
        m_ui.maximumPeaksSpinBox->setDisabled(true);
      }
  }

  
  bool
  SpectrumCalculationDlg::fetchValidateInputData()
  {
    if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      return fetchValidateInputDataIsotopicClusterMode();
    else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM)
      return fetchValidateInputDataSpectrumMode();
    else
      return false;
  }
  

  bool 
  SpectrumCalculationDlg::fetchValidateInputDataIsotopicClusterMode()
  {
    Application *application = static_cast<Application *>(qApp);

    // The ionization stuff, the formula for which the isotopic
    // pattern is to be calculated, the mz ratio (in m_mono) all have
    // been checked previously by automated procedures (see the
    // "changed" slots). We have to make sure that either the
    // resolution or the FWHM values are OK. All the other bits must
    // be clear. Only either the RESOLUTION or the FWHM bit might be
    // set.

    int testBitset = 0;
    testBitset |= MXP_VALIDATION_RESOLUTION_ERRORS;
    testBitset |= MXP_VALIDATION_FWHM_ERRORS;
    
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      testBitset, "testBitset");

    if(m_validationErrors == testBitset)
      return false;
      
    if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) ==
       MXP_VALIDATION_FORMULA_ERRORS)
      return false;

    // Now that we know that the formula was validated, we can ask
    // that all the isotopes be deep-copied into the formula.

    // The list of atomCount objects has to be made with fully detailed
    // isotopes...
    if (!m_formula.deepAtomCopy(m_atomList))
      {
	QMessageBox::warning(0,
                             tr("massXpert: Spectrum Calculator"),
                             tr("Failed to deep-copy atom list."),
                             QMessageBox::Ok);

	return false;
      }
    
    // We still have to fetch a number of parameters.

    // Get to know if we want gaussian or lorentzian shapes.
    if(m_ui.gaussianRadioButton->isChecked())
      {
        m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN);
      }
    else
      {
        m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_LORENTZIAN);
      }
  
    // Shall we use localization for all the numerical output?
    bool isUsingLocale = m_ui.localeCheckBox->checkState() == Qt::Checked ?
      true : false;
    
    int totAtoms = m_formula.totalAtoms();
    m_ui.progressBar->setRange(0, totAtoms);
    m_ui.progressBar->setValue(0);
    
    int totIsotopes = m_formula.totalIsotopes(m_atomList);

    QString textEditText;

    if (isUsingLocale)
      textEditText += tr("INPUT\n=====\n\n"
                         "Formula: %1\n"
                         "Charge: %2\n"
                         "Mono Mass: %3 \t Avg mass: %4\n"
                         "Total number of atoms: %5\t"
                         "Total number of isotopes: %6\n\n")
        .arg(m_formula.text())
	.arg(m_charge)
        .arg(application->locale().
             toString(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES))
	.arg(application->locale().
             toString(m_avg, 'f', MXP_OLIGOMER_DEC_PLACES))
        .arg(totAtoms)
	.arg(totIsotopes);
    else
      textEditText += tr("INPUT\n=====\n\n"
                         "Formula: %1\n"
                         "Charge: %2\n"
                         "Mono Mass: %3 \t Avg mass: %4\n"
                         "Total number of atoms: %5\t"
                         "Total number of isotopes: %6\n\n")
	.arg(m_formula.text())
        .arg(m_charge)
	.arg(QString().setNum(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES))
	.arg(QString().setNum(m_avg, 'f', MXP_OLIGOMER_DEC_PLACES))
	.arg(totAtoms)
	.arg(totIsotopes);
    
    // Put that description in the text edit widget.
    m_ui.resultPlainTextEdit->appendPlainText(textEditText);
    
    // qDebug() << __FILE__ << __LINE__
    //          << textEditText;
    
    // At this point we have to sort out wether the user wants to use
    // the resolution or the FWHM for the calculations. This is
    // automatically known by looking at which value is 0 (see the
    // corresponding changed() slots).

    // Get the points value.
    m_config.setPointNumber(m_ui.pointNumberSpinBox->value());
    
    // How about setting the increment, that is the size of the gap
    // between two points ? If it is not set (its value is 0), then
    // increment is fwhm() / m_pointNumber;

    // First however, see if the user wants to use the FWHM value or
    // the resolution.
    
    if(m_config.fwhm())
      {
        // FWHM is not zero, which means the user has set a value for
        // it. Keep it.
      }
    else
      {
        // We have to compute the FWHM starting from the mz ratio and
        // the resolution.
        
        m_config.setFwhm(m_mono / m_resolution);
      }

    // Set the maximum number of peaks in the isotopic curve.
    m_maximumPeaks = m_ui.maximumPeaksSpinBox->value();
    
    // Set the minimum probability each isotopic peak must have to be
    // retained in the final curve.

    QString minProbString = m_ui.minimumProbabilityLineEdit->text();
    bool ok = false;
    
    double minProb = minProbString.toDouble(&ok);
    
    if(!minProb && !ok)
      {
	QMessageBox::warning(0,
                             tr("massXpert: Spectrum Calculator"),
                             tr("Please, fix the minimum probability."),
                             QMessageBox::Ok);
        
	return false;
      }
      
    m_minimumProbability = minProb;
    
    return true;
  }
  
  
  bool 
  SpectrumCalculationDlg::fetchValidateInputDataSpectrumMode()
  {
    // The ionization stuff, the formula for which the isotopic
    // pattern is to be calculated, the mz ratio (in m_mono) all have
    // been checked previously by automated procedures (see the
    // "changed" slots). We have to make sure that either the
    // resolution or the FWHM values are OK. All the other bits must
    // be clear. Only either the RESOLUTION or the FWHM bit might be
    // set.

    int testBitset = 0;
    testBitset |= MXP_VALIDATION_RESOLUTION_ERRORS;
    testBitset |= MXP_VALIDATION_FWHM_ERRORS;
    
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      testBitset, "testBitset");

    if(m_validationErrors == testBitset)
      return false;
      
    // We do not care of the formula in our spectrum calculation
    // situation. Each oligomer from a cleavage contains a chemical
    // composition formula that we'll use each time.
    
    // We still have to fetch a number of parameters.

    // Are we working on the mono or the avg masses of the oligomers?
    
    m_withMonoMass = m_ui.monoRadioButton->isChecked();
    
    // Get to know if we want gaussian or lorentzian shapes.
    if(m_ui.gaussianRadioButton->isChecked())
      {
        m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN);
      }
    else
      {
        m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_LORENTZIAN);
      }
  
    // At this point we have to sort out wether the user wants to use
    // the resolution or the FWHM for the calculations. This is
    // automatically known by looking at which value is 0 (see the
    // corresponding changed() slots).

    // Get the points value.
    m_config.setPointNumber(m_ui.pointNumberSpinBox->value());
    
    // How about setting the increment, that is the size of the gap
    // between two points ? If it is not set (its value is 0), then
    // increment is fwhm() / m_pointNumber;

    // First however, see if the user wants to use the FWHM value or
    // the resolution.
    
    if(m_config.fwhm())
      {
        // FWHM is not zero, which means the user has set a value for
        // it. Keep it.
      }
    else
      {
        // We cannot compute fwhm starting from the resolution because
        // we cannot know what is the m/z of the oligomers beforehand.

        // All we can check is that resolution is non-zero.

        if(m_resolution <= 0)
          {
            QMessageBox::warning(0,
                                 tr("massXpert: Spectrum Calculator"),
                                 tr("Please, fix the resolution or the FWHM."),
                                 QMessageBox::Ok);
            
            return false;
          }
      }
    
    // Set the maximum number of peaks in the isotopic curve.
    m_maximumPeaks = m_ui.maximumPeaksSpinBox->value();
    
    // Set the minimum probability each isotopic peak must have to be
    // retained in the final curve.

    QString minProbString = m_ui.minimumProbabilityLineEdit->text();
    bool ok = false;
    
    double minProb = minProbString.toDouble(&ok);
    
    if(!minProb && !ok)
      {
	QMessageBox::warning(0,
                             tr("massXpert: Spectrum Calculator"),
                             tr("Please, fix the minimum probability."),
                             QMessageBox::Ok);
        
	return false;
      }
      
    m_minimumProbability = minProb;
    
    return true;
  }
  

  // Returns false if formula is bad or aborts if calculation fails..
  bool
  SpectrumCalculationDlg::fetchFormulaMass(double *mono, double *avg)
  {
    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ fetchFormulaMass");

    // Check if there are currently errors set.
    
    if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) ==
       MXP_VALIDATION_FORMULA_ERRORS)
      return false;
    
    double monoMass = 0;
    double avgMass = 0;
    
    // It is impossible that we have an error here, since the formula
    // was previously validated (MXP_VALIDATION_FORMULA_ERRORS above).
    if (!m_formula.accountMasses(m_atomList, &monoMass, &avgMass, 1))
      qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
    
    if(mono)
      *mono = monoMass;
    
    if(avg)
      *avg = avgMass;

    // qDebug() << __FILE__ << __LINE__
    //          << "Formula mono/avg masses:"
    //          << monoMass << "/" << avgMass;

    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ fetchFormulaMass");

    return true;
  }
    
  // Returns false if the formula is not validated and aborts if an
  // error occurs while the formula was validated.
  bool
  SpectrumCalculationDlg::fetchMzRatio(double *mono, double *avg)
  {
    // Check if there are currently errors set either in the formula
    // or in the ionization rule.

    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ ENTER fetchMzRatio");
        
    if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) ==
       MXP_VALIDATION_FORMULA_ERRORS)
      {
        return false;
      }
    
    // The formula data have already been automatically fetched and
    // set to m_formula when the data in the widgets changed using the
    // correponding slot.
    
    // We can compute the m/z ratio.
    
    double monoMass = 0;
    double avgMass = 0;

    fetchFormulaMass(&monoMass, &avgMass);

    // qDebug() << __FILE__ << __LINE__ 
    //          << "monoMass:" << monoMass
    //          << "avgMass:" << avgMass;
    
    // Note that the only acceptable value returned here is mono > 0,
    // because the formula was validated and thus mass calculation
    // should not fail. Indeed, if there is a failure in the called
    // function, qFatal is triggered. No need, thus, to check the
    // status of the call. It cannot fail.
    
    // Compute the m/z ratio.
    monoMass = monoMass / m_charge;
    avgMass = avgMass / m_charge;
    
    // At this point the ionizable has the right mzRatio.
    
    if(mono)
      *mono = monoMass;
    
    if(avg)
      *avg = avgMass;

    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ EXIT fetchMzRatio");
        
    return true;
  }
  
  void 
  SpectrumCalculationDlg::massTypeRadioButtonToggled(bool checked)
  {
    Q_UNUSED(checked);
    
    m_withMonoMass = m_ui.monoRadioButton->isChecked();

    // If the mass type is average, then, of course, no isotopic
    // cluster calculation can be required.

    if(!m_withMonoMass)
      m_ui.isotopicClusterCheckBox->setChecked(false);
  }
  
  void
  SpectrumCalculationDlg::isotopicClusterCheckBoxToggled(bool checked)
  {
    m_withCluster = checked;

    if(checked)
      {
        // If the isotopic cluster is to be computed for each oligomer,
        // then that means necessarily that the mass is mono!

        m_withMonoMass = true;
        m_ui.monoRadioButton->setChecked(true);

        m_ui.pointNumberSpinBox->setDisabled(false);
        m_ui.minimumProbabilityLineEdit->setDisabled(false);
        m_ui.maximumPeaksSpinBox->setDisabled(false);
      }
    else
      {
        // Not willing to compute isotopic clusters, thus all the
        // related widgets should be inactive.

        m_ui.pointNumberSpinBox->setDisabled(true);
        m_ui.minimumProbabilityLineEdit->setDisabled(true);
        m_ui.maximumPeaksSpinBox->setDisabled(true);
      }
    
  }
  

  void 
  SpectrumCalculationDlg::formulaEdited(const QString &text)
  {
    // We only handle the automatic recalculations on the basis of
    // this change if we are in a isotopic cluster calculation task.
    if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      return;

    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ ENTER formulaChanged");

    // qDebug() << __FILE__ << __LINE__
    //          << "formulaChanged has text:" << text;
    
    // New text contains a new formula. Check it.
    
    if(text.isEmpty())
      {
        // Set the error bit.
        m_validationErrors |= MXP_VALIDATION_FORMULA_ERRORS;

        m_ui.monoMzRatioLineEdit->setText("0.000");
        m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error"));

        return;
      }
          
    Formula formula(text);

    // Do not bother storing all the atoms in the formula, this is
    // only a check (false param below).  m_atomList is a reference to
    // the atom list of the polymer chemistry definition currently
    // used in the caller calculator window.

    if (!formula.validate(m_atomList, false, true))
      {
        // Set the error bit.
        m_validationErrors |= MXP_VALIDATION_FORMULA_ERRORS;

        m_ui.monoMzRatioLineEdit->setText("0.000");
        m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error"));

        return;
      }
    
    // qDebug() << __FILE__ << __LINE__
    //          << "Formula:" << formula.formula() << "validated." ;

    // At this point we know we could actually validate the formula,
    // so do that work with the member formula:

    // We want to validate the formula and in the mean time construct
    // the list of all the AtomCount objects(first true), and since
    // the formula is reused we also ensure that that list is reset
    // (second true). m_atomList is a reference to the atom list of
    // the polymer chemistry definition currently used in the caller
    // calculator window.

    m_formula.setFormula(text);
    m_formula.validate(m_atomList, true, true);
    
    // Clear the bit as there is no error here.
    m_validationErrors &= ~MXP_VALIDATION_FORMULA_ERRORS;
    
    m_ui.inputDataFeedbackLineEdit->setText(tr("Formula fine"));
    
    // qDebug() << __FILE__ << __LINE__
    //          << "Going to call updateMzRatio.";
    
    updateMzRatio();
    updateIncrement();
    
    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ EXIT formulaChanged");

    return;
  }
  

  void 
  SpectrumCalculationDlg::chargeChanged(int value)
  {
    // We only handle the automatic recalculations on the basis of
    // this change if we are in a isotopic cluster calculation task.
    if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      return;
    
    //The charge has changed, we should compute the mzRatio.
    m_charge = m_ui.chargeSpinBox->value();
    
    updateMzRatio();
    updateIncrement();
  }
  

  void 
  SpectrumCalculationDlg::pointsChanged(int value)
  {
    // We only handle the automatic recalculations on the basis of
    // this change if we are in a isotopic cluster calculation task.
    if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      return;

    //The points have changed, we should compute the increment.
    m_config.setPointNumber(m_ui.pointNumberSpinBox->value());
    
    updateIncrement();
  }
  

  void 
  SpectrumCalculationDlg::resolutionChanged(int value)
  {
    // If the value changed, that means that the user wants the
    // resolution to be taken into account for calculation of the peak
    // width, and not the FWHM. Only if resolution is set to 0, FWHM
    // will be the value taken into account.

    // Show what's the value of the errors.
    
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ ENTER resolutionChanged");
    
    if(value <= 0)
      {
        // Tell the user to set a valid FWHM value, then.
        m_ui.inputDataFeedbackLineEdit->setText
          (tr("Set a valid FWHM value"));
            
        m_ui.resolutionSpinBox->setValue(0);
        
        // Set the error bit so that we can let the increment
        // calculation know that we can not perform that computation
        // using FWHM.

        m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS;

        return;
      }
    
    m_resolution = value;
    
    m_ui.inputDataFeedbackLineEdit->setText
      (tr("Will use the resolution"));

    // Clear the bit as there is no error here.
    m_validationErrors &= ~MXP_VALIDATION_RESOLUTION_ERRORS;

    // We only handle the automatic recalculations on the basis of
    // this change if we are in a isotopic cluster calculation task
    // because only in that case do we know before starting the
    // calculation what will be the mz.
    if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      return;
    
    // We can try to compute the fwhm value corresponding to the
    // current mono m/z value and the resolution.

    double mono;
    double avg;
    
    if(fetchFormulaMass(&mono, &avg))
      {
        Application *application = static_cast<Application *>(qApp);

        QString value = application->locale().
          toString(mono / m_resolution, 'f', MXP_OLIGOMER_DEC_PLACES);

        // Note that the setText call below will not trigger automatic
        // fwhm setting because the lineEdit widget is connected to a
        // textEdited signal, not a textChanged signal.

        m_ui.fwhmLineEdit->setText(value);
      }
    
    // Now, because we want the resolution to be the governing
    // parameter when validation will occur, we do this:
    
    m_config.setFwhm(0);
    m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;
    
    // Show what's the value of the errors.

    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ EXIT resolutionChanged");

    updateIncrement();
    
    return;
  }
  

  void 
  SpectrumCalculationDlg::fwhmEdited(const QString &text)
  {
    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ ENTER fwhmEdited");

    // If the text was edited in the line edit, that means that the
    // user wants the FWHM to be taken into account for calculation of
    // the peak width, and not the resolution. Only if FWHM is set to
    // 0, resolution will be the value taken into account.
    
    QString txt = m_ui.fwhmLineEdit->text();
    
    bool ok = false;
    
    double fwhmValue = txt.toDouble(&ok);
    
    if(!fwhmValue && !ok)
      {
        // Set the error bit.
        m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;

        m_ui.inputDataFeedbackLineEdit->setText
          (tr("Fix the FWHM value, please"));
        
        return;
      }
    
    // But the value might be faithfully 0, if the user is telling us
    // that she wants to take the resolution into account and not the
    // FWHM.

    if(!fwhmValue)
      {
        m_ui.inputDataFeedbackLineEdit->setText
          (tr("Set a valid resolution value"));

        // Set the error bit to let the increment calculation know
        // that it cannot base the calculation on the FWHM value.

        m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS;

        return;
      }
    
    // At this point we know that fwhmValue contains a proper value.
    
    m_ui.inputDataFeedbackLineEdit->setText
      (tr("Will use the FWHM"));
    
    m_validationErrors &= ~MXP_VALIDATION_FWHM_ERRORS;

    m_config.setFwhm(fwhmValue);

    // Set the resolution spinbox to 0 and set the error bit
    // associated with it to let the increment calculation know that
    // it cannot be based on that value.

    m_resolution = 0;
    m_ui.resolutionSpinBox->setValue(0);
    m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS;
    
    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ EXIT fwhmEdited");

    // We only handle the automatic recalculation of the increment on
    // the basis of this change if we are in a isotopic cluster
    // calculation task because only in this case do we know the mono
    // mz value.
    if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      return;

    updateIncrement();
    
    return;
  }

    
  bool 
  SpectrumCalculationDlg::updateMzRatio()
  {
    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ ENTER updateMzRatio");

    // Set the mz ratio to our member data variable.
    bool res = fetchMzRatio(&m_mono, &m_avg);
    
    // If res is falsed, that means that the formula is not validated
    // at present. Silently return.
    if(!res)
      {
        QMessageBox::warning(0, 
                             tr("massXpert - Spectrum Calculator"),
                             tr("Failed to fetch the mz ratio."),
                             QMessageBox::Ok);
        
      return false;
      }
    
    Application *application = static_cast<Application *>(qApp);
    
    QString mzRatio = application->locale().
      toString(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES);
    
    m_ui.monoMzRatioLineEdit->setText(mzRatio);

    // Show what's the value of the errors.
    // debugPutStdErrBitset(__FILE__, __LINE__,
    //                      m_validationErrors, 
    //                      "m_validationErrors @ EXIT updateMzRatio");

    return true;
  }
  

  bool 
  SpectrumCalculationDlg::updateIncrement()
  {
    // We can only calculate the increment if we have the number of
    // points to construct the curve and either the resolution and the
    // mzRatio or the FWHM.

    // If we know the FWHM, then, fine, we can compute the increment
    // right away.
    
    double increment = 0;
    
    if((m_validationErrors & MXP_VALIDATION_FWHM_ERRORS) !=
       MXP_VALIDATION_FWHM_ERRORS)
      {
        increment = 
          (MXP_FWHM_PEAK_SPAN_FACTOR * m_config.fwhm()) / 
          m_config.pointNumber();
      }
    else if((m_validationErrors & MXP_VALIDATION_RESOLUTION_ERRORS) !=
       MXP_VALIDATION_RESOLUTION_ERRORS)
      {
        // If we know the resolution and we can get the mzRatio, we'll
        // be able to first compute the FWHM as (mzRatio / FWHM). From
        // there, we'll be able to compute the increment.
        
        double mono = 0;
        double avg = 0;
        
        // We compute the increment by using the mass of the formula,
        // not its mzRatio.
        if(fetchFormulaMass(&mono, &avg))
          {
            double fwhm = mono / m_resolution;
            
            increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) / 
              m_config.pointNumber();

            // Because we'll need more points when the charge
            // increases... Formally, we have to check that charge is
            // not 0, but we should not be required to do it because
            // the charge can never be 0 (gui-limited values for the
            // charge spin box).
            if(m_charge)
              increment = increment / m_charge;
          }
      }

    if(increment)
      {
        m_config.setIncrement(increment);
        
        QString text;
        text.setNum(m_config.increment());
        
        m_ui.incrementLabel->setText(text);
        
        return true;
      }
    else
      m_ui.incrementLabel->setText("0");
    
    return false;
  }
  

  void 
  SpectrumCalculationDlg::execute()
  {
    // We might be called to perform two different things:
    //
    // 1. Calculate a single istotopic cluster pattern.
    //
    // 2. Calculate a spectrum for a whole set of oligomers.

    if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER)
      return executeCluster();
    else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM)
      return executeSpectrum();
  }
  
    
  void 
  SpectrumCalculationDlg::executeCluster()
  {
    Application *application = static_cast<Application *>(qApp);

    // Clear the textEdit widget so that we can start out-putting text
    // into it, note that fetchValidateInputData() puts text in it.
    m_ui.resultPlainTextEdit->clear();

    // Clear the results string for the creation of the calculator
    // below to receive an empty string.
    m_resultsString.clear();
    
    // We are asked to launch the calculation. But we ought to make
    // sure that all the required data are set fine. The call below
    // will fill-in the m_config data.
    
    if(!fetchValidateInputData())
      {
        QMessageBox::warning(0, 
                             tr("massXpert - Spectrum Calculator"),
                             tr("Please fix the input data first."),
                             QMessageBox::Ok);
        return;
      }
    
    // Before starting the calculations, swith to the log tab so that
    // the user can monitor what's going on.

    m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_LOG);

    // Send to both the log and result text edit widget the data we
    // are going to use for the calculation.

    QString *text = new QString();
    
    bool isUsingLocale = m_ui.localeCheckBox->checkState() == Qt::Checked ?
      true : false;
    
    if (isUsingLocale)
      text->append(tr("FWHM: %1 \t Max. peaks %2 "
                      "\t Min. probability: %3\n\n")
                   .arg(application->locale().toString(m_config.fwhm(),
                                                       'f', 4))
                   .arg(application->locale().toString(m_maximumPeaks))
                   .arg(application->locale().toString(m_minimumProbability,
                                                       'f', 15)));
    else
      text->append(tr("FWHM: %1 \t Max. peaks %2 "
                      "\t Min. probability: %3\n\n")
                   .arg(QString().setNum(m_config.fwhm(),
                                         'f', 4))
                   .arg(QString().setNum(m_maximumPeaks))
                   .arg(QString().setNum(m_minimumProbability,
                                         'f', 15)));
    
    m_ui.logPlainTextEdit->appendPlainText(*text);
    m_ui.resultPlainTextEdit->appendPlainText(*text);
    
    // Free the string, we will reuse it.
    delete text;
    
    // We will allocate a IsotopicPatternCalculator instance and let
    // it do the work.

    IsotopicPatternCalculator *calculator = 
      new IsotopicPatternCalculator(m_formula, m_charge,
                                    m_maximumPeaks, m_minimumProbability,
                                    m_atomList, m_config);
    
    connect(calculator,
            SIGNAL(isotopicCalculationProgressValueChanged(int)),
            this,
            SLOT(spectrumCalculationProgressValueChanged(int)));

    connect(calculator,
            SIGNAL(isotopicCalculationMessageChanged(QString)),
            this,
            SLOT(spectrumCalculationMessageChanged(QString)));
    
    connect(this,
            SIGNAL(spectrumCalculationAborted()),
            calculator,
            SLOT(spectrumCalculationAborted()));
    

    // Perform the calculation...
    calculator->sumPeakShapes();

    // Get the list of peaks as "1251.14 0.025" pairs.
    text = calculator->peakCentroidListAsString();
    
    // Now prepend a header:
    
    text->prepend("\nList of peak centroids:\n");
        
    // Display the data to the user.
    m_ui.resultPlainTextEdit->appendPlainText(*text);
    
    // We want to get the calculation configuration.

    // Let's get the detailed calculation configuration
    // string, so that we can append it to the textEdit
    // widget.
    text->clear();
    text->append(m_config.config());
    m_ui.resultPlainTextEdit->appendPlainText(*text);
  
    // Finally delete the text.
    delete text;

    // Finally, if the user had asked for the data to be sent to a
    // file... or to a string... Get a reference to the list of
    // QPointF instances in the calculator and send them either to the
    // file or to the text edit widget.

    const QList<QPointF *> &spectrumPoints = calculator->pointList();
    QString dataString;
    
    if(!m_filePath.isEmpty())
      {
        // Send all the data (that is, the points located in
        // QList<QPointF *>m_patternPointList) to the output file.
        
        QFile file(m_filePath);
        
        if(!file.open(QFile::WriteOnly | QFile::Truncate))
          {
            QMessageBox::warning(this, 
                                 tr("massXpert - Spectrum Calculator"),
                                 tr("Failed to export the isotopic pattern "
                                    "data to file."),
                                 QMessageBox::Ok);
            return;
          }
        
        QTextStream stream(&file);

        for(int iter = 0, size = spectrumPoints.size();
            iter < size ; ++iter)
          {
            QPointF *point = spectrumPoints.at(iter);
        
            dataString = QString().setNum(point->x(), 'f', 10);
            dataString += " ";
            dataString += QString().setNum(point->y(), 'f', 10);
            dataString += "\n";
            stream << dataString;
            // qDebug() << __FILE__ << __LINE__ << dataString;
          }
        
        stream.flush();
        file.close();

        // At this moment, tell the user in the log that the data have
        // been written to the given file.

        QString message;
        
        message += tr("\n\nData have been written to file %1\n\n")
          .arg(m_filePath);

        message += tr("\n\nAlso check the results tab\n\n");
        
        m_ui.logPlainTextEdit->appendPlainText(message);

        return;
      }
    else
      {
        for(int iter = 0, size = spectrumPoints.size();
            iter < size ; ++iter)
          {
            QPointF *point = spectrumPoints.at(iter);
            
            dataString += QString().setNum(point->x(), 'f', 10);
            dataString += " ";
            dataString += QString().setNum(point->y(), 'f', 10);
            dataString += "\n";
          }
        
        m_ui.resultPlainTextEdit->appendPlainText(dataString);

        // At this moment, tell the user in the log that the data have
        // been written to result tab.

        QString message;
        
        message += tr("\n\nData have been written to the results tab\n\n");

        m_ui.logPlainTextEdit->appendPlainText(message);

      }
    
    delete calculator;
  }
  

  void 
  SpectrumCalculationDlg::executeSpectrum()
  {
    // Clear the textEdit widget so that we can start out-putting text
    // into it, note that fetchValidateInputData() puts text in it.
    m_ui.resultPlainTextEdit->clear();
    m_ui.logPlainTextEdit->clear();

    // Verify that there are oligomers in the list.

    if(!mp_oligomerList)
      return;

    if(mp_oligomerList->isEmpty())
      return;

    if(!fetchValidateInputData())
      {
        QMessageBox::warning(0, 
                             tr("massXpert - Spectrum Calculator"),
                             tr("Please fix the input data first."),
                             QMessageBox::Ok);
        return;
      }

    // List of lists of QPointF, that is list of spectra.
    QList<QList<QPointF *> *> spectrumList;

    // Each time a new spectrum is calculated, store the corresponding
    // increment we'll need later to computed the summed final
    // spectrum.
    QHash<QList<QPointF *> *, double> incrementHash;

    // Before starting the calculations, swith to the log tab so that
    // the user can monitor what's going on.

    m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_LOG);
    
    if(m_withCluster)
      executeSpectrumWithCluster(&spectrumList, &incrementHash);
    else
      executeSpectrumWithoutCluster(&spectrumList, &incrementHash);


  // At this point, with one method or the other, we should have as
  // many allocated QList<QPointF *> * instances (that is, spectrum
  // instances) as there are oligomers. Make that sanity check.

  if(spectrumList.isEmpty())
    return;
    
  if(spectrumList.size() < mp_oligomerList->size())
    qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
  if(spectrumList.size() < incrementHash.size())
    qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);

  // qDebug() << __FILE__ << __LINE__
  //          << "Done generating the unitary spectra for each oligomer.";
  
  // Before computing the sum of all spectra to yield the last
  // spectrum, we have to perform the ordering of the spectra so
  // that the m/z regions covered by each are in an ascending order.

  // We want to sort the spectra in ascending order. There are two
  // concepts:

  // 1. one spectrum is greater than the other if its first point is
  //  greater than the other's.

  // 2. one spectrum is greater than the other, even if their first
  // points are equal, if its last point is greater then the
  // other's. That means two spectra starting at the same point are
  // different if they end at different points and the longest
  // spectrum sorts after the shortest.

  // Only sort if there are at least two spectra !

  m_ui.logPlainTextEdit->
    appendPlainText(tr("Sorting all oligomer-related sub-spectra..."));
  // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
  qApp->processEvents();

  if(spectrumList.size() >= 2)
    {
      bool sorted = false;
      
      while(!sorted)
        {
          bool changed = false;
          
          for(int iter = 0; iter < spectrumList.size() - 1; ++iter)
            {
              QList<QPointF *> *thisSpectrum = spectrumList.at(iter);
              QList<QPointF *> *nextSpectrum = spectrumList.at(iter + 1);
              
              QPointF *thisFirstPoint = thisSpectrum->first();
              QPointF *thisLastPoint = thisSpectrum->last();
              
              QPointF *nextFirstPoint = nextSpectrum->first();
              QPointF *nextLastPoint = nextSpectrum->last();
              
              if(thisFirstPoint->x() > nextFirstPoint->x())
                {
                  // Reverse the spectra in the list, as this spectrum
                  // is farther away in the mz axis than is the next
                  // spectrum.
                  
                  int thisIdx = iter;
                  int nextIdx = iter + 1;
                  
                  spectrumList.removeAt(thisIdx);
                  spectrumList.insert(nextIdx, thisSpectrum);
                  
                  changed = true;
                  
                  continue;
                }
              
              if(thisFirstPoint->x() == nextFirstPoint->x() &&
                 thisLastPoint->x() > nextLastPoint->x())
                {
                  // Reverse the spectra in the list, as this spectrum
                  // is longer than is the next spectrum.
                  
                  int thisIdx = iter;
                  int nextIdx = iter + 1;
                  
                  spectrumList.removeAt(thisIdx);
                  spectrumList.insert(nextIdx, thisSpectrum);

                  changed = true;
                  
                  continue;
                }
            }
          // End of
          // for(int iter = 0; iter < spectrumList.size() + 1; ++iter)
          // That is done making one iteration in the spectrum list.
              
          // If changed is true, then that means that we still have at
          // least one round to do to finalize the sorting. 

          // If changed is false, then the spectrum list is sorted !

          sorted = !changed;
        }
      // End of 
      // while(!sorted)
      // 
      // That is, finished sorting of the spectrum list.
    }
  // End of 
  // if(spectrumList.size() >= 2)

  m_ui.logPlainTextEdit->textCursor().insertText(" Done.\n");
  // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
  qApp->processEvents();

  // for(int iter = 0; iter < spectrumList.size(); ++iter)
  //   {
  //     QList<QPointF *> *spectrum = spectrumList.at(iter);
      
  //     QPointF *firstPoint = spectrum->first();
  //     QPointF *lastPoint = spectrum->last();
              
  //     qDebug() << __FILE__ << __LINE__
  //              << "List of sorted spectra:\n";
      
  //     qDebug()
  //       << "Spectrum index:" << iter
  //       << "spectrum: " << spectrum
  //       << "first point:" << firstPoint->x()
  //       << "last point:" << lastPoint->x();
  //   }

  m_ui.logPlainTextEdit->
    appendPlainText(tr("Computing sets of overlapping/non-overlapping "
                       "sub-spectra..."));
  // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
  qApp->processEvents();

  // Compute sets of overlapping spectra or of a single spectra
  // non-overlapping spectrum, without loosing the sorting made
  // before. One set of spectra is thus a list of lists of QPointF
  // instances.
  // To make things clear: 
  // 
  // A spectrum: QList<QPointF *>
  // A set of spectra: QList<QList<QPointF *> *>
  // A list of set of spectra: Qlist<QList<QList<QPointF *> *> *>
  QList<QList<QList<QPointF *> *> *>spectrumSetList;
  
  // Seed the iterations in the spectrum list at 0

  // During one iteration, all the members of the spectrum list are
  // tested to check if there is an overlap between these iterated
  // members and the seeded spectrum. Each time an overlap is found,
  // the new range border points are stored for the next test.

  // qDebug() << __FILE__ << __LINE__
  //          << "Before making spectral sets"
  //          << "spectrumList size:" << spectrumList.size();
  
  //  for(int iter = 0 ; iter < spectrumList.size() - 1; ++iter)
  while(!spectrumList.isEmpty())
    {
      QList<QPointF *> *thisSpectrum = spectrumList.first();
      
      // Allocate a new set of spectra that will hold all the spectra
      // that are overlapping with thisSpectrum.
      
      QList<QList<QPointF *> *> *newSpectrumSet = 
        new QList<QList<QPointF *> *>;

      // Transer right away thisSpectrum to the new set of overlapping
      // spectra (if it remains alone because no spectrum overlaps
      // with it, then fine.

      spectrumList.removeOne(thisSpectrum);
      newSpectrumSet->append(thisSpectrum);
      spectrumSetList.append(newSpectrumSet);
      
      // If there are overlaps, we'll have to document them using new
      // 'borders' of the current spectrum set. We seed these border's
      // points with thisSpectrum's first and last points.
      
      // double newRangeFirstPointX = thisSpectrum->first()->x();
      double newRangeLastPointX = thisSpectrum->last()->x();
      
      // qDebug() << __FILE__ << __LINE__
      //          << "\nNew set seeded with thisSpectrum:" << thisSpectrum
      //          << "\n\t with last point x:" << thisSpectrum->last()->x()
      //          << "\n\t and newRangeLastPointX:" << newRangeLastPointX;
      
      // Now iterate in each remaining spectrum and check the overlap
      // with thisSpectrum. Remark that we can start at jter = 0
      // because we already removed the seeding spectrum for the list.

      for(int jter = 0 ; jter < spectrumList.size(); ++jter)
        {
          QList<QPointF *> *nextSpectrum = spectrumList.at(jter);
          
          double nextFirstPointX = nextSpectrum->first()->x();
          // double nextLastPointX = nextSpectrum->last()->x();
      
          // Check if there is an overlap of thisSpectrum with
          // nextSpectrum. Because we have sorted the spectra in a
          // previous step, thisSpectrum is necessarily <=
          // nextSpectrum. So, make the tests accordingly.
      
          if(nextFirstPointX < newRangeLastPointX)
            {
              // There is an overlap, as next spectrum has its first
              // point located inside the first spectrum.
              
              // That means that we have to move nextSpectrum to the
              // set of overlapping spectra.
              
              spectrumList.removeOne(nextSpectrum);
              newSpectrumSet->append(nextSpectrum);
              
              // Update the new right border of the range.
              
              newRangeLastPointX = nextSpectrum->last()->x();

              // We removed a spectrum from the list, thus decrement
              // jter so as to start a new loop at what was the next
              // spectrum in the list prior to the removal.
              --jter;
            }
          else
            {
              // There is no overlap between the last processed
              // spectrum, thus we break, which will "close" this
              // current set and start a new one.

              break;
            }
        }
      // End of 
      // for(int jter = 0 ; jter < spectrumList.size(); ++jter)
    }
  // End of 
  // for(int iter = 0 ; iter < spectrumList.size() + 1; ++iter)

  // If there was a last item in the spectrumList, it is necessarily a
  // non-overlapping item, thus allocate a last spectrumSet and
  // append the item to it.

  if(spectrumList.size())
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  m_ui.logPlainTextEdit->textCursor().insertText(" Done.\n\n");
  // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
  qApp->processEvents();

  // At this point we have a list of spectrum sets which each might
  // contain one or more spectra. When more than one spectra are
  // contained in a spectrum set, then that means that they overlap
  // with each other.

  m_ui.logPlainTextEdit->
    textCursor().insertText(tr("Creating final spectrum... "));
  // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
  qApp->processEvents();

  // Iterate in each spectrum set and see how to create the final
  // spectrum.

  QList<QPointF *> finalSpectrum;

  double pointCount = 0;
  
  for(int iter = 0; iter < spectrumSetList.size(); ++iter)
    {
      QList<QList<QPointF *> *> *set = spectrumSetList.at(iter);
      
      // Only iterate in the spectrum set if it contains overlapping
      // spectra, that is more than one single spectrum. Otherwise
      // just dump all the points of the non-overlapping spectrum into
      // the final spectrum.
      
      if(set->size() == 1)
        {
          // Only one non-overlapping spectrum in the set, dump all
          // its points into the final spectrum.
          
          while(!set->first()->isEmpty())
            {
              finalSpectrum.append(set->first()->takeFirst());
              
              ++pointCount;
              if(pointCount >= 1000)
                {
                  m_ui.logPlainTextEdit->textCursor().insertText(". ");
                  // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
                  qApp->processEvents();
                  
                  pointCount = 0;

                  qApp->processEvents();
                }
            }
          
          continue;
        }
      
      // There are more than one spectrum in the set, that is these
      // spectra are overlapping. We have to ensure we sum all the
      // spectra one with the other. minMz is the first point of the
      // first spectrum of the set, while maxMz is the last point of
      // the last spectrum of the set.

      double minMz = set->first()->first()->x();
      double maxMz = set->last()->last()->x();

      double curMz = minMz;
      
      while(curMz <= maxMz)
        {
          // qDebug() << __FILE__ << __LINE__
          //          << "Handling mz value:" << curMz;
        
          // curMz now has a mz value for which we have to sum all the
          // intensities in each pointList.
      
          double summedIntensity = 0;
      
          // Iterate in each spectrum of the set of spectra and seek
          // the intensity at curMz. when found, add that intensity to
          // summedIntensity.

          double increment = 0;
        
          for(int jter = 0, jSize = set->size(); jter < jSize; ++jter)
            {
              QList<QPointF *> *curSpectrum = set->at(jter);
              
              increment = incrementHash.value(curSpectrum);
                
              // We are iterating in the list of points of a
              // spectrum. What is the intensity of the point at
              // x=curMz ? We provide a tolerance for x corresponding
              // to (increment / 2).
                
              for(int kter = 0,
                    kSize = curSpectrum->size(); kter < kSize; ++kter)
                {
                  QPointF *point = curSpectrum->at(kter);
                    
                  double mz = point->x();
                    
                  if(mz <= (curMz + increment / 2) &&
                     mz >= (curMz - increment / 2))
                    {
                      summedIntensity += point->y();
                        
                      break;
                    }
                  // Else, go on to next point. If not point is
                  // found at all, then, ok, we do not increment the
                  // summedIntensity variable.
                }
              // At this point we have finished iterating in a given
              // spectrum (that is a point list).
            }
          // We have finished iterating in all the spectra of the set
          // for the current mz. Make a point (curMz,summedIntensity).
            
          QPointF *newPoint = new QPointF(curMz, summedIntensity);

          finalSpectrum.append(newPoint);

          ++pointCount;
          if(pointCount >= 1000)
            {
              m_ui.logPlainTextEdit->textCursor().insertText(". ");
              // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
              qApp->processEvents();

              pointCount = 0;

              if(m_aborted)
                break;
            }
          
          // qDebug() << __FILE__ << __LINE__
          //          << "Added new point:"
          //          << "x:" << newPoint->x() << "," << "y:" << newPoint->y()
          //          << "\n";
                    
          curMz += increment;
        }
      // End of while(curMz <= maxMz). That is, we have finished
      // creating (x,y) pairs for the whole x axis, that is the mz
      // ratio axis.
    }
  // End of 
  // for(int iter = 0; iter < spectrumSetList.size(); ++iter)
  //
  // We have finished iterating in the set of spectra, that is we have
  // finished going through all the spectra, that is, we have finished
  // doing the final spectrum.
  
   // qDebug() << __FILE__ << __LINE__
   //          << "Ended the creation of the final spectrum.";

  m_ui.logPlainTextEdit->
    textCursor().insertText("\n\nDone creating the spectrum.\n\n");
  // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
  qApp->processEvents();

   // Finally, if the user had asked for the data to be sent to a
   // file... or to a string... Send the spectrum data either to the
   // file or to the text edit widget.
   
   QString dataString;
    
   if(!m_filePath.isEmpty())
     {
       // Send all the data (that is, the points located in
       // QList<QPointF *>m_patternPointList) to the output file.
        
       QFile file(m_filePath);
        
       if(!file.open(QFile::WriteOnly | QFile::Truncate))
         {
           QMessageBox::warning(this, 
                                tr("massXpert - Spectrum Calculator"),
                                tr("Failed to export the isotopic pattern "
                                   "data to file."),
                                QMessageBox::Ok);
           return;
         }
        
       QTextStream stream(&file);

       m_ui.logPlainTextEdit->
         textCursor().insertText("Writing data to file... ");
       // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
       qApp->processEvents();

       for(int iter = 0, size = finalSpectrum.size();
           iter < size ; ++iter)
         {
           QPointF *point = finalSpectrum.at(iter);
        
           dataString += QString().setNum(point->x(), 'f', 10);
           dataString += " ";
           dataString += QString().setNum(point->y(), 'f', 10);
           dataString += "\n";
           
           // Stream the data by packets of 4096 or less characters.
           if(dataString.size() > 4096)
             {
               stream << dataString;
               dataString.clear();

               m_ui.logPlainTextEdit->textCursor().insertText(". ");
               // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
               qApp->processEvents();

               if(m_aborted)
                 break;
             }
           // qDebug() << __FILE__ << __LINE__ << dataString;
         }
        
       // One last time:
       stream << dataString;
       dataString.clear();

       // And finally:
       stream.flush();
       file.close();
        
       m_ui.logPlainTextEdit->
         textCursor().insertText("\n\nDone writing "
                                 "data to file.\n\n");
       // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
       qApp->processEvents();

       return;
     }
   else
     {
       m_ui.logPlainTextEdit->
         textCursor().insertText("Writing data to results tab... ");
       // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
       qApp->processEvents();

       for(int iter = 0, size = finalSpectrum.size();
           iter < size ; ++iter)
         {
           QPointF *point = finalSpectrum.at(iter);
            
           dataString += QString().setNum(point->x(), 'f', 10);
           dataString += " ";
           dataString += QString().setNum(point->y(), 'f', 10);
           dataString += "\n";

           // Append the data by packets of 4096 or less characters.
           if(dataString.size() > 4096)
             {
               m_ui.resultPlainTextEdit->appendPlainText(dataString);
               dataString.clear();

               m_ui.logPlainTextEdit->textCursor().insertText(". ");
               // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
               qApp->processEvents();
               
               if(m_aborted)
                 break;
             }
           
         }
        
       // One last time:
       m_ui.resultPlainTextEdit->appendPlainText(dataString);
       dataString.clear();

       m_ui.logPlainTextEdit->
         textCursor().insertText("\n\nDone writing "
                                 "data to results tab.\n\n");
       // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
       qApp->processEvents();
     }


    // Finally, we have a bunch of data to free.
    
    QPointF *point = 0;

    // Free all the spectrum sets along with all the QPointF instances
    // contained in their spectra.
    while(!spectrumSetList.isEmpty())
      {
        QList<QList<QPointF *> *> *spectrumList = spectrumSetList.takeFirst();
        
        for(int iter = 0, size = spectrumList->size(); iter < size; ++iter)
          {
            QList<QPointF *> *curSpectrum = spectrumList->at(iter);
            
            foreach(point, *curSpectrum)
              delete point;
        
            curSpectrum->clear();

            delete curSpectrum;
          }

        delete spectrumList;
      }
    
    // qDebug() << __FILE__ << __LINE__
    //          << "Done freeing up material.";
    
    foreach(point, finalSpectrum)
      {
        // qDebug() << point->x() << " " << point->y();
        
        delete point;
      }
        
    finalSpectrum.clear();
  }
  

  void 
  SpectrumCalculationDlg::executeSpectrumWithCluster
  (QList<QList<QPointF *> *> *spectrumList, 
   QHash<QList<QPointF *> *, double> *incrementHash)
  {
    if(!spectrumList)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    if(!incrementHash)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    m_ui.logPlainTextEdit->
      textCursor().insertText(tr("Simulating a spectrum "
                                 "with calculation of an isotopic "
                                 "cluster for each oligomer.\n\n"));
    
    m_ui.logPlainTextEdit->
      textCursor().insertText(tr("There are %1 oligomers\n"
                                 "Calculating sub-spectrum "
                                 "for each\n\n")
                              .arg(mp_oligomerList->size()));
    // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
    qApp->processEvents();

    for(int iter = 0,
          size = mp_oligomerList->size(); iter < size; ++iter)
      {
        Oligomer *oligomer = mp_oligomerList->at(iter);
        
        // 1. Does the user want a isotopic cluster calculation for
        // each oligomer in the list? If so we have to manage a list
        // of IsotopicPatternCalculator instances.

        // Since we have to perform an isotopic cluster
        // calculation, retrieve the elemental composition.
        StringProp *prop = 
          static_cast<StringProp *>(oligomer->
                                    prop("ELEMENTAL_COMPOSITION"));
            
        if(!prop)
          {
            // qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

            QMessageBox::warning(this, 
                                tr("massXpert - Spectrum Calculator"),
                                tr("Elemental composition not found for "
                                   "current oligomer.\n Uncheck the Isotopic "
                                   "cluster checkbox for this calculation."),
                                QMessageBox::Ok);

            return;
          }
            
        QString *formulaString = static_cast<QString *>(prop->data());
            
        Formula formula(*formulaString);

        m_ui.logPlainTextEdit->
          textCursor().insertText(tr("Computing isotopic cluster"
                                     " for oligomer %1\n"
                                     "\tformula: %2.\n")
                                  .arg(iter + 1)
                                  .arg(*formulaString));
        
        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
        qApp->processEvents();
        
        // We want to validate the formula and in the mean time
        // construct the list of all the AtomCount objects(first
        // param true), and since the formula is never reused we
        // do not need to reset (second param false). m_atomList
        // is a reference to the atom list of the polymer
        // chemistry definition currently used in the caller
        // calculator window.

        m_ui.logPlainTextEdit->
          textCursor().insertText(tr(" Validating formula... "));
        qApp->processEvents();

        if(!formula.validate(m_atomList, true, false))
          {
            m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error"));
                
            m_ui.logPlainTextEdit->textCursor().insertText(tr(" Failure.\n\n"));
            // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
            qApp->processEvents();

            return;
          }

        m_ui.logPlainTextEdit->textCursor().insertText(tr("Success.\n"));
        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
        qApp->processEvents();
        
        double mono = oligomer->mono();
        double charge = oligomer->charge();

        // Because we have the mono m/z, we can compute the fwhm:
        double fwhm = (mono / m_resolution);
        m_config.setFwhm(fwhm);
            
        double increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) / 
          m_config.pointNumber();

        // But we have to take into account the charge:
        if(charge)
          increment = increment / charge;
            
        m_config.setIncrement(increment);
            
        m_ui.logPlainTextEdit->
          textCursor().insertText(tr("\tmono m/z: %1\n"
                                     "\tcharge: %2\n"
                                     "\tfwhm: %3\n"
                                     "\tincrement: %4\n\n")
                                  .arg(mono)
                                  .arg(charge)
                                  .arg(fwhm)
                                  .arg(increment));
        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
        qApp->processEvents();

        // qDebug() << __FILE__ << __LINE__
        //          << "index:" << iter << "\n"
        //          << "formula: " << formula.formula() << "\n"
        //          << "charge:" << charge << "\n"
        //          << "mono m/z:" << mono << "\n"
        //          << "avg m/z:" << oligomer->avg() << "\n"
        //          << "resolution:" << m_resolution << "\n"
        //          << "fwhm:" << fwhm << "\n"
        //          << "Whole config:" << m_config.config();
            
        IsotopicPatternCalculator *calculator = 
          new IsotopicPatternCalculator(formula, charge,
                                        m_maximumPeaks, 
                                        m_minimumProbability,
                                        m_atomList, m_config);
            
        // Perform the calculation...
        const QList<QPointF *>  &pointList = calculator->sumPeakShapes();

        if(pointList.isEmpty())
          qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
            
        QList<QPointF *> *newPointList = new QList<QPointF *>;

        calculator->transferPoints(newPointList);
            
        spectrumList->append(newPointList);

        m_ui.logPlainTextEdit->
          textCursor().insertText(tr("\t\tDone computing the cluster\n\n"));
        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
        qApp->processEvents();
        
        // Store along with the point list, the increment that was
        // used for that calculation. We'll need it later when
        // computing the whole spectrum.

        incrementHash->insert(newPointList, m_config.increment());
            
        // At this point we have the points, we can delete the
        // calculator.
        delete calculator;

        if(m_aborted)
          break;
      }
    // End of 
    // for(int iter = 0, 
    //         size = mp_oligomerList->size(); iter < size; ++iter)

    m_ui.logPlainTextEdit->
      textCursor().insertText(tr("Done computing *all* the clusters\n\n"));
    // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
    qApp->processEvents();
  }
    
  
  void 
  SpectrumCalculationDlg::executeSpectrumWithoutCluster
    (QList<QList<QPointF *> *> *spectrumList, 
     QHash<QList<QPointF *> *, double> *incrementHash)
  {
    if(!spectrumList)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);
    
    if(!incrementHash)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    m_ui.logPlainTextEdit->
      textCursor().insertText(tr("Simulating a spectrum "
                                 "without calculation of isotopic "
                                 "clusters for each "
                                 "oligomer.\n\n"));
    
    m_ui.logPlainTextEdit->appendPlainText(tr("There are %1 oligomers\n"
                                              "Calculating sub-spectrum "
                                              "for each\n\n")
                                           .arg(mp_oligomerList->size()));
    // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
    qApp->processEvents();

    for(int iter = 0,
          size = mp_oligomerList->size(); iter < size; ++iter)
      {
        Oligomer *oligomer = mp_oligomerList->at(iter);
        
        // Not asking for an isotopic cluster to be computed for
        // each oligomer. The user want the mono or avg mass of
        // the oligomer to be considered the centroid of a peak
        // 
        double mz = 0;

        if(m_withMonoMass)
          mz = oligomer->mono();
        else
          mz = oligomer->avg();

        double charge = oligomer->charge();

        PeakCentroid centroid(mz, 100, 1);
            
        // Because we have the m/z, we can compute the fwhm:
        double fwhm = (mz / m_resolution);
        m_config.setFwhm(fwhm);
            
        double increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) / 
          m_config.pointNumber();

        // But we have to take into account the charge:
        if(charge)
          increment = increment / charge;
            
        m_config.setIncrement(increment);

        m_ui.logPlainTextEdit->
          textCursor().insertText(tr("Computing peak shape for oligomer %1\n")
                                  .arg(iter + 1));
        
        if(m_withMonoMass)
          m_ui.logPlainTextEdit->
            textCursor().insertText(tr("\tmono m/z: %1\n")
                                    .arg(mz));
        else
          m_ui.logPlainTextEdit->
            textCursor().insertText(tr("\tavg m/z: %1\n")
                            .arg(mz));
        
        m_ui.logPlainTextEdit->
          textCursor().insertText(tr("\tcharge: %2\n"
                                     "\tfwhm: %3\n"
                                     "\tincrement: %4\n\n")
                                  .arg(charge)
                                  .arg(fwhm)
                                  .arg(increment));
        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
        qApp->processEvents();
        
        // qDebug() << __FILE__ << __LINE__
        //          << "index:" << iter << "\n"
        //          << "charge:" << charge << "\n"
        //          << "m/z:" << mz << "\n"
        //          << "avg m/z:" << oligomer->avg() << "\n"
        //          << "resolution:" << m_resolution << "\n"
        //          << "fwhm:" << fwhm << "\n"
        //          << "Whole config:" << m_config.config();

        PeakShape *shape = new PeakShape(centroid, m_config);
            
        shape->calculatePeakShape();
            
        const QList<QPointF *>  &pointList = shape->pointList();

        if(pointList.isEmpty())
          qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);
                  
        QList<QPointF *> *newPointList = new QList<QPointF *>;
            
        shape->transferPoints(newPointList);

        spectrumList->append(newPointList);

        m_ui.logPlainTextEdit->
          textCursor().insertText(tr("\t\tDone computing the peak shape\n\n"));
        // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
        qApp->processEvents();

        // Store along with the point list, the increment that was
        // used for that calculation. We'll need it later when
        // computing the whole spectrum.

        incrementHash->insert(newPointList, m_config.increment());
            
        // At this point we have the points, we can delete the
        // peack shape.
        delete shape;

        if(m_aborted)
          break;
      }
    // End of 
    // for(int iter = 0, 
    //         size = mp_oligomerList->size(); iter < size; ++iter)

    m_ui.logPlainTextEdit->
      textCursor().insertText(tr("Done computing *all* the peak shapes\n\n"));
    // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down);
    qApp->processEvents();
  }


  void 
  SpectrumCalculationDlg::spectrumCalculationProgressValueChanged
  (int value)
  {
    m_ui.progressBar->setValue(value);
    // qDebug() << __FILE__ << __LINE__
    //          << "spectrumCalculationProgressValueChanged:" << value;
    qApp->processEvents();
  }
  

  void 
  SpectrumCalculationDlg::spectrumCalculationMessageChanged
  (QString text)
  {
    m_ui.logPlainTextEdit->appendPlainText(text);
    // qDebug() << __FILE__ << __LINE__
    //          << "spectrumCalculationMessageChanged:" << text;
    qApp->processEvents();
  }
  

  void 
  SpectrumCalculationDlg::abort()
  {
    m_aborted = true;
    emit(spectrumCalculationAborted());
  }
  

  void 
  SpectrumCalculationDlg::outputFile()
  {
    QString name;
  
    m_filePath = 
      QFileDialog::getSaveFileName(this, tr("Export Raw Text File"),
				    QDir::homePath(),
				    tr("Any file type(*)"));
  }

  void 
  SpectrumCalculationDlg::debugPutStdErrBitset(QString file, int line,
                                                      int value,
                                                      const QString &text)
  {
    qDebug() << file << line << "\n"
             << GlobBinaryRepresentation(MXP_VALIDATION_ERRORS_NONE)
             << ": MXP_VALIDATION_ERRORS_NONE" << "\n"
             << GlobBinaryRepresentation(MXP_VALIDATION_FORMULA_ERRORS)
             << ": MXP_VALIDATION_FORMULA_ERRORS" << "\n"
             << GlobBinaryRepresentation(MXP_VALIDATION_RESOLUTION_ERRORS)
             << ": MXP_VALIDATION_RESOLUTION_ERRORS" << "\n"
             << GlobBinaryRepresentation(MXP_VALIDATION_FWHM_ERRORS)
             << ": MXP_VALIDATION_FWHM_ERRORS" << "\n"
             << "\n"
             << GlobBinaryRepresentation(value)
             << ":" << text << ":" << value
             << "\n";
  }
  
} // namespace massXpert



#if 0
  /***************************************************************************
   *  file                 :  ipc.c                                          *
   *  copyright            :(C) 2001-2005 by Dirk Nolting                   *
   *  email                : nolting@uni-duesseldorf.de                      *
   ***************************************************************************/

  /***************************************************************************
   *                                                                         *
   *   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~2 of the License, or     *
   *  (at your option) any later version.                                   *
   *                                                                         *
   ***************************************************************************/

#include "global.h"
#include "ipc.h"
#include "pars.h"
#include "element.h"
#include "gp_out.h"
#include <time.h>
#include <signal.h>
#include <math.h>

//#define SUMMARIZE_LEVEL 0.00000001
//#define SUMMARIZE_LEVEL 0.0001
//#define SUMMARIZE_LEVEL 0.001
#define SUMMARIZE_LEVEL 0.01

  compound *verbindung=NULL;
  isotope *m_peakList;
  int fast_calc=0;

  void free_list(isotope *target)
  {
    while(target->next)
      {
	target=target->next;
	free(target->previous);
      }
    free(target);
  }

  void cut_peaks(isotope *spectrum)
  {
    int dummy=1;

    while((spectrum->next) &&(dummy<fast_calc) )
      {
	++dummy;
	spectrum=spectrum->next;
      }

    if(spectrum->next)
      {
	free_list(spectrum->next);
	spectrum->next=NULL;
      }
  }

  void summarize_peaks()
  {  isotope *dummy,*d2;

    for(dummy=m_peakList;dummy;dummy=dummy->next)
      /* Differenz wegen Rundungsfehlern */
      while( dummy->next &&(dummy->next->mass - dummy->mass < SUMMARIZE_LEVEL) )
	{
	  d2=dummy->next;
	  dummy->next=d2->next;
	  if(dummy->next)
	    dummy->next->previous=dummy;
	  dummy->p+=d2->p;
	  free(d2);
	}
  }

  isotope *add_peak(isotope *base,isotope *peak)
  {
    static isotope *IsotopeIterator;

    if(!(base->mass))
      {
	peak->next=NULL;
	peak->previous=NULL;
	IsotopeIterator = peak;
	return peak;
      }

    if( peak->mass >= IsotopeIterator->mass )
      while((IsotopeIterator->next) &&(IsotopeIterator->mass < peak->mass))
	IsotopeIterator=IsotopeIterator->next;
    else
      {
	while((IsotopeIterator->previous) &&(IsotopeIterator->mass > peak->mass) )
	  IsotopeIterator=IsotopeIterator->previous;
	IsotopeIterator=IsotopeIterator->next;
      }

    if((IsotopeIterator->mass) >=(peak->mass) )
      {
	peak->next=IsotopeIterator;
	peak->previous=IsotopeIterator->previous;
	peak->previous->next=peak;
	IsotopeIterator->previous=peak;
	return base;
      }
    else
      {
	IsotopeIterator->next=peak;
	peak->next=NULL;
	peak->previous=IsotopeIterator;
	return base;
      }
    return 0;
  }

  int calculate_peaks(){
    compound *c;
    isotope *newPeakList,*p,*i,*np1;
    int amount;

    if(!(m_peakList=malloc(sizeof(isotope))))
      return 0;
    m_peakList->mass=0;
    m_peakList->p=1;
    m_peakList->previous=NULL;
    m_peakList->next=NULL;

    for (c = verbindung; c; c = c->next)
      {
	for(amount = 0; amount < c->amount; ++amount)
	  {
	    if (!(newPeakList=malloc(sizeof(isotope))))
	      return 0;
	  
	    newPeakList->mass = 0;
	  
	    for (p = m_peakList; p; p = p->next)
	      {
		for(i = c->isotopes; i; i = i->next)
		  {
		    //printf("working on isotope of mass %f\n", i->mass);

		    if (!(np1 = malloc(sizeof(isotope))))
		      return 0;
		  
		    np1->mass=p->mass + i->mass;
		    np1->p=p->p * i->p;
		  
		    if(!(newPeakList = add_peak(newPeakList,np1)))
		      return 0;
		  }
	      }
	  
	    free_list(m_peakList);
	    m_peakList = newPeakList;
	    summarize_peaks();
	  
	    if (fast_calc)
	      cut_peaks(m_peakList);
	  }
      }
  
    return 1;
  }


  void print_result(int digits,int charge){
    isotope *d;
    double maxp=0,relint=0,sump=0;
    int permutationen=0;

    printf("\n");
 
    for(d=m_peakList;d;d=d->next)
      {
	++permutationen;
	sump+=d->p;
	d->mass=d->mass / charge;
	d->mass=(rint( d->mass * pow(10,digits) ) / pow(10,digits) );
      }

    summarize_peaks();
    for(d=m_peakList;d;d=d->next)
      if(d->p > maxp)
	maxp=d->p;

    for(d=m_peakList;d;d=d->next)
      {
	if(( relint=(d->p/maxp)*100) > MIN_INT )
	  printf("M= %f, p= %e, rel. Int.= %f%%\n",
		 d->mass,d->p,relint);
      }
    if(!(fast_calc))
      printf("\nNumber of  permutations: %i\n",permutationen);
    else
      {
	sump=(rint(sump*10000)/100); 
	printf("\nCovered Intensity: %2.2f%% \n",sump);
      }
  }

  int main(int argc,char **argv){
    long seconds;
    int d=1,zeig_summenformel=0,calc_peaks=1,gnuplot=0,use_digits=USE_DIGITS,charge=1;
    char *gnuplotfile=NULL;
  
    if(!argv[d])
      {
	usage();
	return 1;
      }

#ifdef HAVE_SIGNAL
    signal(SIGHUP,SIG_IGN);
#endif

    if(!init_elements()){
      printf("Error in init_elements\n");
      return 1;
    }
    while(argv[d])
      {
	if(!strcmp(argv[d],"-f"))
	  {
	    ++d;
	    if(!argv[d])
	      {
		printf("Missing argument for -f\n");
		return 1;
	      }
	    fast_calc=strtol(argv[d],NULL,10);
	  }

	else if(!strcmp(argv[d],"-z"))
	  {
	    ++d;
	    if(!argv[d])
	      {
		printf("Missing argument for -z\n");
		return 1;
	      }
	    charge=strtol(argv[d],NULL,10);
	  }

	else if(!strcmp(argv[d],"-d"))
	  {
	    ++d;
	    if(!argv[d])
	      {
		printf("Missing argument for -d\n");
		return 1;
	      }
	    use_digits=strtol(argv[d],NULL,10);
	  }

	else if(!strcmp(argv[d],"-c")){
	  ++d;
	  if(!argv[d])
	    {
	      printf("Missing argument for -c\n");
	      return 1;
	    }
	  if(!pars_chem_form(argv[d])){
	    printf("Parser error.\n");
	    return 1;
	  }
	}

	else if(!strcmp(argv[d],"-g")){
	  ++d;
	  if(!argv[d]){
	    printf("Missing argument for -g\n");
	    return 1;
	  }
	  gnuplot=1;
	  if(!(gnuplotfile=strdup(argv[d]))){
	    printf("Not enough memory\n");
	    return 1;
	  }
	}


	else if(!strcmp(argv[d],"-p")){
	  ++d;
	  if(!argv[d]){
	    printf("Missing argument for -p\n");
	    return 1;
	  }
	  if(!(pars_peptid(argv[d]))){
	    printf("Error in peptid parser.\n");
	    return 1;
	  }
	}

	else if(!strcmp(argv[d],"-a")){
	  ++d;
	  if(!argv[d]){
	    printf("Missing argument for -a\n");
	    return 1;
	  }
	  if(!pars_amino_acid(argv[d])){
	    printf("Error in pars_amino_acid.\n");
	    return 1;
	  }
	}

	else if(!strcmp(argv[d],"-s"))
	  zeig_summenformel=1;

	else if(!strcmp(argv[d],"-h"))
	  {
	    usage();
	    return 1;
	  }

	else if(!strcmp(argv[d],"-x"))
	  calc_peaks=0;

	else
	  {
	    printf("Unknown flag: %s\n",argv[d]);
	    usage();
	    return 1;
	  }
	++d;
      }

    if(zeig_summenformel)
      {
	if(!print_sum())
	  {
	    printf("error while showing chemical formula.\n");
	    return 1;
	  }
      }
#ifdef HAVE_TIME
    seconds=time(0);
#endif

    if(calc_peaks)
      if(!calculate_peaks()){
	printf("Error in calculate_peaks\n");
	return 1;
      }

    print_result(use_digits,charge);

#ifdef HAVE_TIME
    seconds=time(0)-seconds;
    printf("Computing time: %li seconds.\n",seconds);
#endif

    if(gnuplot)
      if(!(make_gnuplot_output(gnuplotfile))){
	printf("Error while generating gnuplot file\n");
	return 1;
      }

    return 0;
  }


  void usage()
  {
    printf("\nThis is IPC v %s\nCopyright Dirk Nolting 2001-2005\n\n",VERSION);
    printf("\nSynopsis:\n ipc -d <int> -z <int> -f <int> <-a <amino acid> -c <chemical formula> -p <File> -g <name> -s -x -h\n\n");

    printf("-c <chemical formula> calculates the isotopic pattern of the \n");
    printf("   given chemical formula. Additive with -p\n");

    printf("-p <File> reads peptide sequenz(one letter notation) from the \n");
    printf("   given file and calculates the isotopic pattern. Additive with -c\n");

    printf("-a <amino acids> calculate isotopic pattern from given amino acids\n");
    printf("   in one letter notation\n");

    printf("-s gives the chemical formula. Usefull for -a or -p\n");
    printf("-g creates gnuplot output and stores it in the file <name> and <name>.gnu\n");

    printf("-x no calculation of the isotopic pattern. Usefull for -s\n");
    printf("-f fast calc, calculates only first <int> peaks\n");
    printf("-d <int> digits significant\n");
    printf("-z assume <int> charges on ion \n");
    printf("-h show this text\n\n");
  }
#endif
