/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 * * This file is part of the msXpertSuite project.  * * The msXpertSuite
 * project is the successor of the massXpert project. This project now includes
 * various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes
#include <vector>
#include <cmath>


/////////////////////// Qt includes
#include <QDebug>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/exception/exceptioninterrupted.h>
#include <pappsomspp/core/vendors/tims/timsdata.h>
#include <pappsomspp/core/msrun/private/timsframesmsrunreader.h>

/////////////////////// Local includes
#include "BrukerTimsTofMsRunProber.hpp"


int BrukerTimsTofMsRunProberMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::BrukerTimsTofMsRunProber>(
    "MsXpS::MineXpert::BrukerTimsTofMsRunProber");

int BrukerTimsTofMsRunProberSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::BrukerTimsTofMsRunProberSPtr>(
    "MsXpS::MineXpert::BrukerTimsTofMsRunProberSPtr");

namespace MsXpS
{
namespace MineXpert
{

// Needed for qRegisterMetaType
BrukerTimsTofMsRunProber::BrukerTimsTofMsRunProber(
  pappso::MsFileAccessorSPtr ms_file_accessor_sp)
  : MsRunProber(ms_file_accessor_sp)
{
}

BrukerTimsTofMsRunProber::~BrukerTimsTofMsRunProber()
{
}

MsRunProbeInfo
BrukerTimsTofMsRunProber::probe()
{
  // The main idea is to use a specific ms run reader that will merge all the
  // ion mobility scans into a single mass spectrum, which accelerates
  // dramatically the data loading process. This accelerated process is used to
  // probe the ms run data and extract some meaningful parameters out of it. The
  // parameters are then provided to the user as a means to filter the actual
  // full data loading process using criteria as retention time range, m/z
  // range, ion mobility range, merge or not the ion mobility scans...

  // This object is for us to fill in here and return it.
  // Note that it has a probeErrors QString member.
  MsRunProbeInfo ms_run_probe_info;

  // Configure the accessor to prefer pappso::Enums::FileReaderType::tims.
  msp_msFileAccessor->setPreferredFileReaderType(
    pappso::Enums::MsDataFormat::brukerTims,
    pappso::Enums::FileReaderType::tims);

  // Get a MsRunReader now to actually look at the MS run data. The caller has
  // already set the selected MsRunId.
  pappso::MsRunReaderSPtr ms_run_reader_sp =
    msp_msFileAccessor->msRunReaderSPtrForSelectedMsRunId();

  pappso::TimsDataSp tims_data_sp =
    static_cast<pappso::TimsFramesMsRunReader *>(ms_run_reader_sp.get())
      ->getTimsDataSPtr();

  // Get the retention time line for the whole dataset
  std::vector<double> rt_timeline =
    tims_data_sp->getRetentionTimeLineInSeconds();
  double first_rt_seconds = rt_timeline.front();
  double last_rt_seconds  = rt_timeline.back();

  QString data_exploration_results_for_debug_output =
    QString(
      "The acquisition was performed over the RT "
      "range [%1-%2] for a duration of %3 min.\n")
      .arg(first_rt_seconds)
      .arg(last_rt_seconds)
      .arg((last_rt_seconds - first_rt_seconds) / 60);

  ms_run_probe_info.retentionTimeBeginInSeconds = first_rt_seconds;
  ms_run_probe_info.retentionTimeEndInSeconds   = last_rt_seconds;

  // Get the list of MS1 frames over the full ms run rt range:
  std::vector<std::size_t> ms1_frame_ids_in_rt_range =
    tims_data_sp->getTimsMS1FrameIdsInRtRange(first_rt_seconds,
                                              last_rt_seconds);

  std::size_t frame_count           = ms1_frame_ids_in_rt_range.size();
  ms_run_probe_info.ms1SpectraCount = frame_count;
  // qDebug() << "Number of MS1 frames in full RT range:" << frame_count;

  emit appendStatusTextSignal(
    QString(": %1").arg(ms_run_probe_info.ms1SpectraCount));

  // We have a list of MS1 frame ids, and we now want to craft a sub-list
  // that has ten evenly distributed frame ids. Let's compute the interval
  // that these frame ids will be separted apart.

  std::size_t ms1_every_other_ms1_frame_interval =
    static_cast<std::size_t>(std::floor(frame_count / 10));

  // qDebug() << "Singling out frame ids at a"
  //          << ms1_every_other_ms1_frame_interval
  //          << "interval in the list of frame ids.";

  std::vector<std::size_t>::iterator iterator_frame_ids_rt_range =
    ms1_frame_ids_in_rt_range.begin();

  std::vector<std::size_t> frame_ids_to_probe;
  std::size_t position_in_the_frame_id_list = 0;

  while(position_in_the_frame_id_list < frame_count)
    {
      std::size_t tims_frame_id = *iterator_frame_ids_rt_range;
      frame_ids_to_probe.push_back(tims_frame_id);
      position_in_the_frame_id_list += ms1_every_other_ms1_frame_interval;
      // qDebug() << "Next round will be for position"
      //          << position_in_the_frame_id_list;
      iterator_frame_ids_rt_range   += ms1_every_other_ms1_frame_interval;
    }

  // qDebug() << "We now have a list of" << frame_ids_to_probe.size()
  //          << "to deep-probe.";

  std::size_t summed_im_scans_per_frame = 0;
  std::pair<double, double> mz_range(std::numeric_limits<double>::max(), 0);
  std::pair<quint32, quint32> tof_index_range(
    std::numeric_limits<double>::max(), 0);

  // Attention over the logic here! we know that 1/K0 begin is greater than
  // 1 / K0 end:
  std::pair<double, double> one_over_k0_range(
    0, std::numeric_limits<double>::max());

  std::vector<std::size_t>::iterator iterator_frame_ids_to_probe =
    frame_ids_to_probe.begin();
  std::vector<std::size_t>::iterator iterator_frame_ids_to_probe_end =
    frame_ids_to_probe.end();

  std::size_t probe_count = 0;

  while(iterator_frame_ids_to_probe != iterator_frame_ids_to_probe_end)
    {
      std::size_t tims_frame_id = *iterator_frame_ids_to_probe;

      pappso::TimsFrameCstSPtr tims_frame_csp =
        tims_data_sp->getTimsFrameCstSPtrCached(tims_frame_id);

      std::size_t frame_scan_count  = tims_frame_csp->getTotalNumberOfScans();
      // qDebug() << "Current frame" << *iterator_frame_ids_to_probe << "has" <<
      // frame_scan_count
      //          << "scans";
      summed_im_scans_per_frame    += frame_scan_count;

      // We sample data for the scan lying at the middle of the set of acquired
      // scans for current frame.

      std::size_t half_scan_for_frame = frame_scan_count / 2;
      pappso::MassSpectrumCstSPtr mass_spectrum_csp =
        tims_frame_csp->getMassSpectrumCstSPtr(half_scan_for_frame);

      if(mass_spectrum_csp == nullptr)
        qFatal() << "The mass spectrum pointer is nullptr.";

      std::size_t spectrum_size = mass_spectrum_csp->size();
      if(!spectrum_size)
        qFatal() << "The spectrum is empty.";

      double first_mz = mass_spectrum_csp->front().x;
      double last_mz  = mass_spectrum_csp->back().x;
      mz_range.first  = std::min(mz_range.first, first_mz);
      mz_range.second = std::max(mz_range.second, last_mz);

      // qDebug() << "The current mz range:" << first_mz << "--" << last_mz;

      quint32 first_tof_index = tims_frame_csp->getMzCalibrationInterfaceSPtr()
                                  .get()
                                  ->getTofIndexFromMz(first_mz);
      // qDebug() << "For current first mz, TOF index:" << first_tof_index;


      quint32 last_tof_index = tims_frame_csp->getMzCalibrationInterfaceSPtr()
                                 .get()
                                 ->getTofIndexFromMz(last_mz);
      // qDebug() << "For current last mz, TOF index:" << last_tof_index;

      tof_index_range.first  = std::min(tof_index_range.first, first_tof_index);
      tof_index_range.second = std::max(tof_index_range.second, last_tof_index);

      // qDebug() << "The smallest TOF index:" << tof_index_range.first;
      // qDebug() << "The greatest TOF index:" << tof_index_range.second;

      double first_one_over_k0 = tims_frame_csp->getOneOverK0Transformation(0);
      double last_one_over_k0 =
        tims_frame_csp->getOneOverK0Transformation(frame_scan_count - 1);

      // qDebug() << "first_one_over_k0:" << first_one_over_k0;
      // qDebug() << "last_one_over_k0:" << last_one_over_k0;

      // Attention over the logic here: 1/K0 begin is greater than
      // 1/K0 end!
      one_over_k0_range.first =
        std::max(one_over_k0_range.first, first_one_over_k0);
      one_over_k0_range.second =
        std::min(one_over_k0_range.second, last_one_over_k0);

      // qDebug() << "Greatest one_over_k0:" << one_over_k0_range.first;
      // qDebug() << "Smallest one_over_k0:" << one_over_k0_range.second;

      ++probe_count;
      ++iterator_frame_ids_to_probe;
    }

  // qDebug() << "The count of probes:" << probe_count;

  if(!probe_count)
    {
      qFatal() << "Failed to perform at least one probe into the MS run data.";
      // Fake because of qFatal() but silences one warning.
      return ms_run_probe_info;
    }

  double mean_im_scans_per_frame = summed_im_scans_per_frame / probe_count;

  ms_run_probe_info.mzBinCount = tof_index_range.second - tof_index_range.first;

  data_exploration_results_for_debug_output +=
    QString(
      "The sampled TOF indices count (that is, the number of m/z bins) is %1\n")
      .arg(ms_run_probe_info.mzBinCount);

  ms_run_probe_info.mzBegin = mz_range.first;
  ms_run_probe_info.mzEnd   = mz_range.second;

  double mz_range_delta = mz_range.second - mz_range.first;

  data_exploration_results_for_debug_output +=
    QString("Mass spectra have m/z range [%1-%2] = %3  m/z range delta \n")
      .arg(ms_run_probe_info.mzBegin)
      .arg(ms_run_probe_info.mzEnd)
      .arg(mz_range_delta);

  // qDebug("Mass spectra span [ %f.5 - %f.5 ]",
  //        ms_run_probe_info.mzBegin,
  //        ms_run_probe_info.mzEnd);

  double mz_delta_per_tof_bin = mz_range_delta / ms_run_probe_info.mzBinCount;
  // qDebug("Rough m/z bin per TOF index: %f.5", mz_delta_per_tof_bin);

  data_exploration_results_for_debug_output +=
    QString("The digitizer thus sets one TOF index to a m/z delta of %1\n")
      .arg(mz_delta_per_tof_bin, 0, 'f', 10);

  // This is always true, since the scans numbers are actually indices
  ms_run_probe_info.ionMobilityScansBegin = 0;
  ms_run_probe_info.ionMobilityScansEnd   = mean_im_scans_per_frame - 1;

  ms_run_probe_info.ionMobilityOneOverKoBegin = one_over_k0_range.first;
  ms_run_probe_info.ionMobilityOneOverKoEnd   = one_over_k0_range.second;

  data_exploration_results_for_debug_output +=
    QString(
      "The mass spectrum has ion mobility scans in the 1/K0 range [%1-%2] "
      "over %3 ion mobility scans\n")
      .arg(one_over_k0_range.first)
      .arg(one_over_k0_range.second)
      .arg(mean_im_scans_per_frame);

  qDebug("The data exploration results: %s.\nNow opening the dialog.",
         data_exploration_results_for_debug_output.toLatin1().data());

  // Get the list of MS2 frames over the full ms run rt range:
  std::vector<std::size_t> ms2_frame_ids_in_rt_range =
    tims_data_sp->getTimsMS2FrameIdsInRtRange(first_rt_seconds,
                                              last_rt_seconds);

  frame_count                       = ms2_frame_ids_in_rt_range.size();
  ms_run_probe_info.ms2SpectraCount = frame_count;
  // qDebug() << "Number of MS2 frames in full RT range:" << frame_count;
  // qDebug("Number of MS2 spectra: %zu", frame_count);

  emit appendStatusTextSignal(
    QString(": %1").arg(ms_run_probe_info.ms2SpectraCount));

  return ms_run_probe_info;
}


} // namespace MineXpert

} // namespace MsXpS
