/**
 * \file pappsomspp/vendors/tims/timsdata.cpp
 * \date 27/08/2019
 * \author Olivier Langella
 * \brief main Tims data handler
 */

/*******************************************************************************
 * 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 "timsdata.h"
#include "../../exception/exceptionnotfound.h"
#include "../../exception/exceptioninterrupted.h"
#include "../../processing/combiners/tracepluscombiner.h"
#include "../../processing/filters/filtertriangle.h"
#include "../../processing/filters/filterpass.h"
#include "../../processing/filters/filtersuitestring.h"
#include "./xicextractor/timsdirectxicextractor.h"
#include <QDebug>
#include <solvers.h>
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QMutexLocker>
#include <QThread>
#include <set>

namespace pappso
{

TimsData::TimsData(QDir timsDataDirectory)
  : m_timsDataDirectory(timsDataDirectory)
{

  qDebug() << "Start of construction of TimsData";
  mpa_mzCalibrationStore = new MzCalibrationStore();
  if(!m_timsDataDirectory.exists())
    {
      throw PappsoException(
        QObject::tr("ERROR TIMS data directory %1 not found")
          .arg(m_timsDataDirectory.absolutePath()));
    }

  if(!QFileInfo(m_timsDataDirectory.absoluteFilePath("analysis.tdf")).exists())
    {

      throw PappsoException(
        QObject::tr("ERROR TIMS data directory, %1 sqlite file not found")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf")));
    }

  // Open the database
  QSqlDatabase qdb = openDatabaseConnection();


  QSqlQuery q(qdb);
  if(!q.exec("select Key, Value from GlobalMetadata where "
             "Key='TimsCompressionType';"))
    {

      qDebug();
      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(q.lastError().databaseText())
          .arg(q.lastError().driverText())
          .arg(q.lastError().nativeErrorCode()));
    }


  int compression_type = 0;
  if(q.next())
    {
      compression_type = q.value(1).toInt();
    }
  qDebug() << " compression_type=" << compression_type;
  mpa_timsBinDec = new TimsBinDec(
    QFileInfo(m_timsDataDirectory.absoluteFilePath("analysis.tdf_bin")),
    compression_type);

  qDebug();

  // get number of precursors
  if(!q.exec("SELECT COUNT( DISTINCT Id) FROM Precursors;"))
    {
      qDebug();
      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }
  if(q.next())
    {
      m_totalNumberOfPrecursors = q.value(0).toLongLong();
    }


  fillFrameIdDescrList();

  // get number of scans
  if(!q.exec("SELECT SUM(NumScans) FROM Frames"))
    {
      qDebug();
      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }
  if(q.next())
    {
      m_totalNumberOfScans = q.value(0).toLongLong();
    }

  if(!q.exec("select * from MzCalibration;"))
    {
      qDebug();
      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(q.lastError().databaseText())
          .arg(q.lastError().driverText())
          .arg(q.lastError().nativeErrorCode()));
    }

  while(q.next())
    {
      QSqlRecord record = q.record();
      m_mapMzCalibrationRecord.insert(
        std::pair<int, QSqlRecord>(record.value(0).toInt(), record));
    }

  // m_mapTimsCalibrationRecord

  if(!q.exec("select * from TimsCalibration;"))
    {
      qDebug();
      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(q.lastError().databaseText())
          .arg(q.lastError().driverText())
          .arg(q.lastError().nativeErrorCode()));
    }
  while(q.next())
    {
      QSqlRecord record = q.record();
      m_mapTimsCalibrationRecord.insert(
        std::pair<int, QSqlRecord>(record.value(0).toInt(), record));
    }


  // store frames
  if(!q.exec("select Frames.TimsId, Frames.AccumulationTime, "        // 1
             "Frames.MzCalibration, "                                 // 2
             "Frames.T1, Frames.T2, "                                 // 4
             "Frames.Time, Frames.MsMsType, Frames.TimsCalibration, " // 7
             "Frames.Id "                                             // 8
             " FROM Frames;"))
    {
      qDebug();
      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(q.lastError().databaseText())
          .arg(q.lastError().driverText())
          .arg(q.lastError().nativeErrorCode()));
    }
  while(q.next())
    {
      QSqlRecord record = q.record();
      m_mapFramesRecord.insert(std::pair<std::size_t, QSqlRecord>(
        record.value(8).toULongLong(), record));
    }

  mcsp_ms2Filter = std::make_shared<pappso::FilterSuiteString>(
    "chargeDeconvolution|0.02dalton mzExclusion|0.01dalton");


  std::shared_ptr<FilterTriangle> ms1filter =
    std::make_shared<FilterTriangle>();
  ms1filter.get()->setTriangleSlope(50, 0.01);
  mcsp_ms1Filter = ms1filter;
  qDebug();
}

QSqlDatabase
TimsData::openDatabaseConnection() const
{
  QString database_connection_name = QString("%1_%2")
                                       .arg(m_timsDataDirectory.absolutePath())
                                       .arg((quintptr)QThread::currentThread());
  // Open the database
  QSqlDatabase qdb = QSqlDatabase::database(database_connection_name);
  if(!qdb.isValid())
    {
      qDebug() << database_connection_name;
      qdb = QSqlDatabase::addDatabase("QSQLITE", database_connection_name);
      qdb.setDatabaseName(m_timsDataDirectory.absoluteFilePath("analysis.tdf"));
    }


  if(!qdb.open())
    {
      qDebug();
      throw PappsoException(
        QObject::tr("ERROR opening TIMS sqlite database file %1, database name "
                    "%2 :\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(database_connection_name)
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }
  return qdb;
}

TimsData::TimsData([[maybe_unused]] const pappso::TimsData &other)
{
  qDebug();
}

TimsData::~TimsData()
{
  // m_qdb.close();
  if(mpa_timsBinDec != nullptr)
    {
      delete mpa_timsBinDec;
    }
  if(mpa_mzCalibrationStore != nullptr)
    {
      delete mpa_mzCalibrationStore;
    }
}

void
TimsData::setMs2BuiltinCentroid(bool centroid)
{
  m_builtinMs2Centroid = centroid;
}

bool
TimsData::getMs2BuiltinCentroid() const
{
  return m_builtinMs2Centroid;
}

void
TimsData::fillFrameIdDescrList()
{
  qDebug();
  QSqlDatabase qdb = openDatabaseConnection();

  QSqlQuery q =
    qdb.exec(QString("SELECT Id, NumScans FROM "
                     "Frames ORDER BY Id"));
  if(q.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }
  TimsFrameSPtr tims_frame;
  bool index_found = false;
  std::size_t timsId;
  std::size_t numberScans;
  std::size_t cumulScans = 0;
  while(q.next() && (!index_found))
    {
      timsId      = q.value(0).toULongLong();
      numberScans = q.value(1).toULongLong();

      // qDebug() << timsId;

      m_thousandIndexToFrameIdDescrListIndex.insert(
        std::pair<std::size_t, std::size_t>((cumulScans / 1000),
                                            m_frameIdDescrList.size()));

      m_frameIdDescrList.push_back({timsId, numberScans, cumulScans});
      cumulScans += numberScans;
    }
  qDebug();
}

std::pair<std::size_t, std::size_t>
TimsData::getScanCoordinateFromRawIndex(std::size_t raw_index) const
{

  std::size_t fast_access = raw_index / 1000;
  qDebug() << " fast_access=" << fast_access;
  auto map_it = m_thousandIndexToFrameIdDescrListIndex.find(fast_access);
  if(map_it == m_thousandIndexToFrameIdDescrListIndex.end())
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR raw index %1 not found (fast_access)")
          .arg(raw_index));
    }
  std::size_t start_point_index = map_it->second;
  while((start_point_index > 0) &&
        (m_frameIdDescrList[start_point_index].m_cumulSize > raw_index))
    {
      start_point_index--;
    }
  for(std::size_t i = start_point_index; i < m_frameIdDescrList.size(); i++)
    {

      if(raw_index <
         (m_frameIdDescrList[i].m_cumulSize + m_frameIdDescrList[i].m_size))
        {
          return std::pair<std::size_t, std::size_t>(
            m_frameIdDescrList[i].m_frameId,
            raw_index - m_frameIdDescrList[i].m_cumulSize);
        }
    }

  throw ExceptionNotFound(
    QObject::tr("ERROR raw index %1 not found").arg(raw_index));
}


std::size_t
TimsData::getRawIndexFromCoordinate(std::size_t frame_id,
                                    std::size_t scan_num) const
{

  for(auto frameDescr : m_frameIdDescrList)
    {
      if(frameDescr.m_frameId == frame_id)
        {
          return frameDescr.m_cumulSize + scan_num;
        }
    }

  throw ExceptionNotFound(
    QObject::tr("ERROR raw index with frame=%1 scan=%2 not found")
      .arg(frame_id)
      .arg(scan_num));
}

/** @brief get a mass spectrum given its spectrum index
 * @param raw_index a number begining at 0, corresponding to a Tims Scan in
 * the order they lies in the binary data file
 */
