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


/////////////////////// Qt includes
#include <QDebug>


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

#include <math.h>


namespace msXpSlibmass
{

class PeakCentroid;


PeakShape::PeakShape()
  : PeakCentroid(0, 0, 0),
    m_config(0, 0, 0, 1, PeakShapeType::PEAK_SHAPE_TYPE_GAUSSIAN)
{
  // m_config.setConfig(0, 0, 0, 1, PEAK_SHAPE_TYPE_GAUSSIAN);
}


PeakShape::PeakShape(double mz,
                     double intensity,
                     double probability,
                     PeakShapeConfig &config)
  : PeakCentroid(mz, intensity, probability), m_config(config)
{
}


PeakShape::PeakShape(PeakCentroid &centroid, PeakShapeConfig &config)
  : PeakCentroid(centroid), m_config(config)
{
}


PeakShape::PeakShape(const PeakShape &other)
  : PeakCentroid(other.m_mz, other.m_relativeIntensity, other.m_probability),
    m_config(other.m_config)
{
}


PeakShape::~PeakShape()
{
  emptyPointList();
}

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

void
PeakShape::appendPoint(QPointF *point)
{
  if(!point)
    qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__);

  m_pointList.append(point);
}


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


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


QList<QPointF *> *
PeakShape::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
PeakShape::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;
}


void
PeakShape::emptyPointList()
{
  while(!m_pointList.isEmpty())
    delete m_pointList.takeFirst();
}


void
PeakShape::setPeakShapeConfig(const PeakShapeConfig &config)
{
  m_config.setConfig(config);
}


const PeakShapeConfig &
PeakShape::config() const
{
  return m_config;
}


void
PeakShape::setMz(double value)
{
  m_mz = value;
}


double
PeakShape::mz()
{

  return m_mz;
}


QString
PeakShape::mzString()
{
  QString string;
  string.setNum(m_mz);

  return string;
}


void
PeakShape::setRelativeIntensity(double value)
{
  m_relativeIntensity = value;
}


double
PeakShape::relativeIntensity()
{
  return m_relativeIntensity;
}


QString
PeakShape::relativeIntensityString()
{
  QString string;
  string.setNum(m_relativeIntensity);

  return string;
}


int
PeakShape::calculatePeakShape()
{
  if(m_config.peakShapeType() == PeakShapeType::PEAK_SHAPE_TYPE_GAUSSIAN)
    return calculateGaussianPeakShape();
  else
    return calculateLorentzianPeakShape();
}


int
PeakShape::calculateGaussianPeakShape()
{
  // qDebug() << __FILE__ << __LINE__
  //          << "calculating gaussian shape.";

  double aValue = m_config.a();
  // We set a to 1
  aValue = 1;

  // The calls below will trigger the computation of m_fwhm, if it is
  // equal to -1 because it was not set manually in the parameters of
  // the command line.
  double cValue  = m_config.c();
  double c2Value = cValue * cValue;

  // Were are the left and right points of the shape ? We have to
  // determine that using the m_pointNumber and m_increment values.

  // Compute the increment that will separate two consecutive points
  // of the shape.
  m_config.increment();

  // double leftPoint = m_mz - ((m_points * m_increment) / 2);
  // double rightPoint = m_mz + ((m_points * m_increment) / 2);

  double leftPoint  = m_mz - (2 * m_config.fwhm());
  double rightPoint = m_mz + (2 * m_config.fwhm());

  int iterations = (rightPoint - leftPoint) / m_config.increment();
  double x       = leftPoint;

  // qDebug() << __FILE__ << __LINE__
  //          << "Calculting shape"
  //          << "\n\tm_mz:" << m_mz
  //          << "\n\tm_config.fwhm:" << m_config.fwhm()
  //          << "\n\tm_config.points:" << m_config.pointNumber()
  //          << "\n\tm_config.increment:" << m_config.increment()
  //          << "\n\tleftPoint:" << leftPoint
  //          << "\n\trightPoint:" << rightPoint
  //          << "\n\tcValue:" << cValue
  //          << "\n\tc2Value:" << c2Value
  //          << "\n\titerations for calculation:" << iterations
  //          << "\n";


  // QString debugString;
  for(int iter = 0; iter < iterations; ++iter)
    {
      // debugString += QString().setNum(x, 'f', 6);

      double y = m_relativeIntensity * aValue *
                 exp(-1 * (pow((x - m_mz), 2) / (2 * c2Value)));

      // debugString += " ";
      // debugString += QString().setNum(y, 'f', 6);
      // debugString += "\n";

      QPointF *point = new QPointF(x, y);

      m_pointList.append(point);

      x += m_config.increment();
    }

  // qDebug() << debugString;

  return m_pointList.size();
}


