/**
 * \file core/qvalue/computeqvalues.cpp
 * \date 04/09/2019
 * \author Olivier Langella
 * \brief compute q-value for each peptide evidence (PSM) of a project
 */

/*******************************************************************************
 * Copyright (c) 2019 Olivier Langella <olivier.langella@u-psud.fr>.
 *
 * This file is part of i2MassChroQ.
 *
 *     i2MassChroQ 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.
 *
 *     i2MassChroQ 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 i2MassChroQ.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#include <set>
#include "computeqvalues.h"
#include "../../utils/utils.h"
#include <pappsomspp/core/exception/exceptionnotimplemented.h>
#include <pappsomspp/core/psm/deepprot/deepprotenum.h>
#include <QDebug>

ComputeQvalues::ComputeQvalues(Project *project_p)
{
  std::vector<IdentificationDataSourceSp> identification_source_list =
    project_p->getIdentificationDataSourceStore()
      .getIdentificationDataSourceList();


  std::vector<PeptideEvidence *> tandem_peptide_evidence_list;
  std::vector<PeptideEvidence *> sage_peptide_evidence_list;
  std::vector<PeptideEvidence *> mascot_peptide_evidence_list;
  std::vector<PeptideEvidence *> deepprot_peptide_evidence_negative_list;
  std::vector<PeptideEvidence *> deepprot_peptide_evidence_list;
  std::vector<PeptideEvidence *> deepprot_peptide_evidence_positive_list;
  for(auto &identification_source_sp : identification_source_list)
    {
      if(identification_source_sp.get()->getIdentificationEngine() ==
         IdentificationEngine::XTandem)
        {

          tandem_peptide_evidence_list.reserve(
            tandem_peptide_evidence_list.size() +
            identification_source_sp.get()->getPeptideEvidenceStore().size());
          for(auto &pe_sp : identification_source_sp.get()
                              ->getPeptideEvidenceStore()
                              .getPeptideEvidenceList())
            {
              tandem_peptide_evidence_list.push_back(pe_sp.get());
            }
        }
      else if(identification_source_sp.get()->getIdentificationEngine() ==
              IdentificationEngine::sage)
        {
          sage_peptide_evidence_list.reserve(
            sage_peptide_evidence_list.size() +
            identification_source_sp.get()->getPeptideEvidenceStore().size());
          for(auto &pe_sp : identification_source_sp.get()
                              ->getPeptideEvidenceStore()
                              .getPeptideEvidenceList())
            {
              sage_peptide_evidence_list.push_back(pe_sp.get());
            }
        }
      else if(identification_source_sp.get()->getIdentificationEngine() ==
              IdentificationEngine::mascot)
        {
          mascot_peptide_evidence_list.reserve(
            mascot_peptide_evidence_list.size() +
            identification_source_sp.get()->getPeptideEvidenceStore().size());
          for(auto &pe_sp : identification_source_sp.get()
                              ->getPeptideEvidenceStore()
                              .getPeptideEvidenceList())
            {
              mascot_peptide_evidence_list.push_back(pe_sp.get());
            }
        }
      else if(identification_source_sp.get()->getIdentificationEngine() ==
              IdentificationEngine::SpecOMS)
        {
          for(auto &pe_sp : identification_source_sp.get()
                              ->getPeptideEvidenceStore()
                              .getPeptideEvidenceList())
            {
              if(pe_sp.get()
                   ->getParam(
                     PeptideEvidenceParam::deepprot_peptide_candidate_status)
                   .toUInt() ==
                 (std::uint8_t)
                   pappso::DeepProtPeptideCandidateStatus::ZeroMassDelta)
                {
                  deepprot_peptide_evidence_list.push_back(pe_sp.get());
                }
              else
                {
                  double mass_delta =
                    pe_sp->getParam(PeptideEvidenceParam::deepprot_mass_delta)
                      .toDouble();
                  if(mass_delta < 0)
                    deepprot_peptide_evidence_negative_list.push_back(
                      pe_sp.get());
                  else
                    deepprot_peptide_evidence_positive_list.push_back(
                      pe_sp.get());
                }
            }
        }
      else
        {

          throw pappso::ExceptionNotImplemented(
            QObject::tr("q-value computation for %1 identification engine is "
                        "not yet implemented")
              .arg(
                identification_source_sp.get()->getIdentificationEngineName()));
        }
    }

  for(auto &identification_group : project_p->getIdentificationGroupList())
    {
      for(auto &p_protein_match : identification_group->getProteinMatchList())
        {
          if(p_protein_match->getProteinXtpSp().get()->isDecoy())
            {
              qDebug()
                << "p_protein_match "
                << p_protein_match->getProteinXtpSp().get()->getAccession()
                << " is decoy";
              p_protein_match->collectPeptideEvidences(
                m_falsePeptideEvidenceList, ValidationState::notValid);
            }
        }
    }

  qDebug() << m_falsePeptideEvidenceList.size();
  // short list  of false peptide evidence pointers
  std::sort(m_falsePeptideEvidenceList.begin(),
            m_falsePeptideEvidenceList.end());
  auto last = std::unique(m_falsePeptideEvidenceList.begin(),
                          m_falsePeptideEvidenceList.end());
  m_falsePeptideEvidenceList.erase(last, m_falsePeptideEvidenceList.end());
  // end short list
  qDebug() << m_falsePeptideEvidenceList.size();

  computeTandemPeptideEvidenceQvalues(tandem_peptide_evidence_list);

  computeSagePeptideEvidenceQvalues(sage_peptide_evidence_list);

  computeMascotPeptideEvidenceQvalues(mascot_peptide_evidence_list);

  computeDeepProtPeptideEvidenceQvalues(deepprot_peptide_evidence_list);
  computeDeepProtPeptideEvidenceQvalues(
    deepprot_peptide_evidence_negative_list);
  computeDeepProtPeptideEvidenceQvalues(
    deepprot_peptide_evidence_positive_list);
}

ComputeQvalues::~ComputeQvalues()
{
}

void
ComputeQvalues::computeMascotPeptideEvidenceQvalues(
  std::vector<PeptideEvidence *> &mascot_peptide_evidence_list) const
{

  // mascot_expectation_value
  std::sort(mascot_peptide_evidence_list.begin(),
            mascot_peptide_evidence_list.end(),
            [](const PeptideEvidence *pepa, const PeptideEvidence *pepb) {
              QVariant evalue_a =
                pepa->getParam(PeptideEvidenceParam::mascot_expectation_value);
              QVariant evalue_b =
                pepb->getParam(PeptideEvidenceParam::mascot_expectation_value);
              if(evalue_a.isNull() || evalue_b.isNull())
                {
                  throw pappso::ExceptionNotImplemented(QObject::tr(
                    "Mascot Evalue is missing, unable to compute q-value"));
                }
              return (evalue_a.toDouble() < evalue_b.toDouble());
            });
  std::size_t count_decoy  = 0;
  std::size_t count_target = 0;
  for(PeptideEvidence *pep : mascot_peptide_evidence_list)
    {
      if(isPeptideEvidenceDecoy(pep))
        {
          count_decoy++;
        }
      else
        {
          count_target++;
        }
      double qvalue = Utils::computeFdr(count_decoy, count_target);
      pep->setParam(PeptideEvidenceParam::pappso_qvalue, QVariant(qvalue));
    }

  cleanPeptideEvidenceList(mascot_peptide_evidence_list);
}


void
ComputeQvalues::computeDeepProtPeptideEvidenceQvalues(
  std::vector<PeptideEvidence *> &deepprot_peptide_evidence_list) const
{
  // qvalues for deepprot
  std::sort(deepprot_peptide_evidence_list.begin(),
            deepprot_peptide_evidence_list.end(),
            [](const PeptideEvidence *pepa, const PeptideEvidence *pepb) {
              QVariant evalue_a =
                pepa->getParam(PeptideEvidenceParam::deepprot_fitted_count);
              QVariant evalue_b =
                pepb->getParam(PeptideEvidenceParam::deepprot_fitted_count);
              if(evalue_a.isNull() || evalue_b.isNull())
                {
                  throw pappso::ExceptionNotImplemented(QObject::tr(
                    "DeepProt score is missing, unable to compute q-value"));
                }
              return (evalue_a.toUInt() > evalue_b.toUInt());
            });
  std::size_t count_decoy  = 0;
  std::size_t count_target = 0;
  for(PeptideEvidence *pep : deepprot_peptide_evidence_list)
    {
      if(isPeptideEvidenceDecoy(pep))
        {
          count_decoy++;
        }
      else
        {
          count_target++;
        }
      qDebug() << "target=" << count_target << " decoy=" << count_decoy;
      double qvalue = Utils::computeFdr(count_decoy, count_target);
      pep->setParam(PeptideEvidenceParam::pappso_qvalue, QVariant(qvalue));
    }

  cleanPeptideEvidenceList(deepprot_peptide_evidence_list);
}


void
ComputeQvalues::computeTandemPeptideEvidenceQvalues(
  std::vector<PeptideEvidence *> &tandem_peptide_evidence_list)
{
  std::sort(tandem_peptide_evidence_list.begin(),
            tandem_peptide_evidence_list.end(),
            [](const PeptideEvidence *pepa, const PeptideEvidence *pepb) {
              return (pepa->getEvalue() < pepb->getEvalue());
            });
  std::size_t count_decoy  = 0;
  std::size_t count_target = 0;
  for(PeptideEvidence *pep : tandem_peptide_evidence_list)
    {
      if(isPeptideEvidenceDecoy(pep))
        {
          count_decoy++;
        }
      else
        {
          count_target++;
        }
      double qvalue = Utils::computeFdr(count_decoy, count_target);
      pep->setParam(PeptideEvidenceParam::pappso_qvalue, QVariant(qvalue));
    }

  cleanPeptideEvidenceList(tandem_peptide_evidence_list);
  m_tandemPeptideEvidenceList = tandem_peptide_evidence_list;
}

void
ComputeQvalues::computeSagePeptideEvidenceQvalues(
  std::vector<PeptideEvidence *> &tandem_peptide_evidence_list)
{
  qDebug();
  std::sort(
    tandem_peptide_evidence_list.begin(),
    tandem_peptide_evidence_list.end(),
    [](const PeptideEvidence *pepa, const PeptideEvidence *pepb) {
      QVariant evalue_a =
        pepa->getParam(PeptideEvidenceParam::sage_sage_discriminant_score);
      QVariant evalue_b =
        pepb->getParam(PeptideEvidenceParam::sage_sage_discriminant_score);
      if(evalue_a.isNull() || evalue_b.isNull())
        {
          throw pappso::ExceptionNotImplemented(QObject::tr(
            "Sage discriminant score is missing, unable to compute q-value"));
        }
      return (evalue_a.toDouble() > evalue_b.toDouble());
    });
  std::size_t count_decoy  = 0;
  std::size_t count_target = 0;
  for(PeptideEvidence *pep : tandem_peptide_evidence_list)
    {
      if(isPeptideEvidenceDecoy(pep))
        {
          count_decoy++;
        }
      else
        {
          count_target++;
        }
      double qvalue = Utils::computeFdr(count_decoy, count_target);
      pep->setParam(PeptideEvidenceParam::pappso_qvalue, QVariant(qvalue));
    }

  cleanPeptideEvidenceList(tandem_peptide_evidence_list);
}

void
ComputeQvalues::cleanPeptideEvidenceList(
  std::vector<PeptideEvidence *> &peptide_evidence_list) const
{
  qDebug();
  // check reverse list to clean q-values
  auto rit = peptide_evidence_list.rbegin();

  double qvalue_max = 99999999;
  while(rit != peptide_evidence_list.rend())
    {
      if((*rit)->getParam(PeptideEvidenceParam::pappso_qvalue).toDouble() >
         qvalue_max)
        {
          (*rit)->setParam(PeptideEvidenceParam::pappso_qvalue,
                           QVariant(qvalue_max));
        }
      qvalue_max =
        (*rit)->getParam(PeptideEvidenceParam::pappso_qvalue).toDouble();

      qDebug() << "qvalue_max=" << qvalue_max;
      rit++;
    }
  qDebug();
}


void
ComputeQvalues::writeDistributionsByEngines(CalcWriterInterface *p_writer) const
{

  std::size_t count_decoy  = 0;
  std::size_t count_target = 0;
  for(PeptideEvidence *pep : m_tandemPeptideEvidenceList)
    {
      if(isPeptideEvidenceDecoy(pep))
        {
          count_decoy++;
        }
      else
        {
          count_target++;
        }
      // double qvalue = Utils::computeFdr(count_decoy, count_target);
      // pep->setParam(PeptideEvidenceParam::pappso_qvalue, QVariant(qvalue));
      p_writer->writeCell((int)count_target);

      p_writer->writeCell((int)count_decoy);

      p_writer->writeCell(pep->getEvalue());

      p_writer->writeCell(
        pep->getParam(PeptideEvidenceParam::pappso_qvalue).toDouble());
      p_writer->writeLine();
    }
}

bool
ComputeQvalues::isPeptideEvidenceDecoy(const PeptideEvidence *pep) const
{
  // intended that m_falsePeptideEvidenceList is sorted
  auto it    = m_falsePeptideEvidenceList.begin();
  auto itend = m_falsePeptideEvidenceList.end();


  while(it != itend)
    {
      if(pep == *it)
        return true;
      if(pep < (*it))
        return false;
      it++;
    }

  return false;
}