pappso::MassSpectrumCstSPtr
TimsData::getMassSpectrumCstSPtrByRawIndex(std::size_t raw_index)
{

  qDebug() << " raw_index=" << raw_index;
  try
    {
      auto coordinate = getScanCoordinateFromRawIndex(raw_index);
      return getMassSpectrumCstSPtr(coordinate.first, coordinate.second);
    }
  catch(PappsoException &error)
    {
      throw PappsoException(
        QObject::tr(
          "Error TimsData::getMassSpectrumCstSPtrByRawIndex raw_index=%1 :\n%2")
          .arg(raw_index)
          .arg(error.qwhat()));
    }
}


TimsFrameBaseCstSPtr
TimsData::getTimsFrameBaseCstSPtr(std::size_t timsId)
{

  qDebug() << " timsId=" << timsId;

  auto it_map_record_frames = m_mapFramesRecord.find(timsId);
  if(it_map_record_frames == m_mapFramesRecord.end())
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR Frames database id %1 not found").arg(timsId));
    }
  TimsFrameBaseSPtr tims_frame;


  tims_frame = std::make_shared<TimsFrameBase>(
    TimsFrameBase(timsId, it_map_record_frames->second.value(0).toULongLong()));

  auto it_map_record = m_mapMzCalibrationRecord.find(
    it_map_record_frames->second.value(2).toInt());
  if(it_map_record != m_mapMzCalibrationRecord.end())
    {

      double T1_frame =
        it_map_record_frames->second.value(3).toDouble(); // Frames.T1
      double T2_frame =
        it_map_record_frames->second.value(4).toDouble(); // Frames.T2


      tims_frame.get()->setMzCalibrationInterfaceSPtr(
        mpa_mzCalibrationStore->getInstance(
          T1_frame, T2_frame, it_map_record->second));
    }
  else
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR MzCalibration database id %1 not found")
          .arg(it_map_record_frames->second.value(2).toInt()));
    }

  tims_frame.get()->setAccumulationTime(
    it_map_record_frames->second.value(1).toDouble());

  tims_frame.get()->setTime(it_map_record_frames->second.value(5).toDouble());
  tims_frame.get()->setMsMsType(it_map_record_frames->second.value(6).toUInt());


  auto it_map_record_tims_calibration = m_mapTimsCalibrationRecord.find(
    it_map_record_frames->second.value(7).toInt());
  if(it_map_record_tims_calibration != m_mapTimsCalibrationRecord.end())
    {

      tims_frame.get()->setTimsCalibration(
        it_map_record_tims_calibration->second.value(1).toInt(),
        it_map_record_tims_calibration->second.value(2).toDouble(),
        it_map_record_tims_calibration->second.value(3).toDouble(),
        it_map_record_tims_calibration->second.value(4).toDouble(),
        it_map_record_tims_calibration->second.value(5).toDouble(),
        it_map_record_tims_calibration->second.value(6).toDouble(),
        it_map_record_tims_calibration->second.value(7).toDouble(),
        it_map_record_tims_calibration->second.value(8).toDouble(),
        it_map_record_tims_calibration->second.value(9).toDouble(),
        it_map_record_tims_calibration->second.value(10).toDouble(),
        it_map_record_tims_calibration->second.value(11).toDouble());
    }
  else
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR TimsCalibration database id %1 not found")
          .arg(it_map_record_frames->second.value(7).toInt()));
    }

  return tims_frame;
}

std::vector<std::size_t>
TimsData::getTimsMS1FrameIdRange(double rt_begin, double rt_end) const
{

  qDebug() << " rt_begin=" << rt_begin << " rt_end=" << rt_end;
  if(rt_begin < 0)
    rt_begin = 0;
  std::vector<std::size_t> tims_frameid_list;
  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q      = qdb.exec(QString("SELECT Frames.Id FROM Frames WHERE "
                                 "Frames.MsMsType=0 AND (Frames.Time>=%1) AND "
                                 "(Frames.Time<=%2) ORDER BY Frames.Time;")
                           .arg(rt_begin)
                           .arg(rt_end));
  if(q.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, database name %2, "
                    "executing SQL "
                    "command %3:\n%4\n%5\n%6")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(qdb.databaseName())
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }
  while(q.next())
    {

      tims_frameid_list.push_back(q.value(0).toULongLong());
    }
  return tims_frameid_list;
}

TimsFrameCstSPtr
TimsData::getTimsFrameCstSPtr(std::size_t timsId)
{

  qDebug() << " timsId=" << timsId
           << " m_mapFramesRecord.size()=" << m_mapFramesRecord.size();

  QMutexLocker lock(&m_mutex);
  /*
     for(auto pair_i : m_mapFramesRecord)
     {

     qDebug() << " pair_i=" << pair_i.first;
     }
     */

  auto it_map_record_frames = m_mapFramesRecord.find(timsId);
  if(it_map_record_frames == m_mapFramesRecord.end())
    {

      throw ExceptionNotFound(
        QObject::tr("ERROR Frames database id %1 not found").arg(timsId));
    }


  TimsFrameSPtr tims_frame;


  tims_frame = mpa_timsBinDec->getTimsFrameSPtrByOffset(
    timsId, it_map_record_frames->second.value(0).toULongLong());

  qDebug();
  auto it_map_record = m_mapMzCalibrationRecord.find(
    it_map_record_frames->second.value(2).toInt());
  if(it_map_record != m_mapMzCalibrationRecord.end())
    {

      double T1_frame =
        it_map_record_frames->second.value(3).toDouble(); // Frames.T1
      double T2_frame =
        it_map_record_frames->second.value(4).toDouble(); // Frames.T2


      tims_frame.get()->setMzCalibrationInterfaceSPtr(
        mpa_mzCalibrationStore->getInstance(
          T1_frame, T2_frame, it_map_record->second));
    }
  else
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR MzCalibration database id %1 not found")
          .arg(it_map_record_frames->second.value(2).toInt()));
    }

  tims_frame.get()->setAccumulationTime(
    it_map_record_frames->second.value(1).toDouble());

  tims_frame.get()->setTime(it_map_record_frames->second.value(5).toDouble());
  tims_frame.get()->setMsMsType(it_map_record_frames->second.value(6).toUInt());

  qDebug();
  auto it_map_record_tims_calibration = m_mapTimsCalibrationRecord.find(
    it_map_record_frames->second.value(7).toInt());
  if(it_map_record_tims_calibration != m_mapTimsCalibrationRecord.end())
    {

      tims_frame.get()->setTimsCalibration(
        it_map_record_tims_calibration->second.value(1).toInt(),
        it_map_record_tims_calibration->second.value(2).toDouble(),
        it_map_record_tims_calibration->second.value(3).toDouble(),
        it_map_record_tims_calibration->second.value(4).toDouble(),
        it_map_record_tims_calibration->second.value(5).toDouble(),
        it_map_record_tims_calibration->second.value(6).toDouble(),
        it_map_record_tims_calibration->second.value(7).toDouble(),
        it_map_record_tims_calibration->second.value(8).toDouble(),
        it_map_record_tims_calibration->second.value(9).toDouble(),
        it_map_record_tims_calibration->second.value(10).toDouble(),
        it_map_record_tims_calibration->second.value(11).toDouble());
    }
  else
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR TimsCalibration database id %1 not found")
          .arg(it_map_record_frames->second.value(7).toInt()));
    }
  qDebug();
  return tims_frame;
}


pappso::MassSpectrumCstSPtr
TimsData::getMassSpectrumCstSPtr(std::size_t timsId, std::size_t scanNum)
{
  qDebug() << " timsId=" << timsId << " scanNum=" << scanNum;
  pappso::TimsFrameCstSPtr frame = getTimsFrameCstSPtrCached(timsId);

  return frame->getMassSpectrumCstSPtr(scanNum);
}

std::size_t
TimsData::getTotalNumberOfScans() const
{
  return m_totalNumberOfScans;
}


std::size_t
TimsData::getTotalNumberOfPrecursors() const
{
  return m_totalNumberOfPrecursors;
}