int
PeakShape::calculateLorentzianPeakShape()
{
  // qDebug() << __FILE__ << __LINE__
  //          << "calculating lorentzian shape.";

  double aValue = m_config.a();
  // We set a to 1
  aValue = 1;

  // The calls below will trigger the computation of m_fwhm, if it is
  // equal to -1 because it was not set manually in the parameters of
  // the command line.
  double gammaValue  = m_config.gamma();
  double gamma2Value = gammaValue * gammaValue;

  // Were are the left and right points of the shape ? We have to
  // determine that using the m_points and m_increment values.

  // Compute the increment that will separate two consecutive points
  // of the shape.
  m_config.increment();

  // double leftPoint = m_mz - ((m_points * m_increment) / 2);
  // double rightPoint = m_mz + ((m_points * m_increment) / 2);

  double leftPoint  = m_mz - (2 * m_config.fwhm());
  double rightPoint = m_mz + (2 * m_config.fwhm());

  // qDebug() << __FILE__ << __LINE__
  //          << "m_mz:" << m_mz
  //          << "m_points:" << m_points
  //          << "m_increment:" << m_increment
  //          << "gammaValue:" << gammaValue
  //          << "gamma2Value:" << gamma2Value
  //          << "leftPoint:" << leftPoint
  //          << "rightPoint:" << rightPoint;

  int iterations = (rightPoint - leftPoint) / m_config.increment();
  double x       = leftPoint;

  // QString debugString;
  for(int iter = 0; iter < iterations; ++iter)
    {
      // debugString += QString().setNum(x, 'f', 6);

      double y = m_relativeIntensity * aValue *
                 (gamma2Value / (pow((x - m_mz), 2) + gamma2Value));

      // debugString += " ";
      // debugString += QString().setNum(y, 'f', 6);
      // debugString += "\n";

      QPointF *point = new QPointF(x, y);

      m_pointList.append(point);

      x += m_config.increment();
    }

  // qDebug() << debugString;

  return m_pointList.size();
}


double
PeakShape::intensityAt(double mz, double tolerance, bool *ok)
{
  double minMzRatio = mz - (tolerance / 2);
  double maxMzRatio = mz + (tolerance / 2);

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

      double mz = point->x();

      if(mz <= maxMzRatio && mz >= minMzRatio)
        {
          *ok = true;

          return point->y();
        }
    }

  // If we are here, that means that we have not found the wavenumber
  // in the shape, which is perfectly admissible. But let the caller
  // know that, because the 0 that is returned might well be a sincere
  // intensity...

  *ok = false;

  return 0;
}

QString
PeakShape::dataAsString()
{
  QString output;

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

      output += QString().setNum(point->x(), 'f', 10);
      output += " ";
      output += QString().setNum(point->y(), 'f', 10);
      output += "\n";
    }

  return output;
}


bool
PeakShape::dataToFile(QString filePath)
{
  QFile file(filePath);

  if(!file.open(QFile::WriteOnly | QFile::Truncate))
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to open output file for writing.";

      return false;
    }

  QTextStream stream(&file);

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

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

  return true;
}


} // namespace msXpSlibmass
