/**
 * \file core/onescanprocess.cpp
 * \date 24/09/2025
 * \author Olivier Langella
 * \brief Running specpeptidoms on one scan
 */


/*
 * PeptidOMS, Spectra to protein alignment tool
 * Copyright (C) 2025  Olivier Langella
 * <olivier.langella@universite-paris-saclay.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 "onescanprocess.h"
#include "runningpeptidoms.h"
#include <pappsomspp/core/processing/specpeptidoms/spomsspectrum.h>
#include <pappsomspp/core/processing/filters/filterresample.h>
#include <pappsomspp/core/processing/filters/filterpass.h>
#include <pappsomspp/core/processing/filters/filterchargedeconvolution.h>
#include <pappsomspp/core/exception/exceptionoutofrange.h>
#include <pappsomspp/core/processing/cbor/psm/psmcborutils.h>

OneScanProcess::OneScanProcess(
  RunningPeptidOms *parent_p,
  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum)
  : mp_parent(parent_p), msp_qualifiedMassSpectrum(qualified_mass_spectrum)
{
}

OneScanProcess::OneScanProcess(const OneScanProcess &other [[maybe_unused]])
{
  throw pappso::PappsoException("prevent OneScanProcess copy");
}


OneScanProcess::~OneScanProcess()
{
}

const pappso::QualifiedMassSpectrum &
OneScanProcess::getQualifiedMassSpectrum() const
{
  return *(msp_qualifiedMassSpectrum.get());
}

void
OneScanProcess::process()
{

  const pappso::QualifiedMassSpectrum &spectrum =
    *msp_qualifiedMassSpectrum.get();
  pappso::QualifiedMassSpectrum spectrum_simple =
    *msp_qualifiedMassSpectrum.get();
  // copy spectrum before filtering
  spectrum_simple.setMassSpectrumSPtr(msp_qualifiedMassSpectrum.get()
                                        ->getMassSpectrumSPtr()
                                        .get()
                                        ->makeMassSpectrumSPtr());
  if(mp_parent->m_deisotope)
    pappso::FilterChargeDeconvolution(mp_parent->m_fragmentTolerance)
      .filter(*(spectrum_simple.getMassSpectrumSPtr().get()));
  pappso::FilterResampleKeepGreater(mp_parent->m_minimumMz)
    .filter(*(spectrum_simple.getMassSpectrumSPtr().get()));
  pappso::FilterGreatestY(mp_parent->m_nMostIntense)
    .filter(*(spectrum_simple.getMassSpectrumSPtr().get()));

  // psm_list
  // p_writer->append("psm_list");
  // p_writer->startArray();

  // 4512 ?
  qDebug() << "spectrum index:"
           << spectrum.getMassSpectrumId().getSpectrumIndex();
  mp_parent->onOneScanProcessStarted(
    spectrum.getMassSpectrumId().getSpectrumIndex());

  pappso::specpeptidoms::SpOMSSpectrum experimental_spectrum(
    spectrum_simple, mp_parent->m_fragmentTolerance, mp_parent->m_aaCode);

  pappso::specpeptidoms::SemiGlobalAlignment semi_global_alignment(
    mp_parent->m_scoreValues,
    mp_parent->m_fragmentTolerance,
    mp_parent->m_aaCode);


  for(const pappso::specpeptidoms::SpOMSProtein &protein : mp_parent->m_proteinList)
    {
      try
        {

          // qWarning() << "accession:" << protein.getAccession();
          //  qDebug() << protein.getAccession();
          semi_global_alignment.fastAlign(experimental_spectrum, &protein);
        }
      catch(const pappso::PappsoException &err)
        {
          // GRMZM2G499900_P01
          throw pappso::PappsoException(
            QString("Spectral (index=%1) alignment failed for protein "
                    "%2:\n%3\nwith error "
                    "message\n%4")
              .arg(spectrum.getMassSpectrumId().getSpectrumIndex())
              .arg(protein.getAccession())
              .arg(protein.getSequence())
              .arg(err.qwhat()));
        }
    }

  // qDebug() << "Completed fastAlign";


  std::vector<pappso::specpeptidoms::Location> locations;
  std::vector<double> potential_mass_errors;
  locations = semi_global_alignment.getLocationSaver().getLocations();

  qDebug() << "locations.size():" << locations.size();
  for(auto loc : locations)
    {
      try
        {
          qDebug() << "beginning=" << loc.beginning << "length=" << loc.length
                   << "tree=" << loc.tree << "score=" << loc.score
                   << "protein=" << loc.proteinPtr->getAccession()
                   << " spectrum_index="
                   << spectrum.getMassSpectrumId().getSpectrumIndex();
          semi_global_alignment.preciseAlign(
            experimental_spectrum, loc.proteinPtr, loc.beginning, loc.length);
          qDebug() << "Completed preciseAlign";

          pappso::specpeptidoms::Alignment best_alignment =
            semi_global_alignment.getBestAlignment();
          /*  qDebug() << "Best alignment" << best_alignment.interpretation
                     << best_alignment.score << "SPC" << best_alignment.SPC
                     << "beginning" << best_alignment.beginning << "end"
                     << best_alignment.end;*/
          if(best_alignment.end >
             (std::size_t)loc.proteinPtr->getSequence().size())
            {
              throw pappso::ExceptionOutOfRange(
                QString("best_alignment.end > "
                        "(std::size_t)protein.getSequence().size() : %1 %2")
                  .arg(best_alignment.end)
                  .arg(loc.proteinPtr->getSequence().size()));
            }
          if(best_alignment.begin_shift > 0 || best_alignment.end_shift > 0 ||
             best_alignment.shifts.size() > 0)
            {
              qDebug();
              potential_mass_errors =
                pappso::specpeptidoms::SemiGlobalAlignment::
                  getPotentialMassErrors(mp_parent->m_aaCode,
                                         best_alignment,
                                         loc.proteinPtr->getSequence());
              qDebug();
              semi_global_alignment.postProcessingAlign(experimental_spectrum,
                                                        loc.proteinPtr,
                                                        loc.beginning,
                                                        loc.length,
                                                        potential_mass_errors);

              qDebug() << "semi_global_alignment.getBestAlignment()";
              pappso::specpeptidoms::Alignment best_post_processed_alignment =
                semi_global_alignment.getBestAlignment();
              if(best_post_processed_alignment.SPC > best_alignment.SPC)
                {
                  qDebug() << "Best post-processed alignment"
                           << best_post_processed_alignment.m_peptideModel
                                .toInterpretation()
                           << best_post_processed_alignment.score << "SPC"
                           << best_post_processed_alignment.SPC;
                  storeAlignment(loc.proteinPtr, best_post_processed_alignment);
                }
              else
                {
                  qDebug() << "no improvement in post-processing";
                  storeAlignment(loc.proteinPtr, best_alignment);
                }
            }
          else
            {

              storeAlignment(loc.proteinPtr, best_alignment);
            }
        }
      catch(const pappso::PappsoException &err)
        {
          // GRMZM2G499900_P01
          throw pappso::PappsoException(
            QString("Spectral (index=%1) alignment failed for protein "
                    "%2:\n%3\nwith error "
                    "message\n%4")
              .arg(spectrum.getMassSpectrumId().getSpectrumIndex())
              .arg(loc.proteinPtr->getAccession())
              .arg(loc.proteinPtr->getSequence())
              .arg(err.qwhat()));
        }
    }

  mp_parent->onOneScanProcessFinished(
    spectrum.getMassSpectrumId().getSpectrumIndex());
  qDebug() << "end spectrum index:"
           << spectrum.getMassSpectrumId().getSpectrumIndex();
}

