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

using namespace pappso;

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

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


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


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

  std::shared_ptr<pappso::FilterPseudoCentroid> ms2filter =
    std::make_shared<pappso::FilterPseudoCentroid>(35000, 5, 0.5, 0.1);
  mcsp_ms2Filter = ms2filter;


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

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;
    }
}

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

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

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

  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()));
    }
  pappso::TimsFrameSPtr tims_frame;
  bool index_found = false;
  std::size_t timsId;
  std::size_t numberScans;
  std::size_t offset = 0;
  while(q.next() && (!index_found))
    {
      timsId      = q.value(0).toUInt();
      numberScans = q.value(1).toUInt();

      if(raw_index < (offset + numberScans))
        {
          return std::pair<std::size_t, std::size_t>(timsId,
                                                     raw_index - offset);
        }

      offset += numberScans;
    }

  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
{


  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()));
    }
  bool index_found = false;
  std::size_t timsId;
  std::size_t numberScans;
  std::size_t offset = 0;
  while(q.next() && (!index_found))
    {
      timsId      = q.value(0).toUInt();
      numberScans = q.value(1).toUInt();

      if(timsId == frame_id)
        {
          return offset + scan_num;
        }

      offset += numberScans;
    }

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

  auto coordinate = getScanCoordinateFromRawIndex(raw_index);
  return getMassSpectrumCstSPtr(coordinate.first, coordinate.second);
}


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

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

  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q      = qdb.exec(
    QString("SELECT Frames.TimsId, Frames.AccumulationTime, " // 1
            "Frames.MzCalibration, "                          // 2
            "Frames.T1, Frames.T2, "                          // 4
            "Frames.Time, Frames.MsMsType, TimsCalibration.ModelType, " // 7
            "TimsCalibration.C0, TimsCalibration.C1, TimsCalibration.C2, " // 10
            "TimsCalibration.C3, TimsCalibration.C4, TimsCalibration.C5, " // 13
            "TimsCalibration.C6, TimsCalibration.C7, TimsCalibration.C8, " // 16
            "TimsCalibration.C9   FROM  Frames" // 17
            " INNER JOIN TimsCalibration "
            "ON Frames.TimsCalibration=TimsCalibration.Id where "
            "Frames.Id=%1;")
      .arg(timsId));
  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()));
    }
  pappso::TimsFrameBaseSPtr tims_frame;
  if(q.next())
    {


      tims_frame = std::make_shared<TimsFrameBase>(
        TimsFrameBase(timsId, q.value(0).toUInt()));

      auto it_map_record = m_mapMzCalibrationRecord.find(q.value(2).toInt());
      if(it_map_record != m_mapMzCalibrationRecord.end())
        {

          double T1_frame = q.value(3).toDouble(); // Frames.T1
          double T2_frame = q.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(q.value(2).toInt()));
        }

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

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

      tims_frame.get()->setTimsCalibration(q.value(7).toInt(),
                                           q.value(8).toDouble(),
                                           q.value(9).toDouble(),
                                           q.value(10).toDouble(),
                                           q.value(11).toDouble(),
                                           q.value(12).toDouble(),
                                           q.value(13).toDouble(),
                                           q.value(14).toDouble(),
                                           q.value(15).toDouble(),
                                           q.value(16).toDouble(),
                                           q.value(17).toDouble());
      return tims_frame;
    }

  throw ExceptionNotFound(QObject::tr("ERROR timsId %1 not found").arg(timsId));
  // return TimsFrameCstSPtr;
}

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

  qDebug() << " rt_begin=" << rt_begin << " rt_end=" << rt_end;
  std::vector<std::size_t> tims_frameid_list;
  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q      = qdb.exec(QString("SELECT Frames.TimsId FROM Frames WHERE "
                                 "Frames.MsMsType=0 AND (Frames.Time=>%1) AND "
                                 "(Frames.Time<=%2) SORT 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).toUInt());
    }
  return tims_frameid_list;
}

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

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

  QSqlDatabase qdb = openDatabaseConnection();
  QSqlQuery q      = qdb.exec(
    QString("SELECT Frames.TimsId, Frames.AccumulationTime, " // 1
            "Frames.MzCalibration, "                          // 2
            "Frames.T1, Frames.T2, "                          // 4
            "Frames.Time, Frames.MsMsType, TimsCalibration.ModelType, " // 7
            "TimsCalibration.C0, TimsCalibration.C1, TimsCalibration.C2, " // 10
            "TimsCalibration.C3, TimsCalibration.C4, TimsCalibration.C5, " // 13
            "TimsCalibration.C6, TimsCalibration.C7, TimsCalibration.C8, " // 16
            "TimsCalibration.C9   FROM  Frames" // 17
            " INNER JOIN TimsCalibration "
            "ON Frames.TimsCalibration=TimsCalibration.Id where "
            "Frames.Id=%1;")
      .arg(timsId));
  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()));
    }
  pappso::TimsFrameSPtr tims_frame;
  if(q.next())
    {


      tims_frame =
        mpa_timsBinDec->getTimsFrameSPtrByOffset(timsId, q.value(0).toUInt());


      auto it_map_record = m_mapMzCalibrationRecord.find(q.value(2).toInt());
      if(it_map_record != m_mapMzCalibrationRecord.end())
        {

          double T1_frame = q.value(3).toDouble(); // Frames.T1
          double T2_frame = q.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(q.value(2).toInt()));
        }

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

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

      tims_frame.get()->setTimsCalibration(q.value(7).toInt(),
                                           q.value(8).toDouble(),
                                           q.value(9).toDouble(),
                                           q.value(10).toDouble(),
                                           q.value(11).toDouble(),
                                           q.value(12).toDouble(),
                                           q.value(13).toDouble(),
                                           q.value(14).toDouble(),
                                           q.value(15).toDouble(),
                                           q.value(16).toDouble(),
                                           q.value(17).toDouble());
      return tims_frame;
    }

  throw ExceptionNotFound(QObject::tr("ERROR timsId %1 not found").arg(timsId));
  // return TimsFrameCstSPtr;
}


