/**
 * \file pappsomspp/amino_acid/aamodification.h
 * \date 7/3/2015
 * \author Olivier Langella
 * \brief amino acid modification 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
 ******************************************************************************/


#pragma once

#include <QDebug>
#include <QString>
#include <QMutex>
#include <memory>
#include <map>
#include "pappsomspp/core/types.h"
#include "pappsomspp/core/amino_acid/chemicalformula.h"
#include "pappsomspp/core/obo/obopsimod.h"

namespace pappso
{


class Peptide;
typedef std::shared_ptr<const Peptide> PeptideSp;

class AaModification;
typedef std::unique_ptr<const AaModification> AaModificationUp;
typedef const AaModification *AaModificationP;

class Aa;

class PMSPP_LIB_DECL AaModification : public AtomNumberInterface
{

  public:
  AaModification(AaModification &&toCopy); // move constructor

  static AaModificationP getInstance(const QString &accession);
  static AaModificationP getInstance(const OboPsiModTerm &oboterm);

  /** @brief get a fake modification coding a mutation from an amino acid to an
   * other
   * @param mut_from orginal amino acid
   * @param mut_to targeted amino acid
   */
  static AaModificationP getInstanceMutation(const QChar &mut_from, const QChar &mut_to);

  /** @brief get a PSI MOD instance corresponding to the removal of the given
   * amino acid find the modifications that corresponds to the removal of a
   * residue id: MOD:01651 name: natural, standard, encoded residue removal
   * @param amino_acid orginal amino acid letter
   * @return AaModificationP
   */
  static AaModificationP getInstanceRemovalAccessionByAaLetter(const QChar &amino_acid);


  /** @brief get a PSI MOD instance corresponding to the insertion of the given
   * amino acid find the modifications.
   *
   * insertion of residue id: MOD:01441 name: natural, standard, encoded residue
   * @param amino_acid orginal amino acid letter
   * @return AaModificationP
   */
  static AaModificationP getInstanceInsertionAccessionByAaLetter(const QChar &amino_acid);


  static AaModificationP getInstanceXtandemMod(const QString &type,
                                               pappso_double mass,
                                               const PeptideSp &peptide_sp,
                                               unsigned int position);
  static AaModificationP getInstanceCustomizedMod(pappso_double modificationMass);

  const QString &getAccession() const;
  const QString &getName() const;

  /** @brief get the amino acid in ProForma notation
   * https://github.com/HUPO-PSI/ProForma/blob/master/README.md
   * @return QString as described in ProForma
   */
  const QString toProForma() const;

  ~AaModification();

  pappso_double getMass() const;
  int getNumberOfAtom(Enums::AtomIsotopeSurvey atom) const override final;
  int getNumberOfIsotope(Enums::Isotope isotope) const override final;
  bool isInternal() const;

  /** @brief get list of amino acid on which this modification takes place
   *
   * @return origin string of the form "S T" for Serine or Threonine, "X" for
   * any amino acid (see OBO PSI format)
   * */
  const QString &getXrefOrigin() const;


  protected:
  const QString m_accession;
  QString m_name;

  protected:
  void setDiffFormula(const pappso::ChemicalFormula &diff_formula);

  /** @brief set list of amino acid on which this modification takes place
   *
   * @arg origin string of the form "S T" for Serine or Threonine, "X" for any
   * amino acid (see OBO PSI format)
   * */
  void setXrefOrigin(const QString &origin);


  private:
  AaModification(const QString &accession, pappso_double mass);
  static AaModificationP createInstance(const QString &saccession);
  static AaModificationP createInstance(const OboPsiModTerm &term);
  static AaModificationP createInstanceMutation(const Aa &aa_from, const Aa &aa_to);
  /** @brief get heavy amino acid modification C13 N15
   * @param aa_from orginal amino acid
   */
  static AaModificationP createInstanceC13N15LabelledAminoAcid(const QChar &aa_from);
  void calculateMassFromChemicalComponents();

  using MapAccessionModifications = std::map<QString, AaModificationP>;


  private:
  pappso_double m_mass;
  QString m_origin;
  std::map<Enums::AtomIsotopeSurvey, int> m_atomCount;
  std::map<Enums::Isotope, int> m_mapIsotope;


  static MapAccessionModifications m_mapAccessionModifications;

  static QMutex m_mutex;
};

} // namespace pappso
