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

/////////////////////// OpenMP include
// #include <omp.h>

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

/////////////////////// pappsomspp includes
#include <pappsomspp/core/processing/combiners/tracepluscombiner.h>


/////////////////////// Local includes
#include "MassDataIntegrator.hpp"
#include "BaseMsRunDataSetTreeNodeVisitor.hpp"
#include "TicChromTreeNodeCombinerVisitor.hpp"
#include "MultiTreeNodeCombinerVisitor.hpp"

namespace MsXpS
{
namespace MineXpert
{


MassDataIntegrator::MassDataIntegrator(QObject *parent)
  : libXpertMassCore::MonitoredTaskInterface(parent),
    mp_processingFlow(new ProcessingFlow(dynamic_cast<QObject *>(this)))
{
  qFatal() << "Cannot be that the default constructor be used.";
}

MassDataIntegrator::MassDataIntegrator(MsRunDataSetCstSPtr ms_run_data_set_csp,
                                       QObject *parent)
  : libXpertMassCore::MonitoredTaskInterface(parent),
    mcsp_msRunDataSet(ms_run_data_set_csp),
    mp_processingFlow(new ProcessingFlow(dynamic_cast<QObject *>(this)))
{
  mp_processingFlow->setMsRunDataSetCstSPtr(mcsp_msRunDataSet);

  // qDebug() << "Allocating new integrator:" << this;
  // qDebug() << ms_run_data_set_csp->getMsRunId()->getFileName();
}

MassDataIntegrator::MassDataIntegrator(MsRunDataSetCstSPtr ms_run_data_set_csp,
                                       const ProcessingFlow &processing_flow,
                                       QObject *parent)
  : libXpertMassCore::MonitoredTaskInterface(parent),
    mcsp_msRunDataSet(ms_run_data_set_csp),
    mp_processingFlow(processing_flow.clone(dynamic_cast<QObject *>(this)))
{
  mp_processingFlow->setMsRunDataSetCstSPtr(mcsp_msRunDataSet);

  // qDebug() << "Allocating new integrator:" << this;
  // qDebug() << ms_run_data_set_csp->getMsRunId()->getFileName();
}

MassDataIntegrator *
MassDataIntegrator::clone(QObject *parent) const
{
  MassDataIntegrator *copy =
    new MassDataIntegrator(mcsp_msRunDataSet, *mp_processingFlow, parent);

  copy->m_mapTrace              = m_mapTrace;
  copy->m_doubleMapTraceMapSPtr = m_doubleMapTraceMapSPtr;
  copy->m_isOperationCancelled  = m_isOperationCancelled;

  return copy;
}

MassDataIntegrator::~MassDataIntegrator()
{
  // qDebug() << "Destroying integrator:" << this;
}

void
MassDataIntegrator::setMsRunDataSet(MsRunDataSetCstSPtr ms_run_data_set_csp)
{
  if(ms_run_data_set_csp == nullptr)
    qFatal() << "Pointer cannot be nullptr. Program aborted.";

  mcsp_msRunDataSet = ms_run_data_set_csp;
}

MsRunDataSetCstSPtr
MassDataIntegrator::getMsRunDataSet() const
{
  return mcsp_msRunDataSet;
}

void
MassDataIntegrator::setProcessingFlow(const ProcessingFlow &processing_flow)
{
  delete mp_processingFlow;
  mp_processingFlow = processing_flow.clone(dynamic_cast<QObject *>(this));
}

const ProcessingFlow &
MassDataIntegrator::getProcessingFlowCstRef() const
{
  return *mp_processingFlow;
}

ProcessingFlow &
MassDataIntegrator::getProcessingFlowRef()
{
  return *mp_processingFlow;
}

ProcessingFlow *
MassDataIntegrator::getProcessingFlow()
{
  return mp_processingFlow;
}

const pappso::MapTrace &
MassDataIntegrator::getMapTrace() const
{
  return m_mapTrace;
}

const std::shared_ptr<std::map<double, pappso::MapTrace>>
MassDataIntegrator::getDoubleMapTraceMapSPtr() const
{
  return m_doubleMapTraceMapSPtr;
}

void
MassDataIntegrator::cancelTask()
{
  m_isOperationCancelled = true;

  qDebug() << "Cancellation asked, setting m_isOperationCancelled to true and "
              "emitting taskCancelledSignal";

  emit taskCancelledSignal();
}

std::size_t
MassDataIntegrator::bestThreadCountForMsRunDataSetTreeNodeCount(
  std::size_t node_count)
{

  std::size_t ideal_thread_count = QThread::idealThreadCount();
  // qDebug() << "ideal_thread_count:" << ideal_thread_count;

  // Depending on the number of nodes (node_count) and on the number of thread
  // available, we need to compute the right values to parallelize correctly the
  // computations. For example, if we load a single mass spectrum from an
  // XY-formatted file, then we cannot image to distribute that loading process
  // on more than one thread. This is what this function is for: ensure that the
  // proper values for thread count and nodes per thread are correct. Otherwise
  // crash !

  if(node_count < ideal_thread_count)
    {
      return 1;
    }

  return ideal_thread_count;
}

std::pair<std::size_t, std::size_t>
MassDataIntegrator::bestParallelIntegrationParams(std::size_t node_count)
{
  std::size_t ideal_thread_count =
    bestThreadCountForMsRunDataSetTreeNodeCount(node_count);

  int nodes_per_thread = node_count / ideal_thread_count;

  return std::pair<std::size_t, int>(ideal_thread_count, nodes_per_thread);
}

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

std::vector<std::pair<Iterator, Iterator>>
MassDataIntegrator::calculateMsRunDataSetTreeNodeIteratorPairs(
  std::size_t node_count, std::size_t thread_count, int nodes_per_thread)
{
  std::vector<std::pair<Iterator, Iterator>> iterators;

  Iterator begin_iterator =
    mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr()->getRootNodes().begin();
  Iterator end_iterator =
    mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr()->getRootNodes().end();

  for(std::size_t iter = 0; iter < thread_count; ++iter)
    {
      // qDebug() << "thread index:" << iter;

      std::size_t first_index = iter * nodes_per_thread;

      Iterator first_iterator = begin_iterator + first_index;

      // Declare variables
      std::size_t last_index = 0;
      Iterator last_iterator;

      if(iter == (thread_count - 1))
        {
          // qDebug() << "In the last iteration in the for loop.";

          // We are in the last iteration of the loop, so make sure we account
          // for all the remaining spectra.
          last_iterator = end_iterator;
        }
      else
        {

          // We are not yet at the last of the loop iteration. We want to
          // provide the last() iterator as the iterator that matches the
          // spectrum *after* the one we really consider to be the last spectrum
          // to be accounted for. Which is why we add one below.

          last_index = (iter + 1) * nodes_per_thread;

          // qDebug() << "Test last_index:" << last_index;

          if(last_index >= node_count)
            {
              // qDebug() << "Too large, setting last_iterator to
              // end_iterator.";
              last_iterator = end_iterator;
            }
          else
            {
              // qDebug() << "Setting last_itertor to begin + last_index.";
              last_iterator = begin_iterator + last_index;
            }
        }

      // qDebug().noquote() << QString(
      //"Prepared visitor index %1 for interval [%2-%3]")
      //.arg(iter)
      //.arg(first_iterator - begin_iterator)
      //.arg(last_iterator - begin_iterator)
      //<< "with distance:"
      //<< std::distance(first_iterator, last_iterator);

      iterators.push_back(
        std::pair<Iterator, Iterator>(first_iterator, last_iterator));
    }

  return iterators;
}

std::vector<std::pair<Iterator, Iterator>>
MassDataIntegrator::calculateMsRunDataSetTreeNodeIteratorPairs(
  Iterator begin_iterator,
  Iterator end_iterator,
  std::size_t thread_count,
  int nodes_per_thread)
{
  std::vector<std::pair<Iterator, Iterator>> iterators;

  std::size_t node_count = std::distance(begin_iterator, end_iterator);

  // qDebug() << "The distance between start and end iterators:" << node_count;

  for(std::size_t iter = 0; iter < thread_count; ++iter)
    {
      // qDebug() << "thread index:" << iter;

      std::size_t first_index = iter * nodes_per_thread;

      Iterator first_iterator = begin_iterator + first_index;

      // Declare variables
      std::size_t last_index = 0;
      Iterator last_iterator;

      if(iter == (thread_count - 1))
        {
          // qDebug() << "In the last iteration in the for loop.";

          // We are in the last iteration of the loop, so make sure we account
          // for all the remaining spectra.
          last_iterator = end_iterator;
        }
      else
        {

          // We are not yet at the last of the loop iteration. We want to
          // provide the last() iterator as the iterator that matches the
          // spectrum *after* the one we really consider to be the last spectrum
          // to be accounted for. Which is why we add one below.

          last_index = (iter + 1) * nodes_per_thread;

          // qDebug() << "Test last_index:" << last_index;

          if(last_index >= node_count)
            {
              // qDebug() << "Too large, setting last_iterator to
              // end_iterator.";
              last_iterator = end_iterator;
            }
          else
            {
              // qDebug() << "Setting last_itertor to begin + last_index.";
              last_iterator = begin_iterator + last_index;
            }
        }

      // qDebug().noquote() << QString(
      //"Prepared visitor index %1 for interval [%2-%3]")
      //.arg(iter)
      //.arg(first_iterator - begin_iterator)
      //.arg(last_iterator - begin_iterator)
      //<< "with distance:"
      //<< std::distance(first_iterator, last_iterator);

      iterators.push_back(
        std::pair<Iterator, Iterator>(first_iterator, last_iterator));
    }

  return iterators;
}


} // namespace MineXpert

} // namespace MsXpS
