/* 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 <map>
#include <cmath>
#include <vector>
#include <set>
#include <algorithm>


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


/////////////////////// pappsomspp includes
#include <pappsomspp/core/massspectrum/qualifiedmassspectrum.h>
#include <pappsomspp/core/processing/combiners/mzintegrationparams.h>


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


int msRunDataSetMetaTypeId = qRegisterMetaType<MsXpS::MineXpert::MsRunDataSet>(
  "MsXpS::MineXpert::MsRunDataSet");

int msRunDataSetPtrMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::MsRunDataSet *>(
    "MsXpS::MineXpert::MsRunDataSetPtr");

int msRunDataSetSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::MsRunDataSetSPtr>(
    "MsXpS::MineXpert::MsRunDataSetSPtr");

int msRunDataSetCstSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::MsRunDataSetCstSPtr>(
    "MsXpS::MineXpert::MsRunDataSetCstSPtr");

namespace MsXpS
{
namespace MineXpert
{


MsRunDataSet::MsRunDataSet()
{
}

MsRunDataSet::MsRunDataSet(QObject *parent,
                           const pappso::MsRunReaderSPtr ms_run_reader_sp,
                           bool isStreamed)
  : QObject(parent),
    msp_msRunReader(ms_run_reader_sp),
    mcsp_msRunId(ms_run_reader_sp->getMsRunId()),
    m_isStreamed(isStreamed)
{
  // qDebug() << "ms run id:" << ms_run_reader_sp->getMsRunId()->toString();

  msp_msRunDataSetTree =
    std::make_shared<pappso::MsRunDataSetTree>(ms_run_reader_sp->getMsRunId());
}

MsRunDataSet::MsRunDataSet(QObject *parent,
                           const pappso::MsRunIdCstSPtr ms_run_id_csp,
                           bool isStreamed)
  : QObject(parent),
    msp_msRunReader(nullptr),
    mcsp_msRunId(ms_run_id_csp),
    m_isStreamed(isStreamed)
{
  // qDebug() << "ms run id:" << ms_run_reader_sp->getMsRunId()->toString();

  msp_msRunDataSetTree =
    std::make_shared<pappso::MsRunDataSetTree>(ms_run_id_csp);
}

MsRunDataSet::MsRunDataSet(const MsRunDataSet &other)
  : QObject(other.parent()),
    msp_msRunReader(other.msp_msRunReader),
    mcsp_msRunId(other.mcsp_msRunId),
    m_isStreamed(other.m_isStreamed)
{
}

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

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

  \sa deleteSpectra().

*/
MsRunDataSet::~MsRunDataSet()
{
  // qDebug() << "In the destructor of MsRunDataSet:" << this;
}

//! Get a const reference to the mass spectra data set statistics.
const MsRunDataSetStats &
MsRunDataSet::getMsRunDataSetStats() const
{
  return m_msRunDataSetStats;
}

void
MsRunDataSet::setMsRunDataSetStats(
  const MsRunDataSetStats &ms_run_data_set_stats)
{
  m_msRunDataSetStats = ms_run_data_set_stats;
}

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

void
MsRunDataSet::setFileFormat(pappso::Enums::MsDataFormat file_format)
{
  m_fileFormat = file_format;
}

pappso::Enums::MsDataFormat
MsRunDataSet::getFileFormat() const
{
  return m_fileFormat;
}

bool
MsRunDataSet::isStreamed() const
{
  return m_isStreamed;
}

void
MsRunDataSet::setMsRunId(pappso::MsRunIdCstSPtr ms_run_id_csp)
{
  mcsp_msRunId = ms_run_id_csp;
}

pappso::MsRunIdCstSPtr
MsRunDataSet::getMsRunId() const
{
  return mcsp_msRunId;
}

pappso::MsRunReaderSPtr
MsRunDataSet::getMsRunReaderSPtr()
{
  return msp_msRunReader;
}

pappso::MsRunReaderCstSPtr
MsRunDataSet::getMsRunReaderCstSPtr() const
{
  return msp_msRunReader;
}

void
MsRunDataSet::resetStatistics()
{
  m_msRunDataSetStats.reset();
}

