/**
 * \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 <QDebug>
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QMutexLocker>
#include <QThread>
#include <set>
#include <QtConcurrent>
#include "timsddaprecursors.h"
#include "timsdiaslices.h"

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

  try
    {

      qDebug();
      mpa_timsDdaPrecursors = new TimsDdaPrecursors(q, this);
    }
  catch(pappso::ExceptionNotFound &not_found)
    {

      qDebug();
      mpa_timsDdaPrecursors = nullptr;
    }


  qDebug();
  try
    {
      qDebug();
      mpa_timsDiaSlices = new TimsDiaSlices(q, this);
    }
  catch(pappso::ExceptionNotFound &not_found)
    {
      qDebug();
      mpa_timsDiaSlices = nullptr;
    }

  fillFrameIdDescrList();

  // get number of scans
  if(!q.exec("SELECT SUM(NumScans),COUNT(Id) 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())
    {
      qDebug();
      m_totalScanCount = q.value(0).toLongLong();
      m_frameCount     = q.value(1).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()));
    }

  m_mapFramesRecord.resize(m_frameCount + 1);
  while(q.next())
    {
      QSqlRecord record = q.record();
      TimsFrameRecord &frame_record =
        m_mapFramesRecord[record.value(8).toULongLong()];

      frame_record.frame_id            = record.value(8).toULongLong();
      frame_record.tims_offset         = record.value(0).toULongLong();
      frame_record.accumulation_time   = record.value(1).toDouble();
      frame_record.mz_calibration_id   = record.value(2).toULongLong();
      frame_record.frame_t1            = record.value(3).toDouble();
      frame_record.frame_t2            = record.value(4).toDouble();
      frame_record.frame_time          = record.value(5).toDouble();
      frame_record.msms_type           = record.value(6).toInt();
      frame_record.tims_calibration_id = record.value(7).toULongLong();
    }

  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 TimsData &other)
{
  qDebug();
}

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

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;
  /** @brief number of scans in mobility dimension (number of TOF scans)
   */
  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
{
  return getScanCoordinatesByGlobalScanIndex(raw_index);
}

std::pair<std::size_t, std::size_t>
TimsData::getScanCoordinatesByGlobalScanIndex(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_globalScanIndex > 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_globalScanIndex +
                      m_frameIdDescrList[i].m_scanCount))
        {
          return std::pair<std::size_t, std::size_t>(
            m_frameIdDescrList[i].m_frameId,
            raw_index - m_frameIdDescrList[i].m_globalScanIndex);
        }
    }

  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 index) const
{

  return getGlobalScanIndexByScanCoordinates(frame_id, index);
}

std::size_t
TimsData::getGlobalScanIndexByScanCoordinates(std::size_t frame_id,
                                              std::size_t index) const
{

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

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

/** @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
 */
MassSpectrumCstSPtr
TimsData::getMassSpectrumCstSPtrByRawIndex(std::size_t raw_index)
{
  return getMassSpectrumCstSPtrByGlobalScanIndex(raw_index);
}

pappso::MassSpectrumCstSPtr
TimsData::getMassSpectrumCstSPtrByGlobalScanIndex(std::size_t raw_index)
{

  qDebug() << " raw_index=" << raw_index;
  try
    {
      auto coordinate = getScanCoordinatesByGlobalScanIndex(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;

  const TimsFrameRecord &frame_record = m_mapFramesRecord[timsId];
  if(timsId > m_totalScanCount)
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR Frames database id %1 not found").arg(timsId));
    }
  TimsFrameBaseSPtr tims_frame;


  tims_frame = std::make_shared<TimsFrameBase>(
    TimsFrameBase(timsId, frame_record.tims_offset));

  auto it_map_record =
    m_mapMzCalibrationRecord.find(frame_record.mz_calibration_id);
  if(it_map_record != m_mapMzCalibrationRecord.end())
    {

      double T1_frame = frame_record.frame_t1; // Frames.T1
      double T2_frame = frame_record.frame_t2; // 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(frame_record.mz_calibration_id));
    }


  tims_frame.get()->setAcqDurationInMilliseconds(
    frame_record.accumulation_time);

  tims_frame.get()->setRtInSeconds(frame_record.frame_time);
  tims_frame.get()->setMsMsType(frame_record.msms_type);


  auto it_map_record_tims_calibration =
    m_mapTimsCalibrationRecord.find(frame_record.tims_calibration_id);
  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(frame_record.tims_calibration_id));
    }

  return tims_frame;
}

std::vector<std::size_t>
TimsData::getTimsMS1FrameIdsInRtRange(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;
}

