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


/*
   NOTE : This work is based on a previous work by Dirk Nolting,
   licensed under the GNU General Public License:

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


/////////////////////// Qt includes
#include <QtGui>


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

using namespace std;

/////////////////////// Local includes
#include <libmass/IsotopicPatternCalculator.hpp>


namespace msXpSlibmass
{
IsotopicPatternCalculator::IsotopicPatternCalculator(
  Formula formula,
  int charge,
  int maxPeakCentroids,
  double minProb,
  const QList<Atom *> &atomRefList,
  PeakShapeConfig config)
  : m_formula(formula),
    m_charge(charge),
    m_maxPeakCentroids(maxPeakCentroids),
    m_minProb(minProb),
    m_atomRefList(atomRefList),
    m_config(config)
{
  if(m_atomRefList.isEmpty())
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  if(!m_formula.validate(m_atomRefList, true, true))
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  if(m_charge <= 0)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  if(m_config.fwhm() <= 0)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  if(m_maxPeakCentroids < 1)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  m_sumProbabilities    = 0;
  m_greatestProbability = 0;

  m_aborted = false;
  // Check every n isotopes if we should abort.
  m_abortCheckCount = 100;

  m_progressValueOld = 0;
  m_progressValueNew = 0;

  m_formula.deepAtomCopy(m_atomRefList);

  // Debug stuff: make sure we get a formula that's correct.
  // const QList<AtomCount *> &atomCountList = m_formula.atomCountList();

  // qDebug() << "m_formula:" << m_formula.formula() << "\n";

  // for(int iter = 0; iter < atomCountList.size(); ++iter)
  //   {
  //     AtomCount *atomCount = atomCountList.at(iter);

  //     qDebug() << "atomCount name:" << atomCount->name()
  //              << "symbol:" << atomCount->symbol();

  //     const QList<Isotope *> &isotopeList = atomCount->isotopeList();

  //     for(int jter = 0; jter < isotopeList.size(); ++jter)
  //       {
  //         Isotope *isotope = isotopeList.at(jter);


  //         qDebug() << "isotope mass:" << isotope->mass();
  //       }
  //   }
}


IsotopicPatternCalculator::~IsotopicPatternCalculator()
{
  freeClearPeakCentroidList();
  freeClearTempPeakCentroidList();
  freeClearPeakShapeList();
  freeClearPointList();
}


void
IsotopicPatternCalculator::isotopicCalculationAborted()
{
  m_aborted = true;
  emit(isotopicCalculationMessageChanged(tr("Aborting...")));
}


const QList<PeakCentroid *> &
IsotopicPatternCalculator::peakCentroidList() const
{
  return m_peakCentroidList;
}


const QList<QPointF *> &
IsotopicPatternCalculator::pointList() const
{
  return m_pointList;
}


QString *
IsotopicPatternCalculator::peakCentroidListAsString() const
{
  QString *text = new QString();

  for(int iter = 0, size = m_peakCentroidList.size(); iter < size; ++iter)
    {
      PeakCentroid *peakCentroid = m_peakCentroidList.at(iter);
      text->append(QString().setNum(peakCentroid->mz(), 'f', 6));
      text->append(" ");
      text->append(
        QString().setNum(peakCentroid->relativeIntensity(), 'f', 15));
      text->append("\n");
    }

  return text;
}


bool
IsotopicPatternCalculator::seedPeakCentroidList()
{
  // Let's seed all the calculations with the first centroid peak.
  PeakCentroid *firstPeakCentroid = new PeakCentroid(0, 0, 0);
  firstPeakCentroid->setMz(0);
  firstPeakCentroid->setProbability(1);

  freeClearPeakCentroidList();

  m_peakCentroidList.append(firstPeakCentroid);

  return true;
}


void
IsotopicPatternCalculator::freeClearPeakCentroidList()
{
  while(!m_peakCentroidList.isEmpty())
    delete m_peakCentroidList.takeFirst();
}


void
IsotopicPatternCalculator::freeClearTempPeakCentroidList()
{
  while(!m_tempPeakCentroidList.isEmpty())
    delete m_tempPeakCentroidList.takeFirst();

  m_tempPeakCentroidList.clear();
}


void
IsotopicPatternCalculator::freeClearPeakShapeList()
{
  while(!m_peakShapeList.isEmpty())
    delete m_peakShapeList.takeFirst();

  m_peakShapeList.clear();
}


void
IsotopicPatternCalculator::freeClearPointList()
{
  while(!m_pointList.isEmpty())
    delete m_pointList.takeFirst();

  m_pointList.clear();
}


int
IsotopicPatternCalculator::calculatePeakCentroids()
{
  // Get a shortcut handle to the list of AtomCount object that were
  // set in the formula during the validation in the constructor
  // this *this object.

  // One atom count instance is made of two entities : an atom and
  // the number of times that atom occurs in the formula. In a
  // formula, thus, there can be atomCounts [C:4] [H:10] and so
  // on. For the atomCount [C:4] (4 atoms of carbon in the formula),
  // we'll have to account for all the carbon isotopes, 4 times in
  // sequence. That is handled in the function call
  // accountAtomCount() below.

  freeClearTempPeakCentroidList();

  seedPeakCentroidList();

  // Set the m_progressValue to 0, as we are going to make a
  // calculation. Each time 10 new isotopes are added, we increment
  // that value and emit a signal so that the progress bar can be
  // updated in the calling dialog window.
  m_progressValueOld = 0;
  m_progressValueNew = 0;

  emit(isotopicCalculationMessageChanged(tr("Starting calculations.")));
  qApp->processEvents();

  const QList<AtomCount *> &atomCountList = m_formula.atomCountList();

  for(int iter = 0, atomCountCount = atomCountList.size();
      iter < atomCountCount;
      ++iter)
    {
      // Get the currently iterated atom count instance ([C:4], for
      // example).
      AtomCount *atomCount = atomCountList.at(iter);

      // Account for these atoms, in sequence.
      accountAtomCount(atomCount);
    }

  emit(
    isotopicCalculationMessageChanged(tr("Calculating sum of probabilities")));
  calculateSumOfProbabilities();

  emit(
    isotopicCalculationMessageChanged(tr("Calculating relative intensities")));
  calculateRelativeIntensity();

  emit(isotopicCalculationMessageChanged(
    tr("Removing too low probability peaks")));
  removeTooLowProbPeaks();

  if(m_aborted)
    emit(isotopicCalculationMessageChanged(tr("Aborted")));
  else
    emit(isotopicCalculationMessageChanged(
      tr("Done computing the isotopic peaks.")));

  qApp->processEvents();

  return 1;
}


int
IsotopicPatternCalculator::calculatePeakShapes()
{
  // Once all the peak centroids have been computed, it necessary to
  // produce the peak shapes for each one.

  if(m_peakCentroidList.isEmpty())
    {
      int res = calculatePeakCentroids();

      if(!res)
        return 0;
    }

  for(int iter = 0, peakCentroidListSize = m_peakCentroidList.size();
      iter < peakCentroidListSize;
      ++iter)
    {
      PeakCentroid *peakCentroid = m_peakCentroidList.at(iter);

      double mz     = peakCentroid->mz();
      double relInt = peakCentroid->relativeIntensity();

      PeakShape *peakShape =
        new PeakShape(mz, relInt, 0 /* probability */, m_config);

      peakShape->calculatePeakShape();

      m_peakShapeList.append(peakShape);
    }

  return m_peakShapeList.size();
}