const MsRunDataSetStats &
MsRunDataSet::incrementStatistics(
  const pappso::MassSpectrumCstSPtr massSpectrum)
{
  m_msRunDataSetStats.incrementStatistics(massSpectrum);
  return m_msRunDataSetStats;
}

const MsRunDataSetStats &
MsRunDataSet::consolidateStatistics()
{
  m_msRunDataSetStats.consolidate();
  return m_msRunDataSetStats;
}

QString
MsRunDataSet::statisticsToString() const
{
  return m_msRunDataSetStats.toString();
}

pappso::MsRunDataSetTreeCstSPtr
MsRunDataSet::getMsRunDataSetTreeCstSPtr() const
{
  return msp_msRunDataSetTree;
}

// Heap-allocated
pappso::MzIntegrationParams *
MsRunDataSet::craftMzIntegrationParams() const
{
  return new pappso::MzIntegrationParams(
    // Absolute min m/z value of the whole data set
    m_msRunDataSetStats.m_minMz,
    // Absolute max m/z value of the whole data set
    m_msRunDataSetStats.m_maxMz,
    pappso::MzIntegrationParams::BinningType::ARBITRARY,
    pappso::PrecisionFactory::getResInstance(40000),
    1 /*binSizeDivisor*/,
    -1 /*decimalPlaces*/,
    true /*removeZeroValDataPoints*/);
}

//! Return the number of mass spectra in \c this MsRunDataSet instance.
int
MsRunDataSet::size() const
{
  return msp_msRunDataSetTree->size();
  return 1;
}

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

void
MsRunDataSet::mapMassSpectrum(
  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp)
{
  // qDebug();

  // We need to store the mass spectrum. The mass spec data tree has functions
  // to do that.

  if(qualified_mass_spectrum_csp == nullptr ||
     qualified_mass_spectrum_csp.get() == nullptr)
    qFatal() << "Cannot be nullptr.";

  pappso::MsRunDataSetTreeNode *new_node_p =
    msp_msRunDataSetTree->addMassSpectrum(qualified_mass_spectrum_csp);

  // At this point, finish the mapping with the retention time and drift time
  // (if applicable)

  // We want to standardize retention time to be in min
  double rt = qualified_mass_spectrum_csp->getRtInMinutes();
  if(rt == -1)
    qFatal() << "Programming error.";

  // A single rt value in the map is associated to a list of ms run
  // data set tree nodes.

  using map_iterator = MapDoubleMsRunDataSetTreeNode::iterator;

  map_iterator found_iter = m_rtMsRunDataSetTreeNodeMap.find(rt);

  if(found_iter == m_rtMsRunDataSetTreeNodeMap.end())
    {
      // This is the first time that we encounter rt.

      std::vector<pappso::MsRunDataSetTreeNode *> new_node_vector;
      new_node_vector.push_back(new_node_p);

      m_rtMsRunDataSetTreeNodeMap.insert(
        std::pair<double, std::vector<pappso::MsRunDataSetTreeNode *>>(
          rt, new_node_vector));
    }
  else
    {
      // A rt value was encountered already, thus push back the new mass
      // spectrum to the corresponding vector of qualified mass spectra (second
      // member of the pair).
      found_iter->second.push_back(new_node_p);
    }

  // Handle the mobility data, either drift time or 1/K0 depending
  // on the file type.

  double ion_mobility_value = -1;

  // Get the format of the file, because that will condition the
  // way we provide mobility values.

  pappso::Enums::MsDataFormat file_format =
    qualified_mass_spectrum_csp->getMassSpectrumId()
      .getMsRunIdCstSPtr()
      ->getMsDataFormat();

  if(file_format == pappso::Enums::MsDataFormat::brukerTims)
    {
      QVariant ion_mobility_variant_value =
        qualified_mass_spectrum_csp->getParameterValue(
          pappso::QualifiedMassSpectrumParameter::IonMobOneOverK0);

      if(ion_mobility_variant_value.isValid())
        {
          bool ok            = false;
          ion_mobility_value = ion_mobility_variant_value.toDouble(&ok);
          if(!ok)
            {
              qFatal() << "Failed to convert QVariant 1/K0 value to double.";
            }
        }
      // else
      // the QVariant is not valid, just let ion_mobility_value to be -1.
    }
  else
    {
      // If the format is not Bruker timsTOF, then we knoow the
      // ion mobility is stored in the qualified_mass_spectrum_csp as
      // a drift time.
      ion_mobility_value = qualified_mass_spectrum_csp->getDtInMilliSeconds();
    }

  if(ion_mobility_value != -1)
    {
      // A single dt value in the map is associated to a list of ms
      // run data set tree nodes.

      using map_iterator = MapDoubleMsRunDataSetTreeNode::iterator;

      map_iterator found_iter =
        m_dtMsRunDataSetTreeNodeMap.find(ion_mobility_value);

      if(found_iter == m_dtMsRunDataSetTreeNodeMap.end())
        {
          // This is the first time that we encounter dt.

          std::vector<pappso::MsRunDataSetTreeNode *> new_node_vector;
          new_node_vector.push_back(new_node_p);

          m_dtMsRunDataSetTreeNodeMap.insert(
            std::pair<double, std::vector<pappso::MsRunDataSetTreeNode *>>(
              ion_mobility_value, new_node_vector));
        }
      else
        {
          // A dt value was encountered already, thus push back the new mass
          // spectrum to the corresponding vector of qualified mass spectra
          // (second member of the pair).
          found_iter->second.push_back(new_node_p);
        }
    }
}

