//
// File: test_aacode.cpp
// Created by: Olivier Langella
// Created on: 29/4/2015
//
/*******************************************************************************
 * Copyright (c) 2025 Olivier Langella <Olivier.Langella@universite-paris-saclay.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/>.
 *
 ******************************************************************************/


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

// ./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/core/amino_acid/aacode.h>
#include <pappsomspp/core/amino_acid/aastringcodec.h>
#include <pappsomspp/core/amino_acid/aastringcodemassmatching.h>
#include <iostream>
#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/exception/exceptionnotpossible.h>

using namespace pappso;
using namespace std;


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);
  }


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

    uint8_t aa =
      aa_code.getAaCodeByMass(103.0091849597, pappso::PrecisionFactory::getDaltonInstance(0.01));

    REQUIRE(aa == 7);
  }
}


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");
  }
}