pappso::MassSpectrumCstSPtr
TimsData::getMassSpectrumCstSPtr(std::size_t timsId, std::size_t 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 "
      "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();
}


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

  spectrum_id.setSpectrumIndex(spectrum_index);
  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)
    {

      QSqlDatabase qdb = openDatabaseConnection();
      QSqlQuery q      = qdb.exec(
        QString(
          "SELECT PasefFrameMsMsInfo.*, Precursors.* FROM "
          "PasefFrameMsMsInfo INNER JOIN Precursors ON "
          "PasefFrameMsMsInfo.Precursor=Precursors.Id where "
          "PasefFrameMsMsInfo.Frame=%1 and (PasefFrameMsMsInfo.ScanNumBegin "
          "<= %2 and PasefFrameMsMsInfo.ScanNumEnd >= %2);")
          .arg(coordinate.first)
          .arg(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())
        {
          // mass_spectrum.setPrecursorCharge(q.value(11).toInt());
          // mass_spectrum.setPrecursorMz(q.value(10).toDouble());
          // mass_spectrum.setPrecursorIntensity(q.value(13).toDouble());
          // mass_spectrum.setPrecursorSpectrumIndex();

          mass_spectrum.appendPrecursorIonData(
            PrecursorIonData(q.value(10).toDouble(),
                             q.value(11).toInt(),
                             q.value(13).toDouble()));


          MassSpectrumId spectrum_id;
          std::size_t prec_spectrum_index = getRawIndexFromCoordinate(
            q.value(14).toDouble(), coordinate.second);

          mass_spectrum.setPrecursorSpectrumIndex(prec_spectrum_index);
          mass_spectrum.setPrecursorNativeId(
            QString("frame=%1 scan=%2 index=%3")
              .arg(q.value(14).toDouble())
              .arg(coordinate.second)
              .arg(prec_spectrum_index));

          mass_spectrum.setParameterValue(
            QualifiedMassSpectrumParameter::IsolationMz, q.value(3).toDouble());
          mass_spectrum.setParameterValue(
            QualifiedMassSpectrumParameter::IsolationWidth,
            q.value(4).toDouble());

          mass_spectrum.setParameterValue(
            QualifiedMassSpectrumParameter::CollisionEnergy,
            q.value(5).toFloat());
          mass_spectrum.setParameterValue(
            QualifiedMassSpectrumParameter::BrukerPrecursorIndex,
            q.value(6).toInt());
        }
    }

  return mass_spectrum;
}