std::vector<std::size_t>
TimsData::getPrecursorsFromMzRtCharge(int charge,
                                      double mz_val,
                                      double rt_sec,
                                      double k0)
{
  std::vector<std::size_t> precursor_ids;
  std::vector<std::vector<double>> ids;

  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q      = qdb.exec(
    QString(
      "SELECT Frames.Time, Precursors.MonoisotopicMz, Precursors.Charge, "
      "Precursors.Id, Frames.Id, PasefFrameMsMsInfo.ScanNumBegin, "
      "PasefFrameMsMsInfo.scanNumEnd "
      "FROM Frames "
      "INNER JOIN PasefFrameMsMsInfo ON Frames.Id = PasefFrameMsMsInfo.Frame "
      "INNER JOIN Precursors ON PasefFrameMsMsInfo.Precursor = Precursors.Id "
      "WHERE Precursors.Charge == %1 "
      "AND Precursors.MonoisotopicMz > %2 -0.01 "
      "AND Precursors.MonoisotopicMz < %2 +0.01 "
      "AND Frames.Time >= %3 -1 "
      "AND Frames.Time < %3 +1; ")
      .arg(charge)
      .arg(mz_val)
      .arg(rt_sec));
  if(q.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, database name %2, "
                    "executing SQL "
                    "command %3:\n%4\n%5\n%6")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(qdb.databaseName())
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }
  while(q.next())
    {
      // qInfo() << q.value(0).toDouble() << q.value(1).toDouble()
      //       << q.value(2).toDouble() << q.value(3).toDouble();

      std::vector<double> sql_values;
      sql_values.push_back(q.value(4).toDouble()); // frame id
      sql_values.push_back(q.value(3).toDouble()); // precursor id
      sql_values.push_back(q.value(5).toDouble()); // scan num begin
      sql_values.push_back(q.value(6).toDouble()); // scan num end
      sql_values.push_back(q.value(1).toDouble()); // mz_value

      ids.push_back(sql_values);


      if(std::find(precursor_ids.begin(),
                   precursor_ids.end(),
                   q.value(3).toDouble()) == precursor_ids.end())
        {
          precursor_ids.push_back(q.value(3).toDouble());
        }
    }

  if(precursor_ids.size() > 1)
    {
      // std::vector<std::size_t> precursor_ids_ko =
      // getMatchPrecursorIdByKo(ids, values[3]);
      if(precursor_ids.size() > 1)
        {
          precursor_ids = getClosestPrecursorIdByMz(ids, k0);
        }
      return precursor_ids;
    }
  else
    {
      return precursor_ids;
    }
}

std::vector<std::size_t>
TimsData::getMatchPrecursorIdByKo(std::vector<std::vector<double>> ids,
                                  double ko_value)
{
  std::vector<std::size_t> precursor_id;
  for(std::vector<double> index : ids)
    {
      auto coordinate = getScanCoordinateFromRawIndex(index[0]);

      TimsFrameBaseCstSPtr tims_frame;
      tims_frame = getTimsFrameBaseCstSPtrCached(coordinate.first);

      double bko = tims_frame.get()->getOneOverK0Transformation(index[2]);
      double eko = tims_frame.get()->getOneOverK0Transformation(index[3]);

      // qInfo() << "diff" << (bko + eko) / 2;
      double mean_ko = (bko + eko) / 2;

      if(mean_ko > ko_value - 0.1 && mean_ko < ko_value + 0.1)
        {
          precursor_id.push_back(index[1]);
        }
    }
  return precursor_id;
}

std::vector<std::size_t>
TimsData::getClosestPrecursorIdByMz(std::vector<std::vector<double>> ids,
                                    double mz_value)
{
  std::vector<std::size_t> best_precursor;
  double best_value     = 1;
  int count             = 1;
  int best_val_position = 0;

  for(std::vector<double> values : ids)
    {
      double new_val = abs(mz_value - values[4]);
      if(new_val < best_value)
        {
          best_value        = new_val;
          best_val_position = count;
        }
      count++;
    }
  best_precursor.push_back(ids[best_val_position][1]);
  return best_precursor;
}


unsigned int
TimsData::getMsLevelBySpectrumIndex(std::size_t spectrum_index)
{
  auto coordinate = getScanCoordinateFromRawIndex(spectrum_index);
  auto tims_frame = getTimsFrameCstSPtrCached(coordinate.first);
  return tims_frame.get()->getMsLevel();
}


void
TimsData::getQualifiedMassSpectrumByRawIndex(
  const MsRunIdCstSPtr &msrun_id,
  QualifiedMassSpectrum &mass_spectrum,
  std::size_t spectrum_index,
  bool want_binary_data)
{
  try
    {
      auto coordinate = getScanCoordinateFromRawIndex(spectrum_index);
      TimsFrameBaseCstSPtr tims_frame;
      if(want_binary_data)
        {
          tims_frame = getTimsFrameCstSPtrCached(coordinate.first);
        }
      else
        {
          tims_frame = getTimsFrameBaseCstSPtrCached(coordinate.first);
        }
      MassSpectrumId spectrum_id;

      spectrum_id.setSpectrumIndex(spectrum_index);
      spectrum_id.setMsRunId(msrun_id);
      spectrum_id.setNativeId(QString("frame=%1 scan=%2 index=%3")
                                .arg(coordinate.first)
                                .arg(coordinate.second)
                                .arg(spectrum_index));

      mass_spectrum.setMassSpectrumId(spectrum_id);

      mass_spectrum.setMsLevel(tims_frame.get()->getMsLevel());
      mass_spectrum.setRtInSeconds(tims_frame.get()->getTime());

      mass_spectrum.setDtInMilliSeconds(
        tims_frame.get()->getDriftTime(coordinate.second));
      // 1/K0
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::OneOverK0,
        tims_frame.get()->getOneOverK0Transformation(coordinate.second));

      mass_spectrum.setEmptyMassSpectrum(true);
      if(want_binary_data)
        {
          mass_spectrum.setMassSpectrumSPtr(
            tims_frame.get()->getMassSpectrumSPtr(coordinate.second));
          if(mass_spectrum.size() > 0)
            {
              mass_spectrum.setEmptyMassSpectrum(false);
            }
        }
      else
        {
          // if(tims_frame.get()->getNbrPeaks(coordinate.second) > 0)
          //{
          mass_spectrum.setEmptyMassSpectrum(false);
          // }
        }
      if(tims_frame.get()->getMsLevel() > 1)
        {

          auto spectrum_descr = getSpectrumDescrWithScanCoordinate(coordinate);
          if(spectrum_descr.precursor_id > 0)
            {

              mass_spectrum.appendPrecursorIonData(
                spectrum_descr.precursor_ion_data);


              MassSpectrumId spectrum_id;
              std::size_t prec_spectrum_index = getRawIndexFromCoordinate(
                spectrum_descr.parent_frame, coordinate.second);

              mass_spectrum.setPrecursorSpectrumIndex(prec_spectrum_index);
              mass_spectrum.setPrecursorNativeId(
                QString("frame=%1 scan=%2 index=%3")
                  .arg(spectrum_descr.parent_frame)
                  .arg(coordinate.second)
                  .arg(prec_spectrum_index));

              mass_spectrum.setParameterValue(
                QualifiedMassSpectrumParameter::IsolationMz,
                spectrum_descr.isolationMz);
              mass_spectrum.setParameterValue(
                QualifiedMassSpectrumParameter::IsolationWidth,
                spectrum_descr.isolationWidth);

              mass_spectrum.setParameterValue(
                QualifiedMassSpectrumParameter::CollisionEnergy,
                spectrum_descr.collisionEnergy);
              mass_spectrum.setParameterValue(
                QualifiedMassSpectrumParameter::BrukerPrecursorIndex,
                (quint64)spectrum_descr.precursor_id);
            }
        }
    }
  catch(PappsoException &error)
    {
      throw pappso::PappsoException(
        QObject::tr("Error TimsData::getQualifiedMassSpectrumByRawIndex "
                    "spectrum_index=%1 :\n%2")
          .arg(spectrum_index)
          .arg(error.qwhat()));
    }
}


Trace
TimsData::getTicChromatogram()
{
  // In the Frames table, each frame has a record describing the
  // SummedIntensities for all the mobility spectra.


  MapTrace rt_tic_map_trace;

  using Pair     = std::pair<double, double>;
  using Map      = std::map<double, double>;
  using Iterator = Map::iterator;


  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q =
    qdb.exec(QString("SELECT Time, SummedIntensities "
                     "FROM Frames WHERE MsMsType = 0 "
                     "ORDER BY Time;"));

  if(q.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, database name %2, "
                    "executing SQL "
                    "command %3:\n%4\n%5\n%6")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(qdb.databaseName())
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }

  while(q.next())
    {

      bool ok = false;

      int cumulated_results = 2;

      double rt = q.value(0).toDouble(&ok);
      cumulated_results -= ok;

      double sumY = q.value(1).toDouble(&ok);
      cumulated_results -= ok;

      if(cumulated_results)
        {
          throw PappsoException(
            QObject::tr(
              "ERROR in TIMS sqlite database file: could not read either the "
              "retention time or the summed intensities (%1, database name %2, "
              "executing SQL "
              "command %3:\n%4\n%5\n%6")
              .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
              .arg(qdb.databaseName())
              .arg(q.lastQuery())
              .arg(qdb.lastError().databaseText())
              .arg(qdb.lastError().driverText())
              .arg(qdb.lastError().nativeErrorCode()));
        }

      // Try to insert value sumY at key rt.
      std::pair<Iterator, bool> res = rt_tic_map_trace.insert(Pair(rt, sumY));

      if(!res.second)
        {
          // One other same rt value was seen already (like in ion mobility mass
          // spectrometry, for example). Only increment the y value.

          res.first->second += sumY;
        }
    }

  // qDebug().noquote() << "The TIC chromatogram:\n"
  //<< rt_tic_map_trace.toTrace().toString();

  return rt_tic_map_trace.toTrace();
}


