// ./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/CleavageConfig.hpp"
#include "MsXpS/libXpertMassCore/Cleaver.hpp"

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

ErrorList error_list_cleaver;

SCENARIO("Cleaver instances can be copy-constructed", "[Cleaver]")
{
  test_utils_1_letter_cleaver.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cleaver.msp_polChemDef;

  QString polymer_file_path =
    QString("%1/polymer-sequences/%2")
      .arg(TESTS_INPUT_DIR)
      .arg("cyan-fluorescent-protein-no-cross-link.mxp");

  GIVEN(
    "A Polymer instance (as a shared pointer) is first created and data are "
    "loaded from an XML file")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 240);

    AND_GIVEN(
      "The allocation of CleavageConfig (monoprotonation) and an Ionizer, and "
      "a Cleaver object")
    {
      CleavageConfig m_cleavage_config(
        polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
      m_cleavage_config.setIonizeLevels(1, 1);
      m_cleavage_config.setSequenceEmbedded(true);

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

      Cleaver cleaver(polymer_sp,
                      polymer_sp->getPolChemDefCstSPtr(),
                      m_cleavage_config,
                      calc_options,
                      protonation);

      WHEN("A new Cleaver object is copy-contructed")
      {
        Cleaver another_cleaver(cleaver);
        Cleaver other_cleaver;
        other_cleaver = another_cleaver;

        THEN("Both Cleaver instances should be identical")
        {
          REQUIRE(other_cleaver.getCleaveAgentName().toStdString() ==
                  cleaver.getCleaveAgentName().toStdString());

          REQUIRE(other_cleaver == cleaver);
        }
      }
    }
  }
}