const QList<QPointF *> &
IsotopicPatternCalculator::sumPeakShapes()
{
  // Clear the point list.
  freeClearPointList();

  // Make sure that we have computed each individual peak shape for
  // the centroids. If not, then do it first.

  int peakShapeListSize = m_peakShapeList.size();

  if(!peakShapeListSize)
    {
      if(!calculatePeakShapes())
        return m_pointList;

      peakShapeListSize = m_peakShapeList.size();
    }

  // At this point, each centroid peak, that is each
  // mzRatio/intensity pair in m_peakCentroidList has given rise to
  // a full gaussian/lorentzian curve that's been appended in the
  // list of peak shapes.

  // We now have to perform the sum of all these peak shapes so as
  // to generate a single (x,y) curve that encompasses all of the
  // isotopic cluster.

  // We need to know the min and max mzRatio values of all the
  // curves.

  // Peak shapes should be generated from left to right in the
  // pointList spectral view, that is from lowest mz to higest
  // mz. So, logically, the first point of the first peak shape
  // should be the point of lowest mz. Likewise, the last point of
  // the last peak shape should be the highest mz.

  PeakShape *peakShape = 0;
  QPointF *point       = 0;

  // Get first point of the first peakShape and its x value.

  peakShape    = m_peakShapeList.first();
  point        = peakShape->pointList().first();
  double minMz = point->x();

  peakShape    = m_peakShapeList.last();
  point        = peakShape->pointList().last();
  double maxMz = point->x();


#ifdef NOT_DEFINED

  // Old version that is more CPU-consuming.

  double tempMz = 0;
  double maxMz  = 0;
  double minMz  = 1000000000;

  for(int iter = 0; iter < peakShapeListSize; ++iter)
    {
      PeakShape *peakShape = m_peakShapeList.at(iter);

      // Get to the list of points in the peak shape
      const QList<QPointF *> &peakCentroidList = peakShape->pointList();

      // Get first point of the peakShape.
      QPointF *point = peakCentroidList.first();

      // Get the minimum mz
      tempMz = point->x();

      if(tempMz < minMz)
        minMz = tempMz;

      // Get last point of the peakShape.
      point = peakCentroidList.last();

      // Get the maximum mz
      tempMz = point->x();

      if(tempMz > maxMz)
        maxMz = tempMz;
    }
#endif

  // At this point we know that our mass point list (the spectral
  // view of the isotopic cluster) should range [minMz -- maxMz]. We
  // can perform the actual sum of all the peak shapes. The result
  // of the sum should go into the spectral list of points.

  double curMz = minMz;

  while(curMz <= maxMz)
    {
      // curMz now has a mz value for which we have to sum all the
      // intensities in each curve.

      double summedIntensity = 0;

      for(int iter = 0; iter < peakShapeListSize; ++iter)
        {
          PeakShape *peakShape = m_peakShapeList.at(iter);

          bool ok = false;

          summedIntensity +=
            peakShape->intensityAt(curMz, m_config.increment(), &ok);
        }

      QPointF *point = new QPointF(curMz, summedIntensity);

      m_pointList.append(point);

      curMz += m_config.increment();
    }

  return m_pointList;
}


