/* 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>
#include <QFile>


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

#include <math.h>
#include <iostream>
#include <iomanip>


namespace msXpSlibmass
{

class DataPoint;


PeakShaper::PeakShaper()
  : m_peakCentroid{0, 0},
    m_config{0 /*resolution*/,
             0 /*fwhm*/,
             "" /*ionization formula*/,
             1 /*charge*/,
             0 /*data point count*/,
             0 /*mz step*/,
             1 /*norm factor*/,
             PeakShapeType::GAUSSIAN}
{
  // m_config.setConfig(0, 0, 0, 1, PEAK_SHAPE_TYPE_GAUSSIAN);
}


PeakShaper::PeakShaper(double mz, double i, const PeakShaperConfig &config)
  : m_peakCentroid{mz, i}, m_config{config}
{
}


PeakShaper::PeakShaper(const DataPoint &peakCentroid,
                       const PeakShaperConfig &config)
  : m_peakCentroid{peakCentroid}, m_config{config}
{
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "m_config:" << m_config.asText(800);
}


PeakShaper::PeakShaper(const PeakShaper &other)
  : m_peakCentroid{other.m_peakCentroid},
    m_config(other.m_config),
    m_relativeIntensity{other.m_relativeIntensity},
    m_trace{other.m_trace}
{
}


PeakShaper::~PeakShaper()
{
}

void
PeakShaper::setPeakCentroid(const DataPoint &data_point)
{
  m_peakCentroid = data_point;
}


const DataPoint &
PeakShaper::getPeakCentroid() const
{
  return m_peakCentroid;
}


const Trace &
PeakShaper::getTrace() const
{
  return m_trace;
}


void
PeakShaper::clearTrace()
{
  m_trace.clear();
}


void
PeakShaper::setConfig(const PeakShaperConfig &config)
{
  m_config.setConfig(config);
}


const PeakShaperConfig &
PeakShaper::getConfig() const
{
  return m_config;
}


bool
PeakShaper::computeRelativeIntensity(double sum_probabilities)
{
  if(sum_probabilities == 0)
    return false;

  m_relativeIntensity = (m_peakCentroid.val() / sum_probabilities) * 100;

  return true;
}


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


double
PeakShaper::getRelativeIntensity() const
{
  return m_relativeIntensity;
}


int
PeakShaper::computePeakShape()
{
  if(m_config.peakShapeType() == PeakShapeType::GAUSSIAN)
    return computeGaussianPeakShape();
  else
    return computeLorentzianPeakShape();
}


int
PeakShaper::computeGaussianPeakShape()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  // Convenience mz value
  double mz          = m_peakCentroid.key();
  double probability = m_peakCentroid.val();

  // First off, we need to tell what the
  double aValue = m_config.a(mz);

  // We set a to 1
  aValue = 1;

  // The calls below will trigger the computation of fwhm in the config
  // instance, if it is equal to 0 because it was not set manually.

  double cValue  = m_config.c(mz);
  double c2Value = cValue * cValue;

  // Were are the left and right points of the shape ? We have to
  // determine that using the point count and mz step values.

  // Compute the mz step that will separate two consecutive points of the
  // shape. This mzStep is function of the number of points we want for a given
  // peak shape and the size of the peak shape left and right of the centroid.

  // Will return the fwhm if non-zero or compute it using mz and resolution.
  double fwhm = m_config.fwhm(mz);

  // Will compute fwhm anyway.
  double mzStep = m_config.mzStep(mz);

  double leftPoint  = mz - (2 * fwhm);
  double rightPoint = mz + (2 * fwhm);

  int iterations = (rightPoint - leftPoint) / mzStep;
  double x       = leftPoint;

  // qDebug() << __FILE__ << __LINE__ << "Calculting shape"
  //<< "\n\tm/z:" << mz << "\n\ti:" << m_peakCentroid.val()
  //<< "\n\tfwhm:" << fwhm
  //<< "\n\tpoint count:" << m_config.getPointCount()
  //<< "\n\tm/z step:" << mzStep << "\n\tleftPoint:" << leftPoint
  //<< "\n\trightPoint:" << rightPoint << "\n\tcValue:" << cValue
  //<< "\n\tc2Value:" << c2Value
  //<< "\n\titerations for calculation:" << iterations << "\n";

  std::setprecision(10);

  for(int iter = 0; iter < iterations; ++iter)
    {

      // Original way of doing, with the previous computation of the reltaive
      // intensity, as (prob / sum(probs) ) * 100.

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

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

      // std::cout << __FILE__ << "@" << __LINE__ << " " << x << " " << y
      //<< std::endl;

      m_trace.append(new DataPoint(x, y));

      x += mzStep;
    }

  std::cout << "\n";

  return m_trace.size();
}


int
PeakShaper::computeLorentzianPeakShape()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  // Convenience mz value
  double mz = m_peakCentroid.key();

  double probability = m_peakCentroid.val();

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

  // The calls below will trigger the computation of fwhm, if it is
  // equal to 0 because it was not set manually.
  double gammaValue  = m_config.gamma(mz);
  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 mz step that will separate two consecutive points of the
  // shape. This mzStep is function of the number of points we want for a given
  // peak shape and the size of the peak shape left and right of the centroid.

  // Will return the fwhm if non-zero or compute it using mz and resolution.
  double fwhm = m_config.fwhm(mz);

  // Will compute fwhm anyway.
  double mzStep = m_config.mzStep(mz);

  double leftPoint  = mz - (2 * fwhm);
  double rightPoint = mz + (2 * fwhm);

  int iterations = (rightPoint - leftPoint) / mzStep;
  double x       = leftPoint;

  // qDebug() << __FILE__ << __LINE__ << "Calculting shape"
  //<< "\n\tm/z:" << mz << "\n\tfwhm:" << fwhm
  //<< "\n\tpoint count:" << m_config.getPointCount()
  //<< "\n\tm/z step:" << mzStep << "\n\tleftPoint:" << leftPoint
  //<< "\n\tgammaValue:" << gammaValue
  //<< "\n\tgamma2Value:" << gamma2Value
  //<< "\n\titerations for calculation:" << iterations << "\n";

  // QString debugString;

  for(int iter = 0; iter < iterations; ++iter)
    {

      // Original way of doing, with the previous computation of the reltaive
      // intensity, as (prob / sum(probs) ) * 100.

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

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

      // debugString += QString("new shape point: (%1,%2)\n")
      //.arg(x, 0, 'f', 6)
      //.arg(y, 0, 'f', 6);

      m_trace.append(new DataPoint(x, y));

      x += mzStep;
    }

  // qDebug() << debugString;

  return m_trace.size();
}


double
PeakShaper::intensityAt(double mz, double tolerance, bool &ok)
{
  return m_trace.valueAt(mz, tolerance, ok);
}


QString
PeakShaper::traceAsText()
{
  return m_trace.asText();
}


bool
PeakShaper::traceToFile(const QString &file_name)
{
  return m_trace.exportToFile(file_name);
}

} // namespace msXpSlibmass