std::vector<std::size_t>
TimsData::getTimsMS2FrameIdsInRtRange(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=8 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();

  /*
     for(auto pair_i : m_mapFramesRecord)
     {

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

  const TimsFrameRecord &frame_record = m_mapFramesRecord[timsId];
  if(timsId > m_totalScanCount)
    {
      throw ExceptionNotFound(
        QObject::tr("ERROR Frames database id %1 not found").arg(timsId));
    }

  TimsFrameSPtr tims_frame;


  // QMutexLocker lock(&m_mutex);
  tims_frame =
    mpa_timsBinDec->getTimsFrameSPtrByOffset(timsId, m_mapFramesRecord);
  // lock.unlock();

  qDebug();
  auto it_map_record =
    m_mapMzCalibrationRecord.find(frame_record.mz_calibration_id);
  if(it_map_record != m_mapMzCalibrationRecord.end())
    {

      double T1_frame = frame_record.frame_t1; // Frames.T1
      double T2_frame = frame_record.frame_t2; // 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 for frame_id=%2")
          .arg(frame_record.mz_calibration_id)
          .arg(timsId));
    }


  tims_frame.get()->setAcqDurationInMilliseconds(
    frame_record.accumulation_time);

  tims_frame.get()->setRtInSeconds(frame_record.frame_time);
  tims_frame.get()->setMsMsType(frame_record.msms_type);

  qDebug();
  auto it_map_record_tims_calibration =
    m_mapTimsCalibrationRecord.find(frame_record.tims_calibration_id);
  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(frame_record.tims_calibration_id));
    }
  qDebug();
  return tims_frame;
}


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

  return frame->getMassSpectrumCstSPtr(scanNum);
}


std::size_t
TimsData::getTotalNumberOfFrames() const
{
  return m_frameCount;
}


std::size_t
TimsData::getFrameCount() const
{
  return m_frameCount;
}


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


std::size_t
TimsData::getTotalScanCount() const
{
  return m_totalScanCount;
}

unsigned int
TimsData::getMsLevelBySpectrumIndex(std::size_t index)
{
  return getMsLevelByGlobalScanIndex(index);
}


unsigned int
TimsData::getMsLevelByGlobalScanIndex(std::size_t index)
{
  auto coordinate = getScanCoordinatesByGlobalScanIndex(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 global_scan_index,
  bool want_binary_data)
{

  return getQualifiedMassSpectrumByGlobalScanIndex(
    msrun_id, mass_spectrum, global_scan_index, want_binary_data);
}

void
TimsData::getQualifiedMassSpectrumByGlobalScanIndex(
  const MsRunIdCstSPtr &msrun_id,
  QualifiedMassSpectrum &mass_spectrum,
  std::size_t global_scan_index,
  bool want_binary_data)
{


  try
    {
      auto coordinate = getScanCoordinatesByGlobalScanIndex(global_scan_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(global_scan_index);
      spectrum_id.setMsRunId(msrun_id);
      spectrum_id.setNativeId(
        QString("frame_id=%1 scan_index=%2 global_scan_index=%3")
          .arg(coordinate.first)
          .arg(coordinate.second)
          .arg(global_scan_index));

      mass_spectrum.setMassSpectrumId(spectrum_id);

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

      mass_spectrum.setDtInMilliSeconds(
        tims_frame.get()->getDriftTimeInMilliseconds(coordinate.second));
      // 1/K0
      mass_spectrum.setParameterValue(
        QualifiedMassSpectrumParameter::IonMobOneOverK0,
        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)
        {

          if(mpa_timsDdaPrecursors != nullptr)
            {
              auto spectrum_descr =
                mpa_timsDdaPrecursors->getSpectrumDescrWithScanCoordinates(
                  coordinate);
              if(spectrum_descr.precursor_id > 0)
                {

                  mass_spectrum.appendPrecursorIonData(
                    spectrum_descr.precursor_ion_data);


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

                  mass_spectrum.setPrecursorSpectrumIndex(prec_spectrum_index);
                  mass_spectrum.setPrecursorNativeId(
                    QString("frame_id=%1 scan_index=%2 global_scan_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::IsolationMzWidth,
                    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 PappsoException(
        QObject::tr("Error TimsData::getQualifiedMassSpectrumByRawIndex "
                    "spectrum_index=%1 :\n%2")
          .arg(global_scan_index)
          .arg(error.qwhat()));
    }
}

Trace
TimsData::getTicChromatogram() const
{
  // 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();
}


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);
          if(m_timsFrameBaseCache.size() > m_cacheSize)
            m_timsFrameBaseCache.pop_front();
          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)
{
  qDebug();
  QMutexLocker locker(&m_mutex);
  for(auto &tims_frame : m_timsFrameCache)
    {
      if(tims_frame.get()->getId() == timsId)
        {
          m_timsFrameCache.push_back(tims_frame);
          if(m_timsFrameCache.size() > m_cacheSize)
            m_timsFrameCache.pop_front();
          return tims_frame;
        }
    }
  TimsFrameCstSPtr frame_sptr = getTimsFrameCstSPtr(timsId);

  // locker.relock();
  qDebug();

  m_timsFrameCache.push_back(frame_sptr);
  if(m_timsFrameCache.size() > m_cacheSize)
    m_timsFrameCache.pop_front();
  qDebug();
  return m_timsFrameCache.back();


  /*
// the frame is not in the cache
if(std::find(m_someoneIsLoadingFrameId.begin(),
             m_someoneIsLoadingFrameId.end(),
             timsId) == m_someoneIsLoadingFrameId.end())
  {
    // not found, we are alone on this frame
    m_someoneIsLoadingFrameId.push_back(timsId);
    qDebug();
    //locker.unlock();
    TimsFrameCstSPtr frame_sptr = getTimsFrameCstSPtr(timsId);

   // locker.relock();
    qDebug();
    m_someoneIsLoadingFrameId.erase(
      std::find(m_someoneIsLoadingFrameId.begin(),
                m_someoneIsLoadingFrameId.end(),
                timsId));

    m_timsFrameCache.push_back(frame_sptr);
    if(m_timsFrameCache.size() > m_cacheSize)
      m_timsFrameCache.pop_front();
    qDebug();
    return m_timsFrameCache.back();
  }
else
  {
    // this frame is loading by someone else, we have to wait
    qDebug();
    // locker.unlock();
    // std::size_t another_frame_id = timsId;
    while(true)
      {
        QThread::usleep(1);
  // locker.relock();

  for(auto &tims_frame : m_timsFrameCache)
    {
      if(tims_frame.get()->getId() == timsId)
        {
          m_timsFrameCache.push_back(tims_frame);
          return tims_frame;
        }
    }
  // locker.unlock();
}
} // namespace pappso
*/
}


