// ./tests/catch2-tests [section] -s


/////////////////////// Qt includes
#include <QDebug>
#include <QString>
#include <QStringList>
#include <QDir>


/////////////////////// IsoSpec
#include <IsoSpec++/isoSpec++.h>
#include <IsoSpec++/element_tables.h>


/////////////////////// Catch2 includes
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>


/////////////////////// Local includes
#include "tests-config.h"
#include "TestUtils.hpp"
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/Polymer.hpp"
#include "MsXpS/libXpertMassCore/CrossLink.hpp"
#include "MsXpS/libXpertMassCore/CalcOptions.hpp"
#include "MsXpS/libXpertMassCore/FragmentationConfig.hpp"
#include "MsXpS/libXpertMassCore/Fragmenter.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{
TestUtils test_utils_1_letter_fragmenter("protein-1-letter", 1);

ErrorList error_list_fragmenter;

SCENARIO("Construction of a Fragmenter", "[Fragmenter]")
{
  test_utils_1_letter_fragmenter.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_fragmenter.msp_polChemDef;

  QString polymer_file_path = QString("%1/polymer-sequences/%2")
                                .arg(TESTS_INPUT_DIR)
                                .arg("chicken-telokin.mxp");

  PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
  REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
  REQUIRE(polymer_sp->size() == 157);

  FragmentationConfig immo_frag_config(polymer_sp->getPolChemDefCstSPtr(),
                                       "imm",
                                       "-C1O1",
                                       Enums::FragEnd::NE,
                                       "immonium ions",
                                       /*sequence_embedded*/ false);

  FragmentationConfig a_frag_config(polymer_sp->getPolChemDefCstSPtr(),
                                    "a",
                                    "-C1O1H1",
                                    Enums::FragEnd::LE,
                                    "a series ions",
                                    /*sequence_embedded*/ false);

  FragmentationConfig b_frag_config(polymer_sp->getPolChemDefCstSPtr(),
                                    "b",
                                    "-H1",
                                    Enums::FragEnd::LE,
                                    "b series ions",
                                    /*sequence_embedded*/ false);

  std::vector<FragmentationConfig> frag_configs = {immo_frag_config,
                                                   a_frag_config};

  CalcOptions calc_options(/*deep_calculation*/ false,
                           Enums::MassType::BOTH,
                           Enums::CapType::BOTH,
                           Enums::ChemicalEntity::NONE,
                           Enums::ChemicalEntity::NONE);
  calc_options.setIndexRange(0, 239);

  Ionizer ionizer(polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
                  Formula("+H"),
                  1,
                  1);

  Fragmenter fragmenter(polymer_sp,
                        polymer_sp->getPolChemDefCstSPtr(),
                        frag_configs,
                        calc_options,
                        ionizer);

  fragmenter.addFragmentationConfig(b_frag_config);

  WHEN("Checking member data, like the fragmentation configs")
  {
    const std::vector<FragmentationConfig> &check_cst_frag_configs =
      fragmenter.getFragmentationConfigsCstRef();
    const std::vector<FragmentationConfig> &check_frag_configs =
      fragmenter.getFragmentationConfigsRef();

    const Ionizer &check_cst_ionizer = fragmenter.getIonizerCstRef();
    const Ionizer &check_ionizer     = fragmenter.getIonizerRef();

    const CalcOptions &check_cst_calc_options =
      fragmenter.getCalcOptionsCstRef();
    const CalcOptions &check_calc_options = fragmenter.getCalcOptionsRef();

    THEN("The data are set correctly")
    {
      REQUIRE(frag_configs.size() == check_cst_frag_configs.size() - 1);
      REQUIRE(frag_configs.size() == check_frag_configs.size() - 1);
      REQUIRE(ionizer == check_cst_ionizer);
      REQUIRE(ionizer == check_ionizer);
      REQUIRE(calc_options == check_cst_calc_options);
      REQUIRE(calc_options == check_calc_options);
    }
  }
}

