/**
 * \file pappsomspp/peptide/peptide.cpp
 * \date 7/3/2015
 * \author Olivier Langella
 * \brief peptide model
 */

/*******************************************************************************
 * Copyright (c) 2015 Olivier Langella <Olivier.Langella@moulon.inra.fr>.
 *
 * This file is part of the PAPPSOms++ library.
 *
 *     PAPPSOms++ 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.
 *
 *     PAPPSOms++ 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 PAPPSOms++.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Olivier Langella <Olivier.Langella@moulon.inra.fr> - initial API and
 *implementation
 ******************************************************************************/

#include <QDebug>
#include <algorithm>
#include <qobject.h>
#include "peptide.h"
#include "pappsomspp/core/exception/exceptionnotimplemented.h"
#include "pappsomspp/core/exception/exceptionoutofrange.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"
#include "pappsomspp/core/types.h"
#include "pappsomspp/core/utils.h"


namespace pappso
{


bool
operator<(const pappso::Peptide &l, const pappso::Peptide &r)
{
  return (l.m_aaVec < r.m_aaVec);
}

bool
operator==(const pappso::Peptide &l, const pappso::Peptide &r)
{
  if(l.getCleavageNterModification() != r.getCleavageNterModification())
    return false;
  if(l.getCleavageCterModification() != r.getCleavageCterModification())
    return false;
  if(l.getNterModification() && r.getNterModification() &&
     (l.getNterModification() != r.getNterModification()))
    return false;
  if(l.getCterModification() && r.getCterModification() &&
     (l.getCterModification() != r.getCterModification()))
    return false;

  return (l.m_aaVec == r.m_aaVec);
}

bool
peptideIonTypeIsComplement(Enums::PeptideIon ion_type_ref, Enums::PeptideIon ion_type)
{
  if(peptideIonIsNter(ion_type))
    std::swap(ion_type_ref, ion_type);
  if(peptideIonIsNter(ion_type))
    return false;
  if((ion_type_ref == Enums::PeptideIon::b) && (ion_type == Enums::PeptideIon::y))
    return true;
  if((ion_type_ref == Enums::PeptideIon::ao) && (ion_type == Enums::PeptideIon::yo))
    return true;
  if((ion_type_ref == Enums::PeptideIon::bstar) && (ion_type == Enums::PeptideIon::ystar))
    return true;

  return false;
}

bool
peptideIonIsNter(Enums::PeptideIon ion_type)
{
  if((std::int8_t)ion_type < (std::int8_t)8)
    {
      return true;
    }
  return false;
}

PeptideDirection
getPeptideIonDirection(Enums::PeptideIon ion_type)
{
  if(peptideIonIsNter(ion_type))
    {
      return PeptideDirection::Nter;
    }
  return PeptideDirection::Cter;
}

Peptide::Peptide(const QString &pepstr)
{
  qDebug();
  m_cleavageNterMod = AaModification::getInstance("internal:Nter_hydrolytic_cleavage_H");
  m_cleavageCterMod = AaModification::getInstance("internal:Cter_hydrolytic_cleavage_HO");

  m_NterMod = nullptr;
  m_CterMod = nullptr;
  QString::const_iterator it(pepstr.begin());

  while(it != pepstr.end())
    {
      qDebug() << it->toLatin1();
      m_aaVec.push_back(Aa(it->toLatin1()));
      it++;
    }
  qDebug();
  getMass();
}

Peptide::~Peptide()
{
}

Peptide::Peptide(const Peptide &peptide)
  : m_aaVec(peptide.m_aaVec),
    m_fullC13(peptide.m_fullC13),
    m_fullN15(peptide.m_fullN15),
    m_fullH2(peptide.m_fullH2),
    m_proxyMass(peptide.m_proxyMass),
    m_cleavageNterMod(peptide.m_cleavageNterMod),
    m_cleavageCterMod(peptide.m_cleavageCterMod),
    m_NterMod(peptide.m_NterMod),
    m_CterMod(peptide.m_CterMod)
{
  qDebug();
}


Peptide::Peptide(Peptide &&toCopy) // move constructor
  : m_aaVec(std::move(toCopy.m_aaVec)),
    m_fullC13(toCopy.m_fullC13),
    m_fullN15(toCopy.m_fullN15),
    m_fullH2(toCopy.m_fullH2),
    m_proxyMass(toCopy.m_proxyMass),
    m_cleavageNterMod(toCopy.m_cleavageNterMod),
    m_cleavageCterMod(toCopy.m_cleavageCterMod),
    m_NterMod(toCopy.m_NterMod),
    m_CterMod(toCopy.m_CterMod)
{
}


PeptideSp
Peptide::makePeptideSp() const
{
  return std::make_shared<const Peptide>(*this);
}

NoConstPeptideSp
Peptide::makeNoConstPeptideSp() const
{
  return std::make_shared<Peptide>(*this);
}


std::vector<Aa>::iterator
Peptide::begin()
{
  return m_aaVec.begin();
}

std::vector<Aa>::iterator
Peptide::end()
{
  return m_aaVec.end();
}

std::vector<Aa>::const_iterator
Peptide::begin() const
{
  return m_aaVec.begin();
}

std::vector<Aa>::const_iterator
Peptide::end() const
{
  return m_aaVec.end();
}

std::vector<Aa>::const_reverse_iterator
Peptide::rbegin() const
{
  return m_aaVec.rbegin();
}

std::vector<Aa>::const_reverse_iterator
Peptide::rend() const
{
  return m_aaVec.rend();
}


pappso_double
Peptide::getMass() const
{
  return m_proxyMass;
}


unsigned int
Peptide::size() const
{
  return m_aaVec.size();
}
void
Peptide::addAaModification(AaModificationP aaModification, unsigned int position)
{
  if(position >= size())
    {
      throw ExceptionOutOfRange(QObject::tr("position (%1) > size (%2)").arg(position).arg(size()));
    }
  m_proxyMass = -1;
  qDebug() << "Peptide::addAaModification begin " << position;
  std::vector<Aa>::iterator it = m_aaVec.begin() + position;
  it->addAaModification(aaModification);
  getMass();
  qDebug() << "Peptide::addAaModification end";
}

void
Peptide::addAaModificationOnAllAminoAcid(AaModificationP aaModification,
                                         Enums::AminoAcidChar amino_acid)
{

  for(auto &aa : *this)
    {
      if(aa.getAminoAcidChar() == amino_acid)
        {
          aa.addAaModification(aaModification);
        }
    }


  m_proxyMass = -1;
  getMass();
}

const QString
Peptide::getSequence() const
{
  QString seq = "";
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      seq += it->getLetter();
      it++;
    }
  return seq;
}
const QString
Peptide::toAbsoluteString() const
{
  QString seq = "";
  std::vector<Aa>::const_iterator it(m_aaVec.begin());

  QStringList modification_str_list;
  modification_str_list << m_cleavageNterMod->getAccession();
  if(m_NterMod != nullptr)
    modification_str_list << m_NterMod->getAccession();
  while(it != m_aaVec.end())
    {
      seq += it->getLetter();
      if(it == m_aaVec.end() - 1)
        {
          modification_str_list << m_cleavageCterMod->getAccession();
          if(m_CterMod != nullptr)
            modification_str_list << m_CterMod->getAccession();
        }
      for(auto &pmod : it->getModificationList())
        {
          modification_str_list << pmod->getAccession();
        }
      if(modification_str_list.size() > 0)
        seq += QString("(%1)").arg(modification_str_list.join(","));
      modification_str_list.clear();
      it++;
    }
  return seq;
}