QualifiedMassSpectrum
TimsData::getQualifiedMs1MassSpectrumByPrecursorId(std::size_t ms2_index,
                                                   std::size_t precursor_index,
                                                   bool want_binary_data)
{
  QualifiedMassSpectrum mass_spectrum;

  try
    {
      QSqlDatabase qdb = openDatabaseConnection();
      mass_spectrum.setMsLevel(1);
      mass_spectrum.setPrecursorSpectrumIndex(0);
      mass_spectrum.setEmptyMassSpectrum(true);
      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())
        {

          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.size() == 0)
        {

          throw ExceptionNotFound(
            QObject::tr(
              "ERROR in getQualifiedMassSpectrumByPrecursorId, precursor "
              "id=%1 not found")
              .arg(precursor_index));
        }
      else
        {
          TracePlusCombiner combiner;
          MapTrace combiner_result;


          bool first                      = true;
          std::size_t scan_mobility_start = 0;
          std::size_t scan_mobility_end   = 0;
          std::set<std::size_t> tims_frame_list;
          while(q.next())
            {
              // get MS1 frame
              tims_frame_list.insert(q.value(14).toLongLong());
              if(first)
                {


                  MassSpectrumId spectrum_id;

                  spectrum_id.setSpectrumIndex(precursor_index);
                  spectrum_id.setNativeId(
                    QString("frame=%1 begin=%2 end=%3 precursor=%4 idxms2=%5")
                      .arg(q.value(0).toLongLong())
                      .arg(q.value(1).toLongLong())
                      .arg(q.value(2).toLongLong())
                      .arg(precursor_index)
                      .arg(ms2_index));


                  mass_spectrum.setMassSpectrumId(spectrum_id);


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

                  first = false;
                }
            }

          first = true;
          for(std::size_t tims_id : tims_frame_list)
            {
              TimsFrameBaseCstSPtr tims_frame =
                getTimsFrameCstSPtrCached(tims_id);
              if(first)
                {
                  mass_spectrum.setRtInSeconds(tims_frame.get()->getTime());

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

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

                  first = false;
                }


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


          if(first == true)
            {
              throw ExceptionNotFound(
                QObject::tr(
                  "ERROR in getQualifiedMassSpectrumByPrecursorId, precursor "
                  "id=%1 not found")
                  .arg(precursor_index));
            }


          if(want_binary_data)
            {

              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());
    }
  return mass_spectrum;
}


void
TimsData::getQualifiedMs2MassSpectrumByPrecursorId(
  QualifiedMassSpectrum &mass_spectrum,
  std::size_t ms2_index,
  std::size_t precursor_index,
  bool want_binary_data)
{
  qDebug();
  try
    {
      QSqlDatabase qdb = openDatabaseConnection();
      MassSpectrumId spectrum_id;

      spectrum_id.setSpectrumIndex(precursor_index);
      spectrum_id.setNativeId(
        QString("precursor=%1 idxms2=%2").arg(precursor_index).arg(ms2_index));

      mass_spectrum.setMassSpectrumId(spectrum_id);

      mass_spectrum.setMsLevel(2);
      mass_spectrum.setPrecursorSpectrumIndex(ms2_index - 1);

      mass_spectrum.setEmptyMassSpectrum(true);

      qdb = openDatabaseConnection();
      // m_mutex.lock();
      //       if(m_query != nullptr)
      //       {
      //         *m_query =
      //         qdb.exec(QString("SELECT PasefFrameMsMsInfo.*, Precursors.*
      //         FROM "
      //                          "PasefFrameMsMsInfo INNER JOIN Precursors ON "
      //                          "PasefFrameMsMsInfo.Precursor=Precursors.Id
      //                          where " "Precursors.Id=%1;")
      //                    .arg(precursor_index));
      //       }
      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)
                {
                  // mass_spectrum.setPrecursorCharge(q.value(11).toInt());
                  // mass_spectrum.setPrecursorMz(q.value(10).toDouble());
                  // mass_spectrum.setPrecursorIntensity(q.value(13).toDouble());

                  mass_spectrum.appendPrecursorIonData(
                    PrecursorIonData(q.value(10).toDouble(),
                                     q.value(11).toInt(),
                                     q.value(13).toDouble()));

                  mass_spectrum.setPrecursorNativeId(
                    QString("frame=%1 begin=%2 end=%3 precursor=%4 idxms2=%5")
                      .arg(q.value(14).toLongLong())
                      .arg(q.value(1).toLongLong())
                      .arg(q.value(2).toLongLong())
                      .arg(precursor_index)
                      .arg(ms2_index - 1));
                  // mass_spectrum.setPrecursorSpectrumIndex();

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

                  mass_spectrum.setParameterValue(
                    QualifiedMassSpectrumParameter::IsolationMz,
                    q.value(3).toDouble());
                  mass_spectrum.setParameterValue(
                    QualifiedMassSpectrumParameter::IsolationWidth,
                    q.value(4).toDouble());

                  mass_spectrum.setParameterValue(
                    QualifiedMassSpectrumParameter::CollisionEnergy,
                    q.value(5).toFloat());
                  mass_spectrum.setParameterValue(
                    QualifiedMassSpectrumParameter::BrukerPrecursorIndex,
                    q.value(6).toInt());

                  first = false;
                }
            }
          // QMutexLocker locker(&m_mutex_spectrum);
          qDebug();
          pappso::TimsFrameCstSPtr tims_frame, previous_frame;
          // TracePlusCombiner combiner;
          // MapTrace combiner_result;
          std::map<quint32, quint32> raw_spectrum;
          first = true;
          for(std::size_t tims_id : tims_frame_list)
            {

              tims_frame = getTimsFrameCstSPtrCached(tims_id);
              if(first)
                {
                  mass_spectrum.setRtInSeconds(tims_frame.get()->getTime());

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

                  mass_spectrum.setParameterValue(
                    QualifiedMassSpectrumParameter::OneOverK0end,
                    tims_frame.get()->getOneOverK0Transformation(
                      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, 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));
            }
          if(want_binary_data)
            {
              qDebug();
              // 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() << 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();
                  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 error;
    }
  catch(std::exception &error)
    {
      qDebug() << QString("Failure %1 ").arg(error.what());
    }
  qDebug();
}


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;
}