QString *
IsotopicPatternCalculator::pointListAsString() const
{
  QString *text = new QString();

  for(int iter = 0, size = m_pointList.size(); iter < size; ++iter)
    {
      QPointF *point = m_pointList.at(iter);

      text->append(QString().setNum(point->x(), 'f', 10));
      text->append(" ");
      text->append(QString().setNum(point->y(), 'f', 10));
      text->append("\n");
    }

  return text;
}


QList<QPointF *> *
IsotopicPatternCalculator::duplicatePointList() const
{
  QList<QPointF *> *pointList = new QList<QPointF *>;

  for(int iter = 0, size = m_pointList.size(); iter < size; ++iter)
    {
      QPointF *point = new QPointF(*(m_pointList.at(iter)));
      pointList->append(point);
    }

  return pointList;
}


int
IsotopicPatternCalculator::transferPoints(QList<QPointF *> *pointList)
{
  if(!pointList)
    qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);

  int count = 0;

  while(m_pointList.size())
    {
      pointList->append(m_pointList.takeFirst());
      ++count;
    }

  return count;
}


QPointF *
IsotopicPatternCalculator::firstPoint() const
{
  return m_pointList.first();
}


QPointF *
IsotopicPatternCalculator::lastPoint() const
{
  return m_pointList.last();
}


int
IsotopicPatternCalculator::accountAtomCount(const AtomCount *atomCount)
{
  if(!atomCount)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // One atom count instance is made of two entities : an atom and
  // the number of times that atom occurs in the formula. Such pair
  // of values can be represented this way : [C:4], for example, to
  // say that there are 4 Carbon atoms in the formula.

  // Each atom, in turn has a list of isotopes (2 isotopes for
  // Carbon). We have to account in sequence for each atom in the
  // atomCount instance.

  QString message;
  message += tr("Accounting atom: ");
  message += atomCount->symbol();

  emit(isotopicCalculationMessageChanged(message));

  // qDebug() << __FILE__ << __LINE__
  //          << message
  //          << "m_peakCentroidList size:" << m_peakCentroidList.size();

  int count = atomCount->count();

  while(count)
    {
      // We check if abortion was requested every m_abortCheckCount
      // isotopes.
      if(m_progressValueNew && (m_progressValueNew % m_abortCheckCount == 0) &&
         m_aborted)
        break;

      updatePeakCentroidListWithAtom(atomCount);

      // Each time a new atomCount count is dealt with, we increment
      // that progress report value and emit a signal so that the
      // progress bar can be updated in the calling dialog window.
      emit(isotopicCalculationProgressValueChanged(++m_progressValueNew));

      --count;
    }

  // qDebug() << __FILE__ << __LINE__
  //          << "Done accouting atomCount" << atomCount->symbol()
  //          << "m_peakCentroidList size:" << m_peakCentroidList.size();

  // return the number of atomCount instances actually accounted
  // for.
  return (atomCount->count() - count);
}