SCENARIO(
  "Construction of a Cleaver and use with a Polymer without CrossLink "
  "instances",
  "[Cleaver]")
{
  test_utils_1_letter_cleaver.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cleaver.msp_polChemDef;

  QString polymer_file_path =
    QString("%1/polymer-sequences/%2")
      .arg(TESTS_INPUT_DIR)
      .arg("cyan-fluorescent-protein-no-cross-link.mxp");

  GIVEN(
    "A Polymer instance (as a shared pointer) is first created and data are "
    "loaded from an XML file")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 240);

    AND_GIVEN(
      "The allocation of CleavageConfig (monoprotonation) and an Ionizer, and "
      "a Cleaver object")
    {
      CleavageConfig m_cleavage_config(
        polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
      m_cleavage_config.setIonizeLevels(1, 1);
      m_cleavage_config.setSequenceEmbedded(true);

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

      Cleaver cleaver(polymer_sp,
                      polymer_sp->getPolChemDefCstSPtr(),
                      m_cleavage_config,
                      calc_options,
                      protonation);

      WHEN("The cleavage is asked for")
      {
        REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

          REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[1-5]");
          REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
          REQUIRE_FALSE(oligomer_sp->isModified());
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(461.2723725606, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(461.5355872507, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C19H37N6O7");


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

          REQUIRE(oligomer_sp->getName().toStdString() == "0#26#z=1");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[218-240]");
          REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
          REQUIRE_FALSE(oligomer_sp->isModified());
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(2566.2937091343, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(2568.0082003839, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C116H185N26O35S2");
        }
      }

      AND_GIVEN(
        "The allocation of CleavageConfig (protonation 1->3) and an Ionizer, "
        "and a Cleaver object")
      {
        CleavageConfig m_cleavage_config(
          polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
        m_cleavage_config.setIonizeLevels(1, 3);
        m_cleavage_config.setSequenceEmbedded(true);

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

        Cleaver cleaver(polymer_sp,
                        polymer_sp->getPolChemDefCstSPtr(),
                        m_cleavage_config,
                        calc_options,
                        protonation);

        WHEN("The cleavage is asked for")
        {
          REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

            REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[1-5]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(461.2723725606, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(461.5355872507, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C19H37N6O7");


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

            REQUIRE(oligomer_sp->getName().toStdString() == "0#26#z=3");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[218-240]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(856.1031197330, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(856.6746944402, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C116H187N26O35S2");
          }
        }
      }

      AND_GIVEN(
        "The allocation of CleavageConfig (protonation 1->3) partials = 2 and "
        "an Ionizer, and a Cleaver object")
      {
        CleavageConfig m_cleavage_config(
          polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
        m_cleavage_config.setIonizeLevels(1, 3);
        m_cleavage_config.setPartials(2);
        m_cleavage_config.setSequenceEmbedded(true);

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

        Cleaver cleaver(polymer_sp,
                        polymer_sp->getPolChemDefCstSPtr(),
                        m_cleavage_config,
                        calc_options,
                        protonation);

        WHEN("The cleavage is asked for")
        {
          REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

            REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[1-5]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(461.2723725606, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(461.5355872507, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C19H37N6O7");


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

            REQUIRE(oligomer_sp->getName().toStdString() == "2#24#z=3");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[212-240]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(1102.5568868828, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(1103.2683985892, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C146H236N37O46S2");
          }
        }
      }
      AND_GIVEN(
        "The allocation of CleavageConfig (protonation 1->3) partials = 2 and "
        "an Ionizer, and a Cleaver object and one shortened sequence range")
      {
        CleavageConfig m_cleavage_config(
          polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
        m_cleavage_config.setIonizeLevels(1, 3);
        m_cleavage_config.setPartials(2);
        m_cleavage_config.setSequenceEmbedded(true);

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

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

        Cleaver cleaver(polymer_sp,
                        polymer_sp->getPolChemDefCstSPtr(),
                        m_cleavage_config,
                        calc_options,
                        protonation);

        WHEN("The cleavage is asked for")
        {
          REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

            REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[1-5]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(461.2723725606, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(461.5355872507, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C19H37N6O7");


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

            REQUIRE(oligomer_sp->getName().toStdString() == "2#12#z=3");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[110-121]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(455.9056897620, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(456.1732340835, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C59H99N16O21");
          }
        }
      }
    }
  }
}

SCENARIO(
  "Construction of a Cleaver and use with a Polymer without CrossLink "
  "instances and cleave with CleavageRule instances",
  "[Cleaver]")
{
  test_utils_1_letter_cleaver.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cleaver.msp_polChemDef;

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

  GIVEN(
    "A Polymer instance (as a shared pointer) is first created and data are "
    "loaded from an XML file")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 153);

    AND_GIVEN(
      "The allocation of CleavageConfig (monoprotonation) and an Ionizer, and "
      "a "
      "Cleaver object")
    {
      const CleavageAgentCstSPtr cleavage_agent_csp =
        polymer_sp->getPolChemDefCstSPtr()->getCleavageAgentCstSPtrByName(
          "CyanogenBromide");

      REQUIRE(cleavage_agent_csp != nullptr);
      REQUIRE(cleavage_agent_csp->getName().toStdString() == "CyanogenBromide");

      CleavageConfig m_cleavage_config(
        *cleavage_agent_csp, /*partials*/ 2, /*seq_embedded*/ true);
      m_cleavage_config.setIonizeLevels(1, 3);

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

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

      Cleaver cleaver(polymer_sp,
                      polymer_sp->getPolChemDefCstSPtr(),
                      m_cleavage_config,
                      calc_options,
                      protonation);

      qDebug() << "20241109";

      WHEN("The cleavage is asked for")
      {
        qDebug() << "20241109";
        REQUIRE(cleaver.cleave(/*reset*/ false));
        qDebug() << "20241109";

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

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

          REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[1-55]");
          REQUIRE(oligomer_sp->getDescription().toStdString() ==
                  "CyanogenBromide");
          REQUIRE_FALSE(oligomer_sp->isModified());
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(6231.1943570372, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(6234.9516661300, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C282H434N75O85");


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

          REQUIRE(oligomer_sp->getName().toStdString() == "2#2#z=3");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[1-153]");
          REQUIRE(oligomer_sp->getDescription().toStdString() ==
                  "CyanogenBromide");
          REQUIRE_FALSE(oligomer_sp->isModified());
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(5647.9961615473, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(5651.4605662028, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C769H1215N210O218S2");
        }
      }
    }
  }
}

SCENARIO(
  "Construction of a Cleaver and use with a Monomer-modified Polymer without "
  "CrossLink "
  "instances and cleave with CleavageRule instances",
  "[Cleaver]")
{
  test_utils_1_letter_cleaver.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cleaver.msp_polChemDef;

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

  GIVEN(
    "A Polymer instance (as a shared pointer) is first created and data are "
    "loaded from an XML file")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);

    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 153);

    REQUIRE(polymer_sp->modifyMonomer(54, "Oxidation", /*override*/ false));
    REQUIRE(
      polymer_sp->modifyMonomer(107, "Phosphorylation", /*override*/ false));

    AND_GIVEN(
      "The allocation of CleavageConfig (monoprotonation) and an Ionizer, and "
      "a "
      "Cleaver object")
    {
      const CleavageAgentCstSPtr cleavage_agent_csp =
        polymer_sp->getPolChemDefCstSPtr()->getCleavageAgentCstSPtrByName(
          "CyanogenBromide");

      REQUIRE(cleavage_agent_csp != nullptr);
      REQUIRE(cleavage_agent_csp->getName().toStdString() == "CyanogenBromide");

      CleavageConfig m_cleavage_config(
        *cleavage_agent_csp, /*partials*/ 2, /*seq_embedded*/ true);
      m_cleavage_config.setIonizeLevels(1, 3);

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

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

      Cleaver cleaver(polymer_sp,
                      polymer_sp->getPolChemDefCstSPtr(),
                      m_cleavage_config,
                      calc_options,
                      protonation);

      WHEN("The cleavage is asked for")
      {
        REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

          REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[1-55]");
          REQUIRE(oligomer_sp->getDescription().toStdString() ==
                  "CyanogenBromide");
          REQUIRE(oligomer_sp->isModified());
          REQUIRE(oligomer_sp->modifiedMonomerCount() == 1);

          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(6247.1892716574, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(6250.9510748471, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C282H434N75O86");


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

          REQUIRE(oligomer_sp->getName().toStdString() == "2#2#z=3");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[1-153]");
          REQUIRE(oligomer_sp->getDescription().toStdString() ==
                  "CyanogenBromide");
          REQUIRE(oligomer_sp->isModified());
          REQUIRE(oligomer_sp->modifiedMonomerCount() == 2);
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(5679.9832433845, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(5683.4536789813, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C769H1216N210O222S2P1");
        }
      }
    }
  }
}

SCENARIO(
  "Construction of a Cleaver and use with a Polymer with CrossLink instances",
  "[Cleaver]")
{
  test_utils_1_letter_cleaver.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cleaver.msp_polChemDef;

  QString polymer_file_path = QString("%1/polymer-sequences/%2")
                                .arg(TESTS_INPUT_DIR)
                                .arg("kunitz-inhibitor-human-cross-links.mxp");

  GIVEN(
    "A Polymer instance (as a shared pointer) is first created and data are "
    "loaded from an XML file")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 352);
    REQUIRE(polymer_sp->getCrossLinksCstRef().size() == 7);

    AND_GIVEN(
      "The allocation of CleavageConfig (monoprotonation) and an Ionizer, and "
      "a "
      "Cleaver object")
    {
      CleavageConfig m_cleavage_config(
        polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
      m_cleavage_config.setIonizeLevels(1, 1);
      m_cleavage_config.setSequenceEmbedded(true);

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

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

      Cleaver cleaver(polymer_sp,
                      polymer_sp->getPolChemDefCstSPtr(),
                      m_cleavage_config,
                      calc_options,
                      protonation);

      WHEN("The cleavage is asked for")
      {
        qDebug() << "Right before cleavage.";
        REQUIRE(cleaver.cleave(/*reset*/ false));
        qDebug() << "Right after cleavage.";

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

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

          REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[1-2]");
          REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
          REQUIRE_FALSE(oligomer_sp->isModified());
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(306.1599858302, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(306.4063019185, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C11H24N5O3S1");


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

          REQUIRE(oligomer_sp->getName().toStdString() ==
                  "cl-0#31+0#34+0#36#z=1");
          REQUIRE(oligomer_sp->getCalcOptionsRef()
                    .getIndexRangeCollectionRef()
                    .positionsAsText()
                    .toStdString() == "[294-297][312-326][332-334]");
          REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
          REQUIRE_FALSE(oligomer_sp->isModified());
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(2390.0262359943, 0.0000000001));
          REQUIRE_THAT(
            oligomer_sp->getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(2391.7207845530, 0.0000000001));
          REQUIRE(
            oligomer_sp->getFormulaCstRef().getActionFormula().toStdString() ==
            "C16H30N7O5S1");
        }
      }

      AND_GIVEN(
        "The allocation of CleavageConfig (protonation 1->3) and an Ionizer, "
        "and a Cleaver object")
      {
        CleavageConfig m_cleavage_config(
          polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
        m_cleavage_config.setIonizeLevels(1, 3);
        m_cleavage_config.setSequenceEmbedded(true);

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

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

        Cleaver cleaver(polymer_sp,
                        polymer_sp->getPolChemDefCstSPtr(),
                        m_cleavage_config,
                        calc_options,
                        protonation);

        WHEN("The cleavage is asked for")
        {
          REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

            REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[1-2]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(306.1599858302, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(306.4063019185, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C11H24N5O3S1");


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

            REQUIRE(oligomer_sp->getName().toStdString() ==
                    "cl-0#31+0#34+0#36#z=3");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[294-297][312-326][332-334]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(797.3472953530, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(797.9122224966, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C16H32N7O5S1");
          }
        }
      }

      AND_GIVEN(
        "The allocation of CleavageConfig (protonation 1->3) partials = 2 and "
        "an Ionizer, and a Cleaver object")
      {
        CleavageConfig m_cleavage_config(
          polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
        m_cleavage_config.setIonizeLevels(1, 3);
        m_cleavage_config.setPartials(2);
        m_cleavage_config.setSequenceEmbedded(true);

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

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

        Cleaver cleaver(polymer_sp,
                        polymer_sp->getPolChemDefCstSPtr(),
                        m_cleavage_config,
                        calc_options,
                        protonation);

        WHEN("The cleavage is asked for")
        {
          REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

            REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[1-2]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(306.1599858302, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(306.4063019185, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C11H24N5O3S1");


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


            REQUIRE(oligomer_sp->getName().toStdString() == "2#36#z=3");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[332-352]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(796.6797617835, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(797.1940963187, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C99H153N28O37S2");
          }
        }
      }

      AND_GIVEN(
        "The allocation of CleavageConfig (protonation 1->3) partials = 2 and "
        "an Ionizer, and a Cleaver object and one shortened sequence range")
      {
        CleavageConfig m_cleavage_config(
          polymer_sp->getPolChemDefCstSPtr(), "Trypsin", "K/;R/;-K/P", 0, true);
        m_cleavage_config.setIonizeLevels(1, 3);
        m_cleavage_config.setPartials(2);
        m_cleavage_config.setSequenceEmbedded(true);

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

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

        Cleaver cleaver(polymer_sp,
                        polymer_sp->getPolChemDefCstSPtr(),
                        m_cleavage_config,
                        calc_options,
                        protonation);

        WHEN("The cleavage is asked for")
        {
          REQUIRE(cleaver.cleave(/*reset*/ false));

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

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

            REQUIRE(oligomer_sp->getName().toStdString() == "0#1#z=1");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[1-2]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(306.1599858302, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(306.4063019185, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C11H24N5O3S1");


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

            REQUIRE(oligomer_sp->getName().toStdString() == "2#20#z=3");
            REQUIRE(oligomer_sp->getCalcOptionsRef()
                      .getIndexRangeCollectionRef()
                      .positionsAsText()
                      .toStdString() == "[159-188]");
            REQUIRE(oligomer_sp->getDescription().toStdString() == "Trypsin");
            REQUIRE_FALSE(oligomer_sp->isModified());
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(1099.5362628891, 0.0000000001));
            REQUIRE_THAT(
              oligomer_sp->getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(1100.2372770376, 0.0000000001));
            REQUIRE(oligomer_sp->getFormulaCstRef()
                      .getActionFormula()
                      .toStdString() == "C142H229N38O48S2");
          }
        }
      }
    }
  }
}


} // namespace libXpertMassCore
} // namespace MsXpS