void
TimsData::getQualifiedMs1MassSpectrumByPrecursorId(
  const MsRunIdCstSPtr &msrun_id,
  QualifiedMassSpectrum &mass_spectrum,
  SpectrumDescr &spectrum_descr,
  bool want_binary_data)
{

  qDebug() << " ms2_index=" << spectrum_descr.ms2_index
           << " precursor_index=" << spectrum_descr.precursor_id;

  TracePlusCombiner combiner;
  MapTrace combiner_result;

  try
    {
      mass_spectrum.setMsLevel(1);
      mass_spectrum.setPrecursorSpectrumIndex(0);
      mass_spectrum.setEmptyMassSpectrum(true);

      MassSpectrumId spectrum_id;
      spectrum_id.setSpectrumIndex(spectrum_descr.ms1_index);
      spectrum_id.setNativeId(
        QString("frame=%1 begin=%2 end=%3 precursor=%4 idxms1=%5")
          .arg(spectrum_descr.parent_frame)
          .arg(spectrum_descr.scan_mobility_start)
          .arg(spectrum_descr.scan_mobility_end)
          .arg(spectrum_descr.precursor_id)
          .arg(spectrum_descr.ms1_index));

      spectrum_id.setMsRunId(msrun_id);

      mass_spectrum.setMassSpectrumId(spectrum_id);


      TimsFrameBaseCstSPtr tims_frame;
      if(want_binary_data)
        {
          qDebug() << "bindec";
          tims_frame = getTimsFrameCstSPtrCached(spectrum_descr.parent_frame);
        }
      else
        {
          tims_frame =
            getTimsFrameBaseCstSPtrCached(spectrum_descr.parent_frame);
        }
      mass_spectrum.setRtInSeconds(tims_frame.get()->getTime());

      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::OneOverK0begin,
        tims_frame.get()->getOneOverK0Transformation(
          spectrum_descr.scan_mobility_start));

      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::OneOverK0end,
        tims_frame.get()->getOneOverK0Transformation(
          spectrum_descr.scan_mobility_end));


      if(want_binary_data)
        {
          combiner.combine(combiner_result,
                           tims_frame.get()->cumulateScanToTrace(
                             spectrum_descr.scan_mobility_start,
                             spectrum_descr.scan_mobility_end));

          pappso::Trace trace(combiner_result);
          qDebug();

          if(trace.size() > 0)
            {
              if(mcsp_ms1Filter != nullptr)
                {
                  mcsp_ms1Filter->filter(trace);
                }

              qDebug();
              mass_spectrum.setMassSpectrumSPtr(
                MassSpectrum(trace).makeMassSpectrumSPtr());
              mass_spectrum.setEmptyMassSpectrum(false);
            }
          else
            {
              mass_spectrum.setMassSpectrumSPtr(nullptr);
              mass_spectrum.setEmptyMassSpectrum(true);
            }
        }
    }

  catch(PappsoException &error)
    {
      throw error;
    }
  catch(std::exception &error)
    {
      qDebug() << QString("Failure %1 ").arg(error.what());
    }
}


TimsFrameBaseCstSPtr
TimsData::getTimsFrameBaseCstSPtrCached(std::size_t timsId)
{
  // QMutexLocker locker(&m_mutex);
  for(auto &tims_frame : m_timsFrameBaseCache)
    {
      if(tims_frame.get()->getId() == timsId)
        {
          m_timsFrameBaseCache.push_back(tims_frame);
          return tims_frame;
        }
    }

  m_timsFrameBaseCache.push_back(getTimsFrameBaseCstSPtr(timsId));
  if(m_timsFrameBaseCache.size() > m_cacheSize)
    m_timsFrameBaseCache.pop_front();
  return m_timsFrameBaseCache.back();
}

TimsFrameCstSPtr
TimsData::getTimsFrameCstSPtrCached(std::size_t timsId)
{
  // QMutexLocker locker(&m_mutex);
  for(auto &tims_frame : m_timsFrameCache)
    {
      if(tims_frame.get()->getId() == timsId)
        {
          m_timsFrameCache.push_back(tims_frame);
          return tims_frame;
        }
    }

  m_timsFrameCache.push_back(getTimsFrameCstSPtr(timsId));
  if(m_timsFrameCache.size() > m_cacheSize)
    m_timsFrameCache.pop_front();
  return m_timsFrameCache.back();
}

void
TimsData::setMs2FilterCstSPtr(pappso::FilterInterfaceCstSPtr &filter)
{
  mcsp_ms2Filter = filter;
}
void
TimsData::setMs1FilterCstSPtr(pappso::FilterInterfaceCstSPtr &filter)
{
  mcsp_ms1Filter = filter;
}

pappso::XicCoordTims
pappso::TimsData::getXicCoordTimsFromPrecursorId(std::size_t precursor_id,
                                                 PrecisionPtr precision_ptr)
{

  qDebug();
  XicCoordTims xic_coord_tims_struct;

  try
    {
      if(m_mapXicCoordRecord.size() == 0)
        {
          // Go get records!

          // We proceed in this way:

          // 1. For each Precursor reference to the Precursors table's ID found
          // in the PasefFrameMsMsInfo table, store the precursor ID for
          // step 2.

          // 2. From the Precursors table's ID from step 1, get the
          // MonoisotopicMz.

          // 3. From the PasefFrameMsMsInfo table, for the Precursors table's ID
          // reference, get a reference to the Frames table's ID. Thanks to the
          // Frames ID, look for the Time value (acquisition retention time) for
          // the MS/MS spectrum. The Time value in the Frames tables always
          // corresponds to a Frame of MsMsType 8 (that is, MS/MS), which is
          // expected since we are looking into MS/MS data.

          // 4. From the PasefFrameMsMsInfo table, associate the values
          // ScanNumBegin and ScanNumEnd, the mobility bins in which the
          // precursor was found.


          QSqlDatabase qdb = openDatabaseConnection();
          QSqlQuery q      = qdb.exec(
            QString("SELECT Precursors.id, "
                    "min(Frames.Time), "
                    "min(PasefFrameMsMsInfo.ScanNumBegin), "
                    "max(PasefFrameMsMsInfo.ScanNumEnd), "
                    "Precursors.MonoisotopicMz "
                    "FROM "
                    "PasefFrameMsMsInfo INNER JOIN Precursors ON "
                    "PasefFrameMsMsInfo.Precursor=Precursors.Id INNER JOIN "
                    "Frames ON PasefFrameMsMsInfo.Frame=Frames.Id "
                    "GROUP BY Precursors.id;"));
          if(q.lastError().isValid())
            {
              qDebug();
              throw PappsoException(
                QObject::tr(
                  "ERROR in TIMS sqlite database file %1, executing SQL "
                  "command %2:\n%3\n%4\n%5")
                  .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
                  .arg(q.lastQuery())
                  .arg(qdb.lastError().databaseText())
                  .arg(qdb.lastError().driverText())
                  .arg(qdb.lastError().nativeErrorCode()));
            }

          q.last(); // strange bug : get the last sql record and get back,
          // otherwise it will not retrieve all records.
          q.first();
          // std::size_t i = 0;
          do
            {
              QSqlRecord record = q.record();
              m_mapXicCoordRecord.insert(std::pair<std::size_t, QSqlRecord>(
                (std::size_t)record.value(0).toULongLong(), record));
            }
          while(q.next());
        }


      auto it_map_xiccoord = m_mapXicCoordRecord.find(precursor_id);
      if(it_map_xiccoord == m_mapXicCoordRecord.end())
        {

          throw ExceptionNotFound(
            QObject::tr("ERROR Precursors database id %1 not found")
              .arg(precursor_id));
        }

      auto &q = it_map_xiccoord->second;
      xic_coord_tims_struct.mzRange =
        MzRange(q.value(4).toDouble(), precision_ptr);
      xic_coord_tims_struct.scanNumBegin = q.value(2).toUInt();
      xic_coord_tims_struct.scanNumEnd   = q.value(3).toUInt();
      xic_coord_tims_struct.rtTarget     = q.value(1).toDouble();
      // xic_structure.charge       = q.value(5).toUInt();
      xic_coord_tims_struct.xicSptr = std::make_shared<Xic>();
    }
  catch(PappsoException &error)
    {
      throw error;
    }
  catch(std::exception &error)
    {
      qDebug() << QString("Failure %1 ").arg(error.what());
    }
  return xic_coord_tims_struct;
}


