/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 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
 */


/////////////////////// StdLib includes
#include <cmath>
#include <iostream>
#include <iomanip>
#include <memory>


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


/////////////////////// pappsomspp includes
#include <pappsomspp/core/utils.h>


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/Utils.hpp"
#include "MsXpS/libXpertMassCore/MassPeakShaper.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


/*!
\class MsXpS::libXpertMassCore::MassPeakShaper
\inmodule libXpertMassCore
\ingroup XpertMassCoreMassCalculations
\inheaderfile MassPeakShaper.hpp

\brief The MassPeakShaper class provides the features needed to shape a mass
peak.

\e{Shaping a peak} means creating a shape around a centroid m/z value such that
the m/z peak is represented like it appears in a mass spectrum displayed in
"profile" mode.

The configuration of the mass peak shaping is held in a specific \l
MassPeakShaperConfig class.

\sa MassPeakShaperConfig
*/

/*!
\typedef MsXpS::libXpertMassCore::MassPeakShaperSPtr
\relates MassPeakShaper

Synonym for std::shared_ptr<MassPeakShaper>.
*/

/*!
\variable MsXpS::libXpertMassCore::MassPeakShaper::m_peakCentroid

\brief The peak centroid for which a shape is computed.

A peak centroid is the center of a mass peak profile and is thus the (m/z,
intensity) pair.
*/

/*!
\variable MsXpS::libXpertMassCore::MassPeakShaper::m_config

\brief The configuration needed to drive the mass peak shaping process.
*/

/*!
\variable MsXpS::libXpertMassCore::MassPeakShaper::m_trace

\brief The Trace object that will receive the different points that make the
peak shape.
*/


/*!
\brief Constructs a MassPeakShaper instance.
*/
MassPeakShaper::MassPeakShaper(): m_peakCentroid(0, 0)
{
}

/*!
\brief Constructs a MassPeakShaper instance.

\list
\li \a mz: The peak centroid m/z value.
\li \a intensity: The peak centroid intensity value.
\li \a config: The configuration driving the mass peak shaping process.
\endlist
*/
MassPeakShaper::MassPeakShaper(double mz,
                               double intensity,
                               const MassPeakShaperConfig &config)
  : m_peakCentroid(mz, intensity), m_config(config)
{
}

/*!
\brief Constructs a MassPeakShaper instance.

\list
\li \a data_point: The data point representing the peak centroid.
\li \a config: The configuration driving the mass peak shaping process.
\endlist
*/
MassPeakShaper::MassPeakShaper(const pappso::DataPoint &data_point,
                               const MassPeakShaperConfig &config)
  : m_peakCentroid(data_point), m_config(config)
{
  // qDebug()"m_config:" << m_config.asText(800);
}

/*!
\brief Constructs a MassPeakShaper instance as a copy of \a other.
*/
MassPeakShaper::MassPeakShaper(const MassPeakShaper &other)
  : m_peakCentroid(other.m_peakCentroid),
    m_config(other.m_config),
    m_trace(other.m_trace)
{
}

/*!
\brief Destructs this MassPeakShaper instance.
*/
MassPeakShaper::~MassPeakShaper()
{
}

/*!
\brief Sets the \a peak_centroid_data.
*/
void
MassPeakShaper::setPeakCentroid(const pappso::DataPoint &peak_centroid_data)
{
  m_peakCentroid = peak_centroid_data;
}

/*!
\brief Returns the peak centroid data.
*/
const pappso::DataPoint &
MassPeakShaper::getPeakCentroid() const
{
  return m_peakCentroid;
}

/*!
\brief Returns the peak shape as a pappso::Trace.
*/
const pappso::Trace &
MassPeakShaper::getTrace() const
{
  return m_trace;
}

/*
\brief Clears the peak shape.
*/
void
MassPeakShaper::clearTrace()
{
  m_trace.clear();
}

/*!
\brief Sets the configuration driving the peak shaping process to \a config.
*/
void
MassPeakShaper::setConfig(const MassPeakShaperConfig &config)
{
  m_config.initialize(config);
}