const QString
Peptide::getLiAbsoluteString() const
{
  QString seq = "";
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      seq += it->toAbsoluteString();
      it++;
    }
  return seq.replace("L", "I");
}


const QString
Peptide::toString() const
{
  QString seq = "";
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      seq += it->toString();
      it++;
    }
  return seq;
}

std::vector<double>
Peptide::getRawMassArray() const
{
  std::vector<double> raw_mass_arr;
  for(const Aa &aa : m_aaVec)
    {
      raw_mass_arr.push_back(aa.getMass());
    }
  return raw_mass_arr;
}

std::vector<double>
Peptide::getMassIonSerie(unsigned int charge, pappso::Enums::PeptideIon ion_type) const
{
  std::vector<double> mz_arr;
  double mass_accumulator = 0;
  double mass_shift       = 0;
  if(ion_type == pappso::Enums::PeptideIon::b)
    {
      for(const Aa &aa : m_aaVec)
        {
          mass_accumulator += aa.getMass();
          mz_arr.push_back(mass_accumulator);
        }
    }
  else if(ion_type == pappso::Enums::PeptideIon::y)
    {
      std::vector<Aa>::const_reverse_iterator it(m_aaVec.rbegin());
      while(it != m_aaVec.rend())
        {
          mass_accumulator += it->getMass();
          mz_arr.push_back(mass_accumulator);
          it++;
        }
      mass_shift = MASSH2O;
    }
  else
    {
      throw pappso::ExceptionNotImplemented(
        QObject::tr("Peptide::getMassIonSerie not implemented for ion type %1")
          .arg(Utils::toString(ion_type)));
    }
  for(double &mass : mz_arr)
    {
      mass = (mass + mass_shift + (MHPLUS * (double)charge)) / (double)charge;
    }

  return mz_arr;
}

