/* 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 <libmass/MassSpectrum.hpp>


namespace msXpSmineXpert
{

//! Construct a MassSpecDataSet instance.
/*!

  \param parent QObject parent of this MassSpecDataSet instance. The
  MassSpecDataSet class derives from QObject in order to benefit from the
  signal/slot mechanism implemented in Qt.

  The \c m_massSpectra MassSpectrumList member of this class does not
  automatically delete the MassSpectrum instances allocated on the heap: that
  class is used to get pointers to MassSpectrum instances that do not belong
  to the MassSpectrumList object. It is the MassSpecDataSet class that owns
  the spectra, and that needs to free them when no longer in use.

  \param name string containing the name of the data set.

  \param fileName string containing the name of the file from which the data
  must be loaded.

  \param isStreamed tells if the data should be loaded from file in streamed
  mode.

*/
MassSpecDataSet::MassSpecDataSet(QObject *parent,
                                 const QString &name,
                                 const QString &fileName,
                                 bool isStreamed)
  : QObject{parent},
    m_name{name},
    m_fileName{fileName},
    m_isStreamed{isStreamed}
{
}


//! Destruct \c this MassSpecDataSet instance.
/*!

  The data in \c this instance are owned by this instance. They thus should be
  deleted.

  \sa deleteSpectra().

*/
MassSpecDataSet::~MassSpecDataSet()
{
  int size = m_massSpectra.size();

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "Begin to free" << size
           << "mass spectra";

  // The MassSpectrumList member of this class does not automatically delete
  // the MassSpectrum instances allocated on the heap: that class is used to
  // get pointers to MassSpectrum instances that do not belong to the
  // MassSpectrumList object.

  // The user of the MassSpectrumList object is responsible for calling
  // freeSpectra() at the right moment (typically from here).

  for(int iter = 0; iter < m_massSpectra.size(); ++iter)
    delete m_massSpectra.at(iter);

  m_massSpectra.clear();

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "Done freeing memory for"
           << size << "mass spectra. ";

  m_rtHash.clear();
  m_dtHash.clear();
}


//! Get a const reference to the mass spectra data set statistics.
const MassSpecDataStats &
MassSpecDataSet::statistics() const
{
  return m_massSpecDataStats;
}


//! Get the name of \c this MassSpecDataSet instance.
QString
MassSpecDataSet::name() const
{
  return m_name;
}

//! Get the name of the file from which the data are loaded.
QString
MassSpecDataSet::fileName() const
{
  return m_fileName;
}

//! Tells if the data must be or were loaded from file in streamed mode.
bool
MassSpecDataSet::isStreamed() const
{
  return m_isStreamed;
}


//! Tells if the data must be or were loaded from file in streamed mode.
bool
MassSpecDataSet::isMobilityExperiment() const
{
  return m_dtHash.size();
}


//! Get the mass spectrometry data format of the file.
int
MassSpecDataSet::fileFormat() const
{
  return m_fileFormat;
}


//! Get a reference to the mass spectrometry data format of the file.
int &
MassSpecDataSet::rfileFormat()
{
  return m_fileFormat;
}


//! Get the number of map cells in the m/z axis for the m/z=f(dt) color map.
double
MassSpecDataSet::mzCellCount() const
{
  return m_mzCellCount;
}


//! Get the smallest m/z value of the whole dataset.
double
MassSpecDataSet::mzRangeStart() const
{
  return m_mzRangeStart;
}


//! Get the greatest m/z value of the whole dataset.
double
MassSpecDataSet::mzRangeEnd() const
{
  return m_mzRangeEnd;
}


void
MassSpecDataSet::resetStatistics()
{
  m_massSpecDataStats.reset();
}


const MassSpecDataStats &
MassSpecDataSet::incrementStatistics(
  const msXpSlibmass::MassSpectrum &massSpectrum)
{
  m_massSpecDataStats.increment(massSpectrum);
  return m_massSpecDataStats;
}


const MassSpecDataStats &
MassSpecDataSet::consolidateStatistics()
{
  m_massSpecDataStats.consolidate();
  return m_massSpecDataStats;
}


const MassSpecDataStats &
MassSpecDataSet::makeStatistics(const History &history) const
{
  m_massSpecDataStats.makeStatistics(*this, history);
  return m_massSpecDataStats;
}


QString
MassSpecDataSet::statisticsAsText() const
{
  return m_massSpecDataStats.asText();
}


//! Get a const reference to the mass spectrum list.
const QList<msXpSlibmass::MassSpectrum *> &
MassSpecDataSet::massSpectra() const
{
  return m_massSpectra;
}


//! Get a const reference to the retention time / mass spectra hash.
const msXpSlibmass::MsMultiHash &
MassSpecDataSet::rtHash() const
{
  return m_rtHash;
}


//! Get a const reference to the drift time / mass spectra hash.
const msXpSlibmass::MsMultiHash &
MassSpecDataSet::dtHash() const
{
  return m_dtHash;
}


//! Get a const reference to the retention time / TIC intensity map.
const QMap<double, double> &
MassSpecDataSet::ticChromMap() const
{
  return m_ticChromMap;
}


//! Set the parent object of \c this MassSpecDataSet instance.
void
MassSpecDataSet::setParent(QObject *parent)
{
  QObject::setParent(parent);
}


