/**
 * \file core/onescanprocessproteinmatcher.cpp
 * \date 08/10/2025
 * \author Olivier Langella
 * \brief Running protein matcher 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 "onescanprocessproteinmatcher.h"
#include "runningproteinmatcher.h"
#include <pappsomspp/core/exception/exceptionoutofrange.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/processing/filters/filterpeakdelta.h>
#include <pappsomspp/core/protein/proteinpresenceabsencematrix.h>
#include <pappsomspp/core/processing/cbor/psm/psmcborutils.h>

OneScanProcessProteinMatcher::OneScanProcessProteinMatcher(
  RunningProteinMatcher *parent_p,
  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum)
  : mp_parent(parent_p), msp_qualifiedMassSpectrum(qualified_mass_spectrum)
{
}

OneScanProcessProteinMatcher::~OneScanProcessProteinMatcher()
{
}

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


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


void
OneScanProcessProteinMatcher::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();
}
void
OneScanProcessProteinMatcher::process()
{


  mp_parent->onOneScanProcessStarted(
    msp_qualifiedMassSpectrum.get()->getMassSpectrumId().getSpectrumIndex());

  pappso::QualifiedMassSpectrum spectrum_simple =
    *msp_qualifiedMassSpectrum.get();
  // copy spectrum before filtering
  spectrum_simple.setMassSpectrumSPtr(msp_qualifiedMassSpectrum.get()
                                        ->getMassSpectrumSPtr()
                                        .get()
                                        ->makeMassSpectrumSPtr());


  pappso::MassSpectrum spectrum(*(spectrum_simple.getMassSpectrumSPtr().get()));

  pappso::FilterResampleKeepGreater(mp_parent->m_minimumMz).filter(spectrum);
  if(mp_parent->m_deisotope)
    {
      pappso::FilterChargeDeconvolution(mp_parent->m_fragmentTolerance)
        .filter(spectrum);
    }
  //.applyCutOff(150).takeNmostIntense(100).applyDynamicRange(100);
  pappso::FilterGreatestY(mp_parent->m_nMostIntense).filter(spectrum);

  pappso::FilterPeakDelta filter_peak_delta;
  filter_peak_delta.filter(spectrum);

  pappso::FilterGreatestY(mp_parent->m_peakDeltaNmostIntense).filter(spectrum);
  m_massDeltaList = spectrum.xValues();


  m_shortPeptideCodeList =
    mp_parent->mpa_aaStringCodeMassMatching->getAaCodeFromMassList(
      m_massDeltaList);

  std::sort(m_shortPeptideCodeList.begin(), m_shortPeptideCodeList.end());
  m_shortPeptideCodeList.erase(
    std::unique(m_shortPeptideCodeList.begin(), m_shortPeptideCodeList.end()),
    m_shortPeptideCodeList.end());
  // psm_list
  // p_writer->append("psm_list");
  // p_writer->startArray();

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


  std::vector<double> convolution_score;
  pappso::ProteinPresenceAbsenceMatrix presence_absence_matrix;


  for(const pappso::ProteinIntegerCode &coded_protein :
      mp_parent->m_proteinList)
    {
      try
        {
          pappso::ProteinSp protein_sp = coded_protein.getProteinSp();

          qDebug() << protein_sp.get()->getAccession();

          presence_absence_matrix.fillMatrix(coded_protein,
                                             m_shortPeptideCodeList);

          // qDebug() << protein_sp.get()->getAccession();
          std::vector<double> vec_score = presence_absence_matrix.convolution();

          qDebug() << vec_score.size();
          qDebug() << *std::max_element(vec_score.begin(), vec_score.end());

          storeMatch(protein_sp, vec_score);
        }
      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(msp_qualifiedMassSpectrum.get()
                     ->getMassSpectrumId()
                     .getSpectrumIndex())
              .arg(coded_protein.getProteinSp().get()->getAccession())
              .arg(coded_protein.getProteinSp().get()->getSequence())
              .arg(err.qwhat()));
        }
    }

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

void
OneScanProcessProteinMatcher::storeMatch(
  pappso::ProteinSp &protein_sp, const std::vector<double> &convolution_score)
{

  struct frame
  {
    std::size_t start = 0;
    std::size_t stop  = 0;
    QString peptide_key;

    std::size_t max_score = 0;
  };

  std::vector<frame> frame_list;

  std::size_t window = 7;
  // std::vector<double> wcopy(window);
  if(convolution_score.size() > window)
    {
      auto it_begin = convolution_score.begin();
      auto it_end   = convolution_score.begin() + window;
      while(it_end != convolution_score.end())
        {
          double score = std::accumulate(it_begin, it_end, 0);

          qDebug() << score;

          if(score > mp_parent->m_minimumConvolutionScore)
            {

              qDebug() << score;
              std::size_t start =
                std::distance(convolution_score.begin(), it_begin);

              if(frame_list.size() == 0)
                frame_list.push_back(frame());


              frame *p_current = &frame_list.back();

              if(frame_list.back().stop >= start)
                {
                }
              else
                {
                  frame my_frame;
                  my_frame.start = start;
                  frame_list.push_back(my_frame);
                }
              p_current = &frame_list.back();

              p_current->stop = start + window + mp_parent->m_includeAfterMatch;
              if(p_current->stop >
                 (std::size_t)protein_sp.get()->getSequence().size())
                {
                  p_current->stop = protein_sp.get()->getSequence().size() - 1;
                }

              if(p_current->start < mp_parent->m_includeBeforeMatch)
                {
                  p_current->start = 0;
                }
              else
                {
                  p_current->start =
                    p_current->start - mp_parent->m_includeBeforeMatch;
                }

              p_current->peptide_key = protein_sp.get()->getSequence().mid(
                p_current->start, p_current->stop - p_current->start);

              if(p_current->max_score < score)
                p_current->max_score = score;
            }

          it_begin++;
          it_end++;
        }
    }

  for(const frame &my_frame : frame_list)
    {

      if(my_frame.max_score > 0)
        {
          QString accession = protein_sp.get()->getAccession();
          auto it_peptider =
            m_onePeptideResultMap.insert({my_frame.peptide_key, {}});
          OnePeptideResult &one_peptide_result = it_peptider.first->second;

          auto it_protein_pos = one_peptide_result.map_protein2positions.insert(
            {accession, QCborArray()});
          it_protein_pos.first->second.append((qint64)my_frame.start);

          QCborArray convolution;
          for(const double &score_conv : convolution_score)
            {
              convolution.append(score_conv);
            }

          one_peptide_result.psm_eval.insert(QString("score"),
                                             (qint64)my_frame.max_score);
          // one_peptide_result.psm_eval.insert(QString("convolution"),
          //                                    convolution.toCborValue());

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


          m_proteinMap.insert(psm_protein);
        }
    }
}


void
OneScanProcessProteinMatcher::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()});
        }

      std::sort(
        sort_psm_list.begin(),
        sort_psm_list.end(),
        [](sortPsmResults &a, sortPsmResults &b) { return a.score > b.score; });


      QCborArray psm_list;

      auto it_end = sort_psm_list.begin() + max_psm;

      for(auto it = sort_psm_list.begin();
          it != sort_psm_list.end() && it != it_end;
          it++)
        {
          auto it_map = m_onePeptideResultMap.find(it->peptide_key);
          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 proteinmatcher;
          proteinmatcher.insert(QString("matcher"), it_map->second.psm_eval);

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

          psm_list.append(psm);
        }

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

      p_writer->writeCborMap(scan_map);
    }

  m_proteinMap.reduce(accessions);
}