pappso_double
Peptide::getMass()
{
  qDebug() << "begin";
  if(m_proxyMass < 0)
    {
      if(m_fullC13 || m_fullH2 || m_fullN15)
        {
          ChemicalFormula formula = getChemicalFormula();
          m_proxyMass             = formula.getMass();
        }
      else
        {
          m_proxyMass = m_cleavageNterMod->getMass() + m_cleavageCterMod->getMass();
          if(m_NterMod != nullptr)
            m_proxyMass += m_NterMod->getMass();
          if(m_CterMod != nullptr)
            m_proxyMass += m_CterMod->getMass();
          for(auto aa : m_aaVec)
            {
              m_proxyMass += aa.getMass();
            }
        }
    }
  qDebug() << "end " << m_proxyMass;
  return m_proxyMass;
}

int
Peptide::getNumberOfAtom(Enums::AtomIsotopeSurvey atom) const
{
  int number = m_cleavageNterMod->getNumberOfAtom(atom);
  number += m_cleavageCterMod->getNumberOfAtom(atom);

  if(m_NterMod != nullptr)
    number += m_NterMod->getNumberOfAtom(atom);
  if(m_CterMod != nullptr)
    number += m_CterMod->getNumberOfAtom(atom);
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      number += it->getNumberOfAtom(atom);
      it++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return number;
}

int
Peptide::getNumberOfIsotope(Enums::Isotope isotope) const
{
  int number = m_cleavageNterMod->getNumberOfIsotope(isotope);
  number += m_cleavageCterMod->getNumberOfIsotope(isotope);
  if(m_NterMod != nullptr)
    number += m_NterMod->getNumberOfIsotope(isotope);
  if(m_CterMod != nullptr)
    number += m_CterMod->getNumberOfIsotope(isotope);
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      number += it->getNumberOfIsotope(isotope);
      it++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return number;
}


unsigned int
Peptide::getNumberOfModification(AaModificationP mod) const
{
  unsigned int number = 0;
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      number += it->getNumberOfModification(mod);
      it++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return number;
}

unsigned int
Peptide::countModificationOnAa(AaModificationP mod, const std::vector<char> &aa_list) const
{
  unsigned int number = 0;
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      if(std::find(aa_list.begin(), aa_list.end(), it->getLetter()) != aa_list.end())
        {
          number += it->getNumberOfModification(mod);
        }
      it++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return number;
}

void
Peptide::replaceAaModification(AaModificationP oldmod, AaModificationP newmod)
{
  if(oldmod == newmod)
    return;
  std::vector<Aa>::iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      it->replaceAaModification(oldmod, newmod);
      it++;
    }
  m_proxyMass = -1;
  getMass();
}

