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


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


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


/////////////////////// Local includes
#include "MassPeakShaper.hpp"


namespace msxps
{
namespace minexpert
{


MassPeakShaper::MassPeakShaper()
  : m_peakCentroid(0, 0),
    m_config(
      // resolution
      0,
      // fwhm
      0.0,
      // ionization formula
      "",
      // charge
      1,
      // data point count
      0,
      // mz step
      0.0,
      // norm factor
      1.0,
      // peak shape type
      MassPeakShapeType::GAUSSIAN)
{
}


MassPeakShaper::MassPeakShaper(double mz,
                               double i,
                               const MassPeakShaperConfig &config)
  : m_peakCentroid(mz, i), m_config(config)
{
}


MassPeakShaper::MassPeakShaper(const pappso::DataPoint &peakCentroid,
                               const MassPeakShaperConfig &config)
  : m_peakCentroid(peakCentroid), m_config(config)
{
  // qDebug()"m_config:" << m_config.asText(800);
}


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


MassPeakShaper::~MassPeakShaper()
{
}

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


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


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


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


void
MassPeakShaper::setConfig(const MassPeakShaperConfig &config)
{
  m_config.setConfig(config);
}


const MassPeakShaperConfig &
MassPeakShaper::getConfig() const
{
  return m_config;
}


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

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

  return true;
}


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


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


int
MassPeakShaper::computePeakShape()
{
  if(m_config.getMassPeakShapeType() == MassPeakShapeType::GAUSSIAN)
    return computeGaussianPeakShape();
  else
    return computeLorentzianPeakShape();
}


int
MassPeakShaper::computeGaussianPeakShape()
{
  // qDebug();

  m_trace.clear();

  // Convenience mz value
  double mz          = m_peakCentroid.x;
  double probability = m_peakCentroid.y;

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

  // We set a to 1
  a = 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 c        = m_config.c(mz);
  double c_square = c * c;

  // 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 mz_step = m_config.mzStep(mz);

  double left_point  = mz - (2 * fwhm);
  double right_point = mz + (2 * fwhm);

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

  // qDebug() << "Calculting shape"
  //<< "\n\tm/z:" << mz << "\n\ti:" << m_peakCentroid.y
  //<< "\n\tfwhm:" << fwhm
  //<< "\n\tpoint count:" << m_config.getPointCount()
  //<< "\n\tm/z step:" << mzStep << "\n\tleftPoint:" << leftPoint
  //<< "\n\trightPoint:" << rightPoint << "\n\tc:" << c
  //<< "\n\tcSquare:" << cSquare
  //<< "\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 * a *
      // exp(-1 * (pow((x - mz), 2) / (2 * cSquare)));

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

      // qDebug().noquote()
      //<< QString("(%1,%2").arg(x, 0, 'f', 10).arg(y, 0, 'f', 10);

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

      x += mz_step;
    }

  return m_trace.size();
}


int
MassPeakShaper::computeLorentzianPeakShape()
{
  // qDebug();

  // Convenience mz value
  double mz = m_peakCentroid.x;

  double probability = m_peakCentroid.y;

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

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

  // 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 mz_step = m_config.mzStep(mz);

  double left_point  = mz - (2 * fwhm);
  double right_point = mz + (2 * fwhm);

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

  // qDebug() << "Calculting shape"
  //<< "\n\tm/z:" << mz << "\n\tfwhm:" << fwhm
  //<< "\n\tpoint count:" << m_config.getPointCount()
  //<< "\n\tm/z step:" << mzStep << "\n\tleftPoint:" << leftPoint
  //<< "\n\tgamma:" << gamma
  //<< "\n\tgammaSquare:" << gammaSquare
  //<< "\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 * a *
      //(gammaSquare / (pow((x - mz), 2) + gammaSquare));

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

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

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

      x += mz_step;
    }

  // qDebug() << debugString;

  return m_trace.size();
}


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;
}


QString
MassPeakShaper::traceAsText()
{
  return m_trace.toString();
}


bool
MassPeakShaper::traceToFile(const QString &file_name)
{
  return pappso::Utils::writeToFile(m_trace.toString(), file_name);
}


} // namespace minexpert

} // namespace msxps