std::vector<TimsXicStructure>
TimsData::extractXicListByPrecursorIds(
  const std::vector<std::size_t> &precursor_id_list,
  PrecisionPtr precision_ptr,
  XicExtractMethod xicExtractMethod,
  double rtRange) const
{

  qDebug();
  std::vector<TimsXicStructure> xic_list;

  try
    {
      QSqlDatabase qdb = openDatabaseConnection();

      for(auto precursor_id : precursor_id_list)
        {
          QSqlQuery q = qdb.exec(
            QString("SELECT Frames.Time, "
                    "PasefFrameMsMsInfo.Frame,PasefFrameMsMsInfo.ScanNumBegin,"
                    "PasefFrameMsMsInfo.ScanNumEnd, Precursors.MonoisotopicMz, "
                    "Precursors.Charge  FROM "
                    "PasefFrameMsMsInfo INNER JOIN Precursors ON "
                    "PasefFrameMsMsInfo.Precursor=Precursors.Id INNER JOIN "
                    "Frames ON PasefFrameMsMsInfo.Frame=Frames.Id where "
                    "Precursors.Id=%1;")
              .arg(precursor_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()));
            }

          while(q.next())
            {
              // Frame, ScanNumBegin, ScanNumEnd, IsolationMz, ColliqionEnergy,
              // Precursor
              // Id, LargestPeakMz, AverageMz, MonoisotopicMz, Charge,
              // ScanNumber, Intensity, Parent
              TimsXicStructure xic_structure;

              xic_structure.precursorId = precursor_id;
              xic_structure.mzRange =
                MzRange(q.value(4).toDouble(), precision_ptr);
              xic_structure.scanNumBegin = q.value(2).toUInt();
              xic_structure.scanNumEnd   = q.value(3).toUInt();
              xic_structure.rtTarget     = q.value(0).toDouble();
              // xic_structure.charge       = q.value(5).toUInt();

              xic_list.push_back(xic_structure);
            }
        }
      qDebug();

      TimsXicExtractorInterface *extractor_p = new TimsDirectXicExtractor(this);
      extractor_p->setXicExtractMethod(xicExtractMethod);
      extractor_p->extractTimsXicList(xic_list, rtRange);
    }
  catch(PappsoException &error)
    {
      throw error;
    }
  catch(std::exception &error)
    {
      qDebug() << QString("Failure %1 ").arg(error.what());
    }

  qDebug();
  return xic_list;
}