std::map<quint32, quint32>
TimsData::getRawMs2ByPrecursorId(std::size_t precursor_index)
{
  qDebug();
  std::map<quint32, quint32> raw_spectrum;
  try
    {
      QSqlDatabase qdb = openDatabaseConnection();

      qdb = openDatabaseConnection();
      QSqlQuery q =
        qdb.exec(QString("SELECT PasefFrameMsMsInfo.*, Precursors.* FROM "
                         "PasefFrameMsMsInfo INNER JOIN Precursors ON "
                         "PasefFrameMsMsInfo.Precursor=Precursors.Id where "
                         "Precursors.Id=%1;")
                   .arg(precursor_index));
      if(q.lastError().isValid())
        {
          qDebug();
          throw PappsoException(
            QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                        "command %2:\n%3\n%4\n%5")
              .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
              .arg(q.lastQuery())
              .arg(qdb.lastError().databaseText())
              .arg(qdb.lastError().driverText())
              .arg(qdb.lastError().nativeErrorCode()));
        }
      qDebug();
      // m_mutex.unlock();
      if(q.size() == 0)
        {

          throw ExceptionNotFound(
            QObject::tr(
              "ERROR in getQualifiedMassSpectrumByPrecursorId, precursor "
              "id=%1 not found")
              .arg(precursor_index));
        }
      else
        {
          //  qDebug() << " q.size()="<< q.size();
          qDebug();
          bool first                      = true;
          std::size_t scan_mobility_start = 0;
          std::size_t scan_mobility_end   = 0;
          std::vector<std::size_t> tims_frame_list;

          while(q.next())
            {
              tims_frame_list.push_back(q.value(0).toLongLong());
              if(first)
                {

                  scan_mobility_start = q.value(1).toLongLong();
                  scan_mobility_end   = q.value(2).toLongLong();

                  first = false;
                }
            }
          // QMutexLocker locker(&m_mutex_spectrum);
          qDebug();
          pappso::TimsFrameCstSPtr tims_frame, previous_frame;
          // TracePlusCombiner combiner;
          // MapTrace combiner_result;
          for(std::size_t tims_id : tims_frame_list)
            {
              tims_frame = getTimsFrameCstSPtrCached(tims_id);
              qDebug();
              /*combiner.combine(combiner_result,
                tims_frame.get()->cumulateScanToTrace(
                scan_mobility_start, scan_mobility_end));*/
              if(previous_frame.get() != nullptr)
                {
                  if(previous_frame.get()->hasSameCalibrationData(
                       *tims_frame.get()))
                    {
                    }
                  else
                    {
                      throw ExceptionNotFound(
                        QObject::tr(
                          "ERROR in %1 %2, different calibration data "
                          "between frame id %3 and frame id %4")
                          .arg(__FILE__)
                          .arg(__FUNCTION__)
                          .arg(previous_frame.get()->getId())
                          .arg(tims_frame.get()->getId()));
                    }
                }
              tims_frame.get()->cumulateScansInRawMap(
                raw_spectrum, scan_mobility_start, scan_mobility_end);
              qDebug();

              previous_frame = tims_frame;
            }
          qDebug() << " precursor_index=" << precursor_index
                   << " num_rows=" << tims_frame_list.size()
                   << " sql=" << q.lastQuery() << " "
                   << (std::size_t)QThread::currentThreadId();
          if(first == true)
            {
              throw ExceptionNotFound(
                QObject::tr(
                  "ERROR in getQualifiedMassSpectrumByPrecursorId, precursor "
                  "id=%1 not found")
                  .arg(precursor_index));
            }
          qDebug();
        }
    }

  catch(PappsoException &error)
    {
      throw PappsoException(QObject::tr("ERROR in %1 (precursor_index=%2):\n%3")
                              .arg(__FUNCTION__)
                              .arg(precursor_index)
                              .arg(error.qwhat()));
    }
  catch(std::exception &error)
    {
      qDebug() << QString("Failure %1 ").arg(error.what());
    }
  return raw_spectrum;
  qDebug();
}


void
TimsData::getQualifiedMs2MassSpectrumByPrecursorId(
  const MsRunIdCstSPtr &msrun_id,
  QualifiedMassSpectrum &mass_spectrum,
  SpectrumDescr &spectrum_descr,
  bool want_binary_data)
{
  try
    {
      MassSpectrumId spectrum_id;

      spectrum_id.setSpectrumIndex(spectrum_descr.ms2_index);
      spectrum_id.setNativeId(QString("precursor=%1 idxms2=%2")
                                .arg(spectrum_descr.precursor_id)
                                .arg(spectrum_descr.ms2_index));
      spectrum_id.setMsRunId(msrun_id);

      mass_spectrum.setMassSpectrumId(spectrum_id);

      mass_spectrum.setMsLevel(2);
      qDebug() << "spectrum_descr.precursor_id=" << spectrum_descr.precursor_id
               << " spectrum_descr.ms1_index=" << spectrum_descr.ms1_index
               << " spectrum_descr.ms2_index=" << spectrum_descr.ms2_index;
      mass_spectrum.setPrecursorSpectrumIndex(spectrum_descr.ms1_index);

      mass_spectrum.setEmptyMassSpectrum(true);

      qDebug();


      mass_spectrum.appendPrecursorIonData(spectrum_descr.precursor_ion_data);

      mass_spectrum.setPrecursorNativeId(
        QString("frame=%1 begin=%2 end=%3 precursor=%4 idxms1=%5")
          .arg(spectrum_descr.parent_frame)
          .arg(spectrum_descr.scan_mobility_start)
          .arg(spectrum_descr.scan_mobility_end)
          .arg(spectrum_descr.precursor_id)
          .arg(spectrum_descr.ms1_index));

      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::IsolationMz,
        spectrum_descr.isolationMz);
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::IsolationWidth,
        spectrum_descr.isolationWidth);

      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::CollisionEnergy,
        spectrum_descr.collisionEnergy);
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::BrukerPrecursorIndex,
        (quint64)spectrum_descr.precursor_id);

      // QMutexLocker locker(&m_mutex_spectrum);
      qDebug();
      pappso::TimsFrameBaseCstSPtr tims_frame, previous_frame;
      // TracePlusCombiner combiner;
      // MapTrace combiner_result;
      std::map<quint32, quint32> raw_spectrum;
      bool first = true;
      for(std::size_t tims_id : spectrum_descr.tims_frame_list)
        {
          qDebug() << " precursor_index=" << spectrum_descr.precursor_id
                   << " tims_id=" << tims_id;
          if(want_binary_data)
            {
              qDebug() << "bindec";
              tims_frame = getTimsFrameCstSPtrCached(tims_id);
            }
          else
            {
              tims_frame = getTimsFrameBaseCstSPtrCached(tims_id);
            }
          if(first)
            {
              mass_spectrum.setRtInSeconds(tims_frame.get()->getTime());

              mass_spectrum.setParameterValue(
                QualifiedMassSpectrumParameter::OneOverK0begin,
                tims_frame.get()->getOneOverK0Transformation(
                  spectrum_descr.scan_mobility_start));

              mass_spectrum.setParameterValue(
                QualifiedMassSpectrumParameter::OneOverK0end,
                tims_frame.get()->getOneOverK0Transformation(
                  spectrum_descr.scan_mobility_end));

              first = false;
            }


          if(want_binary_data)
            {
              qDebug();
              /*combiner.combine(combiner_result,
                tims_frame.get()->cumulateScanToTrace(
                scan_mobility_start, scan_mobility_end));*/
              if(previous_frame.get() != nullptr)
                {
                  if(previous_frame.get()->hasSameCalibrationData(
                       *tims_frame.get()))
                    {
                    }
                  else
                    {
                      throw ExceptionNotFound(
                        QObject::tr(
                          "ERROR in %1 %2, different calibration data "
                          "between frame id %3 and frame id %4")
                          .arg(__FILE__)
                          .arg(__FUNCTION__)
                          .arg(previous_frame.get()->getId())
                          .arg(tims_frame.get()->getId()));
                    }
                }
              tims_frame.get()->cumulateScansInRawMap(
                raw_spectrum,
                spectrum_descr.scan_mobility_start,
                spectrum_descr.scan_mobility_end);
              qDebug();
            }
          previous_frame = tims_frame;
        }
      qDebug() << " precursor_index=" << spectrum_descr.precursor_id
               << " num_rows=" << spectrum_descr.tims_frame_list.size()
               << (std::size_t)QThread::currentThreadId();
      if(first == true)
        {
          throw ExceptionNotFound(
            QObject::tr(
              "ERROR in getQualifiedMassSpectrumByPrecursorId, precursor "
              "id=%1 not found")
              .arg(spectrum_descr.precursor_id));
        }
      if(want_binary_data)
        {
          qDebug() << " precursor_index=" << spectrum_descr.precursor_id;
          // peak_pick.filter(trace);
          pappso::Trace trace;
          if(m_builtinMs2Centroid)
            {
              trace =
                tims_frame.get()->getTraceFromCumulatedScansBuiltinCentroid(
                  raw_spectrum);
            }
          else
            {
              // no builtin centroid:

              trace =
                tims_frame.get()->getTraceFromCumulatedScans(raw_spectrum);
            }

          if(trace.size() > 0)
            {
              qDebug() << " precursor_index=" << spectrum_descr.precursor_id
                       << " " << trace.size() << " "
                       << (std::size_t)QThread::currentThreadId();

              if(mcsp_ms2Filter != nullptr)
                {
                  // FilterTriangle filter;
                  // filter.setTriangleSlope(50, 0.02);
                  // filter.filter(trace);
                  // trace.filter(pappso::FilterHighPass(10));
                  mcsp_ms2Filter->filter(trace);
                }

              // FilterScaleFactorY filter_scale((double)1 /
              //                                 (double)tims_frame_list.size());
              // filter_scale.filter(trace);
              qDebug() << " precursor_index=" << spectrum_descr.precursor_id;
              mass_spectrum.setMassSpectrumSPtr(
                MassSpectrum(trace).makeMassSpectrumSPtr());
              mass_spectrum.setEmptyMassSpectrum(false);
            }
          else
            {
              mass_spectrum.setMassSpectrumSPtr(nullptr);
              mass_spectrum.setEmptyMassSpectrum(true);
            }

          qDebug();
        }
      qDebug();
    }

  catch(PappsoException &error)
    {
      throw PappsoException(
        QObject::tr("ERROR in %1 (ms2_index=%2 precursor_index=%3):\n%4")
          .arg(__FUNCTION__)
          .arg(spectrum_descr.ms2_index)
          .arg(spectrum_descr.precursor_id)
          .arg(error.qwhat()));
    }
  catch(std::exception &error)
    {
      qDebug() << QString("Failure %1 ").arg(error.what());
    }
  qDebug();
}