SCENARIO(
  "Construction of a Fragmenter and use with a Polymer without CrossLink "
  "instances with various ionization levelss and accounting of chemical "
  "entities",
  "[Fragmenter]")
{
  test_utils_1_letter_fragmenter.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_fragmenter.msp_polChemDef;

  QString polymer_file_path = QString("%1/polymer-sequences/%2")
                                .arg(TESTS_INPUT_DIR)
                                .arg("chicken-telokin.mxp");

  PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
  REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
  REQUIRE(polymer_sp->size() == 157);

  GIVEN(
    "No end fragmentation (immonium ion-like), monoprotonation, no accounting "
    "for chemical entities")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "imm",
      "-C1O1",
      Enums::FragEnd::NE,
      "immonium ion");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 1);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::NONE,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 25);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "imm#M#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-1]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "imm");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(104.0533955010, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(104.1943046945, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C4H10N1S1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "imm#K#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[25-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "imm");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(101.1078734279, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(101.1707707221, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C5H13N2");
      }
      AND_WHEN(
        "The remaining oligomers are transferred to that same container "
        "(std::move())")
      {
        OligomerCollection dest_oligomers;

        std::size_t count = fragmenter.transferOligomers(
          fragmenter.getOligomerCollectionRef(), dest_oligomers);

        THEN("The Oligomer instances have been moved")
        {
          REQUIRE(count == 25);
          REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 0);
          REQUIRE(dest_oligomers.size() == 25);
        }
      }
    }
  }

  GIVEN(
    "No end fragmentation (immonium ion-like), monoprotonation, accounting for "
    "monomer chemical entities")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "imm",
      "-C1O1",
      Enums::FragEnd::NE,
      "immonium ion");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 1);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::MODIF,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 25);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "imm#M#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-1]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "imm");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(104.0533955010, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(104.1943046945, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C4H10N1S1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().at(12);

        REQUIRE(oligomer_sp->getName().toStdString() == "imm#S#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[13-13]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "imm");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(140.0112697095, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(140.0553408372, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C2H7N1O4P1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "imm#K#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[25-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "imm");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(101.1078734279, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(101.1707707221, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C5H13N2");
      }
    }
  }

  GIVEN(
    "Left end fragmentation, monoprotonation, no accounting for chemical "
    "entities")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "a",
      "-C1O1H1",
      Enums::FragEnd::LE,
      "fragment a");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 1);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::NONE,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 24);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-1]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(104.0533955010, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(104.1943046945, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C4H10N1S1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#24#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-24]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(2307.0895472739, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(2308.6468918738, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C93H160N29O33S3");
      }
    }
  }

  GIVEN(
    "Left end fragmentation, multiprotonation, no accounting for chemical "
    "entities")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "a",
      "-C1O1H1",
      Enums::FragEnd::LE,
      "fragment a");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 2);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::NONE,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 48);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-1]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(104.0533955010, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(104.1943046945, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C4H10N1S1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#24#z=2");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-24]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(1154.0486861531, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(1154.8274166711, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C93H161N29O33S3");
      }
    }
  }

  GIVEN(
    "Left end fragmentation, multiprotonation, Enums::ChemicalEntity::MODIF")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "a",
      "-C1O1H1",
      Enums::FragEnd::LE,
      "fragment a");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 2);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::MODIF,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 48);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-1]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(104.0533955010, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(104.1943046945, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C4H10N1S1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#24#z=2");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-24]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(1194.0318515988, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(1194.8173814803, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C93H162N29O36S3P1");
      }
    }
  }

  GIVEN(
    "Left end fragmentation, multiprotonation, Enums::ChemicalEntity::MODIF "
    "and "
    "Enums::ChemicalEntity::LEFT_END_MODIF")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "a",
      "-C1O1H1",
      Enums::FragEnd::LE,
      "fragment a");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 2);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::MODIF,
                             Enums::ChemicalEntity::LEFT_END_MODIF);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 48);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-1]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(146.0639601857, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(146.2312448490, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C6H12N1O1S1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#24#z=2");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-24]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(1215.0371339412, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(1215.8358515576, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C95H164N29O37S3P1");
      }
    }
  }

  GIVEN(
    "Left end fragmentation, multiprotonation, Enums::ChemicalEntity::MODIF "
    "and "
    "Enums::ChemicalEntity::LEFT_END_MODIF with added -H2O and -NH3 additinal "
    "decompositions")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "a",
      "-C1O1H1",
      Enums::FragEnd::LE,
      "fragment a");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 1);

    REQUIRE(fragmentation_config.addFormula("-H2O"));
    REQUIRE(fragmentation_config.addFormula("-NH3"));

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::MODIF,
                             Enums::ChemicalEntity::LEFT_END_MODIF);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 72);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-1]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(146.0639601857, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(146.2312448490, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C6H12N1O1S1");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "a#24#-NH3#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[1-24]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "a");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(2412.0398937491, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(2413.6332320508, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C95H160N28O37S3P1");
      }
    }
  }

  GIVEN(
    "Right end fragmentation, monoprotonation, no accounting for chemical "
    "entities")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "x",
      "+C1O1-H1",
      Enums::FragEnd::RE,
      "fragment x");

    // Fragmented peptide [0]MAMISGMSGRKASGSSPTSPINADK[24]

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 1);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::NONE,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 24);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[25-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(173.0926172885, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(173.1906453741, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C7H13N2O3");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#24#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[2-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(2376.1287690614, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(2377.6432325534, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C96H163N30O36S2");
      }
    }
  }

  GIVEN(
    "Right end fragmentation, multiprotonation, no accounting for chemical "
    "entities")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "x",
      "+C1O1-H1",
      Enums::FragEnd::RE,
      "fragment x");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 2);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::NONE,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 48);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[25-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(173.0926172885, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(173.1906453741, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C7H13N2O3");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#24#z=2");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[2-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(1188.5682970468, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(1189.3255870109, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C96H164N30O36S2");
      }
    }
  }

  GIVEN(
    "Right end fragmentation, multiprotonation, Enums::ChemicalEntity::MODIF")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "x",
      "+C1O1-H1",
      Enums::FragEnd::RE,
      "fragment x");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 0,
                                             /*stop index*/ 24);
    fragmentation_config.setIonizeLevels(1, 2);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::MODIF,
                             Enums::ChemicalEntity::NONE);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 48);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[25-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(173.0926172885, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(173.1906453741, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C7H13N2O3");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#24#z=2");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[2-25]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(1228.5514624926, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(1229.3155518201, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C96H165N30O39S2P1");
      }
    }
  }

  GIVEN(
    "Right end fragmentation, multiprotonation, Enums::ChemicalEntity::MODIF "
    "and "
    "Enums::ChemicalEntity::RIGHT_END_MODIF")
  {
    FragmentationPathway fragmentation_pathway(
      polymer_sp->getPolChemDefCstSPtr(),
      "x",
      "+C1O1-H1",
      Enums::FragEnd::RE,
      "fragment x");

    std::vector<FragmentationConfig> fragmentation_configs;

    FragmentationConfig fragmentation_config(fragmentation_pathway,
                                             /*start index*/ 140,
                                             /*stop index*/ 156);
    fragmentation_config.setIonizeLevels(1, 2);

    fragmentation_configs.push_back(fragmentation_config);

    CalcOptions calc_options(/*deep_calculation*/ false,
                             Enums::MassType::BOTH,
                             Enums::CapType::BOTH,
                             Enums::ChemicalEntity::MODIF,
                             Enums::ChemicalEntity::RIGHT_END_MODIF);
    calc_options.setIndexRange(0, 239);

    Ionizer protonation(
      polymer_sp->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
      Formula("+H"),
      1,
      1);

    Fragmenter fragmenter(polymer_sp,
                          polymer_sp->getPolChemDefCstSPtr(),
                          fragmentation_configs,
                          calc_options,
                          protonation);

    WHEN("The fragmentation is asked for")
    {
      REQUIRE(fragmenter.fragment());

      THEN(
        "There should be the right number of oligomers and these should as "
        "expected")
      {
        REQUIRE(fragmenter.getOligomerCollectionCstRef().size() == 32);

        OligomerSPtr oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().front();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#1#z=1");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[157-157]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(173.0562317796, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(173.1474639674, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C6H9N2O4");


        oligomer_sp =
          fragmenter.getOligomerCollectionCstRef().getOligomersCstRef().back();

        REQUIRE(oligomer_sp->getName().toStdString() == "x#16#z=2");
        REQUIRE(oligomer_sp->getCalcOptionsCstRef()
                  .getIndexRangeCollectionCstRef()
                  .positionsAsText()
                  .toStdString() == "[142-157]");
        REQUIRE(oligomer_sp->getDescription().toStdString() == "x");
        REQUIRE_FALSE(oligomer_sp->isModified());
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(903.8013928149, 0.0000000001));
        REQUIRE_THAT(oligomer_sp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(904.2980585499, 0.0000000001));
        REQUIRE(
          oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
          "C68H97N17O41");
      }
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
