/* 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 <minexpert/nongui/MassSpecDataSet.hpp>
#include <minexpert/nongui/MassSpecDataStats.hpp>
#include <libmass/MassSpectrum.hpp>


namespace msXpSmineXpert
{


MassSpecDataStats::MassSpecDataStats()
{
}


MassSpecDataStats::MassSpecDataStats(const MassSpecDataStats &other)
{
  m_spectrumCount = other.m_spectrumCount;

  m_smallestSpectrumSize = other.m_smallestSpectrumSize;
  m_greatestSpectrumSize = other.m_greatestSpectrumSize;
  m_spectrumSizeAvg      = other.m_spectrumSizeAvg;
  m_spectrumSizeStdDev   = other.m_spectrumSizeStdDev;

  // First m/z value found in *all* the spectra (that is the minimum m/z
  // value encountered in the whole spectral data set).
  m_minMz         = other.m_minMz;
  m_firstMzAvg    = other.m_firstMzAvg;
  m_firstMzStdDev = other.m_firstMzStdDev;

  // Last m/z value found in *all* the spectra (that is the maximum m/z
  // value encountered in the whole spectral data set).
  m_maxMz        = other.m_maxMz;
  m_lastMzAvg    = other.m_lastMzAvg;
  m_lastMzStdDev = other.m_lastMzStdDev;

  m_smallestMzShift = other.m_smallestMzShift;
  m_greatestMzShift = other.m_greatestMzShift;
  m_mzShiftAvg      = other.m_mzShiftAvg;
  m_mzShiftStdDev   = other.m_mzShiftStdDev;

  // This value is set along with the MASS_TOLERANCE_MZ value!
  // Only account for values that are > 0.
  m_nonZeroSmallestStep = other.m_nonZeroSmallestStep;
  m_smallestStep        = other.m_smallestStep;
  m_smallestStepMedian  = other.m_smallestStepMedian;
  m_smallestStepAvg     = other.m_smallestStepAvg;
  m_smallestStepStdDev  = other.m_smallestStepStdDev;

  m_greatestStep       = other.m_greatestStep;
  m_greatestStepAvg    = other.m_greatestStepAvg;
  m_greatestStepStdDev = other.m_greatestStepStdDev;

  m_massToleranceType = other.m_massToleranceType;

  // Now clear all the lists.
  m_spectrumSizeList.append(other.m_spectrumSizeList);

  m_firstMzList.append(other.m_firstMzList);
  m_lastMzList.append(m_lastMzList);

  m_mzShiftList.append(m_mzShiftList);

  m_smallestStepList.append(other.m_smallestStepList);
  m_greatestStepList.append(other.m_greatestStepList);
  m_avgStepList.append(other.m_avgStepList);
  m_stdDevStepList.append(other.m_stdDevStepList);
}


MassSpecDataStats &
MassSpecDataStats::operator=(const MassSpecDataStats &other)
{
  if(this == &other)
    return *this;

  reset();

  m_spectrumCount = other.m_spectrumCount;

  m_smallestSpectrumSize = other.m_smallestSpectrumSize;
  m_greatestSpectrumSize = other.m_greatestSpectrumSize;
  m_spectrumSizeAvg      = other.m_spectrumSizeAvg;
  m_spectrumSizeStdDev   = other.m_spectrumSizeStdDev;

  // First m/z value found in *all* the spectra (that is the minimum m/z
  // value encountered in the whole spectral data set).
  m_minMz         = other.m_minMz;
  m_firstMzAvg    = other.m_firstMzAvg;
  m_firstMzStdDev = other.m_firstMzStdDev;

  // Last m/z value found in *all* the spectra (that is the maximum m/z
  // value encountered in the whole spectral data set).
  m_maxMz        = other.m_maxMz;
  m_lastMzAvg    = other.m_lastMzAvg;
  m_lastMzStdDev = other.m_lastMzStdDev;

  m_smallestMzShift = other.m_smallestMzShift;
  m_greatestMzShift = other.m_greatestMzShift;
  m_mzShiftAvg      = other.m_mzShiftAvg;
  m_mzShiftStdDev   = other.m_mzShiftStdDev;

  // This value is set along with the MASS_TOLERANCE_MZ value!
  m_nonZeroSmallestStep = other.m_nonZeroSmallestStep;
  m_smallestStep        = other.m_smallestStep;
  m_smallestStepMedian  = other.m_smallestStepMedian;
  m_smallestStepAvg     = other.m_smallestStepAvg;
  m_smallestStepStdDev  = other.m_smallestStepStdDev;

  m_greatestStep       = other.m_greatestStep;
  m_greatestStepAvg    = other.m_greatestStepAvg;
  m_greatestStepStdDev = other.m_greatestStepStdDev;

  m_massToleranceType = other.m_massToleranceType;

  // Now clear all the lists.
  m_spectrumSizeList.append(other.m_spectrumSizeList);

  m_firstMzList.append(other.m_firstMzList);
  m_lastMzList.append(m_lastMzList);

  m_mzShiftList.append(m_mzShiftList);

  m_smallestStepList.append(other.m_smallestStepList);
  m_greatestStepList.append(other.m_greatestStepList);
  m_avgStepList.append(other.m_avgStepList);
  m_stdDevStepList.append(other.m_stdDevStepList);

  return *this;
}


MassSpecDataStats::~MassSpecDataStats()
{
}


void
MassSpecDataStats::reset()
{
  m_spectrumCount = 0;

  m_smallestSpectrumSize = std::numeric_limits<double>::max();
  m_greatestSpectrumSize = 0;
  m_spectrumSizeAvg      = 0;
  m_spectrumSizeStdDev   = 0;

  // First m/z value found in *all* the spectra (that is the minimum m/z
  // value encountered in the whole spectral data set).
  m_minMz         = std::numeric_limits<double>::max();
  m_firstMzAvg    = 0;
  m_firstMzStdDev = 0;

  // Last m/z value found in *all* the spectra (that is the maximum m/z
  // value encountered in the whole spectral data set).
  m_maxMz        = 0;
  m_lastMzAvg    = 0;
  m_lastMzStdDev = 0;

  m_smallestMzShift = std::numeric_limits<double>::max();
  m_greatestMzShift = 0;
  m_mzShiftAvg      = 0;
  m_mzShiftStdDev   = 0;

  m_nonZeroSmallestStep = std::numeric_limits<double>::max();
  m_smallestStep        = std::numeric_limits<double>::max();
  m_smallestStepMedian  = 0;
  m_smallestStepAvg     = 0;
  m_smallestStepStdDev  = 0;

  m_greatestStep       = 0;
  m_greatestStepAvg    = 0;
  m_greatestStepStdDev = 0;

  m_massToleranceType = msXpS::MassToleranceType::MASS_TOLERANCE_NONE;

  // Now clear all the lists.
  m_spectrumSizeList.clear();

  m_firstMzList.clear();
  m_lastMzList.clear();

  m_mzShiftList.clear();

  m_smallestStepList.clear();
  m_greatestStepList.clear();
  m_avgStepList.clear();
  m_stdDevStepList.clear();
}


//! Fill-in \p binList with all the m/z intervals in \p massSpectrum
/*!

  A bin is a m/z interval separating two consecutive m/z values in a mass
  spectrum. By definition, a mass spectrum that has n DataPoint instances has
  (n-1) intervals. A mass spectrum with less than two DataPoint instances is by
  definition not binnable.

  This function postulates that the data in the mass spectrum are sorted in
  key increasing order (that is, m/z increasing values). If not, the program
  crashes.

  The DataPoint instances in \p massSpectrum are iterated over and for each
  previous \e vs current m/z value of two contiguous DataPoint instances, the
  (current - previous) m/z difference is computed and appended to \p binlist.

  \return the number of bins.

*/
int
MassSpecDataStats::spectrumBins(const msXpSlibmass::MassSpectrum &massSpectrum,
                                QList<double> &binList)
{

  binList.clear();

  // We need at least two data points in the spectrum to compute the step
  // between the second and the first.

  if(massSpectrum.size() < 2)
    {
      return 0;
    }

  double prevMz = massSpectrum.first()->key();

  for(int iter = 1; iter < massSpectrum.size(); ++iter)
    {
      double curMz = massSpectrum.at(iter)->key();

      double step = curMz - prevMz;

      // Sanity check:
      // Spectra are ascending-ordered for the mz value. If not, that's fatal.
      if(step < 0)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

      binList.append(step);

      // QString msg = QString("Append step: %1 from computation %2 - %3 with
      // result: %4") .arg(step, 0, 'f', 10) .arg(curMz, 0, 'f', 10) .arg(prevMz,
      //0, 'f', 10) .arg(curMz - prevMz, 0, 'f', 10); qDebug() << __FILE__ <<
      // __LINE__ << msg;

      prevMz = curMz;
    }

  // qDebug() << __FILE__ << __LINE__
  //<< "Number of elements in binList:" << binList->size();

  return binList.size();
}


