/**
 * \file pappsomspp/msrun/private/timsmsrunreader.h
 * \date 05/09/2019
 * \author Olivier Langella
 * \brief MSrun file reader for native Bruker TimsTOF raw data
 */

/*******************************************************************************
 * 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 "timsframesmsrunreader.h"
#include "../../exception/exceptionnotimplemented.h"
#include <QDebug>

using namespace pappso;

TimsFramesMsRunReader::TimsFramesMsRunReader(MsRunIdCstSPtr &msrun_id_csp)
  : MsRunReader(msrun_id_csp)
{
  qDebug() << "Now initializing the TimsFramesMsRunReader.";

  initialize();
}

TimsFramesMsRunReader::~TimsFramesMsRunReader()
{
  msp_timsData = nullptr;
}

void
TimsFramesMsRunReader::initialize()
{
  msp_timsData = std::make_shared<TimsData>(mcsp_msRunId.get()->getFileName());
  if(msp_timsData == nullptr)
    {
      throw PappsoException(
        QObject::tr("ERROR in TimsFramesMsRunReader::initialize "
                    "msp_timsData is null for MsRunId %1")
          .arg(mcsp_msRunId.get()->toString()));
    }
}


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


pappso::MassSpectrumSPtr
TimsFramesMsRunReader::massSpectrumSPtr(
  [[maybe_unused]] std::size_t spectrum_index)
{
  throw ExceptionNotImplemented(
    QObject::tr("Not yet implemented in TimsFramesMsRunReader %1.\n")
      .arg(__LINE__));

  return pappso::MassSpectrumSPtr();
}


pappso::MassSpectrumCstSPtr
TimsFramesMsRunReader::massSpectrumCstSPtr(std::size_t spectrum_index)
{
  return msp_timsData->getMassSpectrumCstSPtrByRawIndex(spectrum_index);
}


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

  QualifiedMassSpectrum mass_spectrum;

  msp_timsData->getQualifiedMassSpectrumByRawIndex(
    getMsRunId(), mass_spectrum, spectrum_index, want_binary_data);
  return mass_spectrum;
}


void
TimsFramesMsRunReader::readSpectrumCollection(
  SpectrumCollectionHandlerInterface &handler)
{
  qDebug() << "Reading the spectrum collection with no specific configuration.";
  MsRunReadConfig config;
  readSpectrumCollection2(config, handler);
}


void
TimsFramesMsRunReader::readSpectrumCollection2(
  const MsRunReadConfig &config, SpectrumCollectionHandlerInterface &handler)
{
  qDebug().noquote() << "20231129 Reading the spectrum collection with this "
                        "specific configuration:"
                     << config.toString();

  // We want to restrict the data reading process to the configuration provided
  // as parameter.
  // The frame record subset will include all frames fulfiling the config
  // requirements (retention time and ms levels)

  std::vector<std::size_t> subset_of_tims_frame_ids;

  // The very first sorting of frames is perfomed on the basis of the
  // retention time range that the user might have defined. Otherwise, all the
  // frames are deemed suitable for next processing steps.

  // Then, the MS level is checked against the config provided by the user.
  for(auto const &frame_record : msp_timsData->getTimsFrameRecordList())
    {
      if(frame_record.frame_id == 0)
        continue;

      if(config.acceptRetentionTimeInSeconds(frame_record.frame_time))
        {
          std::size_t ms_level = 2;
          if(frame_record.msms_type == 0)
            ms_level = 1;
          if(config.acceptMsLevel(ms_level))
            {
              subset_of_tims_frame_ids.push_back(frame_record.frame_id);
            }
        }
    }

  // At this point, we have a subset of frame records.
  std::size_t frames_count = subset_of_tims_frame_ids.size();
  qDebug() << "The number of retained RT range-matching frames:"
           << frames_count;

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

  // Check for ion mobility range :
  bool asked_ion_mobility_range   = false;
  quint32 mobility_scan_num_begin = 0;
  quint32 mobility_scan_num_end   = 0;

  if(!config
        .getParameterValue(
          MsRunReadConfigParameter::TimsFramesMsRunReader_mobility_index_begin)
        .isNull())
    {
      asked_ion_mobility_range = true;

      mobility_scan_num_begin =
        config
          .getParameterValue(MsRunReadConfigParameter::
                               TimsFramesMsRunReader_mobility_index_begin)
          .toUInt();
      mobility_scan_num_end =
        config
          .getParameterValue(
            MsRunReadConfigParameter::TimsFramesMsRunReader_mobility_index_end)
          .toUInt();
    }

  // Check for m/z resolution downgrading
  std::size_t mz_index_merge_window = 0;
  if(!config
        .getParameterValue(
          MsRunReadConfigParameter::TimsFramesMsRunReader_mz_index_merge_window)
        .isNull())
    {
      mz_index_merge_window =
        config
          .getParameterValue(MsRunReadConfigParameter::
                               TimsFramesMsRunReader_mz_index_merge_window)
          .toUInt();
    }

  // Check for m/z range selection
  bool asked_mz_range   = false;
  double mz_range_begin = -1;
  double mz_range_end   = -1;

  if(!config
        .getParameterValue(
          MsRunReadConfigParameter::TimsFramesMsRunReader_mz_begin)
        .isNull())
    {
      asked_mz_range = true;

      mz_range_begin =
        config
          .getParameterValue(
            MsRunReadConfigParameter::TimsFramesMsRunReader_mz_begin)
          .toDouble();
      mz_range_end = config
                       .getParameterValue(
                         MsRunReadConfigParameter::TimsFramesMsRunReader_mz_end)
                       .toDouble();

      qDebug() << "The m/z range asked is: " << mz_range_begin << "--"
               << mz_range_end;
    }

  // Now that we have gathered all the configuration bits from the user,
  // iterate in the vector of frames records and for each frame ask that
  // all its scans be combined into a single mass spectrum, by taking into
  // account the configuration bits provided by the user.

  const std::vector<FrameIdDescr> &frame_id_descr_list =
    msp_timsData->getFrameIdDescrList();

  // The scan index is the index of the scan in the *whole* mass data file, it
  // is a sequential number of scans over all the frames.
  std::size_t scan_index = 0; // iterate in each spectrum

  for(std::size_t tims_frame_id : subset_of_tims_frame_ids)
    {
      const FrameIdDescr &current_frame_record =
        frame_id_descr_list[tims_frame_id];

      scan_index = current_frame_record.m_cumulSize;
      TimsFrameCstSPtr tims_frame_csp =
        msp_timsData->getTimsFrameCstSPtrCached(tims_frame_id);

      // If the user wants to select specific 1/Ko values, we need to
      // compute the ion mobility scan value starting from that 1/Ko value in
      // each frame.

      if(!config
            .getParameterValue(
              MsRunReadConfigParameter::
                TimsFramesMsRunReader_mobility_one_over_k0_begin)
            .isNull())
        {
          asked_ion_mobility_range = true;

          mobility_scan_num_begin =
            tims_frame_csp.get()->getScanNumFromOneOverK0(
              config
                .getParameterValue(
                  MsRunReadConfigParameter::
                    TimsFramesMsRunReader_mobility_one_over_k0_begin)
                .toDouble());

          mobility_scan_num_end = tims_frame_csp.get()->getScanNumFromOneOverK0(
            config
              .getParameterValue(
                MsRunReadConfigParameter::
                  TimsFramesMsRunReader_mobility_one_over_k0_end)
              .toDouble());
        }

      // Get to know the size of the frame, that is,
      // the number of mobility scans in it.
      quint32 mobility_scans_count = tims_frame_csp->getTotalNumberOfScans();

      // Provide these two variables for the function below to fill in the
      // values.
      quint32 min_mz_index_out = 0;
      quint32 max_mz_index_out = 0;

      Trace spectrum;

      if(config.needPeakList())
        {
          if(asked_ion_mobility_range)
            {
              if(mobility_scan_num_end > (mobility_scans_count - 1))
                {
                  mobility_scan_num_end = mobility_scans_count - 1;
                }
            }
          else
            {
              mobility_scan_num_begin = 0;
              mobility_scan_num_end   = mobility_scans_count - 1;
            }

          if(asked_mz_range)
            {
              spectrum = tims_frame_csp->cumulateScansToTraceMzDownResolution2(
                mz_index_merge_window,
                mz_range_begin,
                mz_range_end,
                mobility_scan_num_begin,
                mobility_scan_num_end,
                min_mz_index_out,
                max_mz_index_out);
            }
          else
            {
              spectrum = tims_frame_csp->cumulateScansToTraceMzDownResolution(
                mz_index_merge_window,
                mobility_scan_num_begin,
                mobility_scan_num_end,
                min_mz_index_out,
                max_mz_index_out);
            }
        }

      // qDebug() << "Got min_mz_index_out:" << min_mz_index_out;
      // qDebug() << "Got max_mz_index_out:" << max_mz_index_out;

      // Now, with or without the peak list, we have to craft a qualified mass
      // spectrum that will hold all the data about the data in it.
      QualifiedMassSpectrum mass_spectrum;

      MassSpectrumId spectrum_id;

      spectrum_id.setSpectrumIndex(tims_frame_id);
      spectrum_id.setMsRunId(getMsRunId());

      // Can be modified to add bits that might help our case
      spectrum_id.setNativeId(
        QString("frame id=%1 scan index=%2 im_begin=%3 im_end=%4")
          .arg(tims_frame_id)
          .arg(scan_index)
          .arg(mobility_scan_num_begin)
          .arg(mobility_scan_num_end));

      mass_spectrum.setMassSpectrumId(spectrum_id);

      // We want to document the retention time!
      mass_spectrum.setRtInSeconds(tims_frame_csp.get()->getTime());

      // We do want to document the ms level of the spectrum and possibly
      // the precursor's m/z and charge.
      unsigned int frame_ms_level = tims_frame_csp.get()->getMsLevel();
      mass_spectrum.setMsLevel(frame_ms_level);

      // We cannot set this value because the inverse mobility (1/Ko) measured
      // by Bruker does not correspond formally to a drift time.
      mass_spectrum.setDtInMilliSeconds(-1);

      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::TimsFrameScansCount,
        (qlonglong)tims_frame_csp->getTotalNumberOfScans());
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::TimsFrameInvKoBegin,
        tims_frame_csp->getOneOverK0Transformation(0));
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::TimsFrameInvKoEnd,
        tims_frame_csp->getOneOverK0Transformation(
          tims_frame_csp->getTotalNumberOfScans() - 1));

      // qDebug() << "Setting mass spectrum parameter value mz index begin to:"
      //          << min_mz_index_out;
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::TimsFrameMzIndexBegin,
        min_mz_index_out);

      // qDebug() << "Setting mass spectrum parameter value mz index end to:"
      //          << max_mz_index_out;
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::TimsFrameMzIndexEnd, max_mz_index_out);

      if(config.needPeakList())
        {
          mass_spectrum.setEmptyMassSpectrum(false);

          mass_spectrum.setMassSpectrumSPtr(
            std::make_shared<MassSpectrum>(spectrum));
        }
      else
        {
          mass_spectrum.setEmptyMassSpectrum(true);
        }

      handler.setQualifiedMassSpectrum(mass_spectrum);
    }
}


void
TimsFramesMsRunReader::readSpectrumCollectionByMsLevel(
  [[maybe_unused]] SpectrumCollectionHandlerInterface &handler,
  [[maybe_unused]] unsigned int ms_level)
{
  qDebug();
}


std::size_t
TimsFramesMsRunReader::spectrumListSize() const
{
  return msp_timsData->getTotalNumberOfScans();
}


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


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

bool
TimsFramesMsRunReader::acquireDevice()
{
  if(msp_timsData == nullptr)
    {
      initialize();
    }
  return true;
}


XicCoordSPtr
TimsFramesMsRunReader::newXicCoordSPtrFromSpectrumIndex(
  std::size_t spectrum_index [[maybe_unused]],
  pappso::PrecisionPtr precision [[maybe_unused]]) const
{
  throw ExceptionNotImplemented(QObject::tr("Not implemented %1 %2 %3")
                                  .arg(__FILE__)
                                  .arg(__FUNCTION__)
                                  .arg(__LINE__));
}

XicCoordSPtr
TimsFramesMsRunReader::newXicCoordSPtrFromQualifiedMassSpectrum(
  const pappso::QualifiedMassSpectrum &mass_spectrum [[maybe_unused]],
  pappso::PrecisionPtr precision [[maybe_unused]]) const
{
  throw ExceptionNotImplemented(QObject::tr("Not implemented %1 %2 %3")
                                  .arg(__FILE__)
                                  .arg(__FUNCTION__)
                                  .arg(__LINE__));
}

TimsDataSp
TimsFramesMsRunReader::getTimsDataSPtr()
{
  acquireDevice();
  return msp_timsData;
}


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

  acquireDevice();

  return msp_timsData->getTicChromatogram();
}


Trace
TimsFramesMsRunReader::computeTicChromatogram()
{

  // We want to compute the TIC chromatogram, not load the chromatogram that
  // is located in the SQL database.
  //
  // For this, we need to iterated into the frames and ask for MS1 spectra
  // only. msp_timsData has that information:
  //
  // std::vector<FrameIdDescr> m_frameIdDescrList;
  //
  // and

  // struct FrameIdDescr
  // {
  //   std::size_t m_frameId;   // frame id
  //   std::size_t m_size;      // frame size (number of TOF scans in frame)
  //   std::size_t m_cumulSize; // cumulative size
  // };

  Trace tic_chromatogram;

  const std::vector<FrameIdDescr> frame_descr_list =
    msp_timsData->getFrameIdDescrList();

  for(FrameIdDescr frame_id_descr : frame_descr_list)
    {
      TimsFrameCstSPtr tims_frame_csp =
        msp_timsData->getTimsFrameCstSPtrCached(frame_id_descr.m_frameId);
      std::size_t scan_begin = 0;
      std::size_t scan_end   = tims_frame_csp->getTotalNumberOfScans() - 1;

      // By convention, a TIC chromatogram is only performed using MS1
      // spectra.
      if(tims_frame_csp->getMsLevel() == 1)
        {

          // Retention times are in seconds in the Bruker world.
          double rt = tims_frame_csp->getTime();

          tic_chromatogram.append(DataPoint(
            rt,
            tims_frame_csp->cumulateScansIntensities(scan_begin, scan_end)));
        }
      else
        continue;
    }

  return tic_chromatogram;
}