void
OneScanProcess::storeAlignment(
  const pappso::specpeptidoms::SpOMSProtein *protein_ptr,
  const pappso::specpeptidoms::Alignment &alignment)
{
  qDebug() << protein_ptr->getAccession();
  QString peptide_key(alignment.m_peptideModel.toProForma());

  // do not take into account peptide containing too much redundancy
  if(pappso::specpeptidoms::SemiGlobalAlignment::checkSequenceDiversity(
       peptide_key, 5, 2))
    {

      auto it_peptider = m_onePeptideResultMap.insert({peptide_key, {}});
      OnePeptideResult &one_peptide_result = it_peptider.first->second;

      auto it_protein_pos = one_peptide_result.map_protein2positions.insert(
        {protein_ptr->getAccession(), QCborArray()});
      it_protein_pos.first->second.append((qint64)alignment.getPositionStart());

      one_peptide_result.psm_eval.insert(
        QString("bracket"), alignment.m_peptideModel.toInterpretation());
      one_peptide_result.psm_eval.insert(QString("spc"), (qint64)alignment.SPC);
      one_peptide_result.psm_eval.insert(QString("score"), alignment.score);
      one_peptide_result.psm_eval.insert(QString("nam"),
                                         alignment.getNonAlignedMass());

      pappso::cbor::psm::PsmProtein psm_protein;
      psm_protein.protein_sp = std::make_shared<pappso::Protein>(
        protein_ptr->getCompleteDescription(), protein_ptr->getSequence());
      psm_protein.isContaminant = false;
      psm_protein.isTarget      = true;


      m_proteinMap.insert(psm_protein);
      qDebug() << "m_proteinMap.size()=" << m_proteinMap.size();
    }
}