void
Peptide::replaceAaModificationOnAminoAcid(Enums::AminoAcidChar aa,
                                          AaModificationP oldmod,
                                          AaModificationP newmod)
{
  if(oldmod == newmod)
    return;
  std::vector<Aa>::iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      if(it->getAminoAcidChar() == aa)
        {
          it->replaceAaModification(oldmod, newmod);
        }
      it++;
    }
  m_proxyMass = -1;
  getMass();
}
void
Peptide::removeAaModification(AaModificationP mod)
{
  std::vector<Aa>::iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      it->removeAaModification(mod);
      qDebug() << it->toString() << " " << toAbsoluteString();
      it++;
    }
  m_proxyMass = -1;
  getMass();
  // qDebug() << "Aa::getMass() end " << mass;
}
std::vector<unsigned int>
Peptide::getModificationPositionList(AaModificationP mod) const
{
  std::vector<unsigned int> position_list;
  unsigned int position = 0;
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      unsigned int number = 0;
      number += it->getNumberOfModification(mod);
      for(unsigned int j = 0; j < number; j++)
        {
          position_list.push_back(position);
        }
      it++;
      position++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return position_list;
}

std::vector<unsigned int>
Peptide::getModificationPositionList(AaModificationP mod, const std::vector<char> &aa_list) const
{
  std::vector<unsigned int> position_list;
  unsigned int position = 0;
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      if(std::find(aa_list.begin(), aa_list.end(), it->getLetter()) != aa_list.end())
        {
          unsigned int number = 0;
          number += it->getNumberOfModification(mod);
          for(unsigned int j = 0; j < number; j++)
            {
              position_list.push_back(position);
            }
        }
      it++;
      position++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return position_list;
}

std::vector<unsigned int>
Peptide::getAaPositionList(char aa) const
{
  std::vector<unsigned int> position_list;
  unsigned int number = 0;
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      if(it->getLetter() == aa)
        position_list.push_back(number);
      number++;
      it++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return position_list;
}

std::vector<unsigned int>
Peptide::getAaPositionList(std::list<char> list_aa) const
{
  std::vector<unsigned int> position_list;
  unsigned int number = 0;
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {

      bool found = (std::find(list_aa.begin(), list_aa.end(), it->getLetter()) != list_aa.end());
      if(found)
        {
          position_list.push_back(number);
        }
      number++;
      it++;
    }
  // qDebug() << "Aa::getMass() end " << mass;
  return position_list;
}

void
Peptide::setCleavageNterModification(AaModificationP mod)
{
  if(mod->getAccession().startsWith("internal:Nter_"))
    {
      m_proxyMass       = -1;
      m_cleavageNterMod = mod;
      getMass();
    }
  else
    {
      throw ExceptionNotPossible(
        QObject::tr("modification is not a cleavage Nter modification : %1")
          .arg(mod->getAccession()));
    }
}
void
Peptide::setCleavageCterModification(AaModificationP mod)
{
  if(mod->getAccession().startsWith("internal:Cter_"))
    {
      m_proxyMass       = -1;
      m_cleavageNterMod = mod;
      getMass();
    }
  else
    {
      throw ExceptionNotPossible(
        QObject::tr("modification is not a cleavage Cter modification : %1")
          .arg(mod->getAccession()));
    }
}

AaModificationP
Peptide::getCleavageNterModification() const
{
  return m_cleavageNterMod;
}
AaModificationP
Peptide::getCleavageCterModification() const
{
  return m_cleavageCterMod;
}

void
Peptide::setNterModification(AaModificationP mod)
{
  if(mod != nullptr)
    {
      m_proxyMass = -1;
      m_NterMod   = mod;
      getMass();
    }
  else
    {
      throw ExceptionNotPossible(
        QObject::tr("modification is not a peptide Nter modification : mod == nulptr"));
    }
}
void
Peptide::setCterModification(AaModificationP mod)
{
  if(mod != nullptr)
    {
      m_proxyMass = -1;
      m_CterMod   = mod;
      getMass();
    }
  else
    {
      throw ExceptionNotPossible(
        QObject::tr("modification is not a peptide Cter modification : mod == nulptr"));
    }
}

AaModificationP
Peptide::getCterModification() const
{
  return m_CterMod;
}

