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


#include <cmath>
#include <limits>
#include <iomanip>
#include <iostream>
#include <type_traits>
#include <algorithm>


////////////////////////////// Qt includes
#include <QRegularExpression>
#include <QFileInfo>
#include <QDataStream>
#include <QDebug>
#include <QRegularExpressionMatch>


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

namespace MsXpS
{
namespace MineXpert
{

const double PI = 3.14159265358979323846;

/**@{*/

//! Number of decimal places after the decimal symbol for atom masses.
/*!
 * \sa msXpSmassXpert::DecimalPlacesOptionsDlg
 */
int ATOM_DEC_PLACES = 10;

//! Number of decimal places after the decimal symbol for oligomer masses.
/*!
 * \sa msXpSmassXpert::DecimalPlacesOptionsDlg
 */
int OLIGOMER_DEC_PLACES = 5;

//! Number of decimal places after the decimal symbol for polymer masses.
/*!
 * \sa msXpSmassXpert::DecimalPlacesOptionsDlg
 */
int POLYMER_DEC_PLACES = 3;

//! Number of decimal places after the decimal symbol for pH/pKa values.
/*!
 * \sa msXpSmassXpert::DecimalPlacesOptionsDlg
 */
int PH_PKA_DEC_PLACES = 2;

/**@}*/

//! Regular expression that tracks the m/z,i pairs in text files.
QRegularExpression gXyFormatMassDataRegExp =
  QRegularExpression("^(\\d*\\.?\\d+)([^\\d^\\.^-]+)(-?\\d*\\.?\\d*[e-]?\\d*)");

//! Regular expression that tracks the end of line in text files.
QRegularExpression gEndOfLineRegExp = QRegularExpression("^\\s+$");

//! Return the average of the double list. Also fill-in the variance and
//! standard deviation if the parameters are non-nullptr.
void
doubleVectorStatistics(std::vector<double> &vector,
                       double *sum,
                       double *average,
                       double *variance,
                       double *stdDeviation,
                       double *nonZeroSmallest,
                       double *smallest,
                       double *smallestMedian,
                       double *greatest)
{
  if(sum == Q_NULLPTR || average == Q_NULLPTR || variance == Q_NULLPTR ||
     stdDeviation == Q_NULLPTR || nonZeroSmallest == Q_NULLPTR ||
     smallest == Q_NULLPTR || greatest == Q_NULLPTR ||
     smallestMedian == Q_NULLPTR)
    qFatal() << "Programming error.";

  // Sort the vector, we'll need it sorted to compute the median value.
  std::sort(vector.begin(), vector.end());

  int count = vector.size();

  if(!count)
    return;

  // Sorted vector, the smallest is the first.
  *smallest = vector.front();
  // The greatest is the last.
  *greatest = vector.back();

  *sum             = 0;
  *nonZeroSmallest = std::numeric_limits<double>::max();

  for(auto &&value : vector)
    {
      double current  = value;
      *sum           += current;

      // If current is non-zero, then take its value into account.
      if(current > 0 && current < *nonZeroSmallest)
        *nonZeroSmallest = current;
    }

  *average = *sum / count;

  double varN = 0;

  for(auto &&value : vector)
    {
      varN += (value - *average) * (value - *average);
    }

  *variance     = varN / count;
  *stdDeviation = sqrt(*variance);

  // Now the median value

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

  if(count == 1)
    {
      *smallestMedian = vector.front();
    }
  else
    {
      if(count % 2 == 0)
        *smallestMedian = (vector.at(count / 2 - 1) + vector.at(count / 2)) / 2;
      else
        *smallestMedian = vector.at(count / 2 + 1);
    }
}

//! Tell if both double values, are equal within the double representation
//! capabilities of the platform.
bool
almostEqual(double value1, double value2, int decimalPlaces)
{
  // QString value1String = QString("%1").arg(value1,
  // 0, 'f', 60);
  // QString value2String = QString("%1").arg(value2,
  // 0, 'f', 60);

  // qWarning() << "value1:" << value1String << "value2:" << value2String;

  // The machine epsilon has to be scaled to the magnitude of the values used
  // and multiplied by the desired precision in ULPs (units in the last place)
  // (decimal places).

  double valueSum = std::abs(value1 + value2);
  // QString valueSumString = QString("%1").arg(valueSum,
  // 0, 'f', 60);

  double valueDiff = std::abs(value1 - value2);
  // QString valueDiffString = QString("%1").arg(valueDiff,
  // 0, 'f', 60);

  double epsilon = std::numeric_limits<double>::epsilon();
  // QString epsilonString = QString("%1").arg(epsilon,
  // 0, 'f', 60);

  double scaleFactor = epsilon * valueSum * decimalPlaces;
  // QString scaleFactorString = QString("%1").arg(scaleFactor,
  // 0, 'f', 60);

  // qWarning() << "valueDiff:" << valueDiffString << "valueSum:" <<
  // valueSumString <<
  //"epsilon:" << epsilonString << "scaleFactor:" << scaleFactorString;

  bool res = valueDiff < scaleFactor
             // unless the result is subnormal:
             || valueDiff < std::numeric_limits<double>::min();

  // qWarning() << "returning res:" << res;

  return res;
}

//! Provides a text stream handle to the standard output.
/*!
 * Use:   msXpS::qStdOut() << "text to the standard output,"
 *                         << "not the error standard output."
 *
 * \return a reference to a static QTextStream that directs to the standard
 * output.
 */
QTextStream &
qStdOut()
{
  static QTextStream ts(stdout);
  return ts;
}

//! Remove all space characters from a string.
/*!
 * \param text reference to the string
 * \return reference to the in-place-modified string
 */
QString &
unspacifyString(QString &text)
{
  if(text.isEmpty())
    {
      return text;
    }

  static QRegularExpression regexp("\\s+");
  text.remove(regexp);

  return text;
}

//! Produce a binary representation of an int
/*!
 * \param value int value
 * \return a QString with the value binary representation
 */
QString
binaryRepresentation(int value)
{
  QString string;
  string = QString("%1").arg(value, 32, 2);

  return string;
}

//! Determine the number of zero decimals between the decimal point and the
//! first non-zero decimal.
/*!
 * 0.11 would return 0 (no empty decimal)
 * 2.001 would return 2
 * 1000.0001254 would return 3
 *
 * \param value the value to be analyzed
 * \return the number of '0' decimals between the decimal separator '.' and
 * the first non-0 decimal
 */
int
zeroDecimals(double value)
{

  int intPart = static_cast<int>(value);

  double decimalPart = value - intPart;

  int count = -1;

  while(1)
    {
      ++count;

      decimalPart *= 10;

      if(decimalPart > 1)
        return count;
    }

  return count;
}

//! Return the delta corresponding to m/z \p value and \p ppm part-per-million
double
ppm(double value, double ppm)
{

  return (value * ppm) / 1000000;
}

//! Return \p value incremented with the matching \p ppm part.
double
addPpm(double value, double ppm)
{

  return value + (value * ppm) / 1000000;
}

//! Return \p value decremented with the matching \p ppm part.
double
removePpm(double value, double ppm)
{

  return value - (value * ppm) / 1000000;
}

//! Return the delta corresponding to m/z \p value and resolution \p res
double
res(double value, double res)
{
  return value / res;
}

//! Return \p value incremented with the matching \p res part.
double
addRes(double value, double res)
{
  return value + (value / res);
}

//! Return \p value decremented with the matching \p res part.
double
removeRes(double value, double res)
{
  return value - (value / res);
}

//! Craft a string representing the address location (for debugging).
QString
pointerAsString(void *pointer)
{
  return QString("%1").arg(
    (quintptr)pointer, QT_POINTER_SIZE * 2, 16, QChar('0'));
}


} // namespace MineXpert

} // namespace MsXpS