void
MassSpecDataStats::increment(const msXpSlibmass::MassSpectrum &massSpectrum)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;

  if(massSpectrum.isEmpty())
    return;

  ++m_spectrumCount;

  // Holder of double values.
  double tempValue = 0;

  tempValue = massSpectrum.size();

  m_spectrumSizeList.append(tempValue);
  // The smallest spectrum size
  if(tempValue < m_smallestSpectrumSize)
    m_smallestSpectrumSize = tempValue;
  // The greatest spectrum size
  if(tempValue > m_greatestSpectrumSize)
    m_greatestSpectrumSize = tempValue;

  double firstMz = massSpectrum.first()->key();
  m_firstMzList.append(firstMz);
  // The minimum m/z value
  if(firstMz < m_minMz)
    m_minMz = firstMz;

  // The smallest m/z shift
  tempValue = firstMz - m_firstMzList.first();
  m_mzShiftList.append(tempValue);
  if(tempValue < m_smallestMzShift)
    m_smallestMzShift = tempValue;

  double lastMz = massSpectrum.last()->key();
  m_lastMzList.append(lastMz);
  // The maximum m/z value
  if(lastMz > m_maxMz)
    m_maxMz = lastMz;

  // The current mass spectrum has data in it, as they were loaded from
  // file. We want to compute some of the various data about it. For that
  // we need to first fill in a bin list from it.

  QList<double> binList;

  int binCount = spectrumBins(massSpectrum, binList);
  if(massSpectrum.size() > 1 && binCount == 0)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). "
        "Cannot be that no bin could be computed."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }

  double sum;
  double average;
  double variance;
  double stdDeviation;
  double nonZeroSmallest;
  double smallest;
  double smallestMedian;
  double greatest;

  msXpS::doubleListStatistics(binList,
                              &sum,
                              &variance,
                              &average,
                              &stdDeviation,
                              &nonZeroSmallest,
                              &smallest,
                              &smallestMedian,
                              &greatest);

  // The smallest step
  if(nonZeroSmallest < m_nonZeroSmallestStep)
    m_nonZeroSmallestStep = nonZeroSmallest;

  m_smallestStepList.append(smallest);
  if(smallest < m_smallestStep)
    m_smallestStep = smallest;

  // The greatest step
  m_greatestStepList.append(greatest);
  if(greatest > m_greatestStep)
    m_greatestStep = greatest;

  m_avgStepList.append(average);
  m_stdDevStepList.append(stdDeviation);
}