AaModificationP
Peptide::getNterModification() const
{
  return m_NterMod;
}

void
Peptide::rotate()
{
  std::rotate(m_aaVec.begin(), m_aaVec.begin() + 1, m_aaVec.end());
}

void
Peptide::reverse()
{
  std::reverse(m_aaVec.begin(), m_aaVec.end());
}


bool
Peptide::isPalindrome() const
{
  std::size_t size = m_aaVec.size();
  std::size_t k    = (size - 1);
  for(std::size_t i = 0; i < (size / 2); i++, k--)
    {
      if(m_aaVec[i].getLetter() != m_aaVec[k].getLetter())
        {
          return false;
        }
    }
  return true;
}

Aa &
Peptide::getAa(unsigned int position)
{
  if(position >= m_aaVec.size())
    {
      throw ExceptionOutOfRange(QObject::tr("no AA at position %1").arg(position));
    }
  return m_aaVec.at(position);
}
const Aa &
Peptide::getConstAa(unsigned int position) const
{
  if(position >= m_aaVec.size())
    {
      throw ExceptionOutOfRange(QObject::tr("no AA at position %1").arg(position));
    }
  return m_aaVec.at(position);
}


void
Peptide::replaceLeucineIsoleucine()
{

  std::vector<Aa>::iterator it(m_aaVec.begin());
  std::vector<Aa>::iterator itend(m_aaVec.end());
  for(; it != itend; it++)
    {
      it->replaceLeucineIsoleucine();
    }
}


void
Peptide::removeNterAminoAcid()
{
  std::vector<Aa>::iterator it(m_aaVec.begin());
  if(it != m_aaVec.end())
    {
      m_aaVec.erase(it);
      m_proxyMass = -1;
      getMass();
    }
  else
    {
      throw ExceptionOutOfRange(QObject::tr("peptide is empty"));
    }
}


void
Peptide::removeCterAminoAcid()
{
  std::vector<Aa>::iterator it(m_aaVec.end());
  it--;
  if(it != m_aaVec.end())
    {
      m_aaVec.erase(it);
      m_proxyMass = -1;
      getMass();
    }
  else
    {
      throw ExceptionOutOfRange(QObject::tr("peptide is empty"));
    }
}

QString
Peptide::toProForma() const
{

  QString seq = "";
  if(m_fullC13)
    {
      seq += "<13C>";
    }
  if(m_fullN15)
    {
      seq += "<15N>";
    }
  if(m_fullH2)
    {
      seq += "<D>";
    }

  if(m_NterMod != nullptr)
    {
      QString nter_accession = m_NterMod->getAccession();
      seq                    = QString("[%1]-").arg(nter_accession);
    }
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      seq += it->toProForma();
      it++;
    }

  if(m_CterMod != nullptr)
    {
      QString cter_accession = m_CterMod->getAccession();
      seq += QString("-[%1]").arg(cter_accession);
    }
  return seq;
}
} // namespace pappso

void
pappso::Peptide::setGlobalModification(Enums::Isotope isotope_kind)
{
  if(isotope_kind == Enums::Isotope::C13)
    m_fullC13 = true;
  else if(isotope_kind == Enums::Isotope::N15)
    m_fullN15 = true;
  else if(isotope_kind == Enums::Isotope::H2)
    m_fullH2 = true;
}


const pappso::ChemicalFormula
pappso::Peptide::getChemicalFormula() const
{
  return getChemicalFormulaCharge(0);
}


const pappso::ChemicalFormula
pappso::Peptide::getChemicalFormulaCharge(unsigned int charge) const
{
  ChemicalFormula formula = PeptideInterface::getChemicalFormulaCharge(charge);

  qDebug() << formula.toString();
  if(m_fullC13)
    {
      formula.setFullIsotope(Enums::Isotope::C13);
      qDebug() << formula.toString();
    }
  if(m_fullN15)
    {
      formula.setFullIsotope(Enums::Isotope::N15);
      qDebug() << formula.toString();
    }
  if(m_fullH2)
    {
      formula.setFullIsotope(Enums::Isotope::H2);
      qDebug() << formula.toString();
    }
  return formula;
}
