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


/////////////////////// Qt includes
#include <QDebug>
#include <QObject>
#include <QAtomicInt>
#include <QSharedPointer>
#include <QElapsedTimer>
#include <QThreadPool>
#include <QtConcurrent/QtConcurrent>

/////////////////////// pappsomspp includes


/////////////////////// libXpertMassCore includes
#include "MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp"


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassCore/DeconvolutedMassIdMatcher.hpp>

/////////////////////// Local includes

namespace MsXpS
{
namespace libXpertMassCore
{

DeconvolutedMassIdMatcherParameters::DeconvolutedMassIdMatcherParameters()
{
}

DeconvolutedMassIdMatcherParameters::DeconvolutedMassIdMatcherParameters(
  const QString &dictionary_file_path,
  const QString &isotopic_data_file_path,
  const QString &processing_details_log_file_path,
  Enums::MassType mass_type,
  double mass_tolerance_ppm)
  : dictionaryFilePath(dictionary_file_path),
    isotopicDataFilePath(isotopic_data_file_path),
    processingDetailsLogFilePath(processing_details_log_file_path),
    massType(mass_type),
    massTolerancePpm(mass_tolerance_ppm)
{
}

DeconvolutedMassIdMatcherParameters::DeconvolutedMassIdMatcherParameters(
  const DeconvolutedMassIdMatcherParameters &other)
  : dictionaryFilePath(other.dictionaryFilePath),
    isotopicDataFilePath(other.isotopicDataFilePath),
    processingDetailsLogFilePath(other.processingDetailsLogFilePath),
    massType(other.massType),
    massTolerancePpm(other.massTolerancePpm)
{
}

DeconvolutedMassIdMatcherParameters &
DeconvolutedMassIdMatcherParameters::operator=(
  const DeconvolutedMassIdMatcherParameters &other)
{
  if(&other == this)
    return *this;

  dictionaryFilePath           = other.dictionaryFilePath;
  isotopicDataFilePath         = other.isotopicDataFilePath;
  processingDetailsLogFilePath = other.processingDetailsLogFilePath;
  massType                     = other.massType;
  massTolerancePpm             = other.massTolerancePpm;

  return *this;
}

/*!
\class MsXpS::libXpertMassCore::DeconvolutedMassIdMatcher
\inmodule libXpertMassCore
\ingroup XpertMassCoreMassCalculations
\inheaderfile DeconvolutedMassIdMatcher.hpp

\brief The DeconvolutedMassIdMatcher class provides the mechanics that performs
matches between analyte ID : molecular mass dictionary entries and the masses
obtained by deconvolution of a mass spectrum.

The speadsheet file (CSV with TAB delimiter) should have all the textual content
enclosed in '"' (double quote) characters.

Example:

1 "mass_light"    "mass_heavy"    "Id"     "Structure"     "Formula"
"GMred" "GM"    "GManh" "Ala1"  "Glu2"  "DAP3"  "Ala4"  "Gly4"  "Ala5"  "Gly5"
2 75.032028437    78.035773009    "Gly"   "Gly"   "C2H5N1O2"      0       0 0 0
0       0       0       0       0       1 3 75.032028437    78.035773009 "Gly"
"Gly"   "C2H5N1O2"      0       0       0       0       0       0       0 1 0 0

From the MS Excel spreadsheet program, make sure to save as CSV file
with the following settings:

- {Tab} as the delimiter
- '"' as the text quoting symbol

If the file saving to CSV is not performed with these settings, the export might
fail silently with missing text parts (especially if the text contains '[' or
'{' characters.

The three fundamental columns in a spreadsheet (CSV-formatted) file that need to
be present are "Structure", "Id" (condensed structure ) and "Formula".
*/


DeconvolutedMassIdMatcher::DeconvolutedMassIdMatcher(
  DeconvolutedMassIdMatcherParameters &parameters,
  QSharedPointer<QList<DeconvolutedFeature>> deconvoluted_features_qsp,
  QObject *parent)
  : MonitoredTaskInterface(parent),
    m_params(parameters),
    mqsp_deconvolutedFeatures(deconvoluted_features_qsp)
{
  if(!m_params.processingDetailsLogFilePath.isEmpty())
    {
      mp_logFile = new QFile(m_params.processingDetailsLogFilePath);
      if(mp_logFile == nullptr)
        qFatal() << "Failed to create a new log file instance.";

      if(mp_logFile->open(QIODevice::WriteOnly | QIODevice::Truncate |
                          QIODevice::Text))
        {
          mp_logTextStream = new QTextStream(mp_logFile);
          *mp_logTextStream << "This is a processing details log file.\n\n";
          mp_logTextStream->flush();
        }
      else
        qFatal() << "Failed to open the log file for writing.";
    }
}

DeconvolutedMassIdMatcher::~DeconvolutedMassIdMatcher()
{
  // First destroy the stream!
  if(mp_logTextStream != nullptr)
    {
      // I think the flush is performed automatically upon destruction of the
      // stream, but anyways.
      mp_logTextStream->flush();
      delete mp_logTextStream;
    }

  // Next destroy the file!
  if(mp_logFile != nullptr)
    delete mp_logFile;
}

void
DeconvolutedMassIdMatcher::setData(const Data &data)
{
  m_data = data;
}

const QList<QSharedPointer<DeconvolutedMassIdMatcher::MassIdMatch>> &
DeconvolutedMassIdMatcher::getMassIdMatches() const
{
  return m_massIdMatches;
}

int
DeconvolutedMassIdMatcher::loadDictionaryFile(ErrorList *error_list_p)
{
  // First determine the number of lines in the file
  qint64 line_count = Utils::countLinesInTextFile(m_params.dictionaryFilePath);

  qDebug() << "There are" << line_count << "lines in the dictionary file";
  emit taskMinMaxValuesSignal(0, line_count);

  QString message;
  QString processing_details_string =
    QString("Loading dictionary (file path: %1)\n")
      .arg(m_params.dictionaryFilePath);

  if(m_params.dictionaryFilePath.isEmpty())
    {
      message = "The dictionary file path is empty.";

      qDebug() << message;
      if(error_list_p)
        error_list_p->append(message);

      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskStatusMessageSignal(message);

      return -1;
    }

  QFile file(m_params.dictionaryFilePath);

  if(!file.exists())
    {
      message = "The dictionary file does not exist.";

      qDebug() << message;
      if(error_list_p)
        error_list_p->append(message);

      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskStatusMessageSignal(message);

      return -1;
    }

  if(!file.open(QFile::ReadOnly))
    {
      message = "Failed to open the dictionary file for reading.";

      qDebug() << message;
      if(error_list_p)
        error_list_p->append(message);

      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskStatusMessageSignal(message);

      return -1;
    }

  QTextStream in_stream(&file);
  QString line;

  in_stream.readLineInto(&line);

  if(line.isEmpty())
    {
      message =
        "The very first line of the file is empty. It should have contained "
        "the spreadsheet table header.";

      qDebug() << message;
      if(error_list_p)
        error_list_p->append(message);

      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskStatusMessageSignal(message);

      file.close();
      return -1;
    }

  line.remove("\"");

  emit taskStatusMessageAndCurrentValueSignal(
    QString("Iterating into dictionary file line %1").arg(1), 1);

  qDebug().noquote() << "The very first line of the file:" << line;

  QStringList line_string_list = line.split("\t");

  if(!line_string_list.contains("Id") ||
     !line_string_list.contains("Structure") ||
     !line_string_list.contains("Formula"))
    {
      message =
        QString(
          "The very first line:\n%1\nof the file does not contain a proper "
          "spreadsheet table header. It should have contained at least \"Id\", "
          "\"Structure\" and \"Formula\"")
          .arg(line);

      qDebug().noquote() << message;
      if(error_list_p)
        error_list_p->append(message);

      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      message = "The dictionary file is faulty.";
      emit taskStatusMessageSignal(message);

      file.close();
      return -1;
    }

  // At this point, check the column structure of the spreadsheet:

  int id_index        = line_string_list.indexOf("Id");
  int structure_index = line_string_list.indexOf("Structure");
  int formula_index   = line_string_list.indexOf("Formula");

  message =
    QString(
      "The header index for id: %1 - for structure: %2 - for formula: %3\n")
      .arg(id_index)
      .arg(structure_index)
      .arg(formula_index);

  processing_details_string += message;
  qDebug() << message;

  processing_details_string +=
    "Now reading into all the remaining lines of the dictionary file.";

  // These variables allow for updating the task monitor progress bar
  // less frequently (one over 3 values if the divisor is set to 3).
  int iteration_counter_divisor            = 50;
  int iteration_counter_fraction_remainder = 0;

  qint64 iterated_line_number = 2;

  while(in_stream.readLineInto(&line))
    {
      // Do not output task status string at each iteration, but
      // every other iteration_counter_divisor iteration.

      iteration_counter_fraction_remainder =
        iterated_line_number % iteration_counter_divisor;

      // Only update status every other iteration_counter_divisor iteration.
      if(!iteration_counter_fraction_remainder)
        emit taskStatusMessageAndCurrentValueSignal(
          QString("Iterating into dictionary file line %1")
            .arg(iterated_line_number),
          iterated_line_number);

      if(line.startsWith("\n") || line.startsWith("#"))
        continue;

      line.remove("\"");

      line_string_list = line.split("\t");

      QString id        = line_string_list.at(id_index);
      QString structure = line_string_list.at(structure_index);
      QString formula   = line_string_list.at(formula_index);

      if(id.isEmpty() || structure.isEmpty() || formula.isEmpty())
        {
          message = QString("Line \"%1\" is not acceptable.").arg(line);
          qDebug() << message;
          processing_details_string += message + "\n";

          if(error_list_p)
            error_list_p->append(message);

          continue;
        }

      Formula the_formula(formula);
      bool ok          = false;
      double mono_mass = 0;
      double avg_mass  = 0;
      double times     = 1;

      the_formula.accountMasses(
        ok, mcsp_isotopicData, mono_mass, avg_mass, times);

      if(!ok)
        {
          message =
            QString("Failed to account masses for formula %1\n").arg(formula);
          qDebug() << message;
          if(error_list_p)
            error_list_p->append(message);

          processing_details_string += message + "\n";

          continue;
        }

      double mass =
        (m_params.massType == Enums::MassType::MONO ? mono_mass : avg_mass);

      QSharedPointer<MassIdMatch> mass_id_match_qsp =
        QSharedPointer<MassIdMatch>::create(
          id, structure, formula, nullptr, mass);

      m_massIdMatches.append(mass_id_match_qsp);

      ++iterated_line_number;
    }

  message = QString("Finished reading %1 - items stored: %2")
              .arg(m_params.dictionaryFilePath)
              .arg(m_massIdMatches.size());
  qDebug() << message;
  processing_details_string += message + "\n";

  // If we did not read a single dictionary item, then this is an error.

  if(!m_massIdMatches.size())
    {
      message = QString("Could not read a single dictionary item.");
      qDebug() << message;
      if(error_list_p)
        error_list_p->append(message);

      processing_details_string += message + "\n";
    }
  else
    {
      message = QString("The very first dictionary item:\n%1\n")
                  .arg(m_massIdMatches.first()->toString());
      message += QString("The very last dictionary item:\n%1")
                   .arg(m_massIdMatches.last()->toString());

      processing_details_string += message + "\n";

      qDebug() << message;
    }

  logProcessingDetails(processing_details_string);

  return m_massIdMatches.size();
}

void
DeconvolutedMassIdMatcher::removeUnusedMassIdMatches()
{
  // We now should remove all the MassIdMatch items that have not been used
  // for any successful match.
  // qDebug() << "There are" << m_massIdMatches.size()
  //          << "dictionary items before.";

  auto it = std::remove_if(m_massIdMatches.begin(),
                           m_massIdMatches.end(),
                           [](const QSharedPointer<MassIdMatch> &ptr) {
                             return ptr->massError == 0;
                           });

  m_massIdMatches.erase(it, m_massIdMatches.end());

  // qDebug() << "There are" << m_massIdMatches.size()
  //          << "dictionary items after.";
}

int
DeconvolutedMassIdMatcher::performMatches()
{
  QString message = "Performing matches";

  QString processing_details_string = message + "n\n";

  emit taskStatusMessageSignal(message);

  QElapsedTimer timer;
  timer.start();

  // Setup the monitored task interface.

  emit taskMinMaxValuesSignal(0, mqsp_deconvolutedFeatures->size());

  // These variables allow for updating the task monitor progress bar
  // less frequently (one over 3 values if the divisor is set to 3).
  int iteration_counter_divisor            = 2;
  int iteration_counter_fraction_remainder = 0;

  for(int iter = 0; iter < mqsp_deconvolutedFeatures->size(); ++iter)
    // foreach(const DeconvolutedFeature &deconvoluted_feature,
    //         *mqsp_deconvolutedFeatures)
    {
      if(m_cancelledTask)
        {
          emit taskStatusMessageSignal("The task was cancelled by the user.");
          removeUnusedMassIdMatches();
          return m_massIdMatches.size();
        }

      // Do not output task status string at each iteration, but
      // every other iteration_counter_divisor iteration.

      iteration_counter_fraction_remainder = iter % iteration_counter_divisor;
      // Only update status every other iteration_counter_divisor iteration.
      if(!iteration_counter_fraction_remainder)
        emit taskStatusMessageAndCurrentValueSignal(
          QString("Iterating into deconvoluted feature %1").arg(iter), iter);

      // emit taskStatusMessageAndCurrentValueSignal(
      //   QString("Iterating into deconvoluted feature %1").arg(iter), iter);

      const DeconvolutedFeature &deconvoluted_feature =
        mqsp_deconvolutedFeatures->at(iter);

      double mass_to_test = deconvoluted_feature.neutralMass;

      // We are iterating into a deconvoluted mass and we now look into
      // the whole set of dictionary items that relate a given formula to
      // a given identity. Account for the mass tolerance.

      // No worries for the charges, as these are not accounted for here,
      // since the deconvoluted feature's mass is Mr (that is, neutral) and
      // the dictionary items have formulas for neutral analytes.

      // #if 0

      // Save original, set temporary, restore later
      auto *pool = QThreadPool::globalInstance();
      int oldMax = pool->maxThreadCount();

      // FIXME - for my 20 thread computer, this is enough
      // for a dictionary file of 60k lines.
      pool->setMaxThreadCount(5);

      // New parallel version - frankly for the moment there is no benefit
      // with a dictionary file of 60 000 lines.

      // Thread-local result structure
      struct MatchResult
      {
        int localMatchCount = 0;
        QString localDetails;
      };

      DeconvolutedMassIdMatcherParameters local_params(m_params);

      QList<MatchResult> results(m_massIdMatches.size());

      // qDebug() << "=== Thread Pool Info ===";
      // qDebug() << "Max thread count:"
      //          << QThreadPool::globalInstance()->maxThreadCount();
      // qDebug() << "Active thread count:"
      //          << QThreadPool::globalInstance()->activeThreadCount();
      // qDebug() << "Current thread:" << QThread::currentThreadId();
      // qDebug() << "Ideal thread count (QThread::idealThreadCount()):"
      //          << QThread::idealThreadCount();

      // ✅ Use mapped() instead of map() - it returns
      // QFuture<QList<MatchResult>>

      // static QMutex debug_log_mutex;

      auto future = QtConcurrent::mapped(
        std::as_const(m_massIdMatches),
        [&mass_to_test, &local_params, &deconvoluted_feature](
          const QSharedPointer<MassIdMatch>
            mass_id_match_qsp) // ← pass by value or const-ref
        {
          // QMutexLocker locker(&debug_log_mutex);
          // qDebug() << "[FUNCTOR] Running on thread:"
          //          << QThread::currentThreadId();
          // locker.unlock();

          MatchResult result;

          double left_tolerance_side =
            mass_id_match_qsp->mass -
            ((mass_id_match_qsp->mass * local_params.massTolerancePpm) / 1e6 /
             2);
          double right_tolerance_side =
            mass_id_match_qsp->mass +
            ((mass_id_match_qsp->mass * local_params.massTolerancePpm) / 1e6 /
             2);

          if(mass_to_test >= left_tolerance_side &&
             mass_to_test <= right_tolerance_side)
            {
              // Safe: each thread modifies its own MassIdMatch pointer
              mass_id_match_qsp->deconvolutedFeatureQsp =
                QSharedPointer<DeconvolutedFeature>::create(
                  deconvoluted_feature);
              mass_id_match_qsp->massError =
                mass_to_test - mass_id_match_qsp->mass;

              result.localMatchCount = 1;
              result.localDetails    = mass_id_match_qsp->toString() + "\n";
            }
          return result; // ✅ This return value is NOW collected!
        });

      // Wait for completion and get results
      future.waitForFinished();

      results = future.results(); // ✅ Collect the returned values

      // Reduce: combine thread-local results (single-threaded)
      int match_count = 0;
      for(const auto &result : std::as_const(results))
        {
          match_count               += result.localMatchCount;
          processing_details_string += result.localDetails;
        }

      message =
        QString("Finished performing the matches. Found %1 matches.\n\n")
          .arg(match_count);

      // Output logs after parallel section
      if(!processing_details_string.isEmpty())
        {
          qDebug().noquote() << "\n" << processing_details_string;
        }

      pool->setMaxThreadCount(oldMax); // Restore

      // #endif

#if 0
      // First serial  version - frankly this is sufficient at the moment.
      foreach(QSharedPointer<MassIdMatch> mass_id_match_qsp, m_massIdMatches)
        {
          double left_tolerance_side =
            mass_id_match_qsp->mass -
            ((mass_id_match_qsp->mass * m_params.massTolerancePpm) / 1e6 / 2);
          double right_tolerance_side =
            mass_id_match_qsp->mass +
            ((mass_id_match_qsp->mass * m_params.massTolerancePpm) / 1e6 / 2);

          if(mass_to_test >= left_tolerance_side &&
             mass_to_test <= right_tolerance_side)
            {
              // The match is successful. Document it.
              mass_id_match_qsp->deconvolutedFeatureQsp =
                QSharedPointer<DeconvolutedFeature>::create(
                  deconvoluted_feature);

              mass_id_match_qsp->massError =
                mass_to_test - mass_id_match_qsp->mass;

              message                    = mass_id_match_qsp->toString() + "\n";
              processing_details_string += message + "\n";

              // qDebug().noquote() << "\n" << message;
            }
          // else
          //   qDebug().noquote()
          //     << "Failed Mass vs Id match:" << mass_id_match_qsp->toString();
        }
      // End of
      // foreach(QSharedPointer<MassIdMatch> mass_id_match_qsp, m_massIdMatches)
      // That is, we finished iterating into the dictionary items.
#endif
    }
  // End of
  //   foreach(const DeconvolutedFeature &deconvoluted_feature,
  //           *mqsp_deconvolutedFeatures)
  // That is, we finished iterating in the deconvoluted features.

  qDebug() << "Parallel execution time:" << timer.elapsed() << "ms";

  removeUnusedMassIdMatches();

  int match_count = m_massIdMatches.size();

  message = QString("Finished performing the matches. Found %1 matches.\n\n")
              .arg(match_count);
  qDebug() << message;
  processing_details_string += message + "\n";
  logProcessingDetails(processing_details_string);

  return match_count;
}

void
DeconvolutedMassIdMatcher::run()
{
  if(m_isRunning)
    {
      qWarning() << "Matcher already running. Ignoring duplicate start.";
      return;
    }

  // We do not have values of progress bar limits.
  emit taskMinMaxValuesSignal(-1, -1);

  QString message =
    "Start matching deconvoluted masses with analyte identities";
  QString processing_details_string = message + "\n";
  qDebug() << message;

  emit taskStatusMessageSignal(message);

  // Since we provide the user with the ability to load a specific
  // isotopic data file, that needs to be IsotopicDataUserConfigHandler
  // because otherwise (IsotopicDataLibraryHandler), the file
  // name would be of no use: the data would be loaded from the IsoSpec
  // library material (natural abundances for all the isotopes).
  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler;

  qDebug() << "The isotopic data file path:" << m_params.isotopicDataFilePath;

  emit taskStatusMessageSignal("Loading the isotopes");

  qsizetype loaded_isotope_count =
    isotopic_data_handler.loadData(m_params.isotopicDataFilePath);

  if(!loaded_isotope_count)
    {
      QString msg = QString(
                      "Failed to load any isotopic data item from\n%1\n"
                      "Please check the isotopic data file path setting.")
                      .arg(m_params.isotopicDataFilePath);
      qDebug() << msg;

      emit taskStatusMessageAndCurrentValueSignal(
        "Failed to load the isotopic data from file", 0);
      emit taskFinishedSignal();
    }

  mcsp_isotopicData = isotopic_data_handler.getIsotopicData();

  if(mcsp_isotopicData == nullptr || mcsp_isotopicData.get() == nullptr)
    {
      message = "There are no isotopic data available.";
      qDebug() << message;
      if(m_data.errorList != nullptr)
        m_data.errorList->append(message);

      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskStatusMessageSignal(message);
      emit taskFinishedSignal();

      return;
    }

  emit taskStatusMessageSignal("Done loading the isotopes");

  if(mqsp_deconvolutedFeatures == nullptr ||
     mqsp_deconvolutedFeatures.get() == nullptr)
    {
      message = "There are no deconvoluted features.";

      qDebug() << message;
      if(m_data.errorList != nullptr)
        m_data.errorList->append(message);

      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskStatusMessageSignal(message);
      emit taskFinishedSignal();

      return;
    }

  emit taskStatusMessageSignal(
    "Loading the dictionary file and computing masses");

  ErrorList error_list;

  if(!loadDictionaryFile(&error_list))
    {
      message = "Failed to read the dictionary file";
      emit taskStatusMessageSignal(message);

      if(m_data.errorList != nullptr)
        {
          message += QString(
                       ", with the following errors:\n"
                       "%1\n")
                       .arg(Utils::joinErrorList(error_list, "\n"));
        }

      qDebug() << message;
      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskFinishedSignal();

      return;
    }

  // Sort the loaded dictionary items by mass in ascending order.

  message = "Sorting the loaded dictionary items in ascending order.";
  qDebug() << message;
  processing_details_string += message + "\n";


  emit taskStatusMessageSignal(message);

  // Sort in ascending order by mass
  std::sort(m_massIdMatches.begin(),
            m_massIdMatches.end(),
            [](const QSharedPointer<MassIdMatch> &a,
               const QSharedPointer<MassIdMatch> &b) {
              return a->mass < b->mass;
            });

  emit taskStatusMessageSignal(
    "Done loading the dictionary file and computing masses");

  // At this point we have all the dictionary data required for the matches.
  // We can iterate in the deconvoluted features and make the matches.

  int match_count = 0;

  message = "Now performing the matches";
  emit taskStatusMessageSignal(message);

  match_count = performMatches();

  if(!match_count)
    {
      message = "Failed to perform a single match.";
      qDebug() << message;
      processing_details_string += message + "\n";
      logProcessingDetails(processing_details_string);

      emit taskStatusMessageSignal(message);
    }

  message = QString("Could perform %1 matches").arg(match_count);

  qDebug() << message;

  emit taskStatusMessageSignal(message);
  emit taskFinishedSignal();
  emit finishedSignal(m_data.initialSourcePlottable,
                      m_data.sampleName,
                      m_data.labelColorString,
                      m_data.labelFontString,
                      m_data.labelListName,
                      m_data.shouldWriteLabels,
                      m_data.shouldAddLabelToList);
}

void
DeconvolutedMassIdMatcher::cancelTask()

{
  m_cancelledTask = true;
}

} // namespace libXpertMassCore
} // namespace MsXpS