void
OneScanProcess::writeCborStream(pappso::cbor::CborStreamWriter *p_writer,
                                std::size_t max_psm)
{
  qDebug() << max_psm;
  QStringList accessions;
  if(m_onePeptideResultMap.size() > 0)
    {
      QCborMap scan_map;

      pappso::cbor::psm::PsmCborUtils::prepareCborScanWithSpectrumAndPeakList(
        scan_map, *(msp_qualifiedMassSpectrum.get()));

      struct sortPsmResults
      {
        QString peptide_key;
        qint64 score;
      };

      std::vector<sortPsmResults> sort_psm_list;
      for(auto &it_map : m_onePeptideResultMap)
        {
          sort_psm_list.push_back(
            {it_map.first, it_map.second.psm_eval.value("score").toInteger()});
        }
      qDebug();
      std::sort(
        sort_psm_list.begin(),
        sort_psm_list.end(),
        [](sortPsmResults &a, sortPsmResults &b) { return a.score > b.score; });

      qDebug();

      QCborArray psm_list;

      auto it_end = sort_psm_list.begin() + max_psm;


      qDebug() << sort_psm_list.size();
      for(auto it = sort_psm_list.begin();
          it != sort_psm_list.end() && it != it_end;
          it++)
        {
          qDebug() << it->peptide_key;
          auto it_map = m_onePeptideResultMap.find(it->peptide_key);

          qDebug();
          QCborMap psm;

          psm.insert(QString("proforma"), it->peptide_key);

          QCborArray protein_list;
          for(auto &it_protein : it_map->second.map_protein2positions)
            {
              QCborMap protein;
              protein.insert(QString("accession"), it_protein.first);
              protein.insert(QString("positions"), it_protein.second);
              protein_list.append(protein);
              accessions << it_protein.first;
            }

          psm.insert(QString("protein_list"), protein_list);

          QCborMap specpeptidoms;
          specpeptidoms.insert(QString("peptidoms"), it_map->second.psm_eval);

          psm.insert(QString("eval"), specpeptidoms);

          psm_list.append(psm);
          qDebug();
        }

      qDebug();
      scan_map.insert(QString("psm_list"), psm_list);

      p_writer->writeCborMap(scan_map);
      qDebug();
    }

  m_proteinMap.reduce(accessions);
  qDebug();
  // return accessions;
}

void
OneScanProcess::fillProteinMap(
  pappso::cbor::psm::PsmProteinMap &protein_map) const
{
  qDebug() << " m_proteinMap.size()=" << m_proteinMap.size();
  qDebug() << " protein_map.size()=" << protein_map.size();
  protein_map.merge(m_proteinMap);

  qDebug() << " m_proteinMap.size()=" << m_proteinMap.size();
  qDebug() << " protein_map.size()=" << protein_map.size();
}
