/**
 * \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/exceptionnotfound.h"
#include "../../msrun/xiccoord/xiccoordtims.h"
#include "../../vendors/tims/timsddaprecursors.h"
#include <QDebug>

using namespace pappso;

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

TimsMsRunReaderMs2::TimsMsRunReaderMs2(const TimsMsRunReaderBase &msrun_reader_base)
  : TimsMsRunReaderBase(msrun_reader_base)
{
  initialize();
}

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

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

  if(msp_timsData == nullptr)
    {
      throw PappsoException(
        QObject::tr("ERROR in TimsMsRunReaderMs2::initialize "
                    "msp_timsData is null for MsRunId %1")
          .arg(mcsp_msRunId.get()->toString()));
    }

  mp_timsDdaPrecursors = msp_timsData.get()->getTimsDdaPrecursorsPtr();
}

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

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

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

bool
TimsMsRunReaderMs2::accept(const QString &file_name [[maybe_unused]]) 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;
  TimsDdaPrecursors::SpectrumDescr spectrum_descr;
  try
    {
      spectrum_descr =
        mp_timsDdaPrecursors->getSpectrumDescrWithPrecursorId(precursor_index);
    }
  catch(ExceptionNotFound &error)
    {
      throw ExceptionNotFound(
        QObject::tr("spectrum_index %1 NOT FOUND in file %2 : %3")
          .arg(spectrum_index)
          .arg(getMsRunId().get()->getFileName())
          .arg(error.qwhat()));
    }

  if(spectrum_index % 2 == 0)
    {
      // qDebug() << "MS1 spectrum precursor_index=" << precursor_index;
      // this is an MS1 spectrum
      QualifiedMassSpectrum mass_spectrum_ms1;
      mp_timsDdaPrecursors->getQualifiedMs1MassSpectrumBySpectrumDescr(
        getMsRunId(), mass_spectrum_ms1, spectrum_descr, want_binary_data);
      // qDebug(); // << mass_spectrum_ms1.toString();

      // qDebug() << mass_spectrum_ms1.getMassSpectrumSPtr().get()->toString();
      return mass_spectrum_ms1;
    }
  else
    {
      // qDebug() << "MS2 spectrum precursor_index=" << precursor_index;
      QualifiedMassSpectrum mass_spectrum_ms2;
      if(spectrum_descr.ms2_index != spectrum_index)
        {
          // qDebug();
          throw PappsoException(
            QObject::tr("ERROR in %1 %2 %3 spectrum_descr.ms2_index(%4) != "
                        "spectrum_index(%5)")
              .arg(__FILE__)
              .arg(__FUNCTION__)
              .arg(__LINE__)
              .arg(spectrum_descr.ms2_index)
              .arg(spectrum_index));
        }

      mp_timsDdaPrecursors->getQualifiedMs2MassSpectrumBySpectrumDescr(
        getMsRunId(), mass_spectrum_ms2, spectrum_descr, want_binary_data);
      // qDebug() << mass_spectrum_ms2.toString();

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


void
TimsMsRunReaderMs2::readSpectrumCollection(
  SpectrumCollectionHandlerInterface &handler)
{
  readSpectrumCollectionByMsLevel(handler, 0);
}

void
TimsMsRunReaderMs2::readSpectrumCollection2(
  [[maybe_unused]] const MsRunReadConfig &config,
  [[maybe_unused]] SpectrumCollectionHandlerInterface &handler)
{
  readSpectrumCollectionByMsLevel(handler, 0);
}

void
TimsMsRunReaderMs2::readSpectrumCollectionByMsLevel(
  SpectrumCollectionHandlerInterface &handler, unsigned int ms_level)
{
  // qDebug() << " ms_level=" << ms_level;
  // 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);

  mp_timsDdaPrecursors->setMonoThread(isMonoThread());

  mp_timsDdaPrecursors->ms2ReaderSpectrumCollectionByMsLevel(
    getMsRunId(), handler, ms_level);

  // 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 (mp_timsDdaPrecursors->getTotalPrecursorCount() * 2);
}


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


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

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

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

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


XicCoordSPtr
TimsMsRunReaderMs2::newXicCoordSPtrFromSpectrumIndex(
  std::size_t spectrum_index, pappso::PrecisionPtr precision) const
{
  XicCoordTimsSPtr xic_coord  = std::make_shared<XicCoordTims>();
  std::size_t precursor_index = (spectrum_index / 2) + 1;
  auto xic                    = this->msp_timsData.get()
               ->getTimsDdaPrecursorsPtr()
               ->getXicCoordTimsFromPrecursorId(precursor_index, precision);

  xic_coord.get()->mzRange      = xic.mzRange;
  xic_coord.get()->rtTarget     = xic.rtTarget;
  xic_coord.get()->scanNumBegin = xic.scanNumBegin;
  xic_coord.get()->scanNumEnd   = xic.scanNumEnd;


  return xic_coord;
}

pappso::XicCoordSPtr
TimsMsRunReaderMs2::newXicCoordSPtrFromQualifiedMassSpectrum(
  const pappso::QualifiedMassSpectrum &mass_spectrum,
  pappso::PrecisionPtr precision) const
{
  return newXicCoordSPtrFromSpectrumIndex(
    mass_spectrum.getMassSpectrumId().getSpectrumIndex(), precision);
}

std::vector<double>
pappso::TimsMsRunReaderMs2::getRetentionTimeLine()
{
  return msp_timsData.get()->getRetentionTimeLineInSeconds();
}

Trace
TimsMsRunReaderMs2::getTicChromatogram()
{
  // Use the Sqlite database to fetch the total ion current chromatogram (TIC
  // chromatogram).

  acquireDevice();

  return msp_timsData->getTicChromatogram();
}
