/* 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 <iostream>
#include <cstdio>
#include <iomanip>
#include <thread>
#include <ctime>

/////////////////////// OpenMP include
// #include <omp.h>

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

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

/////////////////////// Local includes
#include "PwizLiteMsRunLoader.hpp"
#include "../MsRunProbeInfoViewerDlg.hpp"
#include "../MsRunReadTask.hpp"
#include "../../nongui/MsRunDataSet.hpp"


int PwizLiteMsRunLoaderMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::PwizLiteMsRunLoader>(
    "MsXpS::MineXpert::PwizLiteMsRunLoader");

int PwizLiteMsRunLoaderSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::MineXpert::PwizLiteMsRunLoaderSPtr>(
    "MsXpS::MineXpert::PwizLiteMsRunLoaderSPtr");

namespace MsXpS
{

namespace MineXpert
{

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

PwizLiteMsRunLoader::~PwizLiteMsRunLoader()
{
  qDebug();
}

void
PwizLiteMsRunLoader::readMsRun(pappso::MsRunReadConfig ms_run_read_config,
                               bool full_in_memory)
{
  const auto start_time = std::chrono::steady_clock::now();

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

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

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

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

  // 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,
          &PwizLiteMsRunLoader::readMsRunDataSignal,
          ms_run_read_task_p,
          &MsRunReadTask::readMsRunData,
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  //  Go on with the MS run loading operations.
  connect(ms_run_read_task_p,
          &MsRunReadTask::finishedReadingMsRunDataSignal,
          this,
          &PwizLiteMsRunLoader::finishedReadingMsRunData,
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  const auto end_time = std::chrono::steady_clock::now();

  double duration_in_seconds =
    std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time)
      .count();

  qDebug() << "Finished preparing the data load -- time used (seconds): "
           << duration_in_seconds;

  // To debug the fact that the signal is effectively emitted,  which is true
  // since the aboutQt dialog shows up.
  // connect(ms_run_read_task_p,
  // SIGNAL(finishedReadingMsRunDataSignal(MsRunDataSetSPtr, double)), qApp,
  // SLOT(aboutQt()));

  //  This one performs the connection fine (result = true) but the function is
  //  not called.
  // bool connection_result = connect(ms_run_read_task_p,
  // SIGNAL(finishedReadingMsRunDataSignal(MsRunDataSetSPtr, double)), this,
  // SLOT(finishedReadingMsRunData(MsRunDataSetSPtr, double)),
  // // Fundamental for signals that travel across QThread instances...
  // Qt::QueuedConnection);
  // qDebug() <<  "The connection result:" <<  connection_result;

  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]() {
            // qFatal() << "Test,  should do boom! Does it?";
            // 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
PwizLiteMsRunLoader::finishedReadingMsRunData(
  MsRunDataSetSPtr ms_run_data_set_sp, double duration_seconds)
{

  // qDebug() << "Finished reading ms run data for sample name:"
  //          << ms_run_data_set_sp->getMsRunId()->getSampleName();

  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: \n" << 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
PwizLiteMsRunLoader::loadMsRun(bool full_in_memory)
{
  // At the moment, with PwizLite-based file loading, we do not
  // need probing the MS run, just read the run and done.
  // See the Bruker timsTOF loader for details on probing
  // a MS run.
  qDebug();
  pappso::MsRunReadConfig ms_run_read_config;
  readMsRun(ms_run_read_config, full_in_memory);
}


} // namespace MineXpert

} // namespace MsXpS