void
MassSpecDataStats::consolidate()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;

  // Perform all the statistics on the lists.

  double sum;
  double average;
  double variance;
  double stdDeviation;
  double nonZeroSmallest;
  double smallest;
  double smallestMedian;
  double greatest;

  // The spectrum size.
  msXpS::doubleListStatistics(m_spectrumSizeList,
                              &sum,
                              &average,
                              &variance,
                              &stdDeviation,
                              &nonZeroSmallest,
                              &smallest,
                              &smallestMedian,
                              &greatest);
  m_spectrumSizeAvg    = average;
  m_spectrumSizeStdDev = stdDeviation;

#if 0
			// Print out the list of all the spectrum sizes, to check how the sizes of
			// the spectra change inside a mass spectrum list.
			QString msg = QString("List of spectrum sizes:\n%1\n");
			double val;
			foreach(val, m_spectralDataStats.spectrumSizeList)
				msg.append(QString("%1\n").arg(val));
			qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
				<< msg;
#endif

#if 0
			// Print out the list of all the mz shift between spectra.
			QString msg = QString("List of mz shifts:\n%1\n");
			double val;
			foreach(val, m_spectralDataStats.mzShiftList)
				msg.append(QString("%1\n").arg(val));
			qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
				<< msg;
#endif

  // The first mz value
  msXpS::doubleListStatistics(m_firstMzList,
                              &sum,
                              &average,
                              &variance,
                              &stdDeviation,
                              &nonZeroSmallest,
                              &smallest,
                              &smallestMedian,
                              &greatest);
  m_firstMzAvg    = average;
  m_firstMzStdDev = stdDeviation;

  // The last mz value
  msXpS::doubleListStatistics(m_lastMzList,
                              &sum,
                              &average,
                              &variance,
                              &stdDeviation,
                              &nonZeroSmallest,
                              &smallest,
                              &smallestMedian,
                              &greatest);
  m_lastMzAvg    = average;
  m_lastMzStdDev = stdDeviation;

  // The mz shift
  msXpS::doubleListStatistics(m_mzShiftList,
                              &sum,
                              &average,
                              &variance,
                              &stdDeviation,
                              &nonZeroSmallest,
                              &smallest,
                              &smallestMedian,
                              &greatest);
  m_mzShiftAvg    = average;
  m_mzShiftStdDev = stdDeviation;

  // The smallest step
  msXpS::doubleListStatistics(m_smallestStepList,
                              &sum,
                              &average,
                              &variance,
                              &stdDeviation,
                              &nonZeroSmallest,
                              &smallest,
                              &smallestMedian,
                              &greatest);
  m_smallestStepMedian = smallestMedian;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "m_smallestStepMedian:" << m_smallestStepMedian;

  m_smallestStepAvg    = average;
  m_smallestStepStdDev = stdDeviation;

  // The greatest step
  msXpS::doubleListStatistics(m_greatestStepList,
                              &sum,
                              &average,
                              &variance,
                              &stdDeviation,
                              &nonZeroSmallest,
                              &smallest,
                              &smallestMedian,
                              &greatest);
  m_greatestStepAvg    = average;
  m_greatestStepStdDev = stdDeviation;

  // QString text = asText();
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< text;
}