int
IsotopicPatternCalculator::updatePeakCentroidListWithAtom(const Atom *atom)
{
  if(!atom)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // For each peak in the centroid peak list, we'll increment it
  // with the mass and probability of each isotope of the new
  // atom. Each time a peak in the peak list is incremented, we'll
  // create a brand new peak that we'll set in a new peak list
  // called m_tempPeakCentroidList.

  for(int iter = 0, peakCount = m_peakCentroidList.size(); iter < peakCount;
      ++iter)
    {
      PeakCentroid *peakCentroid = m_peakCentroidList.at(iter);

      for(int jter = 0, isotopeCount = atom->isotopeList().size();
          jter < isotopeCount;
          ++jter)
        {
          Isotope *isotope = atom->isotopeList().at(jter);

          updatePeakCentroidWithIsotope(peakCentroid, isotope);
        }
    }

  // Now that we have updated all the centroid peaks with all the
  // isotopes of the atom, we can make some housekeeping work. In
  // particular, when doing the updating of all the isotopic peaks
  // in m_peakCentroidList, we actually created a new list of
  // peaks. We want to get rid of all the old peaks and set the
  // m_peakCentroidList reference to point to the new list of peaks.

  freeClearPeakCentroidList();

  m_peakCentroidList = m_tempPeakCentroidList;
  m_tempPeakCentroidList.clear();

  mergePeakCentroidsWithSameMz();

  removePeakCentroidsInExcess();

  return m_peakCentroidList.size();
}


int
IsotopicPatternCalculator::updatePeakCentroidWithIsotope(
  PeakCentroid *peakCentroid, const Isotope *isotope)
{
  if(!peakCentroid)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

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

  PeakCentroid *newPeakCentroid = new PeakCentroid(
    peakCentroid->mz() + isotope->mass() / m_charge,
    0,
    peakCentroid->probability() * (isotope->abundance() / 100));

  // Get the mass of the new peak. We'll use it massively later.
  double newPeakCentroidMz = newPeakCentroid->mz();

  if(m_tempPeakCentroidList.isEmpty())
    {
      // First iteration, the m_tempPeakCentroidList is empty, thus simply
      // append to the list the newly allocated peak list instance.

      m_tempPeakCentroidList.append(newPeakCentroid);

      // qDebug() << "Appended new peak:"
      //          << newPeakCentroid->mass() << "--"
      //          << newPeakCentroid->probability();
    }
  else // m_tempPeakCentroidList is not empty
    {
      // The m_tempPeakCentroidList has items in it. We want to insert the
      // newly allocated peak at the proper location, mass-based,
      // that is right after the peak item that has a mass just less
      // than ours.

      bool inserted = false;

      for(int iter = 0; iter < m_tempPeakCentroidList.size(); ++iter)
        {
          PeakCentroid *iteratedPeakCentroid = m_tempPeakCentroidList.at(iter);

          // The iterated peak has a mass greater than ours, thus go
          // on to next iteration.
          if(newPeakCentroidMz > iteratedPeakCentroid->mz())
            continue;

          // Ah, finally, we found a place to insert our peak.
          m_tempPeakCentroidList.insert(iter, newPeakCentroid);

          // qDebug() << "Inserted new peak:"
          //          << newPeakCentroid->mass() << "--"
          //          << newPeakCentroid->probability();

          inserted = true;

          break;
        }

      // If we did not insert the peak, then that means that we
      // arrived here because we finished iterating in the
      // list... Thus append the peak.
      if(!inserted)
        {
          m_tempPeakCentroidList.append(newPeakCentroid);

          // qDebug() << "Appended peak:"
          //          << newPeakCentroid->mass() << "--"
          //          << newPeakCentroid->probability();
        }
      else
        {
          // Reinitialize the bool to false.
          inserted = false;
        }

      // qDebug() << "List of peaks:";
      //
      // for (int zter = 0; zter < m_tempPeakCentroidList.size(); ++zter)
      //   qDebug() << m_tempPeakCentroidList.at(zter)->mass()
      //            << "--"
      //            << m_tempPeakCentroidList.at(zter)->probability();
    }
  // End of
  // else m_tempPeakCentroidList is not empty

  // So we have dealt with one isotope. Just return.

  return 1;
}

