/**
 * \file pappsomspp/msrun/private/timsmsrunreaderms2.cpp
 * \date 10/09/2019
 * \author Olivier Langella
 * \brief MSrun file reader for native Bruker TimsTOF specialized for MS2
 * purpose
 */


/*******************************************************************************
 * Copyright (c) 2019 Olivier Langella <Olivier.Langella@u-psud.fr>.
 *
 * This file is part of the PAPPSOms++ library.
 *
 *     PAPPSOms++ 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.
 *
 *     PAPPSOms++ 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 PAPPSOms++.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#include "timsmsrunreaderms2.h"
#include "../../exception/exceptionnotimplemented.h"
#include <QDebug>
#include <QtConcurrent/QtConcurrent>

using namespace pappso;

TimsMsRunReaderMs2::TimsMsRunReaderMs2(MsRunIdCstSPtr &msrun_id_csp)
  : MsRunReader(msrun_id_csp)
{
  initialize();
}

TimsMsRunReaderMs2::~TimsMsRunReaderMs2()
{
  if(msp_timsData != nullptr)
    {
      msp_timsData = nullptr;
    }
}

void
pappso::TimsMsRunReaderMs2::initialize()
{
  msp_timsData = std::make_shared<TimsData>(mcsp_msRunId.get()->getFileName());
}

void
TimsMsRunReaderMs2::setMs2BuiltinCentroid(bool centroid)
{
  m_builtinMs2Centroid = centroid;
  if(msp_timsData != nullptr)
    {
      msp_timsData->setMs2BuiltinCentroid(m_builtinMs2Centroid);
    }
  else
    {
      throw PappsoException(
        QObject::tr("ERROR in TimsMsRunReaderMs2::setMs2BuiltinCentroid "
                    "msp_timsData is null"));
    }
}

void
TimsMsRunReaderMs2::setMs2FilterCstSPtr(pappso::FilterInterfaceCstSPtr filter)
{
  msp_ms2Filter = filter;
  if(msp_timsData != nullptr)
    {
      msp_timsData->setMs2FilterCstSPtr(msp_ms2Filter);
    }
  else
    {
      throw PappsoException(
        QObject::tr("ERROR in TimsMsRunReaderMs2::setMs2FilterCstSPtr "
                    "msp_timsData is null"));
    }
}

void
TimsMsRunReaderMs2::setMs1FilterCstSPtr(pappso::FilterInterfaceCstSPtr filter)
{
  msp_ms1Filter = filter;
  if(msp_timsData != nullptr)
    {
      msp_timsData->setMs1FilterCstSPtr(filter);
    }
  else
    {
      throw PappsoException(
        QObject::tr("ERROR in TimsMsRunReaderMs2::setMs1FilterCstSPtr "
                    "msp_timsData is null"));
    }
}

bool
TimsMsRunReaderMs2::accept(const QString &file_name) const
{
  qDebug() << file_name;
  return true;
}


pappso::MassSpectrumSPtr
TimsMsRunReaderMs2::massSpectrumSPtr(std::size_t spectrum_index)
{
  QualifiedMassSpectrum mass_spectrum =
    qualifiedMassSpectrum(spectrum_index, true);
  return mass_spectrum.getMassSpectrumSPtr();
}


pappso::MassSpectrumCstSPtr
TimsMsRunReaderMs2::massSpectrumCstSPtr(std::size_t spectrum_index)
{
  QualifiedMassSpectrum mass_spectrum =
    qualifiedMassSpectrum(spectrum_index, true);
  return mass_spectrum.getMassSpectrumSPtr();
}


QualifiedMassSpectrum
TimsMsRunReaderMs2::qualifiedMassSpectrum(std::size_t spectrum_index,
                                          bool want_binary_data) const
{

  std::size_t precursor_index = (spectrum_index / 2) + 1;

  if(spectrum_index % 2 == 0)
    {
      qDebug();
      // this is an MS1 spectrum
      QualifiedMassSpectrum mass_spectrum_ms1 =
        msp_timsData->getQualifiedMs1MassSpectrumByPrecursorId(
          spectrum_index, precursor_index, want_binary_data);
      MassSpectrumId spectrum_id(mass_spectrum_ms1.getMassSpectrumId());
      spectrum_id.setMsRunId(getMsRunId());
      mass_spectrum_ms1.setMassSpectrumId(spectrum_id);
      qDebug(); // << mass_spectrum_ms1.toString();

      // qDebug() << mass_spectrum_ms1.getMassSpectrumSPtr().get()->toString();
      return mass_spectrum_ms1;
    }
  else
    {
      qDebug();
      QualifiedMassSpectrum mass_spectrum_ms2;
      msp_timsData->getQualifiedMs2MassSpectrumByPrecursorId(
        mass_spectrum_ms2, spectrum_index, precursor_index, want_binary_data);
      MassSpectrumId spectrum_id(mass_spectrum_ms2.getMassSpectrumId());
      spectrum_id.setMsRunId(getMsRunId());
      mass_spectrum_ms2.setMassSpectrumId(spectrum_id);
      qDebug(); // << mass_spectrum_ms2.toString();

      // qDebug() << mass_spectrum_ms2.getMassSpectrumSPtr().get()->toString();
      return mass_spectrum_ms2;
    }
}


void
TimsMsRunReaderMs2::readSpectrumCollection(
  SpectrumCollectionHandlerInterface &handler)
{
  const bool want_binary_data = handler.needPeakList();
  // const bool want_binary_data = false;

  // We'll need it to perform the looping in the spectrum list.
  std::size_t spectrum_list_size = spectrumListSize();

  //   qDebug() << "The spectrum list has size:" << spectrum_list_size;

  // Inform the handler of the spectrum list so that it can handle feedback to
  // the user.
  handler.spectrumListHasSize(spectrum_list_size);

  // Iterate in the full list of spectra.
  bool readAhead = handler.isReadAhead();

  if(readAhead)
    {

      std::size_t process_list_size = 300;

      struct tmp_item
      {
        QualifiedMassSpectrum qualified_mass_spectrum;
        std::size_t iter;
        bool want_binary_data;
      };

      for(std::size_t i = 0; i < spectrum_list_size; i += process_list_size)
        {
          qDebug();
          // If the user of this reader instance wants to stop reading the
          // spectra, then break this loop.
          if(handler.shouldStop())
            {
              qDebug() << "The operation was cancelled. Breaking the loop.";
              break;
            }
          std::vector<tmp_item> item_list;
          for(std::size_t iter = 0;
              (iter < process_list_size) && ((iter + i) < spectrum_list_size);
              iter++)
            {

              bool get_data = want_binary_data;
              if((iter + i) % 2 == 0)
                { // MS1
                  get_data = handler.needMsLevelPeakList(1);
                }
              else
                {
                  get_data = handler.needMsLevelPeakList(2);
                }

              item_list.push_back(
                {QualifiedMassSpectrum(), iter + i, get_data});
            }
          qDebug() << item_list.size();
          // Use QtConcurrentBlocking::mapped to apply the scale function to all
          // the images in the list.
          QtConcurrent::blockingMap(
            item_list.begin(), item_list.end(), [this](tmp_item &one_item) {
              qDebug() << one_item.iter;
              one_item.qualified_mass_spectrum =
                qualifiedMassSpectrum(one_item.iter, one_item.want_binary_data);

              // qDebug() << one_item.qualified_mass_spectrum.size() << " " <<
              // one_item.qualified_mass_spectrum.getMassSpectrumSPtr().get()->toString();
            });

          qDebug() << item_list.size();
          for(auto &item : item_list)
            {
              // qDebug() << item.qualified_mass_spectrum.getMassSpectrumSPtr()
              //              .get()
              //              ->toString();
              handler.setQualifiedMassSpectrum(item.qualified_mass_spectrum);
              qDebug();
            }
        }
    }
  else
    {
      for(std::size_t iter = 0; iter < spectrum_list_size; iter++)
        {
          qDebug();
          // If the user of this reader instance wants to stop reading the
          // spectra, then break this loop.
          if(handler.shouldStop())
            {
              qDebug() << "The operation was cancelled. Breaking the loop.";
              break;
            }
          bool get_data = want_binary_data;
          if(iter % 2 == 0)
            { // MS1
              if(!handler.needMsLevelPeakList(1))
                {
                  get_data = false;
                }
            }
          QualifiedMassSpectrum qualified_mass_spectrum =
            qualifiedMassSpectrum(iter, get_data);
          handler.setQualifiedMassSpectrum(qualified_mass_spectrum);
          qDebug();
        }
    }
  // End of
  // for(std::size_t iter = 0; iter < spectrum_list_size; iter++)

  // Now let the loading handler know that the loading of the data has ended.
  // The handler might need this "signal" to perform additional tasks or to
  // cleanup cruft.

  // qDebug() << "Loading ended";
  handler.loadingEnded();
}


std::size_t
TimsMsRunReaderMs2::spectrumListSize() const
{
  return (msp_timsData->getTotalNumberOfPrecursors() * 2);
}


bool
TimsMsRunReaderMs2::hasScanNumbers() const
{
  return false;
}


bool
TimsMsRunReaderMs2::releaseDevice()
{
  msp_timsData = nullptr;
  return true;
}

bool
TimsMsRunReaderMs2::acquireDevice()
{
  if(msp_timsData == nullptr)
    {
      initialize();
      msp_timsData->setMs2BuiltinCentroid(m_builtinMs2Centroid);
      msp_timsData->setMs1FilterCstSPtr(msp_ms1Filter);
      msp_timsData->setMs2FilterCstSPtr(msp_ms2Filter);
    }
  return true;
}

std::vector<std::size_t>
pappso::TimsMsRunReaderMs2::getPrecursorsIDFromMzRt(int charge,
                                                    double mz_val,
                                                    double rt_sec,
                                                    double k0)
{
  return msp_timsData->getPrecursorsFromMzRtCharge(charge, mz_val, rt_sec, k0);
}

pappso::TimsDataSp
pappso::TimsMsRunReaderMs2::getTimsDataSPtr()
{
  acquireDevice();
  return msp_timsData;
}
