/**
 * \file core/spoms/protein_list.cpp
 * \date 13/02/2026
 * \author Aurélien Berthier, BENOIST Emile, TESSIER Dominique
 * \brief Protein collection and Spectrum align
 */


/*
 * PeptidOMS, Spectra to protein alignment tool
 * Copyright (C) 2026  Olivier Langella
 * <olivier.langella@cnrs.fr>
 *
 * 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/>.
 *
 */

#include "protein_list.h"
#include "alignment.h"
#include "alignmentbucket.h"
#include "interpretationsaver.h"
#include "locationbackup.h"
#include "protein.h"
#include "transformedspectrum.h"
#include "spomsutils.h"
#include <algorithm>
#include <exception>
#include <memory>
#include <pappsomspp/core/pappsoexception.h>
#include <pappsomspp/core/protein/protein.h>
#include <pappsomspp/core/types.h>
#include <qlogging.h>
#include <qobject.h>

namespace pappso
{
namespace spoms
{

ProteinList::ProteinList(const pappso::AaCode &aaCode) : m_aaCode(aaCode)
{
}

ProteinList::~ProteinList()
{
}


void
ProteinList::addProtein(pappso::ProteinSp protein_sp)
{
  // Protein protein(m_aaCode, protein_sp);
  m_proteinList.push_back(std::make_shared<Protein>(m_aaCode, protein_sp));
  m_maximumProteinSequenceLength =
    std::max(m_maximumProteinSequenceLength,
             m_proteinList.back().get()->getSequence().size());
}

const std::vector<std::shared_ptr<Protein>> &
ProteinList::getProteinList() const
{
  return m_proteinList;
}

std::vector<pappso::spoms::ProteinList::Result>
ProteinList::alignQualifiedSpectrum(QualifiedMassSpectrumCstSPtr qmsp) const
{
  Debug empty_debug;
  return alignQualifiedSpectrum(qmsp, empty_debug);
}

std::vector<pappso::spoms::ProteinList::Result>
ProteinList::alignQualifiedSpectrum(QualifiedMassSpectrumCstSPtr qmsp,
                                    Debug &debug_info) const
{

  pappso::spoms::SpomsUtils *spoms_utils_instance_p =
    pappso::spoms::SpomsUtils::GetInstance();
  double LOCATIONS_SCORE_THRESHOLD =
    spoms_utils_instance_p
      ->getParameterValue("alignment", "locations_score_threshold")
      .toDouble();
  int NB_LOCATIONS_SAVED =
    spoms_utils_instance_p->getParameterValue("alignment", "nb_locations_saved")
      .toInt();
  bool b_SHUT_DOWN_NON_ALIGNED_MASS =
    spoms_utils_instance_p
      ->getParameterValue("post_treatment", "shut_down_peaks_cleaning")
      .toBool();
  int NB_RETURNED_ALIGNMENTS =
    spoms_utils_instance_p
      ->getParameterValue("output", "max_interpretations_per_spectrum")
      .toInt();
  int MIN_SCENARIO_SCORE =
    spoms_utils_instance_p->getParameterValue("alignment", "min_scenario_score")
      .toInt();

  int NB_INTERPRETATIONS_SAVED =
    spoms_utils_instance_p
      ->getParameterValue("post_treatment", "nb_interpretations_saved")
      .toInt();

  bool debug_mode = spoms_utils_instance_p->getParameterValueOrDefault(
    "output", "debug", false);

  std::vector<Result> result_list;

  std::shared_ptr<const TransformedSpectrum> transformed_spectrum_sp =
    std::make_shared<const TransformedSpectrum>(m_aaCode, qmsp);


  Protein::MemoryBuffer memory_buffer(
    transformed_spectrum_sp.get()->getColumnCount(),
    m_maximumProteinSequenceLength);

  if(!transformed_spectrum_sp.get()->isUsefull())
    {
      return result_list;
    }

  std::vector<LocationBackup> location_backup_list;
  std::vector<LocationBackup> protein_location_list;
  int max_score_location = 0;
  int minimum_score      = SpomsUtils::GetInstance()
                        ->getParameterValue("alignment", "min_scenario_score")
                        .toInt();
  QJsonObject score_grid = SpomsUtils::GetInstance()->getAlignmentScoreGrid();
  for(const std::shared_ptr<Protein> &protein : m_proteinList)
    {
      try
        {
          // qWarning() << "start spectrum index:"
          //            << qmsp.get()->getMassSpectrumId().getSpectrumIndex()
          //            << " " << qmsp.get()->getMassSpectrumId().getNativeId()
          //            << " " << transformed_spectrum_sp.get()->getNbAaFound()
          //            << " accession:"
          //            << protein.get()->getProteinSp().get()->getAccession();
          protein_location_list = protein.get()->preliminaryTreatmentV1(
            memory_buffer,
            *transformed_spectrum_sp.get(),
            m_aaCode,
            minimum_score,
            score_grid);

          if(debug_mode)
            {
              // if(protein_location_list.size() == 1)
              //   {
              //     if(protein_location_list.front().score > 27)
              //       {
              //         qWarning()
              //           << protein_location_list.front()
              //                .mp_protein->getProteinSp()
              //                .get()
              //                ->getAccession()
              //           << " score:" << protein_location_list.front().score;
              //       }
              //   }
            }


          if(!protein_location_list.empty())
            minimum_score = std::max(minimum_score,
                                     (int)(protein_location_list.front().score *
                                           LOCATIONS_SCORE_THRESHOLD));
          location_backup_list.insert(location_backup_list.end(),
                                      protein_location_list.begin(),
                                      protein_location_list.end());
        }
      catch(const std::exception &std_exception)
        {
          throw pappso::PappsoException(
            QObject::tr(
              "fast alignment failed for protein %1 with std exception:\n%2")
              .arg(protein.get()->getProteinSp().get()->getAccession())
              .arg(std_exception.what()));
        }
    }


  // qWarning() << "pl 164" << location_backup_list.size();
  protein_location_list.clear();

  protein_location_list.resize(
    std::min(NB_LOCATIONS_SAVED, (int)location_backup_list.size()));
  // take only 5 best locations
  std::partial_sort_copy(location_backup_list.begin(),
                         location_backup_list.end(),
                         protein_location_list.begin(),
                         protein_location_list.end(),
                         [](const LocationBackup &a, const LocationBackup &b) {
                           return (a.score > b.score);
                         });


  if(debug_mode)
    {
      for(auto &location : protein_location_list)
        {
          qWarning()
            << location.mp_protein->getProteinSp().get()->getAccession()
            << " score:" << location.score;
        }
    }
  // qWarning() << "pl 176";
  /*
    std::sort(location_backup_list.begin(),
              location_backup_list.end(),
              [](const std::shared_ptr<LocationBackup> &a,
                 const std::shared_ptr<LocationBackup> &b) {
                return (a.get()->score > b.get()->score);
              });
              */

  if(protein_location_list.size() > 0)
    {
      max_score_location = protein_location_list.front().score;
    }

  std::vector<Result> short_result_list;
  if(max_score_location < 1)
    {
      return result_list;
    }
  try
    {
      // qWarning() << "pl 193";
      int locations_score_threshold =
        max_score_location * LOCATIONS_SCORE_THRESHOLD;
      qDebug() << locations_score_threshold;
      auto it = protein_location_list.begin();
      while((it != protein_location_list.end()) &&
            (it->score > locations_score_threshold))
        {
          qDebug();
          // qWarning() << "pl 201 "
          //            << it->mp_protein->getProteinSp().get()->getAccession()
          //            << " " << i << it->toString() << " "
          //            << it->getSubSequence();
          qDebug() << it->toString();
          std::shared_ptr<Alignment> alignment_result =
            it->mp_protein->finalTreatment(transformed_spectrum_sp, *it, false);
          qDebug() << "pl 218 ";

          if(debug_mode)
            {
              // debug message
              // qWarning() << alignment_result.get()
              //                 ->getPeptideModel()
              //                 .get()
              //                 ->toInterpretation();
            }
          if(alignment_result.get() != nullptr)
            {
              result_list.push_back(
                {it->mp_protein->getProteinSp(), alignment_result, nullptr});

              qDebug() << "pl 225 ";
            }

          qDebug();
          it++;
        }

      qDebug() << result_list.size();

      short_result_list.resize(
        std::min(NB_RETURNED_ALIGNMENTS, (int)result_list.size()));

      // take only NB_RETURNED_ALIGNMENTS returns
      std::partial_sort_copy(result_list.begin(),
                             result_list.end(),
                             short_result_list.begin(),
                             short_result_list.end(),
                             [](const Result &a, const Result &b) {
                               return b.alignment_sp.get()->compareTo(
                                 *a.alignment_sp.get());
                             });
      // qWarning() << "pl 217";

      if(debug_mode)
        {
          debug_info.intermediate_results = result_list;
        }
      // qWarning() << "pl 225";
      if(!b_SHUT_DOWN_NON_ALIGNED_MASS)
        {
          auto it = short_result_list.begin();
          while(it != short_result_list.end())
            {

              // qWarning() << "pl 233" << i;
              /*
               * We generate all the masses that can possibly correspond
               * to a non-aligned mass and for each of them, a new
               * transformed spectrum is constructed. A new alignment is
               * then performed on them and the current location and the
               * best one is keep.
               */
              std::vector<double> possible_non_aligned_masses =
                it->alignment_sp.get()->computePossibleNonAlignedMasses();
              AlignmentBucket alignments_bucket(NB_INTERPRETATIONS_SAVED);
              transformed_spectrum_sp =
                it->alignment_sp.get()->getTransformedSpectrum();

              if(possible_non_aligned_masses.size() > 0)
                {
                  for(double non_aligned_mass : possible_non_aligned_masses)
                    {
                      transformed_spectrum_sp.get()->postProcessOneMass(
                        *(it->alignment_sp.get()
                            ->getLocationBackup()
                            ->mp_protein),
                        *(it->alignment_sp.get()->getLocationBackup()),
                        transformed_spectrum_sp.get()->getPeakMask(),
                        non_aligned_mass,
                        alignments_bucket);
                    }

                  qDebug() << alignments_bucket.getBestAlignmentScore() << " "
                           << MIN_SCENARIO_SCORE;
                  /*
                   * If the new alignment is better than the original,
                   * the new one is also saved in
                   * this.interpretation_saver.
                   */
                  if(alignments_bucket.getBestAlignmentScore() >
                     MIN_SCENARIO_SCORE)
                    {
                      if(it->alignment_sp->compareToPostProcessing(
                           *(alignments_bucket.getBestAlignment().get())))
                        {
                          it->post_processed_alignment_sp =
                            alignments_bucket.getBestAlignment();
                        }
                    }
                }
              it++;
            }
        }

      qDebug();
      // qWarning() << "pl 278";
    }
  catch(const std::exception &std_exception)
    {
      throw pappso::PappsoException(
        QObject::tr(
          "protein list final treatment failed with std exception:\n%1")
          .arg(std_exception.what()));
    }
  return short_result_list;
}
} // namespace spoms
} // namespace pappso