int
IsotopicPatternCalculator::mergePeakCentroidsWithSameMz()
{
  // There might be some peaks very near one to the other in mass in
  // the m_peakCentroidList. Our criterium to "merge" two peaks
  // which are too near in mass is that if the masses differ by less
  // than the FWHM, then we'll merge them adding the probabilities
  // together.

  int removedPeaks = 0;

  for(int iter = 0; iter < m_peakCentroidList.size() - 1; ++iter)
    {
      PeakCentroid *curPeakCentroid  = m_peakCentroidList.at(iter);
      PeakCentroid *nextPeakCentroid = m_peakCentroidList.at(iter + 1);

      // qDebug() << __FILE__ << __LINE__ << "\n"
      //          << "\tCentroid peak at" << iter
      //          << ":" << curPeak->mass() << "\n\t"
      //          << "\tCentroid peak at" << (iter + 1)
      //          << ":" << nextPeak->mass() << "\n";

      double absDiff = fabs(curPeakCentroid->mz() - nextPeakCentroid->mz());

      // qDebug() << __FILE__ << __LINE__ << "\n"
      //          << "\tabsDiff is" << absDiff << "with m_fwhm:" << m_fwhm;

      // This dividing m_fwhm by three was added to successfully fix
      // the calculation bug that would center the shape of a
      // non-resolved mass peak (too low a resolution setting) at a
      // mass very much less than average mass !
      if(absDiff <= m_config.fwhm() / 3)
        {

          // Remove the forward (next) peak that is too close to
          // current one, but do not forget to account for that one
          // from the abundance point of vue.

          curPeakCentroid->incrementProbability(
            nextPeakCentroid->probability());

          delete m_peakCentroidList.takeAt(iter + 1);

          // qDebug() << __FILE__ << __LINE__ << "\n"
          //          << "\nRemoved mass-similar peak.";

          ++removedPeaks;

          --iter;
        }
    }

  return removedPeaks;
}


int
IsotopicPatternCalculator::removePeakCentroidsInExcess()
{
  // The maximum number of peaks that are allowed by the user is
  // m_maxPeaks. Remove all the peaks that are found in the
  // m_peakCentroidList after that count.

  int removedPeaks = 0;

  int peakCount = m_peakCentroidList.size();

  // qDebug() << __FILE__ << __LINE__ << "\n"
  //          << "\tm_peakCentroidList size:" << peakCount
  //          << "and m_maxPeaks is:" << m_maxPeaks;

  while(peakCount > m_maxPeakCentroids)
    {
      delete m_peakCentroidList.takeLast();

      // qDebug() << __FILE__ << __LINE__ << "\n"
      //          << "\tRemoved peak.";

      ++removedPeaks;

      --peakCount;
    }

  return removedPeaks;
}


int
IsotopicPatternCalculator::calculateSumOfProbabilities()
{
  PeakCentroid *peakCentroid = 0;
  int count                  = 0;

  foreach(peakCentroid, m_peakCentroidList)
    {
      double probability = peakCentroid->probability();

      m_sumProbabilities += probability;

      m_greatestProbability =
        (probability > m_greatestProbability ? probability
                                             : m_greatestProbability);
      ++count;
    }

  return count;
}


int
IsotopicPatternCalculator::calculateRelativeIntensity()
{
  calculateSumOfProbabilities();

  for(int iter = 0, peakCount = m_peakCentroidList.size(); iter < peakCount;
      ++iter)
    {
      PeakCentroid *peakCentroid = m_peakCentroidList.at(iter);

      double relativeIntensity =
        (peakCentroid->probability() / m_greatestProbability) * 100;

      if(relativeIntensity > std::numeric_limits<double>::min())
        peakCentroid->setRelativeIntensity(relativeIntensity);
    }

  return 1;
}


int
IsotopicPatternCalculator::removeTooLowProbPeaks()
{
  int peakCount = m_peakCentroidList.size();

  if(!peakCount)
    return 1;

  for(int iter = peakCount - 1; iter >= 0; --iter)
    {
      PeakCentroid *peakCentroid = m_peakCentroidList.at(iter);

      if(peakCentroid->probability() < m_minProb)
        {
          // The peak has too low a probability. Remove it.

          // qDebug() << __FILE__ << __LINE__
          //          << "Removing too low prob peak at: " << iter
          //          << "With mass:" << peakCentroid->mass()
          //          << "Prob:" << peakCentroid->probability();

          delete m_peakCentroidList.takeAt(iter);
        }
    }

  return 1;
}

} // namespace msXpSlibmass


#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 *newPeakCentroidList,*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 (!(newPeakCentroidList=malloc(sizeof(isotope))))
	      return 0;

	    newPeakCentroidList->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(!(newPeakCentroidList =
                         add_peak(newPeakCentroidList,np1)))
		      return 0;
		  }
	      }

	    free_list(m_peakList);
	    m_peakList = newPeakCentroidList;
	    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