//! Update the member data of \p p_statistics with newly computed statistics
//! values
void
MassSpecDataStats::makeStatistics(const MassSpecDataSet &massSpecDataSet,
                                  const History &history)
{
  // Start fresh by resetting all the member data.
  reset();

  // Get a sublist of mass spectra on the basis of the integration history
  // or not accounting it if history.isValid() returns false.

  QList<msXpSlibmass::MassSpectrum *> *p_massSpectra =
    massSpecDataSet.extractMassSpectra(history);

  if(p_massSpectra == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The integrator return a nullptr as mass spectrum list."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);


  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "calculating statistics for a set of" << p_massSpectra->size()
           << "spectra"
           << "with history:" << history.asText();

  for(int iter = 0; iter < p_massSpectra->size(); ++iter)
    {
      msXpSlibmass::MassSpectrum *p_spectrum = p_massSpectra->at(iter);

      increment(*p_spectrum);
    }

  // At the end of the single spectrum-based statistics incrementation, we
  // need, to consolidate the statistics:
  consolidate();


#if 0
			// Print out the list of all the spectrum sizes, to check how the sizes of
			// the spectra change inside a mass spectrum list.
			QString msg = QString("List of spectrum sizes:\n%1\n");
			double val;
			foreach(val, m_spectralDataStats.spectrumSizeList)
				msg.append(QString("%1\n").arg(val));
			qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
				<< msg;
#endif

#if 0
			// Print out the list of all the mz shift between spectra.
			QString msg = QString("List of mz shifts:\n%1\n");
			double val;
			foreach(val, m_spectralDataStats.mzShiftList)
				msg.append(QString("%1\n").arg(val));
			qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
				<< msg;
#endif

  // QString text = p_statistics->asText();
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< text;
}


bool
MassSpecDataStats::isValid()
{
  int errors = 0;

  // There must be at least one spectrum
  errors += (m_spectrumCount ? 0 : 1);
  errors += (m_spectrumSizeList.size() ? 0 : 1);

  errors +=
    (m_smallestSpectrumSize == std::numeric_limits<double>::max() ? 1 : 0);

  errors += (m_minMz == std::numeric_limits<double>::max() ? 1 : 0);
  errors += (m_firstMzList.size() ? 0 : 1);
  errors += (m_maxMz == 0 ? 1 : 0);
  errors += (m_lastMzList.size() ? 0 : 1);

  errors += (m_smallestStep == std::numeric_limits<double>::max() ? 1 : 0);
  errors += (m_greatestStep == 0 ? 1 : 0);
  errors += (m_smallestStepList.size() ? 0 : 1);
  errors += (m_greatestStepList.size() ? 0 : 1);

  errors += (m_avgStepList.size() ? 0 : 1);
  errors += (m_stdDevStepList.size() ? 0 : 1);

  if(errors)
    qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
             << "The statistics are not valid...";

  return !errors;
}


QString
MassSpecDataStats::asText()
{
  QString text = "Spectral data set statistics:\n";

  text.append(
    QString::asprintf("\tTotal number of spectra: %d\n"
                      "\tAverage of spectrum size: %.6f\n"
                      "\tStdDev of spectrum size: %.6f\n"
                      "\tMinimum m/z value: %.6f\n"
                      "\tAverage of first m/z value: %.6f\n"
                      "\tStdDev of first m/z value: %.6f\n"
                      "\tMaximum m/z value: %.6f\n"
                      "\tAverage of last m/z value: %.6f\n"
                      "\tStdDev of last m/z value: %.6f\n"
                      "\tMinimum m/z shift: %.6f\n"
                      "\tMaximum m/z shift: %.6f\n"
                      "\tAverage of m/z shift: %.6f\n"
                      "\tStdDev of m/z shift: %.6f\n"
                      "\tSmallest non-zero Delta of m/z (step): %.6f\n"
                      "\tSmallest Delta of m/z (step): %.6f\n"
                      "\tMedian of smallest Delta of m/z (step): %.6f\n"
                      "\tAverage of smallest Delta of m/z (step): %.6f\n"
                      "\tStdDev of smallest Delta of m/z (step): %.6f\n"
                      "\tGreatest Delta of m/z (step): %.6f\n"
                      "\tAverage of greatest Delta of m/z (step): %.6f\n"
                      "\tStdDev of greatest Delta of m/z (step): %.6f\n",
                      m_spectrumCount,
                      m_spectrumSizeAvg,
                      m_spectrumSizeStdDev,
                      m_minMz,
                      m_firstMzAvg,
                      m_firstMzStdDev,
                      m_maxMz,
                      m_lastMzAvg,
                      m_lastMzStdDev,
                      m_smallestMzShift,
                      m_greatestMzShift,
                      m_mzShiftAvg,
                      m_mzShiftStdDev,
                      m_nonZeroSmallestStep,
                      m_smallestStep,
                      m_smallestStepMedian,
                      m_smallestStepAvg,
                      m_smallestStepStdDev,
                      m_greatestStep,
                      m_greatestStepAvg,
                      m_greatestStepStdDev));

  return text;
}


} // namespace msXpSmineXpert