void
pappso::TimsData::ms2ReaderSpectrumCollectionByMsLevel(
  const MsRunIdCstSPtr &msrun_id,
  pappso::SpectrumCollectionHandlerInterface &handler,
  unsigned int ms_level)
{
  qDebug() << " ms_level=" << ms_level;
  QSqlDatabase qdb          = openDatabaseConnection();
  QSqlQuery qprecursor_list = qdb.exec(QString(
    "SELECT PasefFrameMsMsInfo.Frame, "    // 0
    "PasefFrameMsMsInfo.ScanNumBegin, "    // 1
    "PasefFrameMsMsInfo.ScanNumEnd, "      // 2
    "PasefFrameMsMsInfo.IsolationMz, "     // 3
    "PasefFrameMsMsInfo.IsolationWidth, "  // 4
    "PasefFrameMsMsInfo.CollisionEnergy, " // 5
    "PasefFrameMsMsInfo.Precursor, "       // 6
    "Precursors.Id, "                      // 7
    "Precursors.LargestPeakMz, "           // 8
    "Precursors.AverageMz, "               // 9
    "Precursors.MonoisotopicMz, "          // 10
    "Precursors.Charge, "                  // 11
    "Precursors.ScanNumber, "              // 12
    "Precursors.Intensity, "               // 13
    "Precursors.Parent "                   // 14
    "FROM PasefFrameMsMsInfo "
    "INNER JOIN Precursors ON "
    "PasefFrameMsMsInfo.Precursor=Precursors.Id "
    "ORDER BY PasefFrameMsMsInfo.Precursor, PasefFrameMsMsInfo.Frame ;"));
  if(qprecursor_list.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(qprecursor_list.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }


  qDebug() << "qprecursor_list.size()=" << qprecursor_list.size();
  qDebug() << QObject::tr(
                "TIMS sqlite database file %1, executing SQL "
                "command %2:\n%3\n%4\n%5")
                .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
                .arg(qprecursor_list.lastQuery())
                .arg(qdb.lastError().databaseText())
                .arg(qdb.lastError().driverText())
                .arg(qdb.lastError().nativeErrorCode());

  qDebug() << "qprecursor_list.isActive()=" << qprecursor_list.isActive();
  qDebug() << "qprecursor_list.isSelect()=" << qprecursor_list.isSelect();
  bool first = true;
  SpectrumDescr spectrum_descr;
  std::size_t i = 0;      /*
                             while(qprecursor_list.next())
                             {
                             qDebug() << "i=" << i;
                             i++;
                             }*/
  qprecursor_list.last(); // strange bug : get the last sql record and get
  // back, unless it will not retrieve all records.

  qDebug() << "qprecursor_list.at()=" << qprecursor_list.at();
  qprecursor_list.first();
  // std::size_t i = 0;
  do
    {
      qDebug() << "i=" << i;
      // 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.";
          throw ExceptionInterrupted(
            QObject::tr("reading TimsTOF job cancelled by the user :\n%1")
              .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf")));
        }
      qDebug() << " spectrum_descr.precursor_id ="
               << spectrum_descr.precursor_id;
      qDebug() << " qprecursor_list.value(6).toLongLong() ="
               << qprecursor_list.value(6).toLongLong();

      if(spectrum_descr.precursor_id !=
         (std::size_t)qprecursor_list.value(6).toLongLong())
        {
          // new precursor
          if(spectrum_descr.precursor_id > 0)
            {
              qDebug();
              ms2ReaderGenerateMS1MS2Spectrum(
                msrun_id, handler, spectrum_descr, ms_level);
            }

          spectrum_descr.tims_frame_list.clear();
          first = true;
        }
      qDebug() << " qprecursor_list.value(6).toLongLong() ="
               << qprecursor_list.value(6).toLongLong();
      spectrum_descr.precursor_id =
        (std::size_t)qprecursor_list.value(6).toLongLong();
      qDebug() << " spectrum_descr.precursor_id ="
               << spectrum_descr.precursor_id;
      qDebug() << " cumul tims frame:" << qprecursor_list.value(0).toLongLong();
      spectrum_descr.tims_frame_list.push_back(
        qprecursor_list.value(0).toLongLong());
      qDebug() << " first =" << first;
      if(first)
        {
          qDebug();
          // mass_spectrum.setPrecursorCharge(q.value(11).toInt());
          // mass_spectrum.setPrecursorMz(q.value(10).toDouble());
          // mass_spectrum.setPrecursorIntensity(q.value(13).toDouble());
          spectrum_descr.precursor_ion_data =
            PrecursorIonData(qprecursor_list.value(10).toDouble(),
                             qprecursor_list.value(11).toInt(),
                             qprecursor_list.value(13).toDouble());

          // spectrum_descr.precursor_id = q.value(6).toLongLong();
          spectrum_descr.ms2_index = (spectrum_descr.precursor_id * 2) - 1;
          spectrum_descr.ms1_index = (spectrum_descr.precursor_id * 2) - 2;

          spectrum_descr.scan_mobility_start =
            qprecursor_list.value(1).toLongLong();
          spectrum_descr.scan_mobility_end =
            qprecursor_list.value(2).toLongLong();

          spectrum_descr.isolationMz     = qprecursor_list.value(3).toDouble();
          spectrum_descr.isolationWidth  = qprecursor_list.value(4).toDouble();
          spectrum_descr.collisionEnergy = qprecursor_list.value(5).toFloat();
          spectrum_descr.parent_frame = qprecursor_list.value(14).toLongLong();


          first = false;
        }
      // qDebug() << "qprecursor_list.executedQuery()="
      //         << qprecursor_list.executedQuery();
      // qDebug() << "qprecursor_list.last()=" << qprecursor_list.last();
      i++;
    }
  while(qprecursor_list.next());
  qDebug();
  if(spectrum_descr.precursor_id > 0)
    {
      qDebug();
      ms2ReaderGenerateMS1MS2Spectrum(
        msrun_id, handler, spectrum_descr, ms_level);
    }
}


void
pappso::TimsData::ms2ReaderGenerateMS1MS2Spectrum(
  const MsRunIdCstSPtr &msrun_id,
  pappso::SpectrumCollectionHandlerInterface &handler,
  pappso::TimsData::SpectrumDescr &spectrum_descr,
  unsigned int ms_level)
{

  qDebug() << " ms_level=" << ms_level;
  // The handler will receive the index of the mass spectrum in the
  // current run via the mass spectrum id member datum.
  if((ms_level == 0) || (ms_level == 1))
    {

      QualifiedMassSpectrum mass_spectrum_ms1;
      getQualifiedMs1MassSpectrumByPrecursorId(msrun_id,
                                               mass_spectrum_ms1,
                                               spectrum_descr,
                                               handler.needMsLevelPeakList(1));

      handler.setQualifiedMassSpectrum(mass_spectrum_ms1);
    }
  if((ms_level == 0) || (ms_level == 2))
    {


      QualifiedMassSpectrum mass_spectrum_ms2;
      getQualifiedMs2MassSpectrumByPrecursorId(msrun_id,
                                               mass_spectrum_ms2,
                                               spectrum_descr,
                                               handler.needMsLevelPeakList(2));

      handler.setQualifiedMassSpectrum(mass_spectrum_ms2);
    }
}


pappso::TimsData::SpectrumDescr
pappso::TimsData::getSpectrumDescrWithPrecursorId(std::size_t precursor_id)
{

  SpectrumDescr spectrum_descr;
  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q      = qdb.exec(QString("SELECT PasefFrameMsMsInfo.Frame, "   // 0
                                 "PasefFrameMsMsInfo.ScanNumBegin, "   // 1
                                 "PasefFrameMsMsInfo.ScanNumEnd, "     // 2
                                 "PasefFrameMsMsInfo.IsolationMz, "    // 3
                                 "PasefFrameMsMsInfo.IsolationWidth, " // 4
                                 "PasefFrameMsMsInfo.CollisionEnergy, " // 5
                                 "PasefFrameMsMsInfo.Precursor, " // 6
                                 "Precursors.Id, "                // 7
                                 "Precursors.LargestPeakMz, "     // 8
                                 "Precursors.AverageMz, "         // 9
                                 "Precursors.MonoisotopicMz, "    // 10
                                 "Precursors.Charge, "            // 11
                                 "Precursors.ScanNumber, "        // 12
                                 "Precursors.Intensity, "         // 13
                                 "Precursors.Parent "             // 14
                                 "FROM PasefFrameMsMsInfo "
                                 "INNER JOIN Precursors ON "
                                 "PasefFrameMsMsInfo.Precursor=Precursors.Id "
                                 "WHERE Precursors.Id=%1;")
                           .arg(precursor_id));
  if(q.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }


  bool first = true;
  while(q.next())
    {

      qDebug() << " cumul tims frame:" << q.value(0).toLongLong();
      spectrum_descr.tims_frame_list.push_back(q.value(0).toLongLong());
      if(first)
        {
          // mass_spectrum.setPrecursorCharge(q.value(11).toInt());
          // mass_spectrum.setPrecursorMz(q.value(10).toDouble());
          // mass_spectrum.setPrecursorIntensity(q.value(13).toDouble());
          spectrum_descr.precursor_ion_data =
            PrecursorIonData(q.value(10).toDouble(),
                             q.value(11).toInt(),
                             q.value(13).toDouble());

          spectrum_descr.precursor_id = q.value(6).toLongLong();
          spectrum_descr.ms2_index    = (spectrum_descr.precursor_id * 2) - 1;
          spectrum_descr.ms1_index    = (spectrum_descr.precursor_id * 2) - 2;

          spectrum_descr.scan_mobility_start = q.value(1).toLongLong();
          spectrum_descr.scan_mobility_end   = q.value(2).toLongLong();

          spectrum_descr.isolationMz     = q.value(3).toDouble();
          spectrum_descr.isolationWidth  = q.value(4).toDouble();
          spectrum_descr.collisionEnergy = q.value(5).toFloat();
          spectrum_descr.parent_frame    = q.value(14).toLongLong();


          first = false;
        }
    }
  if(spectrum_descr.precursor_id == 0)
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR in %1 %2 : precursor id (%3) NOT FOUND ")
          .arg(__FILE__)
          .arg(__FUNCTION__)
          .arg(precursor_id));
    }
  return spectrum_descr;
}