std::vector<double>
TimsData::getRetentionTimeLine() const
{
  return getRetentionTimeLineInSeconds();
}

std::vector<double>
TimsData::getRetentionTimeLineInSeconds() const
{

  std::vector<double> timeline;
  timeline.reserve(m_mapFramesRecord.size());
  for(const TimsFrameRecord &frame_record : m_mapFramesRecord)
    {
      if(frame_record.mz_calibration_id != 0)
        {
          timeline.push_back(frame_record.frame_time);
        }
    }
  return timeline;
}

TimsDataFastMap &
TimsData::getRawMsBySpectrumIndex(std::size_t index)
{
  return getScanByGlobalScanIndex(index);
}

TimsDataFastMap &
TimsData::getScanByGlobalScanIndex(std::size_t index)
{
  qDebug() << " spectrum_index=" << index;
  auto coordinate = getScanCoordinatesByGlobalScanIndex(index);
  TimsFrameBaseCstSPtr tims_frame;
  tims_frame = getTimsFrameCstSPtrCached(coordinate.first);

  TimsDataFastMap &raw_spectrum = TimsDataFastMap::getTimsDataFastMapInstance();
  raw_spectrum.clear();
  tims_frame.get()->combineScansInTofIndexIntensityMap(
    raw_spectrum, coordinate.second, coordinate.second);
  return raw_spectrum;
}

const std::vector<FrameIdDescr> &
TimsData::getFrameIdDescrList() const
{
  return m_frameIdDescrList;
}

const std::vector<TimsFrameRecord> &
TimsData::getTimsFrameRecordList() const
{
  return m_mapFramesRecord;
}

const QDir &
TimsData::getTimsDataDirectory() const
{
  return m_timsDataDirectory;
}

TimsDdaPrecursors *
TimsData::getTimsDdaPrecursorsPtr() const
{
  if(mpa_timsDdaPrecursors == nullptr)
    {
      throw pappso::PappsoException("TimsData does not contain DDA precursors");
    }
  return mpa_timsDdaPrecursors;
}

TimsBinDec *
TimsData::getTimsBinDecPtr() const
{
  return mpa_timsBinDec;
}

bool
TimsData::isDdaRun() const
{
  if(mpa_timsDdaPrecursors == nullptr)
    return false;
  return true;
}

bool
pappso::TimsData::isDiaRun() const
{
  if(mpa_timsDiaSlices == nullptr)
    return false;
  return true;
}

TimsDiaSlices *
TimsData::getTimsDiaSlicesPtr() const
{
  qDebug();
  if(mpa_timsDiaSlices == nullptr)
    {
      throw pappso::PappsoException("TimsData does not contain DIA slices");
    }
  return mpa_timsDiaSlices;
}

} // namespace pappso
