//
// File: test_aa.cpp
// Created by: Olivier Langella
// Created on: 7/3/2015
//
/*******************************************************************************
 * 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
 ******************************************************************************/


// make test ARGS="-V -I 1,1"

// ./tests/catch2-only-tests [Aa] -s


#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>

#include <QDebug>
#include <QString>
#include <pappsomspp/core/mzrange.h>
#include <pappsomspp/core/amino_acid/aa.h>
#include <iostream>
#include <pappsomspp/core/obo/obopsimod.h>
#include <pappsomspp/core/obo/filterobopsimodtermlabel.h>
#include <pappsomspp/core/obo/filterobopsimodsink.h>
#include <pappsomspp/core/obo/filterobopsimodtermdiffmono.h>
#include <pappsomspp/core/exception/exceptionnotfound.h>
#include <pappsomspp/core/exception/exceptionoutofrange.h>
#include <pappsomspp/core/obo/filterobopsimodmap.h>
#include <pappsomspp/core/obo/obounimod.h>
#include <pappsomspp/core/obo/obopsimod.h>
#include <pappsomspp/core/exception/exceptionnotpossible.h>

using namespace pappso;
using namespace std;


TEST_CASE("Amino Acid test suite.", "[Aa]")
{
  // Set the debugging message formatting pattern.
  qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}"));

  SECTION("..:: test Formula ::..", "[Aa]")
  {
    pappso::FilterOboPsiModMap obo_unimod_map;
    pappso::OboUnimod unimod_parser(obo_unimod_map);


    pappso::FilterOboPsiModMap obo_psimod_map;
    pappso::OboPsiMod psimod_parser(obo_psimod_map);


    pappso::OboPsiModTerm pterm = obo_psimod_map.getOboPsiModTermWithAccession("MOD:00394");

    pappso::ChemicalFormula formula;

    formula.setOboPsiModTerm(pterm);

    REQUIRE(formula.getMass() == Catch::Approx(42.010564684));

    pterm = obo_psimod_map.getOboPsiModTermWithAccession("MOD:00022");
    REQUIRE(pterm.getAccession() == "MOD:00022");
    formula.setOboPsiModTerm(pterm);
    REQUIRE(pterm.m_formula.toStdString() == "C 5 H 9 N 1 O 1 S 1");
    REQUIRE(formula.getMass() == Catch::Approx(131.0404850886));
    REQUIRE(formula.getMass() == Catch::Approx(pterm.m_massMono));

    pappso::AaModificationP mod = AaModification::getInstanceInsertionAccessionByAaLetter('M');

    REQUIRE(mod->getMass() == Catch::Approx(131.0404850886));


    pterm = obo_psimod_map.getOboPsiModTermWithAccession("MOD:00582");
    formula.setOboPsiModTerm(pterm);
    REQUIRE(pterm.m_diffFormula.toStdString() == "(12)C -6 (13)C 6 (14)N -2 (15)N 2");
    REQUIRE(formula.getMass() == Catch::Approx(8.0141988136));

    // UNIMOD:259 == MOD:00582
    pterm = obo_unimod_map.getOboPsiModTermWithAccession("UNIMOD:259");
    REQUIRE(pterm.isUnimod());
    formula.setOboPsiModTerm(pterm);
    REQUIRE(pterm.m_diffFormula.toStdString() == "C(-6) 13C(6) N(-2) 15N(2)");
    REQUIRE(formula.getMass() == Catch::Approx(8.0141988136));

    // UNIMOD:21
    pterm = obo_unimod_map.getOboPsiModTermWithAccession("UNIMOD:21");
    REQUIRE(pterm.isUnimod());
    formula.setOboPsiModTerm(pterm);
    REQUIRE(pterm.m_diffFormula.toStdString() == "H O(3) P");
    REQUIRE(formula.getMass() == Catch::Approx(79.9663308889));


    pterm = obo_unimod_map.getOboPsiModTermWithName("Acetyl");
    REQUIRE(pterm.isUnimod());
    formula.setOboPsiModTerm(pterm);
    REQUIRE(pterm.m_diffFormula.toStdString() == "H(2) C(2) O");
    REQUIRE(formula.getMass() == Catch::Approx(42.010564684));

    pappso::AaModificationP modp = AaModification::getInstance("Acetyl");
    REQUIRE(modp->getAccession().toStdString() == "UNIMOD:1");
    modp = AaModification::getInstance("Phospho");
    REQUIRE(modp->getAccession().toStdString() == "UNIMOD:21");
    REQUIRE(formula.getMass() == Catch::Approx(pterm.m_diffMono));

    // iTRAQ4plex
    pterm = obo_unimod_map.getOboPsiModTermWithName("iTRAQ4plex");
    REQUIRE(pterm.isUnimod());
    formula.setOboPsiModTerm(pterm);
    REQUIRE(pterm.m_diffFormula.toStdString() == "H(12) C(4) 13C(3) N 15N O");
    REQUIRE(formula.getMass() == Catch::Approx(144.1020624229));
    REQUIRE(formula.getMass() == Catch::Approx(pterm.m_diffMono));


    // USER:2-Ethynylbenzaldehyde
    pappso::AaModificationP modpuser = AaModification::getInstance("USER:2-Ethynylbenzaldehyde");
    REQUIRE(modpuser->getAccession().toStdString() == "USER:2-Ethynylbenzaldehyde");
    
    REQUIRE(modpuser->getMass() == Catch::Approx(112.03130012896400558));
  }

  SECTION("..:: test PSI-MOD removal ::..", "[Aa]")
  {
    //%S << std::endl << "..:: AA init ::.." << std::endl;
    //   OboPsiMod test;
    Aa leucine('L');

    REQUIRE(leucine.getMass() == Catch::Approx(113.084063979));

    leucine.addAaModification(AaModification::getInstanceRemovalAccessionByAaLetter('L'));
    REQUIRE(leucine.getMass() == Catch::Approx(0.0));

    leucine.addAaModification(AaModification::getInstanceInsertionAccessionByAaLetter('M'));
    REQUIRE(leucine.getMass() == Catch::Approx(131.0404850886));

    REQUIRE(leucine.toProForma().toStdString() == "L[MOD:00022][MOD:01641]");

    REQUIRE(leucine.getTotalModificationMass() == Catch::Approx(17.9564211096));
    leucine.removeAllButInternalModification();

    REQUIRE(leucine.toProForma().toStdString() == "L");
  }

  SECTION("..:: AA init ::..", "[Aa]")
  {
    //%S << std::endl << "..:: AA init ::.." << std::endl;
    //   OboPsiMod test;
    Aa leucine('L');
    MzRange mz_range(leucine.getMass(), PrecisionFactory::getPpmInstance(1));
    std::cout << "leucine: " << QString::number(leucine.getMass(), 'g', 10).toStdString()
              << std::endl;
    std::cout << "leucine number of N15 : " << leucine.getNumberOfIsotope(Enums::Isotope::N15)
              << std::endl;
    std::cout << mz_range.toString().toStdString() << std::endl;
    /*
    if(!mz_range.contains(pappso_double(131.094635) - MASSH2O))
      {
        std::cerr << QString::number(leucine.getMass(), 'g', 10).toStdString()
             << " != 131.094635 - H2O";
        return 1;
      }
      */
    REQUIRE(mz_range.contains(pappso_double(131.094635) - MASSH2O));

    Aa alanine('A');
    /*
    if(!MzRange(alanine.getMass(), PrecisionFactory::getPpmInstance(1))
          .contains(pappso_double(89.047676 - MASSH2O)))
      {
        std::cerr << QString::number(alanine.getMass(), 'g', 10).toStdString()
             << " != " << (89.047676 - MASSH2O);
        return 1;
      }
      */
    REQUIRE(MzRange(alanine.getMass(), PrecisionFactory::getPpmInstance(1))
              .contains(pappso_double(89.047676 - MASSH2O)));


    alanine.addAaModification(AaModification::getInstanceCustomizedMod(18.022316354654));
    // SUCCESS

    // qDebug() << alanine.getModificationList();
    alanine.addAaModification(AaModification::getInstance("MOD:00397"));
    // alanine.addAaModification(AaModification::getInstance("fdgMOD:00397"));


    FilterOboPsiModSink term_list;
    FilterOboPsiModTermLabel filter_label(term_list, QString("Carba*"));

    OboPsiMod psimod(filter_label);

    std::cout << "term_list.size= " << term_list.size() << std::endl;
    REQUIRE(term_list.size() == 12);


    MzRange range(pappso_double(57.02), PrecisionFactory::getDaltonInstance(pappso_double(0.02)));
    std::cout << range.getMz() << std::endl;

    std::cout << "choses "
              << MzRange(pappso_double(57.02),
                         PrecisionFactory::getDaltonInstance(pappso_double(0.02)))
                   .toString()
                   .toStdString()
              << std::endl;
    FilterOboPsiModSink term_listb;
    FilterOboPsiModTermDiffMono filter_labelb(
      term_listb,
      MzRange(pappso_double(57.02), PrecisionFactory::getDaltonInstance(pappso_double(0.02))));

    OboPsiMod psimodb(filter_labelb);

    for(auto term : term_listb.getOboPsiModTermList())
      {
        //%S << term._accession.toStdString() << " " << mz(term._diff_mono)
        // << std::endl;
      }
    std::cout << "term_listb.size= " << term_listb.size() << std::endl;
    REQUIRE(term_listb.size() == 8);

    Aa alaninebis('A');

    REQUIRE(alaninebis.getNumberOfAtom(Enums::AtomIsotopeSurvey::C) == 3);
    REQUIRE(alaninebis.getNumberOfAtom(Enums::AtomIsotopeSurvey::H) == 5);

    // xref: DiffFormula: "C 2 H 3 N 1 O 1"
    alaninebis.addAaModification(AaModification::getInstance("MOD:00397"));

    REQUIRE(alaninebis.getNumberOfAtom(Enums::AtomIsotopeSurvey::C) == 5);
    REQUIRE(alaninebis.getNumberOfAtom(Enums::AtomIsotopeSurvey::H) == 8);

    REQUIRE(alaninebis.toAbsoluteString().toStdString() == "A(MOD:00397)");

    alaninebis.addAaModification(AaModification::getInstanceCustomizedMod(18.022316354654));
    // SUCCESS

    REQUIRE_FALSE(alanine < alaninebis);

    REQUIRE_FALSE(alaninebis < alanine);

    REQUIRE(alanine == alaninebis);


    Aa alanineter('A');
    qDebug() << "TEST";
    alanineter.addAaModification(AaModification::getInstance("MOD:00397"));

    REQUIRE_FALSE(alanine == alanineter);
    REQUIRE_FALSE(alanine < alanineter);
  }
  SECTION("..:: Test Modifications ::..")
  {
    AaModificationP mod = AaModification::getInstance("MOD:00397");
    std::cout << " MOD:00397 mass=" << QString::number(mod->getMass(), 'g', 15).toStdString()
              << std::endl;
    // 57.021464

    // MOD:00429
    mod = AaModification::getInstance("MOD:00429");
    std::cout << " MOD:00429 mass=" << QString::number(mod->getMass(), 'g', 15).toStdString()
              << std::endl;
    // 28.0313


    // MOD:00382
    mod = AaModification::getInstance("MOD:00382");
    std::cout << " MOD:00382 mass=" << QString::number(mod->getMass(), 'g', 15).toStdString()
              << std::endl;
    //-20.026215
    REQUIRE(mod->getMass() == Catch::Approx(-20.026215));

    // MOD:00234
    mod = AaModification::getInstance("MOD:00234");
    std::cout << " MOD:00234 mass=" << QString::number(mod->getMass(), 'g', 15).toStdString()
              << std::endl;
    // 305.068156

    // MOD:00838
    mod = AaModification::getInstance("MOD:00838");
    std::cout << " MOD:00838 mass=" << QString::number(mod->getMass(), 'g', 15).toStdString()
              << std::endl;


    for(auto amino_acid : pappso::Aa::getAminoAcidCharList())
      {
        pappso::Aa aa_enumi(amino_acid);
        std::cout << " " << (char)amino_acid << " "
                  << QString::number(aa_enumi.getMass(), 'g', 15).toStdString() << std::endl;
      }
    /*
     * A 71.037113785565
    5:  R 156.101111025652
    5:  C 103.009184785565
    5:  D 115.026943024685
    5:  E 129.042593089167
    5:  F 147.068413914529
    5:  G 57.021463721083
    5:  H 137.058911859647
    5:  I 113.084063979011
    5:  K 128.094963016052
    5:  L 113.084063979011
    5:  M 131.040484914529
    5:  N 114.042927442166
    5:  P 97.052763850047
    5:  Q 128.058577506648
    5:  R 156.101111025652
    5:  S 87.032028405125
    5:  T 101.047678469607
    5:  V 99.068413914529
    5:  W 186.07931295157
    5:  Y 163.063328534089
    5:  U 168.964198469607
    5:  O 255.158291550141
    */
    REQUIRE(pappso::Aa('A').getMass() == Catch::Approx(71.0371137856));
    REQUIRE(pappso::Aa('R').getMass() == Catch::Approx(156.101111025652));
    REQUIRE(pappso::Aa('C').getMass() == Catch::Approx(103.0091849597));
    REQUIRE(pappso::Aa('D').getMass() == Catch::Approx(115.026943024685));

    // AaModification::getInstance("MOD:00397_wrong");

    REQUIRE_THROWS_AS(AaModification::getInstance("MOD:00397_wrong"), pappso::ExceptionNotFound);
  }

  /*

  return 0;
  */
}