std::vector<double>
pappso::TimsData::getRetentionTimeLine() const
{
  std::vector<double> timeline;
  auto it_map_record_frame     = m_mapFramesRecord.begin();
  auto it_map_record_frame_end = m_mapFramesRecord.end();
  while(it_map_record_frame != it_map_record_frame_end)
    {
      // Frames.Time 5, Frames.MsMsType 6
      if(it_map_record_frame->second.value(6).toInt() ==
         0) // m_msMsType == 0 is MS level 1
        timeline.push_back(it_map_record_frame->second.value(5).toDouble());
      it_map_record_frame++;
    }
  return timeline;
}

pappso::TimsData::SpectrumDescr
pappso::TimsData::getSpectrumDescrWithScanCoordinate(
  const std::pair<std::size_t, std::size_t> &scan_coordinate)
{

  SpectrumDescr spectrum_descr;
  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q =
    qdb.exec(QString("SELECT PasefFrameMsMsInfo.Frame, "    // 0
                     "PasefFrameMsMsInfo.ScanNumBegin, "    // 1
                     "PasefFrameMsMsInfo.ScanNumEnd, "      // 2
                     "PasefFrameMsMsInfo.IsolationMz, "     // 3
                     "PasefFrameMsMsInfo.IsolationWidth, "  // 4
                     "PasefFrameMsMsInfo.CollisionEnergy, " // 5
                     "PasefFrameMsMsInfo.Precursor, "       // 6
                     "Precursors.Id, "                      // 7
                     "Precursors.LargestPeakMz, "           // 8
                     "Precursors.AverageMz, "               // 9
                     "Precursors.MonoisotopicMz, "          // 10
                     "Precursors.Charge, "                  // 11
                     "Precursors.ScanNumber, "              // 12
                     "Precursors.Intensity, "               // 13
                     "Precursors.Parent "                   // 14
                     "FROM PasefFrameMsMsInfo "
                     "INNER JOIN Precursors ON "
                     "PasefFrameMsMsInfo.Precursor=Precursors.Id "
                     "WHERE "
                     "PasefFrameMsMsInfo.Frame=%1 and "
                     "(PasefFrameMsMsInfo.ScanNumBegin "
                     "<= %2 and PasefFrameMsMsInfo.ScanNumEnd >= %2);")
               .arg(scan_coordinate.first)
               .arg(scan_coordinate.second));
  if(q.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(q.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }

  if(q.next())
    {

      qDebug() << " cumul tims frame:" << q.value(0).toLongLong();
      spectrum_descr.tims_frame_list.push_back(q.value(0).toLongLong());
      // mass_spectrum.setPrecursorCharge(q.value(11).toInt());
      // mass_spectrum.setPrecursorMz(q.value(10).toDouble());
      // mass_spectrum.setPrecursorIntensity(q.value(13).toDouble());
      spectrum_descr.precursor_ion_data = PrecursorIonData(
        q.value(10).toDouble(), q.value(11).toInt(), q.value(13).toDouble());

      spectrum_descr.precursor_id = q.value(6).toLongLong();
      spectrum_descr.ms2_index    = (spectrum_descr.precursor_id * 2) - 1;
      spectrum_descr.ms1_index    = (spectrum_descr.precursor_id * 2) - 2;

      spectrum_descr.scan_mobility_start = q.value(1).toLongLong();
      spectrum_descr.scan_mobility_end   = q.value(2).toLongLong();

      spectrum_descr.isolationMz     = q.value(3).toDouble();
      spectrum_descr.isolationWidth  = q.value(4).toDouble();
      spectrum_descr.collisionEnergy = q.value(5).toFloat();
      spectrum_descr.parent_frame    = q.value(14).toLongLong();
    }
  return spectrum_descr;
}


void
pappso::TimsData::fillSpectrumDescriptionWithSqlRecord(
  pappso::TimsData::SpectrumDescr &spectrum_descr, QSqlQuery &qprecursor_list)
{

  spectrum_descr.tims_frame_list.clear();
  spectrum_descr.tims_frame_list.push_back(
    qprecursor_list.value(0).toLongLong());
  // mass_spectrum.setPrecursorCharge(q.value(11).toInt());
  // mass_spectrum.setPrecursorMz(q.value(10).toDouble());
  // mass_spectrum.setPrecursorIntensity(q.value(13).toDouble());
  spectrum_descr.precursor_ion_data =
    PrecursorIonData(qprecursor_list.value(10).toDouble(),
                     qprecursor_list.value(11).toInt(),
                     qprecursor_list.value(13).toDouble());

  spectrum_descr.precursor_id = qprecursor_list.value(6).toLongLong();
  spectrum_descr.ms2_index    = (spectrum_descr.precursor_id * 2) - 1;
  spectrum_descr.ms1_index    = (spectrum_descr.precursor_id * 2) - 2;

  spectrum_descr.scan_mobility_start = qprecursor_list.value(1).toLongLong();
  spectrum_descr.scan_mobility_end   = qprecursor_list.value(2).toLongLong();

  spectrum_descr.isolationMz     = qprecursor_list.value(3).toDouble();
  spectrum_descr.isolationWidth  = qprecursor_list.value(4).toDouble();
  spectrum_descr.collisionEnergy = qprecursor_list.value(5).toFloat();
  spectrum_descr.parent_frame    = qprecursor_list.value(14).toLongLong();
}


void
pappso::TimsData::rawReaderSpectrumCollectionByMsLevel(
  const pappso::MsRunIdCstSPtr &msrun_id,
  pappso::SpectrumCollectionHandlerInterface &handler,
  unsigned int ms_level)
{


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

  //   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);

  QSqlDatabase qdb          = openDatabaseConnection();
  QSqlQuery qprecursor_list = qdb.exec(QString(
    "SELECT DISTINCT "
    "PasefFrameMsMsInfo.Frame, "           // 0
    "PasefFrameMsMsInfo.ScanNumBegin, "    // 1
    "PasefFrameMsMsInfo.ScanNumEnd, "      // 2
    "PasefFrameMsMsInfo.IsolationMz, "     // 3
    "PasefFrameMsMsInfo.IsolationWidth, "  // 4
    "PasefFrameMsMsInfo.CollisionEnergy, " // 5
    "PasefFrameMsMsInfo.Precursor, "       // 6
    "Precursors.Id, "                      // 7
    "Precursors.LargestPeakMz, "           // 8
    "Precursors.AverageMz, "               // 9
    "Precursors.MonoisotopicMz, "          // 10
    "Precursors.Charge, "                  // 11
    "Precursors.ScanNumber, "              // 12
    "Precursors.Intensity, "               // 13
    "Precursors.Parent "                   // 14
    "FROM PasefFrameMsMsInfo "
    "INNER JOIN Precursors ON "
    "PasefFrameMsMsInfo.Precursor=Precursors.Id "
    "ORDER BY PasefFrameMsMsInfo.Frame, PasefFrameMsMsInfo.ScanNumBegin ;"));
  if(qprecursor_list.lastError().isValid())
    {

      throw PappsoException(
        QObject::tr("ERROR in TIMS sqlite database file %1, executing SQL "
                    "command %2:\n%3\n%4\n%5")
          .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf"))
          .arg(qprecursor_list.lastQuery())
          .arg(qdb.lastError().databaseText())
          .arg(qdb.lastError().driverText())
          .arg(qdb.lastError().nativeErrorCode()));
    }


  std::size_t i = 0; // iterate on each Spectrum

  qprecursor_list.last(); // strange bug : get the last sql record and get
  // back, unless it will not retrieve all records.

  qDebug() << "qprecursor_list.at()=" << qprecursor_list.at();
  qprecursor_list.first();

  TimsFrameBaseCstSPtr tims_frame;
  SpectrumDescr spectrum_descr;

  for(FrameIdDescr &current_frame : m_frameIdDescrList)
    {

      // 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.";
          throw ExceptionInterrupted(
            QObject::tr("reading TimsTOF job cancelled by the user :\n%1")
              .arg(m_timsDataDirectory.absoluteFilePath("analysis.tdf")));
        }

      tims_frame = getTimsFrameBaseCstSPtrCached(current_frame.m_frameId);
      unsigned int tims_ms_level = tims_frame.get()->getMsLevel();

      if((ms_level != 0) && (ms_level != tims_ms_level))
        { // bypass
          i += current_frame.m_size;
        }
      else
        {
          bool want_binary_data = handler.needMsLevelPeakList(tims_ms_level);
          qDebug() << "want_binary_data=" << want_binary_data;
          if(want_binary_data)
            {
              qDebug() << "bindec";
              tims_frame = getTimsFrameCstSPtrCached(current_frame.m_frameId);
            }

          bool possible_precursor = false;
          if(tims_ms_level == 2)
            {
              // seek the precursor record:
              while(qprecursor_list.value(0).toULongLong() <
                    current_frame.m_frameId)
                {
                  qprecursor_list.next();

                  if(qprecursor_list.value(0).toULongLong() ==
                     current_frame.m_frameId)
                    {
                      possible_precursor = true;
                    }
                  fillSpectrumDescriptionWithSqlRecord(spectrum_descr,
                                                       qprecursor_list);
                }
            }

          for(std::size_t scan_num = 0; scan_num < current_frame.m_size;
              scan_num++)
            {
              bool has_a_precursor = false;
              if(possible_precursor)
                {
                  if(spectrum_descr.scan_mobility_end < scan_num)
                    {
                      // seek the precursor record:
                      while(qprecursor_list.value(0).toULongLong() <
                            current_frame.m_frameId)
                        {
                          qprecursor_list.next();

                          if(qprecursor_list.value(0).toULongLong() !=
                             current_frame.m_frameId)
                            {
                              possible_precursor = false;
                            }
                          fillSpectrumDescriptionWithSqlRecord(spectrum_descr,
                                                               qprecursor_list);
                        }
                    }

                  if(possible_precursor &&
                     (spectrum_descr.scan_mobility_start < scan_num))
                    {
                      // we are in
                      has_a_precursor = true;
                    }
                } // end to determine if we are in a precursor for this spectrum

              QualifiedMassSpectrum mass_spectrum;


              MassSpectrumId spectrum_id;

              spectrum_id.setSpectrumIndex(i);
              spectrum_id.setMsRunId(msrun_id);
              spectrum_id.setNativeId(QString("frame=%1 scan=%2 index=%3")
                                        .arg(current_frame.m_frameId)
                                        .arg(scan_num)
                                        .arg(i));

              mass_spectrum.setMassSpectrumId(spectrum_id);

              mass_spectrum.setMsLevel(tims_frame.get()->getMsLevel());
              mass_spectrum.setRtInSeconds(tims_frame.get()->getTime());

              mass_spectrum.setDtInMilliSeconds(
                tims_frame.get()->getDriftTime(scan_num));
              // 1/K0
              mass_spectrum.setParameterValue(
                QualifiedMassSpectrumParameter::OneOverK0,
                tims_frame.get()->getOneOverK0Transformation(scan_num));

              mass_spectrum.setEmptyMassSpectrum(true);
              if(want_binary_data)
                {
                  try
                    {
                      mass_spectrum.setMassSpectrumSPtr(
                        tims_frame.get()->getMassSpectrumSPtr(scan_num));
                    }
                  catch(PappsoException &error)
                    {
                      throw PappsoException(
                        QObject::tr(
                          "ERROR in %1 (scan_num=%2 spectrum_index=%3):\n%4")
                          .arg(__FUNCTION__)
                          .arg(scan_num)
                          .arg(spectrum_id.getSpectrumIndex())
                          .arg(error.qwhat()));
                    }
                  if(mass_spectrum.size() > 0)
                    {
                      mass_spectrum.setEmptyMassSpectrum(false);
                    }
                }
              else
                {
                  // if(tims_frame.get()->getNbrPeaks(coordinate.second) > 0)
                  //{
                  mass_spectrum.setEmptyMassSpectrum(false);
                  // }
                }
              if(has_a_precursor)
                {
                  if(spectrum_descr.precursor_id > 0)
                    {

                      mass_spectrum.appendPrecursorIonData(
                        spectrum_descr.precursor_ion_data);

                      std::size_t prec_spectrum_index =
                        getRawIndexFromCoordinate(spectrum_descr.parent_frame,
                                                  scan_num);

                      mass_spectrum.setPrecursorSpectrumIndex(
                        prec_spectrum_index);
                      mass_spectrum.setPrecursorNativeId(
                        QString("frame=%1 scan=%2 index=%3")
                          .arg(spectrum_descr.parent_frame)
                          .arg(scan_num)
                          .arg(prec_spectrum_index));

                      mass_spectrum.setParameterValue(
                        QualifiedMassSpectrumParameter::IsolationMz,
                        spectrum_descr.isolationMz);
                      mass_spectrum.setParameterValue(
                        QualifiedMassSpectrumParameter::IsolationWidth,
                        spectrum_descr.isolationWidth);

                      mass_spectrum.setParameterValue(
                        QualifiedMassSpectrumParameter::CollisionEnergy,
                        spectrum_descr.collisionEnergy);
                      mass_spectrum.setParameterValue(
                        QualifiedMassSpectrumParameter::BrukerPrecursorIndex,
                        (quint64)spectrum_descr.precursor_id);
                    }
                }

              handler.setQualifiedMassSpectrum(mass_spectrum);
              i++;
            }
        }
    }
}

std::map<quint32, quint32>
pappso::TimsData::getRawMsBySpectrumIndex(std::size_t spectrum_index)
{

  qDebug() << " spectrum_index=" << spectrum_index;
  auto coordinate = getScanCoordinateFromRawIndex(spectrum_index);
  TimsFrameBaseCstSPtr tims_frame;
  tims_frame = getTimsFrameCstSPtrCached(coordinate.first);

  std::map<quint32, quint32> raw_spectrum;
  tims_frame.get()->cumulateScansInRawMap(
    raw_spectrum, coordinate.second, coordinate.second);
  return raw_spectrum;
}
} // namespace pappso
