/* 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


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


/////////////////////// pappsomspp includes
#include <pappsomspp/core/massspectrum/qualifiedmassspectrum.h>


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

namespace MsXpS
{
namespace MineXpert
{


MsFragmentationSpec::MsFragmentationSpec()
{
  // qDebug() << "Creating MsFragmentationSpec.";
}

MsFragmentationSpec::MsFragmentationSpec(
  std::size_t ms_level,
  const std::vector<double> &precursors,
  std::vector<std::size_t> precursor_spectrum_indices)
  : m_msLevel(ms_level),
    m_precursorMzValues(precursors),
    m_precursorSpectrumIndices(precursor_spectrum_indices)
{
  // qDebug() << "Creating MsFragmentationSpec.";
}

MsFragmentationSpec::MsFragmentationSpec(const MsFragmentationSpec &other)
{
  m_msLevel                  = other.m_msLevel;
  mp_precision               = other.mp_precision;
  m_precursorMzValues        = other.m_precursorMzValues;
  m_precursorSpectrumIndices = other.m_precursorSpectrumIndices;

  // qDebug() << "Creating MsFragmentationSpec.";
}

MsFragmentationSpec::~MsFragmentationSpec()
{
  // qDebug() << "Destroying MsFragmentationSpec.";
}

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

  m_msLevel                  = other.m_msLevel;
  mp_precision               = other.mp_precision;
  m_precursorMzValues        = other.m_precursorMzValues;
  m_precursorSpectrumIndices = other.m_precursorSpectrumIndices;

  return *this;
}

void
MsFragmentationSpec::setMsLevel(std::size_t ms_level)
{
  m_msLevel = ms_level;
}

std::size_t
MsFragmentationSpec::getMsLevel() const
{
  return m_msLevel;
}

void
MsFragmentationSpec::setPrecisionPtr(pappso::PrecisionPtr precision_p)
{
  mp_precision = precision_p;
}

pappso::PrecisionPtr
MsFragmentationSpec::getPrecisionPtr() const
{
  return mp_precision;
}

bool
MsFragmentationSpec::containsMzPrecursor(double searched_mz_value) const
{
  // We cannot hope to have the user enter the m/z value with all the decimals
  // right (because they do not even know them by looking a the data !) So we
  // let the user set a precsion with which the match needs to be done.

  auto result =
    std::find_if(m_precursorMzValues.begin(),
                 m_precursorMzValues.end(),
                 [this, searched_mz_value](double frag_spec_iter_value) {
                   double tolerance = mp_precision->delta(searched_mz_value);

                   double left_border  = frag_spec_iter_value - tolerance;
                   double right_border = frag_spec_iter_value + tolerance;

                   bool is_match = ((searched_mz_value >= left_border) &&
                                    (searched_mz_value <= right_border));

                   // qDebug() << "Checked if MS frag spec contains:" <<
                   // searched_mz_value
                   //<< "with tolerance:" << tolerance
                   //<< "current frag spec value being tested:" <<
                   // frag_spec_iter_value
                   //<< "left border:" << QString::number(left_border, 'f', 6)
                   //<< "right border:" << QString::number(right_border, 'f', 6)
                   //<< "the match was:" << is_match;

                   return is_match;
                 });

  // If the returned iterator is not end(), then that means that a match could
  // be found.

  if(result != m_precursorMzValues.end())
    return true;

  return false;


#if 0
  // This version is less modern C++.

  double tolerance = mp_precision->delta(searched_mz_value);

  for(auto &frag_spec_iter_value : m_precursorMzValues)
    {
      double left_border  = frag_spec_iter_value - tolerance;
      double right_border = frag_spec_iter_value + tolerance;

      bool is_match =
        (searched_mz_value >= left_border && searched_mz_value <= right_border);

      if(is_match)
        {
          //qDebug() << "Searching for mz value:" << searched_mz_value
                   //<< "Checked if MS frag spec contains:" << searched_mz_value
                   //<< "with tolerance:" << tolerance
                   //<< "current frag spec value being tested:"
                   //<< frag_spec_iter_value
                   //<< "left border:" << QString::number(left_border, 'f', 6)
                   //<< "right border:" << QString::number(right_border, 'f', 6)
                   //<< "greater than left border:"
                   //<< (searched_mz_value >= left_border)
                   //<< "lower than right border:"
                   //<< (searched_mz_value <= right_border)
                   //<< "the match was:" << is_match;

          return true;
        }
    }

  return false;
#endif
}

bool
MsFragmentationSpec::containsMzPrecursors(
  const std::vector<pappso::PrecursorIonData> &precursor_ion_data) const
{
  // We receive a vector with precursor ion data, typically from a mass
  // spectrum. Each PrecursorIonData contains a mz value. We want to check if
  // there is a match between that mz value and the precursor mz values listed
  // in this MsFragmentationSpec instance.

  bool was_found_at_least_one = false;

#if 0
  QString precursor_ion_data_mz_values_string =
    "List of mass spectrum precursor ion data mz values: ";

  for(auto &item : precursor_ion_data)
    {
      precursor_ion_data_mz_values_string +=
        QString("%1,").arg(item.mz, 0, 'f', 6);
    }

  precursor_ion_data_mz_values_string += "\n";

  qDebug().noquote() << precursor_ion_data_mz_values_string;

#endif


  // QString debug_string_1("List of mass spectrum precursor mz values to search
  // for: "); QString debug_string_2("Precursor mz values that matched: ");

  for(auto &iter_precursor_ion_data : precursor_ion_data)
    {
      // debug_string_1 += QString::number(iter_precursor_ion_data.mz, 'f', 6);
      // debug_string_1 += " ";

      // Find any mz value from the vector in this MsFragmentationSpec that
      // would match the precursor mz value from the PrecursorIonData.

      if(containsMzPrecursor(iter_precursor_ion_data.mz))
        {
          was_found_at_least_one = true;
          // debug_string_2 += QString::number(iter_precursor_ion_data.mz, 'f',
          // 6); debug_string_2 += " ";
        }
    }

  // qDebug().noquote() << debug_string_1;
  // qDebug().noquote() << debug_string_2;

  return was_found_at_least_one;
}

bool
MsFragmentationSpec::containsSpectrumPrecursorIndex(
  std::size_t searched_precursor_index) const
{
  auto result = std::find_if(m_precursorSpectrumIndices.begin(),
                             m_precursorSpectrumIndices.end(),
                             [searched_precursor_index](double value) {
                               return value == searched_precursor_index;
                             });

  if(result != m_precursorSpectrumIndices.end())
    return true;

  return false;
}

std::size_t
MsFragmentationSpec::precursorMzValuesCount() const
{
  std::size_t size = m_precursorMzValues.size();

  // qDebug() << "The MsFragmentationSpec has" << size
  //<< " precursor mz values in its vector.";

  return size;
}

std::size_t
MsFragmentationSpec::precursorSpectrumIndicesCount() const
{
  return m_precursorSpectrumIndices.size();
}

bool
MsFragmentationSpec::isValid() const
{
  // The fragmentation spec is valid if there is either one of the following:

  // qDebug() << "\textsl{fragmentation\textsl{}} spec: " << this;
  return m_msLevel || m_precursorMzValues.size() ||
         m_precursorSpectrumIndices.size();
}

QString
MsFragmentationSpec::msLevelToString() const
{
  return QString("%1 ").arg(m_msLevel);
}

QString
MsFragmentationSpec::mzPrecursorsToString() const
{
  QString text;

  for(auto &&precursor_mz : m_precursorMzValues)
    text += QString("%1\n").arg(precursor_mz, 0, 'f', 6);

  return text;
}

QString
MsFragmentationSpec::precursorSpectrumIndicesToString() const
{
  QString text;

  for(auto &&precursor_spectrum_index : m_precursorSpectrumIndices)
    text += QString("%1\n").arg(precursor_spectrum_index);

  return text;
}

QString
MsFragmentationSpec::toString(int offset, const QString &spacer) const
{
  QString lead;

  for(int iter = 0; iter < offset; ++iter)
    lead += spacer;

  QString text = lead;

  text += "MS fragmentation spec:\n";

  text += lead;
  text += spacer;
  text += "MS level: ";
  text += msLevelToString();
  text += "\n";

  if(m_precursorMzValues.size())
    {
      text += lead;
      text += spacer;
      text += "Precursors' m/z: ";
      text += mzPrecursorsToString();
      text += "\n";

      if(mp_precision == nullptr)
        {
          qFatal() << "Cannot be that precision ptr is nullptr";
        }

      // Indent one space more for the precision
      text += lead;
      text += spacer;
      text += spacer;
      text += QString("m/z value precision: %1").arg(mp_precision->toString());
      text += "\n";
    }

  if(m_precursorSpectrumIndices.size())
    {
      text += lead;
      text += spacer;
      text += "Precursor spectrum indices: ";
      text += precursorSpectrumIndicesToString();
      text += "\n";
    }

  return text;
}


} // namespace MineXpert

} // namespace MsXpS
