/**
 * \file core/onescanprocessspoms.cpp
 * \date 21/02/2026
 * \author Olivier Langella
 * \brief Running specpeptidoms on one scan
 */


/*
 * PeptidOMS, Spectra to protein alignment tool
 * Copyright (C) 2026  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 "onescanprocessspoms.h"
#include "core/spoms/alignment.h"
#include "core/spoms/peptidemodel.h"
#include "core/spoms/spomsutils.h"
#include "runningspoms.h"
#include <cstddef>
#include <exception>
#include <memory>
#include <pappsomspp/core/pappsoexception.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>
#include <pappsomspp/core/protein/protein.h>
#include <QCborMap>
#include <QDebug>
#include <QObject>
#include <qcborarray.h>
#include <qcbormap.h>
#include <qcontainerfwd.h>

OneScanProcessSpoms::OneScanProcessSpoms(
  RunningSpoms *parent_p,
  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum)
  : mp_parent(parent_p), msp_qualifiedMassSpectrum(qualified_mass_spectrum)
{
  m_debugMode =
    pappso::spoms::SpomsUtils::GetInstance()->getParameterValueOrDefault(
      "output", "debug", false);
}

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


OneScanProcessSpoms::~OneScanProcessSpoms()
{
}

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

void
OneScanProcessSpoms::process()
{

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

  if(msp_fileredMassSpectrum.get()->getMassSpectrumCstSPtr().get()->size() >
     (std::size_t)mp_parent->m_nMostIntense)
    {
      throw pappso::PappsoException(QObject::tr("no good %1 %2")
                                      .arg(msp_fileredMassSpectrum.get()
                                             ->getMassSpectrumCstSPtr()
                                             .get()
                                             ->size())
                                      .arg(mp_parent->m_nMostIntense));
    }
  // psm_list
  // p_writer->append("psm_list");
  // p_writer->startArray();

  // 4512 ?
  // qWarning() << "os 106 spectrum index:"
  //            << spectrum_simple.getMassSpectrumId().getSpectrumIndex() << " "
  //            << spectrum_simple.getRtInSeconds();
  mp_parent->onOneScanProcessSpomsStarted(
    msp_fileredMassSpectrum.get()->getMassSpectrumId().getSpectrumIndex());
  // if(spectrum.getMassSpectrumId().getSpectrumIndex() == 2424)
  //{
  try
    {
      if(msp_fileredMassSpectrum.get()
           ->getMassSpectrumCstSPtr()
           .get()
           ->size() >= (std::size_t)pappso::spoms::SpomsUtils::GetInstance()
                         ->getParameterValue("alignment", "nb_peaks_min")
                         .toInt())
        {
          // qWarning() << "os 108";
          std::vector<pappso::spoms::ProteinList::Result> alignment_list;

          m_proteinListDebugInfo.intermediate_results.clear();

          alignment_list = mp_parent->mpa_proteinList->alignQualifiedSpectrum(
            msp_fileredMassSpectrum, m_proteinListDebugInfo);
          // qDebug() << "Completed fastAlign";
          //
          // qWarning() << "os 114";
          for(auto &alignment_element : alignment_list)
            {
              if(alignment_element.alignment_sp.get() != nullptr)
                {
                  alignment_element.alignment_sp.get()
                    ->computeNbPeaksInCommon();
                }
              storeAlignment(alignment_element);
            }
          // qWarning() << "os 117";
        }
    }
  catch(const std::exception &std_err)
    {
      // GRMZM2G499900_P01
      throw pappso::PappsoException(
        QString(
          "Spectral (index=%1 %2) alignment failed with std exception\n%3")
          .arg(msp_fileredMassSpectrum.get()
                 ->getMassSpectrumId()
                 .getSpectrumIndex())
          .arg(msp_fileredMassSpectrum.get()->getMassSpectrumId().getNativeId())
          .arg(std_err.what()));
    }
  //  }
  mp_parent->onOneScanProcessSpomsFinished(
    msp_fileredMassSpectrum.get()->getMassSpectrumId().getSpectrumIndex());
  qDebug()
    << "end spectrum index:"
    << msp_fileredMassSpectrum.get()->getMassSpectrumId().getSpectrumIndex();
}

void
OneScanProcessSpoms::storeAlignment(
  const pappso::spoms::ProteinList::Result &alignment_result)
{
  const pappso::Protein &protein = *alignment_result.protein.get();
  // qWarning() << "os 148 " << protein.getAccession();

  QString peptide_key;
  std::shared_ptr<pappso::spoms::Alignment> ref_alignment;
  std::shared_ptr<pappso::spoms::PeptideModel> first_peptide_model_sp;
  if(alignment_result.alignment_sp.get() != nullptr)
    {
      first_peptide_model_sp =
        alignment_result.alignment_sp.get()->getPeptideModel();
    }
  else
    {
      ref_alignment = alignment_result.post_processed_alignment_sp;
    }

  std::shared_ptr<pappso::spoms::PeptideModel> post_peptide_model_sp;
  if(alignment_result.post_processed_alignment_sp.get() != nullptr)
    {
      post_peptide_model_sp =
        alignment_result.post_processed_alignment_sp.get()->getPeptideModel();
      peptide_key = post_peptide_model_sp.get()->toProForma();
    }
  else
    {
      ref_alignment = alignment_result.alignment_sp;
      peptide_key   = first_peptide_model_sp.get()->toProForma();
    }

  // qWarning() << "os 164 ";
  if(ref_alignment.get() == nullptr)
    {
      if(first_peptide_model_sp.get()->size() >=
         post_peptide_model_sp.get()->size())
        {
          ref_alignment = alignment_result.alignment_sp;
          peptide_key   = first_peptide_model_sp.get()->toProForma();
        }
      else
        {
          ref_alignment = alignment_result.post_processed_alignment_sp;
          peptide_key   = post_peptide_model_sp.get()->toProForma();
        }
    }

  // qWarning() << "os 179 ";
  if(!peptide_key.isEmpty())
    {
      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.getAccession(), QCborArray()});
      it_protein_pos.first->second.append((qint64)ref_alignment.get()
                                            ->getLocation()
                                            .get()
                                            ->getStartPositionOnProtein());

      // qWarning() << "os 191 ";
      if(alignment_result.alignment_sp.get() != nullptr)
        {
          one_peptide_result.psm_eval.insert(
            QString("first"),
            getCborMapFromAlignment(*alignment_result.alignment_sp.get(),
                                    *first_peptide_model_sp.get()));
        }
      if(alignment_result.post_processed_alignment_sp.get() != nullptr)
        {
          one_peptide_result.psm_eval.insert(
            QString("post"),
            getCborMapFromAlignment(
              *alignment_result.post_processed_alignment_sp.get(),
              *post_peptide_model_sp.get()));
        }

      pappso::cbor::psm::PsmProtein psm_protein;
      psm_protein.protein_sp    = std::make_shared<pappso::Protein>(protein);
      psm_protein.isContaminant = false;
      psm_protein.isTarget      = true;


      m_proteinMap.insert(psm_protein);
    }
}

QCborMap
OneScanProcessSpoms::getCborMapFromAlignment(
  const pappso::spoms::Alignment &alignment,
  const pappso::spoms::PeptideModel &peptide_model)
{
  QCborMap alignment_cbor;
  alignment_cbor.insert(QString("bracket"), peptide_model.toInterpretation());
  alignment_cbor.insert(QString("spc"), (qint64)alignment.getNBPeaksInCommon());
  alignment_cbor.insert(QString("score"), alignment.getScore());
  alignment_cbor.insert(QString("nam"), peptide_model.getNonAlignedMass());
  return alignment_cbor;
}

void
OneScanProcessSpoms::writeCborStream(pappso::cbor::CborStreamWriter *p_writer)
{
  QStringList accessions;
  if(m_onePeptideResultMap.size() > 0)
    {
      QCborMap scan_map;

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


      if(m_debugMode)
        {
          // write filtered spectrum in props
          QCborMap map_debug;
          map_debug.insert(
            QString("filtered"),
            pappso::spoms::SpomsUtils::GetInstance()->getMassSpectrumCborMap(
              *msp_fileredMassSpectrum.get()->getMassSpectrumCstSPtr().get()));

          if(m_proteinListDebugInfo.intermediate_results.size() > 0)
            {
              QCborArray debug_results;

              for(auto &result : m_proteinListDebugInfo.intermediate_results)
                {
                  QCborMap result_cbor = getCborMapFromAlignment(
                    *result.alignment_sp.get(),
                    *result.alignment_sp.get()->getPeptideModel().get());
                  result_cbor.insert(QString("accession"),
                                     result.protein.get()->getAccession());
                  debug_results.append(result_cbor);
                }

              map_debug.insert(QString("all_inter"), debug_results);
            }

          QCborMap map_props;
          map_props.insert(QString("debug"), map_debug);
          scan_map.insert(QString("props"), map_props);
        }
      QCborArray psm_list;
      for(auto &it_map : m_onePeptideResultMap)
        {

          qDebug();
          QCborMap psm;

          psm.insert(QString("proforma"), it_map.first);

          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("spoms"), 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
OneScanProcessSpoms::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();
}