/*!
\brief Returns the configuration driving the peak shaping process.
*/
const MassPeakShaperConfig &
MassPeakShaper::getConfig() const
{
  return m_config;
}

/*!
\brief Computes the peak shape of the peak centroid.

Returns the count of points in the peak shape.
*/
int
MassPeakShaper::computePeakShape()
{
  if(m_config.getMassPeakShapeType() == Enums::MassPeakShapeType::GAUSSIAN)
    return computeGaussianPeakShape();
  else
    return computeLorentzianPeakShape();
}

/*!
\brief Computes the peak shape of the peak centroid.

\list
\li \a mz: the peak centroid's m/z value.
\li \a intensity: the peak centroid's intensity value.
\li \a config: the configuration driving the peak shaping process.
\endlist

Returns the pappso::Trace describing the peak shape.
*/
pappso::Trace
MassPeakShaper::computePeakShape(double mz,
                                 double intensity,
                                 const MassPeakShaperConfig &config)
{
  if(config.getMassPeakShapeType() == Enums::MassPeakShapeType::GAUSSIAN)
    return computeGaussianPeakShape(mz, intensity, config);
  else
    return computeLorentzianPeakShape(mz, intensity, config);
}

/*!
\brief Computes the Gaussian peak shape of the peak centroid.
*/
int
MassPeakShaper::computeGaussianPeakShape()
{
  // qDebug();

  m_trace.clear();

  m_trace =
    computeGaussianPeakShape(m_peakCentroid.x, m_peakCentroid.y, m_config);

  return m_trace.size();
}

/*!
\brief Computes the Gaussian peak shape of the peak centroid.

\list
\li \a mz: the peak centroid's m/z value.
\li \a intensity: the peak centroid's intensity value.
\li \a config: the configuration driving the peak shaping process.
\endlist

Returns the pappso::Trace describing the peak shape.
*/
pappso::Trace
MassPeakShaper::computeGaussianPeakShape(double mz,
                                         double intensity,
                                         const MassPeakShaperConfig &config)
{
  pappso::Trace trace;

  // We will use the data in the configuration object. First check that
  // we can rely on it. This call sets all the proper values to the m_config's
  // member data after having validate each.

  MassPeakShaperConfig local_config;
  local_config.initialize(config);

  ErrorList error_list;

  if(!local_config.resolve(error_list))
    {
      qDebug() << "Failed to resolve the MassPeakShaperConfig with errors:\n"
               << Utils::joinErrorList(error_list, "\n");
      return trace;
    }

  // qDebug() << "The peak shaper configuration:" << m_config.toString();

  // First off, we need to tell what the height of the gaussian peak should
  // be.
  double a;
  // a = m_config.a(mz);

  // We actually set a to 1, because it is the intensity above that will
  // provide the height of the peak, see below where the height of the peak is
  // set to a * intensity, that is, intensity if a = 1.
  a = 1;

  // qDebug() << "a:" << a;

  bool ok = false;

  double c = local_config.c(&ok);

  if(!ok)
    {
      return trace;
    }

  double c_square = c * c;

  // qDebug() << "c:" << c << "c²:" << c_square;


  // 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 width of the peak shape left and right of the
  // centroid.

  double mz_step = local_config.getMzStep();

  double half_peak_width_with_lateral_margins =
    (double)FWHM_PEAK_SPAN_FACTOR / 2 * local_config.getFwhm();

  double left_point  = mz - (half_peak_width_with_lateral_margins);
  double right_point = mz + (half_peak_width_with_lateral_margins);

  // qDebug() << "left m/z:" << left_point;
  // qDebug() << "right m/z:" << right_point;

  int iterations = (right_point - left_point) / mz_step;
  double x       = left_point;

  for(int iter = 0; iter < iterations; ++iter)
    {
      double y = intensity * a * exp(-1 * (pow((x - mz), 2) / (2 * c_square)));

      trace.push_back(pappso::DataPoint(x, y));

      x += mz_step;
    }

  // qDebug() << qSetRealNumberPrecision(15) << "For centroid" << mz
  //<< "first shape point:" << left_point
  //<< "with trace:" << trace.toString();

  return trace;
}

