/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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 <cstdio>
#include <ctime>


/////////////////////// Qt includes
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QFileDialog>
#include <QMenuBar>
#include <QMenu>
#include <QThread>

/////////////////////// pappsomspp includes
#include <pappsomspp/core/msfile/msfileaccessor.h>

/////////////////////// Local includes
#include "BrukerTimsTofMsRunLoader.hpp"
#include "../../nongui/vendor/BrukerTimsTofMsRunProber.hpp"
#include "../MsRunProbeInfoViewerDlg.hpp"
#include "../MsRunReadTask.hpp"
#include "../../nongui/MsRunDataSet.hpp"
#include "MsRunProbeTask.hpp"


int BrukerTimsTofMsRunLoaderMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::BrukerTimsTofMsRunLoader>(
    "MsXpS::MineXpert::BrukerTimsTofMsRunLoader");

int BrukerTimsTofMsRunLoaderSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::BrukerTimsTofMsRunLoaderSPtr>(
    "MsXpS::MineXpert::BrukerTimsTofMsRunLoaderSPtr");

namespace MsXpS
{

namespace MineXpert
{

BrukerTimsTofMsRunLoader::BrukerTimsTofMsRunLoader(
  ProgramWindow *program_window_p,
  pappso::MsFileAccessorSPtr msp_msfileAccessor)
  : MsRunLoader(program_window_p, msp_msfileAccessor)
{
}

BrukerTimsTofMsRunLoader::~BrukerTimsTofMsRunLoader()
{
}

void
BrukerTimsTofMsRunLoader::probeMsRun([[maybe_unused]] bool full_in_memory)
{
  // We want to probe the MS run so that we can suggest to the user a specific
  // parameterization of the MS run data load.

  // At this point, we should try to get some data from the data file so as
  // to fill the form in the dialog for the user to select data file loading
  // criteria, like RT range, IM range, m/z binning.

  // Allocate a ms run reader task to read the data.
  MsRunProbeTask *ms_run_probe_task_p = new MsRunProbeTask(mp_programWindow);

  // Allocate a new prober that is specific for Bruker's timsTOF MS runs.
  BrukerTimsTofMsRunProberSPtr bruker_tims_tof_ms_run_prober_sp =
    std::make_shared<BrukerTimsTofMsRunProber>(msp_msFileAccessor);

  if(bruker_tims_tof_ms_run_prober_sp == nullptr)
    qFatal() << "Programming error";

  // Allocate a new TaskMonitorCompositeWidget
  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_programWindow->getTaskMonitorWnd()->addTaskMonitorWidget(Qt::red);

  // Make sure the task monitor window is visible !
  mp_programWindow->showTaskMonitorWnd();

  // Initialize the monitor composite widget's widgets and make all the
  // connections prober <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    msp_msFileAccessor->getSelectedMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Probing Bruker timsTOF MS run data complexity");
  task_monitor_composite_widget_p->taskSetMinValue(0);
  task_monitor_composite_widget_p->taskSetMaxValue(0);

  // When the MsRunProbeTask instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some
  // seconds, the monitor widget.

  connect(ms_run_probe_task_p,
          &MsRunProbeTask::finishedProbingMsRunSignal,
          task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the prober.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          bruker_tims_tof_ms_run_prober_sp.get(),
          &BrukerTimsTofMsRunProber::cancelOperation);

  // We need to register the meta type for std::size_t because otherwise it
  // cannot be shipped through signals.
  qRegisterMetaType<std::size_t>("std::size_t");

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    bruker_tims_tof_ms_run_prober_sp.get());
  // task_monitor_composite_widget_p->makeMsRunProberConnections(
  //   bruker_tims_tof_ms_run_prober_sp.get());