//! Return mass spectrum at index \p index.
pappso::QualifiedMassSpectrumCstSPtr
MsRunDataSet::massSpectrumAtIndex(std::size_t spectrum_index) const
{
  pappso::MsRunDataSetTreeNode *node =
    msp_msRunDataSetTree->findNode(spectrum_index);

  if(node != nullptr)
    return node->getQualifiedMassSpectrum();

  return nullptr;
}

using VectorConstIterator =
  std::vector<pappso::MsRunDataSetTreeNode *>::const_iterator;

std::pair<VectorConstIterator, VectorConstIterator>
MsRunDataSet::treeNodeIteratorRangeForRtRange(double start, double end) const
{
  // qDebug().noquote() << "Determining iterators in the root nodes vector for "
  //                       "retention time range ["
  //                    << start << "," << end << "]";

  // Get a pointer to the root nodes of the data set tree.

  const std::vector<pappso::MsRunDataSetTreeNode *> &data_set_tree_root_nodes =
    msp_msRunDataSetTree->getRootNodes();

  // Locate in the tree node, the node that has the mass spectrum acquired at
  // a time no less (that is, >=) than the start time.

  auto start_iterator = std::lower_bound(
    data_set_tree_root_nodes.begin(),
    data_set_tree_root_nodes.end(),
    start,
    [](pappso::MsRunDataSetTreeNode *p, double d) {
      return p->getQualifiedMassSpectrum()->getRtInMinutes() < d;
    });

  if(start_iterator == data_set_tree_root_nodes.end())
    {
      // qDebug() << "No single mass spectrum was acquired with a rt time "
      //             "greater or equal to"
      //          << start;

      // Return the .end() .end() iterator pairs.
      return std::pair<VectorConstIterator, VectorConstIterator>(
        data_set_tree_root_nodes.end(), data_set_tree_root_nodes.end());
    }

  // qDebug() << "Start iterator points to a spectrum acquired at rt time:"
  //          <<
  //          (*start_iterator)->getQualifiedMassSpectrum()->getRtInMinutes();

  // Likewise, locate in the tree node, the node that has the mass spectrum
  // acquired at a time no greater (that is, <=) than the end time.

  auto end_iterator = std::upper_bound(
    start_iterator,
    data_set_tree_root_nodes.end(),
    end,
    [](double d, pappso::MsRunDataSetTreeNode *p) {
      return p->getQualifiedMassSpectrum()->getRtInMinutes() > d;
    });

  // if(end_iterator == data_set_tree_root_nodes.end())
  //   {
  //     qDebug() << "The end iterator is the root nodes vector .end()";
  //   }
  // else
  //   qDebug() << "End iterator points to a spectrum acquired at rt time:"
  //            <<
  //            (*end_iterator)->getQualifiedMassSpectrum()->getRtInMinutes();

  return std::pair<VectorConstIterator, VectorConstIterator>(start_iterator,
                                                             end_iterator);
}


} // namespace MineXpert
} // namespace MsXpS