/*!
\brief Computes the Lorentzian peak shape of the peak centroid.
*/
int
MassPeakShaper::computeLorentzianPeakShape()
{
  // qDebug();

  m_trace.clear();

  m_trace =
    computeLorentzianPeakShape(m_peakCentroid.x, m_peakCentroid.y, m_config);

  return m_trace.size();
}

/*!
\brief Computes the Lorentzian peak shape of the peak centroid.

\list
\li \a mz: the peak centroid's m/z value.
\li \a intensity: the peak centroid's intensity value.
\li \a config: the configuration driving the peak shaping process.
\endlist

Returns the pappso::Trace describing the peak shape.
*/
pappso::Trace
MassPeakShaper::computeLorentzianPeakShape(double mz,
                                           double intensity,
                                           const MassPeakShaperConfig &config)
{
  pappso::Trace trace;

  // We will use the data in the configuration object. First check that
  // we can rely on it. This call sets all the proper values to the m_config's
  // member data after having validate each.

  MassPeakShaperConfig local_config;
  local_config.initialize(config);

  ErrorList error_list;

  if(!local_config.resolve(error_list))
    {
      qDebug() << "Failed to resolve the MassPeakShaperConfig with errors:\n"
               << Utils::joinErrorList(error_list, "\n");
      return trace;
    }

  // qDebug() << "The peak shaper configuration:" << m_config.toString();

  // First off, we need to tell what the height of the gaussian peak should
  // be.
  double a;
  // a = local_config.a(mz);

  // We actually set a to 1, because it is the intensity above that will
  // provide the height of the peak, see below where the heigh of the peak is
  // set to a * intensity, that is, intensity if a = 1.
  a = 1;

  // qDebug() << "a value:" << a;

  bool ok = false;

  // The calls below will trigger the computation of fwhm, if it is
  // equal to 0 because it was not set manually.
  double gamma = local_config.gamma(&ok);

  if(!ok)
    {
      return trace;
    }

  double gamma_square = gamma * gamma;

  // qDebug() << "gamma:" << gamma << "gamma²:" << gamma_square;

  // 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 width of the peak shape left and right of the
  // centroid.

  double mz_step = local_config.getMzStep();

  double left_point =
    mz - ((double)FWHM_PEAK_SPAN_FACTOR / 2 * local_config.getFwhm());
  double right_point =
    mz + ((double)FWHM_PEAK_SPAN_FACTOR / 2 * local_config.getFwhm());

  // qDebug() << "left m/z:" << left_point;
  // qDebug() << "right m/z:" << right_point;

  int iterations = (right_point - left_point) / mz_step;
  double x       = left_point;

  for(int iter = 0; iter < iterations; ++iter)
    {
      double y =
        intensity * a * (gamma_square / (pow((x - mz), 2) + gamma_square));

      trace.push_back(pappso::DataPoint(x, y));

      x += mz_step;
    }

  // qDebug().noquote() << m_trace.toString();

  return trace;
}

/*!
\brief Returns the intensity of a data point in the member peak shape
pappso::Trace (m_trace).

If the member pappso::Trace contains a data point having its x value equal to
\a mz (comparison performed using tolerance \a precision_p), then return the
intensity (y member) of that data point and set \a ok to true. Otherwise,
return 0 and set \a ok to false.
*/
double
MassPeakShaper::intensityAt(double mz,
                            pappso::PrecisionPtr precision_p,
                            bool &ok)
{
  pappso::DataPoint data_point = m_trace.containsX(mz, precision_p);

  if(data_point.isValid())
    {
      ok = true;
      return data_point.y;
    }

  ok = false;
  return 0;
}

/*!
\brief Returns a string with the data in the member pappso::Trace.
*/
QString
MassPeakShaper::shapetoString()
{
  return m_trace.toString();
}

/*!
\brief Writes to \a file_name a string containing the member pappso::Trace
data.

Returns true if successful, false otherwise.

\sa shapetoString()
*/
bool
MassPeakShaper::shapeToFile(const QString &file_name)
{
  return pappso::Utils::writeToFile(m_trace.toString(), file_name);
}


} // namespace libXpertMassCore

} // namespace MsXpS
