//
// 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
// ./tests/catch2-only-tests [AaCode] -s
// ./tests/catch2-only-tests [AaStringCodec] -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/mzrange.h>
#include <pappsomspp/amino_acid/aa.h>
#include <pappsomspp/amino_acid/aacode.h>
#include <pappsomspp/amino_acid/aastringcodec.h>
#include <pappsomspp/amino_acid/aastringcodemassmatching.h>
#include <iostream>
#include <pappsomspp/obo/obopsimod.h>
#include <pappsomspp/obo/filterobopsimodtermlabel.h>
#include <pappsomspp/obo/filterobopsimodsink.h>
#include <pappsomspp/obo/filterobopsimodtermdiffmono.h>
#include <pappsomspp/exception/exceptionnotfound.h>
#include <pappsomspp/exception/exceptionoutofrange.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 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(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(AtomIsotopeSurvey::C) == 3);
    REQUIRE(alaninebis.getNumberOfAtom(AtomIsotopeSurvey::H) == 5);

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

    REQUIRE(alaninebis.getNumberOfAtom(AtomIsotopeSurvey::C) == 5);
    REQUIRE(alaninebis.getNumberOfAtom(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;
  */
}


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

  SECTION("..:: AA code init ::..", "[AaCode]")
  {
    AaCode aa_code;

    REQUIRE(aa_code.getAa('C').getMass() == Catch::Approx(103.0091849597));
    REQUIRE(aa_code.getAaCode('C') == 7);

    REQUIRE(aa_code.getAa((uint8_t)6).getMass() <
            aa_code.getAa((uint8_t)7).getMass());

    aa_code.addAaModification('C', AaModification::getInstance("MOD:00397"));


    REQUIRE(aa_code.getAaCode('M') == 13);
    REQUIRE(aa_code.getAaCode('A') == 2);
    REQUIRE(aa_code.getAaCode('I') == 7);

    REQUIRE(aa_code.getAa('C').getMass() == Catch::Approx(160.0306486807));
    REQUIRE(aa_code.getAaCode('C') == 17);

    REQUIRE(aa_code.getAa((uint8_t)16).getMass() <
            aa_code.getAa((uint8_t)17).getMass());


    REQUIRE(aa_code.getAa((uint8_t)17).getMass() <
            aa_code.getAa((uint8_t)18).getMass());

    REQUIRE(aa_code.getAa((uint8_t)1).getLetter() == 'G');
    REQUIRE(aa_code.getAa((uint8_t)19).getLetter() == 'W');

    REQUIRE_THROWS_AS(aa_code.getAa((uint8_t)20).getMass(),
                      ExceptionOutOfRange);
  }
}


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

  SECTION("..:: AA string codec init ::..", "[AaStringCodec]")
  {
    AaCode aa_code;
    aa_code.addAaModification('C', AaModification::getInstance("MOD:00397"));

    AaStringCodec codec(aa_code);


    REQUIRE(codec.code("MAMI") == 61253);

    std::uint64_t nbaa = aa_code.getSize() + 1;
    REQUIRE(codec.code("SAM") ==
            (aa_code.getAaCode('S') + aa_code.getAaCode('A') * (uint64_t)nbaa +
             aa_code.getAaCode('M') * (uint64_t)nbaa * (uint64_t)nbaa));

    REQUIRE(codec.codeLlc("SAM") == 873);
    REQUIRE(codec.decode(873).toStdString() == "MSA");

    REQUIRE(codec.getMass(873) == Catch::Approx(codec.getMass(5243)));

    std::vector<CodeToMass> arr_codemass =
      codec.generateLlcCodeListByMaxPeptideSize(3);

    REQUIRE(arr_codemass.size() == 1330);

    std::sort(
      arr_codemass.begin(),
      arr_codemass.end(),
      [](const CodeToMass &a, const CodeToMass &b) { return a.mass < b.mass; });
    REQUIRE(codec.decode(arr_codemass.at(0).code).toStdString() == "GGG");
    REQUIRE(codec.decode(arr_codemass.back().code).toStdString() == "WWW");


    arr_codemass = codec.generateLlcCodeListByMaxPeptideSize(4);

    REQUIRE(arr_codemass.size() == 7315);

    std::sort(
      arr_codemass.begin(),
      arr_codemass.end(),
      [](const CodeToMass &a, const CodeToMass &b) { return a.mass < b.mass; });
    REQUIRE(codec.decode(arr_codemass.at(0).code).toStdString() == "GGGG");
    REQUIRE(codec.decode(arr_codemass.back().code).toStdString() == "WWWW");


    arr_codemass = codec.generateLlcCodeListByMaxPeptideSize(5);

    REQUIRE(arr_codemass.size() == 33649);

    std::sort(
      arr_codemass.begin(),
      arr_codemass.end(),
      [](const CodeToMass &a, const CodeToMass &b) { return a.mass < b.mass; });
    REQUIRE(codec.decode(arr_codemass.at(0).code).toStdString() == "GGGGG");
    REQUIRE(codec.decode(arr_codemass.back().code).toStdString() == "WWWWW");

    qDebug() << std::numeric_limits<uint32_t>::max();
    qDebug() << std::numeric_limits<uint64_t>::max();
    REQUIRE(codec.getLimitMax(5) < std::numeric_limits<uint32_t>::max());
    REQUIRE(codec.getLimitMax(6) < std::numeric_limits<uint32_t>::max());
    REQUIRE(codec.getLimitMax(7) < std::numeric_limits<uint32_t>::max());
    // amino acid code is safe on all platform using at maximum 7 amino acids
    // some architectures ar ok with 8 (armel)
    REQUIRE(codec.getLimitMax(5) == 3199999);


    arr_codemass = codec.generateLlcCodeListUpToMaxPeptideSize(5);
    std::sort(
      arr_codemass.begin(),
      arr_codemass.end(),
      [](const CodeToMass &a, const CodeToMass &b) { return a.mass < b.mass; });
    REQUIRE(codec.decode(arr_codemass.at(0).code).toStdString() == "G");
    REQUIRE(codec.decode(arr_codemass.back().code).toStdString() == "WWWWW");

    REQUIRE(arr_codemass.size() == 42503);
  }

  SECTION("..:: AA string codec match :..", "[AaStringCodec]")
  {
    AaCode aa_code;
    aa_code.addAaModification('C', AaModification::getInstance("MOD:00397"));

    AaStringCodec codec(aa_code);


    pappso::AaStringCodeMassMatching aaMatching(
      aa_code, 7, pappso::PrecisionFactory::getDaltonInstance(0.25));

    auto code_list = aaMatching.getAaCodeFromMass(800);

    REQUIRE(code_list.size() == 19);

    REQUIRE(
      codec.decode(code_list).join(" ").toStdString() ==
      "CCCCC YCCDDS CCMMMS YCCMDA YCCMEG CCFMDS CCHDNN CCHDNGG CHEDDSG CHDDDTG "
      "CHDDDSA CCHMDP WYCCM EDDDDNP QDDDDDP YCMDSSG YCCDTT YCCETS CCFMMA");
  }
}