  // This is how we start the computation in the MsRunProbeTask object.
  connect(this,
          &BrukerTimsTofMsRunLoader::probeMsRunSignal,
          ms_run_probe_task_p,
          &MsRunProbeTask::probeMsRun,
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  connect(ms_run_probe_task_p,
          &MsRunProbeTask::finishedProbingMsRunSignal,
          this,
          &BrukerTimsTofMsRunLoader::finishedProbingMsRun,
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  ms_run_probe_task_p->moveToThread(thread_p);
  thread_p->start();

  // Since we allocated the QThread dynamically we need to be able to
  // destroy it later, so make the connection.
  connect(ms_run_probe_task_p,
          &MsRunProbeTask::finishedProbingMsRunSignal,
          this,
          [thread_p, ms_run_probe_task_p]() {
            // qFatal() << "Test,  should do boom! Does it?";
            // Do not forget that we have to delete the MsRunProbeTask
            // allocated instance.
            ms_run_probe_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
          });

  // qDebug() << "Emitting probeMsRunSignal(ms_run_probe_task_p, "
  //             "ms_run_reader_sp, bruker_tims_tof_ms_run_prober_sp)";

  emit probeMsRunSignal(ms_run_probe_task_p, bruker_tims_tof_ms_run_prober_sp);

  return;
}

void
BrukerTimsTofMsRunLoader::finishedProbingMsRun(
  MsRunProberSPtr ms_run_prober_sp,
  MsRunProbeInfoSPtr ms_run_probe_info_sp,
  double duration_seconds)
{

  // By cancellation in the process of probin, the ms_run_probe_info_sp is
  // nullptr.
  if(ms_run_probe_info_sp == nullptr)
    {
      QMessageBox msgBox;
      QString msg = QString("%1\n%2")
                      .arg("There was a problem probing the MS run:")
                      .arg("Most probably, the operation was cancelled.");

      msgBox.setText("MS run probing.");
      msgBox.setInformativeText(msg);
      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.setDefaultButton(QMessageBox::Ok);
      msgBox.exec();

      return;
    }

  // Else, some other problem might have occurred.
  if(!ms_run_probe_info_sp->probeErrors.isEmpty())
    {
      QMessageBox msgBox;
      QString msg = QString("%1\n%2")
                      .arg("There was a problem probing the MS run:")
                      .arg(ms_run_probe_info_sp->probeErrors);

      msgBox.setText("MS run probing.");
      msgBox.setInformativeText(msg);
      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.setDefaultButton(QMessageBox::Ok);
      msgBox.exec();

      return;
    }


  pappso::MsFileAccessorSPtr msp_msfileAccessor =
    ms_run_prober_sp->getMsFileAccessorSPtr();

  pappso::MsRunIdCstSPtr ms_run_id_csp =
    msp_msfileAccessor->getSelectedMsRunId();

  QString sample_name = ms_run_id_csp->getSampleName();

  // qDebug("Finished probing MS run for sample name: %s",
  //        sample_name.toLatin1().data());

  std::chrono::duration<long double> duration_in_seconds(duration_seconds);

  double duration_minutes =
    std::chrono::duration_cast<std::chrono::minutes>(duration_in_seconds)
      .count();

  QString msg = QString(
                  "MS run probing for MS run %1 "
                  "took %2 s i.e. %3 min")
                  .arg(sample_name)
                  .arg(duration_seconds)
                  .arg(duration_minutes);

  mp_programWindow->logTextToConsole(msg);

  // We are here as a slot to a signal sent by the MsRunProbeTask.

  // At this point we need to display the dialog window with the MS run probe
  // information.

  MsRunProbeInfoViewerDlg *ms_run_probe_info_viewer_dlg_p =
    new MsRunProbeInfoViewerDlg(
      dynamic_cast<QWidget *>(mp_programWindow),
      "Probing Bruker timsTOF data",
      "msRunProbeInfoViewer",
      "Specific data loading procedure for timsTOF data from",
      "Bruker",
      ms_run_prober_sp->getMsFileAccessorSPtr()->getSelectedMsRunId(),
      ms_run_probe_info_sp);

  if(dynamic_cast<QDialog *>(ms_run_probe_info_viewer_dlg_p)->exec() !=
     QDialog::Accepted)
    {
      // qDebug("The dialog was rejected.");
      return;
    }

  // At this point we can effectively load the MS run with the requested
  // configuration.

  // Set aside a pappso::MsRunReadConfig object to store the requested
  // configuration
  pappso::MsRunReadConfig ms_run_read_config;

  // Start with the retention time. The values from the dialog window are
  // immediately usable.

  ms_run_read_config.setRetentionTimeStartInSeconds(
    ms_run_probe_info_sp->retentionTimeBeginInSeconds);
  ms_run_read_config.setRetentionTimeEndInSeconds(
    ms_run_probe_info_sp->retentionTimeEndInSeconds);

  // The MS levels.
  if(ms_run_probe_info_sp->msLevels.size())
    {
      ms_run_read_config.setMsLevels(ms_run_probe_info_sp->msLevels);
      // qDebug() << "The MS levels:" <<
      // ms_run_read_config.getMsLevelsAsString();
    }
  else
    {
      // qDebug() << "The MS levels were not set, using the MS run reader "
      //             "config defaults:"
      //          << ms_run_read_config.getMsLevelsAsString();
    }

  // qDebug() << "The number of configured MS levels:"
  //          << ms_run_probe_info_sp->msLevels.size();

  // The ion mobility ranges
  if(ms_run_probe_info_sp->ionMobilityUnit == IonMobilityUnit::SCAN)
    {
      ms_run_read_config.setParameterValue(
        pappso::MsRunReadConfigParameter::TimsFrameIonMobScanIndexBegin,
        ms_run_probe_info_sp->ionMobilityScansBegin);
      ms_run_read_config.setParameterValue(
        pappso::MsRunReadConfigParameter::TimsFrameIonMobScanIndexEnd,
        ms_run_probe_info_sp->ionMobilityScansEnd);
    }
  else if(ms_run_probe_info_sp->ionMobilityUnit == IonMobilityUnit::ONE_OVER_KO)
    {
      ms_run_read_config.setParameterValue(
        pappso::MsRunReadConfigParameter::TimsFrameIonMobOneOverK0Begin,
        ms_run_probe_info_sp->ionMobilityOneOverKoBegin);
      ms_run_read_config.setParameterValue(
        pappso::MsRunReadConfigParameter::TimsFrameIonMobOneOverK0End,
        ms_run_probe_info_sp->ionMobilityOneOverKoEnd);
    }
  else
    {
      // There is no criterion for ion mobility, do nothing with the config.
    }

  // Finally set the right ms run reader type, which is the one we need to
  // take into account in the MsRunReadConfig.
  if(ms_run_probe_info_sp->ionMobilityScansMergeRequired)
    {
      ms_run_prober_sp->getMsFileAccessorSPtr()->setPreferredFileReaderType(
        pappso::Enums::MsDataFormat::brukerTims,
        pappso::Enums::FileReaderType::tims_frames);
    }
  else
    {
      ms_run_prober_sp->getMsFileAccessorSPtr()->setPreferredFileReaderType(
        pappso::Enums::MsDataFormat::brukerTims,
        pappso::Enums::FileReaderType::tims);
    }

  // Finally the m/z
  if(ms_run_probe_info_sp->mzBegin > -1 && ms_run_probe_info_sp->mzEnd > -1)
    {
      ms_run_read_config.setParameterValue(
        pappso::MsRunReadConfigParameter::MzRangeBegin,
        ms_run_probe_info_sp->mzBegin);
      ms_run_read_config.setParameterValue(
        pappso::MsRunReadConfigParameter::MzRangeEnd,
        ms_run_probe_info_sp->mzEnd);

      // qDebug() << "TimsFramesMsRunReader_mz_begin:"
      //          << ms_run_probe_info_sp->mzBegin
      //          << "and TimsFramesMsRunReader_mz_end"
      //          << ms_run_probe_info_sp->mzEnd;
    }

  if(ms_run_probe_info_sp->mzBinsMergeWindow > -1)
    {
      ms_run_read_config.setParameterValue(
        pappso::MsRunReadConfigParameter::TimsFrameMzIndexMergeWindow,
        (uint)ms_run_probe_info_sp->mzBinsMergeWindow);

      // qDebug() << "TimsFramesMsRunReader_mz_index_merge_window:"
      //          << (uint)ms_run_probe_info_sp->mzBinsMergeWindow;
    }

  readMsRun(ms_run_read_config, ms_run_prober_sp->isReadMsRunFullyInMemory());
}

void
BrukerTimsTofMsRunLoader::readMsRun(pappso::MsRunReadConfig ms_run_read_config,
                                    bool full_in_memory)
{
  // From the file accessor get a shared pointer to the MS run reader for
  // the ms run id of interest.
  pappso::MsRunReaderSPtr ms_run_reader_sp =
    msp_msFileAccessor->msRunReaderSPtrForSelectedMsRunId();

  if(ms_run_reader_sp == nullptr)
    qFatal() << "Failed to get a MS run reader for selected MS run ID.";

  pappso::MsRunIdCstSPtr ms_run_id_csp =
    msp_msFileAccessor->getSelectedMsRunId();

  QString sample_name = ms_run_id_csp->getSampleName();

  // Allocate a ms run reader task to read the data.
  MsRunReadTask *ms_run_read_task_p = new MsRunReadTask(mp_programWindow);

  if(!sample_name.isEmpty())
    {
      // qDebug() << "The sample name is not empty:" << sample_name;

      std::const_pointer_cast<pappso::MsRunId>(ms_run_id_csp)
        ->setSampleName(sample_name);
    }
  else
    {
      QFileInfo file_info(msp_msFileAccessor->getFileName());
      QString base_file_name = file_info.baseName();

      std::const_pointer_cast<pappso::MsRunId>(ms_run_id_csp)
        ->setSampleName(base_file_name);

      qDebug() << "The sample name is empty. Setting it to the file name:"
               << base_file_name;
    }

  // qDebug().noquote() << "The run id as text:" << ms_run_id_csp->toString();

  MassSpecDataFileLoaderSPtr loader_sp =
    std::make_shared<MassSpecDataFileLoader>(msp_msFileAccessor->getFileName(),
                                             full_in_memory);

  if(loader_sp == nullptr)
    qFatal() << "Programming error";

  // Allocate a new TaskMonitorCompositeWidget

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_programWindow->getTaskMonitorWnd()->addTaskMonitorWidget(Qt::red);

  // Make sure the task monitor window is visible !
  mp_programWindow->showTaskMonitorWnd();

  // Initialize the monitor composite widget's widgets and make all the
  // connections loader <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_id_csp->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Reading MS run data");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections
  // When the MsRunReadTask instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some
  // seconds, the monitor widget.

  connect(ms_run_read_task_p,
          &MsRunReadTask::finishedReadingMsRunDataSignal,
          task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          loader_sp.get(),
          &MassSpecDataFileLoader::cancelOperation);

  // We need to register the meta type for std::size_t because otherwise it
  // cannot be shipped through signals.
  qRegisterMetaType<std::size_t>("std::size_t");

  task_monitor_composite_widget_p->makeSignalSlotConnections(loader_sp.get());
  // task_monitor_composite_widget_p->makeMassSpecDataFileLoaderConnections(
  //   loader_sp.get());

  MsRunDataSetSPtr ms_run_data_set_sp =
    std::make_shared<MsRunDataSet>(nullptr /*parent*/, ms_run_reader_sp);

  if(ms_run_data_set_sp == nullptr)
    qFatal() << "Cannot be that pointer is nullptr";

  if(ms_run_data_set_sp.get() == nullptr)
    qFatal() << "Cannot be that pointer is nullptr";

  // Set the ms run id!
  ms_run_data_set_sp->setMsRunId(ms_run_id_csp);

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

  // Inform the loader that the mass spec data is there.
  loader_sp->setMsRunDataSet(ms_run_data_set_sp);

  qRegisterMetaType<MsXpS::MineXpert::MsRunDataSetSPtr>(
    "MsXpS::MineXpert::MsRunDataSetSPtr");

  qRegisterMetaType<MsRunDataSetSPtr>("MsRunDataSetSPtr");

  // This is how we start the computation in the MsRunReadTask object.
  connect(this,
          &BrukerTimsTofMsRunLoader::readMsRunDataSignal,
          ms_run_read_task_p,
          &MsRunReadTask::readMsRunData,
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  // When the read task finishes, it sends a signal that we trap to go on
  // with the file data loading procedure in another function.

  connect(ms_run_read_task_p,
          &MsRunReadTask::finishedReadingMsRunDataSignal,
          this,
          &BrukerTimsTofMsRunLoader::finishedReadingMsRunData,
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  ms_run_read_task_p->moveToThread(thread_p);
  thread_p->start();

  // Since we allocated the QThread dynamically we need to be able to
  // destroy it later, so make the connection.
  connect(ms_run_read_task_p,
          &MsRunReadTask::finishedReadingMsRunDataSignal,
          this,
          [thread_p, ms_run_read_task_p]() {
            // Do not forget that we have to delete the MsRunReadTask
            // allocated instance.
            ms_run_read_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
          });

  qDebug() << "Emitting readMsRunDataSignal(ms_run_read_task_p, "
              "ms_run_reader_sp, loader_sp)";

  emit readMsRunDataSignal(
    ms_run_read_task_p, ms_run_reader_sp, ms_run_read_config, loader_sp);

  return;
}

void
BrukerTimsTofMsRunLoader::finishedReadingMsRunData(
  MsRunDataSetSPtr ms_run_data_set_sp, double duration_seconds)
{

  qDebug() << "20240529 - Finished reading ms run data for sample name:"
           << ms_run_data_set_sp->getMsRunId()->getSampleName() << "with"
           << ms_run_data_set_sp->size() << "spectra loaded.";

  std::chrono::duration<long double> duration_in_seconds(duration_seconds);

  double duration_minutes =
    std::chrono::duration_cast<std::chrono::minutes>(duration_in_seconds)
      .count();

  QString msg = QString(
                  "MS run data loading duration for MS run %1 "
                  "is %2 s / %3 min")
                  .arg(ms_run_data_set_sp->getMsRunId()->getSampleName())
                  .arg(duration_seconds)
                  .arg(duration_minutes);

  mp_programWindow->logTextToConsole(msg);

  // We are here as a slot to a signal sent by the MsRunReadTask.

  // Increment the loaded file count so that next time we'll feed it with a
  // new value to the file accessor constructor (see above).
  mp_programWindow->incrementLoadedFileCount();

  // Since we are load ms run data from file, we need to provide the user with
  // starting points for their data exploration: a TIC chromatogram at least.

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

#if 0
    // Let's control how the data loading process went.

    pappso::MsRunDataSetTreeCstSPtr ms_run_data_set_tree_csp =
    ms_run_data_set_sp->getMsRunDataSetTreeCstSPtr();

    const std::map<std::size_t, pappso::MsRunDataSetTreeNode *> &index_node_map =
    ms_run_data_set_tree_csp->getIndexNodeMap();

    qDebug() << "The number of spectra:"
    << ms_run_data_set_tree_csp->getSpectrumCount()
    << "and the data tree has this depth:"
    << ms_run_data_set_tree_csp->depth()
    << "and the index node map has this number of items:"
    << index_node_map.size();
#endif

  // We want to try the multi-visitor approach: a single visitor that contains
  // a vector of visitors. This is interesting because it makes reading the
  // mass spectral data required only once.

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

#if 0
  // IF BRUKER DATA
  // To debug we can ask that the ms run reader provides us with the TIC
  // chromaogram from the Bruker timsTOF data SQL database.

  pappso::Trace tic_chromatogram =
    ms_run_data_set_sp->getMsRunReaderSPtr()->getTicChromatogram();
  qDebug()
    << "Tic Chromatogram:"
        << tic_chromatogram.toString();
#endif

  mp_programWindow->seedInitialTicChromatogramAndMsRunDataSetStatistics(
    ms_run_data_set_sp);

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

  deleteLater();
}

void
BrukerTimsTofMsRunLoader::loadMsRun([[maybe_unused]] bool full_in_memory)
{
  probeMsRun();
}


} // namespace MineXpert

} // namespace MsXpS