//! Allocate a mass spectrum list according to \c m_history using \c
//! mp_massSpecDataSet data.
QList<msXpSlibmass::MassSpectrum *> *
MassSpecDataSet::extractMassSpectra(const History &history) const
{
  if(m_isStreamed)
    {
      qFatal(
        "Fatal error at %s@%d -- %s(). "
        "At the moment this function is not implemented."
        "Program aborted.",
        __FILE__,
        __LINE__,
        __FUNCTION__);
    }

  // Prepare some variables.

  bool rtIntegration = false;
  double rtStart     = qSNaN();
  double rtEnd       = qSNaN();

  // We do not use the mz integration variables, because in returning a list
  // of mass spectra we cannot modify their contents based on mz range values.
  //
  // bool mzIntegration = false;
  // double mzStart = qSNaN();
  // double mzEnd = qSNaN();

  bool dtIntegration = false;
  double dtStart     = qSNaN();
  double dtEnd       = qSNaN();

  rtIntegration = history.innermostRtRange(&rtStart, &rtEnd);

  // See above for the explanation of the commenting out.
  // mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);

  dtIntegration = history.innermostDtRange(&dtStart, &dtEnd);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
  // qDebug("Mass spectrum list extraction:\n"
  //"rtIntegration: [%.6f-%.6f] \n"
  //// "mzIntegratin: [%.6f-%.6f] \n"
  //"dtIntegration: [%.6f-%.6f] \n",
  // rtStart, rtEnd,

  // See above for the explanation of the commenting out.
  // mzStart, mzEnd,

  // dtStart, dtEnd);

  // At this point we should have qualified fully the integration ranges for
  // the three dimensions: RT, MZ and DT.

  QList<double> rtList;

  if(rtIntegration)
    m_rtHash.rangedKeys(rtList, rtStart, rtEnd);
  else
    rtList = m_rtHash.uniqueKeys();

  qSort(rtList.begin(), rtList.end());

  int rtListSize = rtList.size();

  QString integrationDetails = "Mass spectrum extraction:\n";

  if(rtIntegration)
    integrationDetails += QString("RT range [%1-%2]\n")
                            .arg(rtStart, 0, 'f', 6)
                            .arg(rtEnd, 0, 'f', 6);

  // In this function, we cannot apply mzIntegration, because we cannot cut a
  // single spectrum into pieces. We can only return complete spectra, we are
  // not computing specific intra-spectra dissections.
  // if(mzIntegration)
  // integrationDetails += QString("MZ range [%1-%2]\n")
  //.arg(mzStart, 0, 'f', 6)
  //.arg(mzEnd, 0, 'f', 6);

  if(dtIntegration)
    integrationDetails += QString("DT range [%1-%2]\n")
                            .arg(dtStart, 0, 'f', 6)
                            .arg(dtEnd, 0, 'f', 6);

  QList<msXpSlibmass::MassSpectrum *> *p_massSpectra =
    new QList<msXpSlibmass::MassSpectrum *>;

  for(int iter = 0; iter < rtListSize; ++iter)
    {
      double rt = rtList.at(iter);

      // Append into p_massSpectra all the MassSpectrum * pointers that the
      // rtHash has for rt key.
      p_massSpectra->append(m_rtHash.values(rt));

      // And now apply the DT filtering, if history so requires.
      if(dtIntegration)
        {
          for(int jter = 0; jter < p_massSpectra->size(); ++jter)
            {
              msXpSlibmass::MassSpectrum *spectrum = p_massSpectra->at(jter);

              if(spectrum->dt() < dtStart || spectrum->dt() > dtEnd)
                {
                  p_massSpectra->removeAt(jter);
                  --jter;
                  continue;
                }
            }
        }
    }
  // End of for(int iter = 0; iter < rtListSize; ++iter)

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Returning a list of" << p_massSpectra->size() << "spectra";

  return p_massSpectra;
}


//! Return mass spectrum at index \p index.
const msXpSlibmass::MassSpectrum *
MassSpecDataSet::massSpectrum(int index) const
{
  if(index < 0 || index >= m_massSpectra.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Out of bound error accessing the m_massSpectra member of "
      "MassSpecDataSet."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  return m_massSpectra.at(index);
}


//! Return the number of mass spectra in \c this MassSpecDataSet instance.
int
MassSpecDataSet::size() const
{
  return m_massSpectra.size();
}


//! Append a mass spectrum to the member \c m_massSpectra MassSpectrumList.
void
MassSpecDataSet::appendMassSpectrum(msXpSlibmass::MassSpectrum *massSpectrum)
{
  if(massSpectrum == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_massSpectra.append(massSpectrum);
}


//! Insert a new key/val pair in the retention time / mass spectra hash.
void
MassSpecDataSet::insertRtHash(double key, msXpSlibmass::MassSpectrum *value)
{
  if(value == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_rtHash.insert(key, value);
}


//! Insert a new key/val pair in the drift time / mass spectra hash.
void
MassSpecDataSet::insertDtHash(double key, msXpSlibmass::MassSpectrum *value)
{
  if(value == nullptr)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  m_dtHash.insert(key, value);
}


} // namespace msXpSmineXpert
