/**
 * \file pappsomspp/vendors/tims/timsdata.h
 * \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/>.
 *
 ******************************************************************************/

#pragma once

#include <QDir>
#include <QSqlDatabase>
#include "timsbindec.h"
#include "timsframe.h"
#include "../../massspectrum/qualifiedmassspectrum.h"
#include "../../processing/filters/filterinterface.h"
#include <deque>
#include <QMutex>
#include <QSqlQuery>
#include "mzcalibration/mzcalibrationstore.h"

namespace pappso
{


/** @brief structure needed to extract XIC from Tims data
 */

struct TimsXicStructure
{
  TimsXicStructure()
    : mzRange(pappso_double(1), PrecisionFactory::getPpmInstance(10.0)){};
  std::size_t precursorId;
  MzRange mzRange;
  std::size_t scanNumBegin;
  std::size_t scanNumEnd;
  pappso::pappso_double rtTarget;
  XicSPtr xicSptr = nullptr;
  unsigned int charge;
};

class TimsData;

/** \brief shared pointer on a TimsData object
 */
typedef std::shared_ptr<TimsData> TimsDataSp;

/**
 * @todo write docs
 */
class PMSPP_LIB_DECL TimsData
{
  public:
  /** @brief build using the tims data directory
   */
  TimsData(QDir timsDataDirectory);

  /**
   * Copy constructor
   *
   * @param other TODO
   */
  TimsData(const TimsData &other);

  /**
   * Destructor
   */
  ~TimsData();


  /** @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
  getMassSpectrumCstSPtrByRawIndex(std::size_t raw_index);

  /** @brief get a mass spectrum given the tims frame database id and scan
   * number within tims frame
   */
  pappso::MassSpectrumCstSPtr getMassSpectrumCstSPtr(std::size_t timsId,
                                                     std::size_t scanNum);

  /** @brief get the total number of scans
   */
  std::size_t getTotalNumberOfScans() const;

  /** @brief get the number of precursors analyzes by PASEF
   */
  std::size_t getTotalNumberOfPrecursors() const;

  /** @brief guess possible precursor ids given a charge, m/z, retention time
   * and k0
   * @return a list of possible precursor ids
   */
  std::vector<std::size_t> getPrecursorsFromMzRtCharge(int charge,
                                                       double mz_val,
                                                       double rt_sec,
                                                       double k0);

  unsigned int getMsLevelBySpectrumIndex(std::size_t spectrum_index);

  QualifiedMassSpectrum
  getQualifiedMassSpectrumByRawIndex(std::size_t spectrum_index,
                                     bool want_binary_data);

  void
  getQualifiedMs2MassSpectrumByPrecursorId(QualifiedMassSpectrum &mass_spectrum,
                                           std::size_t ms2_index,
                                           std::size_t precursor_index,
                                           bool want_binary_data);

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

  /** @brief filter interface to apply just after raw MS2 specturm extraction
   * the filter can be a list of filters inside a FilterSuite object
   */
  void setMs2FilterCstSPtr(pappso::FilterInterfaceCstSPtr &filter);

  /** @brief filter interface to apply just after raw MS1 specturm extraction
   * the filter can be a list of filters inside a FilterSuite object
   */
  void setMs1FilterCstSPtr(pappso::FilterInterfaceCstSPtr &filter);

  /** @brief enable or disable simple centroid filter on raw tims data for MS2
   */
  void setMs2BuiltinCentroid(bool centroid);


  /** @brief tells if simple centroid filter on raw tims data for MS2 is enabled
   * or not
   */
  bool getMs2BuiltinCentroid() const;


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


  /** @brief get a Tims frame with his database ID
   */
  TimsFrameCstSPtr getTimsFrameCstSPtr(std::size_t timsId) const;

  private:
  std::pair<std::size_t, std::size_t>
  getScanCoordinateFromRawIndex(std::size_t spectrum_index) const;

  std::size_t getRawIndexFromCoordinate(std::size_t frame_id,
                                        std::size_t scan_num) const;

  QSqlDatabase openDatabaseConnection() const;


  /** @brief get a Tims frame base (no binary data file access) with his
   * database ID
   */
  TimsFrameBaseCstSPtr getTimsFrameBaseCstSPtr(std::size_t timsId) const;


  /** @brief get a Tims frame with his database ID
   * but look in the cache first
   */
  TimsFrameCstSPtr getTimsFrameCstSPtrCached(std::size_t timsId);

  TimsFrameBaseCstSPtr getTimsFrameBaseCstSPtrCached(std::size_t timsId);

  /** @brief extract a list of XICs from Tims data
   *
   * @param precursor_id_list the list of precursors to extract
   * @param precision_ptr precision to compute the mz range to extract for each
   * precursor mass
   * @param xicExtractMethod XIC extraction method (sum or max) to use
   * @param rtRange retention time range in seconds to extract XIC from rtTarget
   * - rtRange to rtTarget + rtRange
   * @result the corresponding XIC list as a specific structure
   */
  std::vector<TimsXicStructure> extractXicListByPrecursorIds(
    const std::vector<std::size_t> &precursor_id_list,
    PrecisionPtr precision_ptr,
    XicExtractMethod xicExtractMethod,
    double rtRange) const;

  std::vector<std::size_t>
  getMatchPrecursorIdByKo(std::vector<std::vector<double>> ids,
                          double ko_value);

  /** @todo documentation
   */
  std::vector<std::size_t>
  getClosestPrecursorIdByMz(std::vector<std::vector<double>> ids,
                            double mz_value);

  private:
  QDir m_timsDataDirectory;
  TimsBinDec *mpa_timsBinDec = nullptr;
  // QSqlDatabase *mpa_qdb      = nullptr;
  std::size_t m_totalNumberOfScans;
  std::size_t m_totalNumberOfPrecursors;
  std::size_t m_cacheSize = 60;
  std::deque<TimsFrameCstSPtr> m_timsFrameCache;
  std::deque<TimsFrameBaseCstSPtr> m_timsFrameBaseCache;

  pappso::FilterInterfaceCstSPtr mcsp_ms2Filter = nullptr;
  pappso::FilterInterfaceCstSPtr mcsp_ms1Filter = nullptr;

  /** @brief enable builtin centroid on raw tims integers by default
   */
  bool m_builtinMs2Centroid = true;


  std::map<int, QSqlRecord> m_mapMzCalibrationRecord;

  MzCalibrationStore * mpa_mzCalibrationStore;

  QMutex m_mutex;
};
} // namespace pappso
