/**
 * \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 "peptide.h"
#include "../pappsoexception.h"
#include "../exception/exceptionoutofrange.h"
#include "../exception/exceptionnotpossible.h"
#include "peptidenaturalisotopelist.h"

namespace pappso
{

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

  return false;
}

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

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

Peptide::Peptide(const QString &pepstr)
{

  QString::const_iterator it(pepstr.begin());
  if(it != pepstr.end())
    {
      // first amino acid is the Nter one
      // by default, it is obtained by hydrolytic cleavage in normal water
      // and it is loaded with one Hydrogen
      Aa nter_aa(it->toLatin1());
      nter_aa.addAaModification(
        AaModification::getInstance("internal:Nter_hydrolytic_cleavage_H"));
      m_aaVec.push_back(nter_aa);
      it++;

      while(it != pepstr.end())
        {
          qDebug() << it->toLatin1();
          m_aaVec.push_back(Aa(it->toLatin1()));
          it++;
        }
      // by default, Nter aa is obtained by hydrolytic cleavage in normal water
      // and it is loaded with Hydrogen + Oxygen
      m_aaVec.back().addAaModification(
        AaModification::getInstance("internal:Cter_hydrolytic_cleavage_HO"));
      getMass();
      qDebug() << "blabla " << m_aaVec.back().toString();
    }
}

Peptide::~Peptide()
{
}

Peptide::Peptide(const Peptide &peptide)
  : m_aaVec(peptide.m_aaVec), m_proxyMass(peptide.m_proxyMass)
{
}


Peptide::Peptide(Peptide &&toCopy) // move constructor
  : m_aaVec(std::move(toCopy.m_aaVec)), m_proxyMass(toCopy.m_proxyMass)
{
}


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,
                                         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());
  while(it != m_aaVec.end())
    {
      seq += it->toAbsoluteString();
      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;
}

pappso_double
Peptide::getMass()
{
  qDebug() << "Aa::getMass() begin";
  if(m_proxyMass < 0)
    {
      m_proxyMass = 0;
      for(auto aa : m_aaVec)
        {
          m_proxyMass += aa.getMass();
        }
    }
  qDebug() << "Aa::getMass() end " << m_proxyMass;
  return m_proxyMass;
}

int
Peptide::getNumberOfAtom(AtomIsotopeSurvey atom) const
{
  int number = 0;
  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(Isotope isotope) const
{
  int number = 0;
  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::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;
}

AaModificationP
Peptide::getInternalNterModification() const
{
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  if(it != m_aaVec.end())
    {
      return it->getInternalNterModification();
    }

  return nullptr;
}
AaModificationP
Peptide::getInternalCterModification() const
{
  std::vector<Aa>::const_iterator it(m_aaVec.end());
  it--;
  if(it != m_aaVec.end())
    {
      return it->getInternalCterModification();
    }
  return nullptr;
}
void
Peptide::removeInternalNterModification()
{
  std::vector<Aa>::iterator it(m_aaVec.begin());
  if(it != m_aaVec.end())
    {
      m_proxyMass -= it->getMass();
      it->removeInternalNterModification();
      m_proxyMass += it->getMass();
    }
}
void
Peptide::removeInternalCterModification()
{
  std::vector<Aa>::iterator it(m_aaVec.end());
  it--;
  if(it != m_aaVec.end())
    {
      m_proxyMass -= it->getMass();
      it->removeInternalCterModification();
      m_proxyMass += it->getMass();
    }
}


void
Peptide::setInternalNterModification(AaModificationP mod)
{
  if(mod->getAccession().startsWith("internal:Nter_"))
    {
      removeInternalNterModification();
      std::vector<Aa>::iterator it(m_aaVec.begin());
      if(it != m_aaVec.end())
        {
          it->addAaModification(mod);
        }
      else
        {
          throw ExceptionOutOfRange(QObject::tr("peptide is empty"));
        }
    }
  else
    {
      throw ExceptionNotPossible(
        QObject::tr("modification is not an internal Nter modification : %1")
          .arg(mod->getAccession()));
    }
}
void
Peptide::setInternalCterModification(AaModificationP mod)
{
  if(mod->getAccession().startsWith("internal:Cter_"))
    {
      removeInternalCterModification();
      std::vector<Aa>::iterator it(m_aaVec.end());
      it--;
      if(it != m_aaVec.end())
        {
          it->addAaModification(mod);
        }
      else
        {
          throw ExceptionOutOfRange(QObject::tr("peptide is empty"));
        }
    }
  else
    {
      throw ExceptionNotPossible(
        QObject::tr("modification is not an internal Cter modification : %1")
          .arg(mod->getAccession()));
    }
}

void
Peptide::rotate()
{
  AaModificationP modNter = getInternalNterModification();
  AaModificationP modCter = getInternalCterModification();
  m_aaVec.begin()->removeInternalNterModification();
  (m_aaVec.end() - 1)->removeInternalCterModification();
  std::rotate(m_aaVec.begin(), m_aaVec.begin() + 1, m_aaVec.end());
  m_aaVec.begin()->addAaModification(modNter);
  (m_aaVec.end() - 1)->addAaModification(modCter);
}

void
Peptide::reverse()
{
  AaModificationP modNter = getInternalNterModification();
  AaModificationP modCter = getInternalCterModification();
  m_aaVec.begin()->removeInternalNterModification();
  (m_aaVec.end() - 1)->removeInternalCterModification();
  std::reverse(m_aaVec.begin(), m_aaVec.end());
  m_aaVec.begin()->addAaModification(modNter);
  (m_aaVec.end() - 1)->addAaModification(modCter);
}


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())
    {
      AaModificationP nter_modification = getInternalNterModification();
      m_aaVec.erase(it);
      if(nter_modification != nullptr)
        {
          m_aaVec.begin()->addAaModification(nter_modification);
        }

      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())
    {
      AaModificationP cter_modification = getInternalCterModification();
      m_aaVec.erase(it);
      if(cter_modification != nullptr)
        {
          it = m_aaVec.end();
          it--;
          it->addAaModification(cter_modification);
        }
      m_proxyMass = -1;
      getMass();
    }
  else
    {
      throw ExceptionOutOfRange(QObject::tr("peptide is empty"));
    }
}

QString
Peptide::toProForma() const
{

  QString seq = "";
  std::vector<Aa>::const_iterator it(m_aaVec.begin());
  while(it != m_aaVec.end())
    {
      seq += it->toProForma();
      it++;
    }
  return seq;
}
} // namespace pappso
