// ./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"

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

ErrorList error_list_cross_link;


SCENARIO("Construction of a CrossLink", "[CrossLink]")
{
  test_utils_1_letter_cross_link.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_link.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 is allocated as a SPtr and then the XML file is loaded from "
    "disk")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 240);

    WHEN(
      "A CrossLink is allocated by specifying all the params in the "
      "constructor")
    {
      CrossLink cross_link(pol_chem_def_csp,
                           polymer_sp,
                           "CFP-chromophore",
                           "-H2OH2",
                           "This is a comment");

      THEN("The data in the cross-link must be set correctly")
      {
        REQUIRE(cross_link.getPolymerCstSPtr() == polymer_sp);
        REQUIRE(cross_link.getComment().toStdString() == "This is a comment");
        REQUIRE(cross_link.getCrossLinkerName().toStdString() ==
                "CFP-chromophore");
        REQUIRE(
          cross_link.getCrossLinkerCstSPtr()->getFormula().toStdString() ==
          "-H2OH2");
      }

      AND_WHEN(
        "Another CrossLink is allocated by copy-construction or assignment")
      {
        CrossLink another_cross_link(cross_link);
        CrossLink other_cross_link;
        other_cross_link = another_cross_link;

        THEN("The new CrossLink instance have to be identical to the first one")
        {
          REQUIRE(other_cross_link.getPolymerCstSPtr() == polymer_sp);
          REQUIRE(other_cross_link.getComment().toStdString() ==
                  "This is a comment");
          REQUIRE(other_cross_link.getCrossLinkerName().toStdString() ==
                  "CFP-chromophore");
          REQUIRE(other_cross_link.getCrossLinkerCstSPtr()
                    ->getFormula()
                    .toStdString() == "-H2OH2");
        }
      }
    }
  }
}

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

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_link.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 is allocated as a SPtr and then the XML file is loaded from "
    "disk")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 240);

    WHEN("A CrossLinker is referenced from the PolChemDef by name")
    {
      CrossLinkerCstSPtr cross_linker_csp =
        pol_chem_def_csp->getCrossLinkerCstSPtrByName("CFP-chromophore");
      REQUIRE(cross_linker_csp != nullptr);
      REQUIRE(cross_linker_csp->getPolChemDefCstSPtr() == pol_chem_def_csp);

      THEN(
        "Its masses have to be ready because the CrossLinker is in the "
        "PolChemDef")
      {
        REQUIRE_THAT(cross_linker_csp->getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(-20.0262147493, 0.0000000001));
        REQUIRE_THAT(cross_linker_csp->getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(-20.0311745907, 0.0000000001));

        double mono = 1000;
        double avg  = 1001;

        cross_linker_csp->accountMasses(mono, avg, 3);
        REQUIRE_THAT(
          mono,
          Catch::Matchers::WithinAbs(1000 - (20.0262147493 * 3), 0.0000000001));
        REQUIRE_THAT(
          avg,
          Catch::Matchers::WithinAbs(1001 - (20.0311745907 * 3), 0.0000000001));
      }

      WHEN(
        "A CrossLink instance is created using that CrossLinker instance, "
        "the "
        "CSPtr is gotten from the raw pointer")
      {
        CrossLinkSPtr cross_link_sp =
          std::make_shared<CrossLink>(cross_linker_csp,
                                      polymer_sp->getCstSharedPtr(),
                                      "This is the CFP-chromophore cross-link");

        AND_WHEN(
          "The cross-link is actually created by adding Monomer instances")
        {

          bool ok = false;

          // Failing addition:
          cross_link_sp->fillInMonomers("", ok);
          REQUIRE_FALSE(ok);
          REQUIRE(cross_link_sp->getMonomersCstRef().size() == 0);

          // And now the real one
          cross_link_sp->fillInMonomers(";66;67;", ok);

          THEN("The Monomer instances are correctly set")
          {
            REQUIRE(ok);
            REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
            REQUIRE(cross_link_sp
                      ->continuumOfLocationsOfInclusiveSequenceMonomersAsText(
                        Enums::LocationType::INDEX)
                      .toStdString() == ";66;67;");
          }
          AND_WHEN("Another Monomer instance is set also")
          {
            MonomerSPtr third_monomer_sp =
              polymer_sp->getSequenceCstRef().getMonomerCstSPtrAt(68);

            cross_link_sp->appendMonomer(third_monomer_sp);

            THEN("The new Monomer instance is set correctly and all are there")
            {
              REQUIRE(cross_link_sp->getMonomersCstRef().size() == 3);
              REQUIRE(cross_link_sp->getMonomersRef().size() == 3);

              REQUIRE(cross_link_sp
                        ->continuumOfLocationsOfInclusiveSequenceMonomersAsText(
                          Enums::LocationType::INDEX)
                        .toStdString() == ";66;67;68;");

              std::vector<std::size_t> extreme_locations =
                cross_link_sp->locationsOfOnlyExtremeSequenceMonomers(
                  Enums::LocationType::INDEX);

              REQUIRE(extreme_locations.front() == 66);
              REQUIRE(extreme_locations.back() == 68);
              REQUIRE(cross_link_sp
                        ->locationsOfOnlyExtremeSequenceMonomersAsText(
                          Enums::LocationType::INDEX)
                        .toStdString() == ";66;68;");

              MonomerCstSPtr first_monomer_csp = cross_link_sp->getMonomerAt(0);
              REQUIRE(cross_link_sp->getFirstMonomer() == first_monomer_csp);
              REQUIRE(cross_link_sp->monomerIndex(first_monomer_csp, ok) == 0);
              REQUIRE(
                cross_link_sp->monomerIndex(first_monomer_csp.get(), ok) == 0);
              REQUIRE(ok);

              MonomerCstSPtr second_monomer_csp =
                cross_link_sp->getMonomerAt(1);
              REQUIRE(cross_link_sp->monomerIndex(second_monomer_csp, ok) == 1);
              REQUIRE(
                cross_link_sp->monomerIndex(second_monomer_csp.get(), ok) == 1);
              REQUIRE(ok);

              REQUIRE(polymer_sp->getSequenceCstRef().monomerIndex(
                        first_monomer_csp, ok) == 66);
              REQUIRE(ok);
              REQUIRE(polymer_sp->getSequenceCstRef().monomerIndex(
                        second_monomer_csp, ok) == 67);
              REQUIRE(ok);

              AND_THEN("Removing and readding the last Monomer should work")
              {
                cross_link_sp->removeMonomerAt(2);
                REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
                cross_link_sp->appendMonomer(third_monomer_sp);
                REQUIRE(cross_link_sp->getMonomersCstRef().size() == 3);
              }

              THEN("The mass of the CrossLink can be computed")
              {
                REQUIRE_THAT(
                  cross_link_sp->getMass(Enums::MassType::MONO),
                  Catch::Matchers::WithinAbs(-20.0262147493, 0.0000000001));
                REQUIRE_THAT(
                  cross_link_sp->getMass(Enums::MassType::AVG),
                  Catch::Matchers::WithinAbs(-20.0311745907, 0.0000000001));

                double mono = 1000;
                double avg  = 1001;

                cross_linker_csp->accountMasses(mono, avg, 3);
                REQUIRE_THAT(mono,
                             Catch::Matchers::WithinAbs(
                               1000 - (20.0262147493 * 3), 0.0000000001));
                REQUIRE_THAT(avg,
                             Catch::Matchers::WithinAbs(
                               1001 - (20.0311745907 * 3), 0.0000000001));

                AND_WHEN(
                  "The CrossLink is actually performed onto the Polymer's "
                  "Monomer "
                  "instances")
                {
                  polymer_sp->crossLink(cross_link_sp);

                  THEN("The CrossLink must be found in the Polymer")
                  {
                    REQUIRE(polymer_sp->getCrossLinksCstRef().size() == 1);
                  }

                  AND_WHEN(
                    "Configuring calculation options NOT to account for "
                    "cross-links")
                  {
                    CalcOptions calc_options(
                      /*deep_calculation*/ false,
                      /*mass_type*/ Enums::MassType::BOTH,
                      /*capping*/ Enums::CapType::BOTH,
                      /*monomer_entities*/ Enums::ChemicalEntity::NONE,
                      /*polymer_entities*/ Enums::ChemicalEntity::NONE);
                    calc_options.setIndexRange(IndexRange(0, 240 - 1));

                    AND_WHEN("The Polymer is asked to compute its masses")
                    {
                      REQUIRE(polymer_sp->getCrossLinksCstRef().size() == 1);
                      REQUIRE(polymer_sp->calculateMasses(calc_options,
                                                          /*reset*/ true));

                      THEN("The masses are checked and are as expected")
                      {
                        REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::MONO),
                                     Catch::Matchers::WithinAbs(
                                       26885.5345715247, 0.0000000001));
                        REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::AVG),
                                     Catch::Matchers::WithinAbs(
                                       26902.1945837392, 0.0000000001));
                      }
                      AND_WHEN(
                        "Configuring calculation options to account for "
                        "cross-links")
                      {
                        REQUIRE(polymer_sp->getCrossLinksCstRef().size() == 1);
                        CalcOptions calc_options(
                          /*deep_calculation*/ false,
                          /*mass_type*/ Enums::MassType::BOTH,
                          /*capping*/ Enums::CapType::BOTH,
                          /*monomer_entities*/ Enums::ChemicalEntity::CROSS_LINKER,
                          /*polymer_entities*/ Enums::ChemicalEntity::NONE);
                        calc_options.setIndexRange(IndexRange(0, 240 - 1));

                        AND_WHEN("The Polymer is asked to compute its masses")
                        {
                          REQUIRE(polymer_sp->getCrossLinksCstRef().size() ==
                                  1);
                          REQUIRE(polymer_sp->calculateMasses(calc_options,
                                                              /*reset*/ true));

                          THEN("The masses are checked and are as expected")
                          {
                            REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::MONO),
                                         Catch::Matchers::WithinAbs(
                                           26865.5083567754, 0.0000000001));
                            REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::AVG),
                                         Catch::Matchers::WithinAbs(
                                           26882.1634091485, 0.0000000001));
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}


SCENARIO(
  "Construction of a set of 7 CrossLink instances and use with a pristine "
  "Polymer",
  "[CrossLink]")
{
  test_utils_1_letter_cross_link.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_link.msp_polChemDef;

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

  GIVEN("A Polymer instance created by loading an XML file")
  {
    PolymerQSPtr polymer_sp = Polymer::createSPtr(pol_chem_def_csp);
    REQUIRE(polymer_sp->renderXmlPolymerFile(polymer_file_path));
    REQUIRE(polymer_sp->size() == 352);

    AND_GIVEN("An allocated CrossLinker as found in the PolChemDef by name")
    {
      CrossLinkerCstSPtr cross_linker_csp =
        pol_chem_def_csp->getCrossLinkerCstSPtrByName("DisulfideBond");
      REQUIRE(cross_linker_csp != nullptr);

      REQUIRE(cross_linker_csp->getPolChemDefCstSPtr() == pol_chem_def_csp);

      //  Now allocated seven "DisulfideBond" CrossLinks

      //  THESE ARE POSITIONS,  NOT INDICES

      // DisulfideBond/;91;188
      // DisulfideBond/;231;281
      // DisulfideBond/;240;264
      // DisulfideBond/;256;277
      // DisulfideBond/;287;337
      // DisulfideBond/;296;320
      // DisulfideBond/;312;333

      bool ok = false;

      CrossLinkSPtr cross_link_sp = std::make_shared<CrossLink>(
        cross_linker_csp,
        polymer_sp->getCstSharedPtr(),
        "This one of the DisulfideBond cross-links");
      cross_link_sp->fillInMonomers(";90;187;", ok);
      REQUIRE(ok);
      REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
      polymer_sp->crossLink(cross_link_sp);

      cross_link_sp = std::make_shared<CrossLink>(
        cross_linker_csp,
        polymer_sp->getCstSharedPtr(),
        "This one of the DisulfideBond cross-links");
      cross_link_sp->fillInMonomers(";230;280;", ok);
      REQUIRE(ok);
      REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
      polymer_sp->crossLink(cross_link_sp);

      cross_link_sp = std::make_shared<CrossLink>(
        cross_linker_csp,
        polymer_sp->getCstSharedPtr(),
        "This one of the DisulfideBond cross-links");
      cross_link_sp->fillInMonomers(";239;263;", ok);
      REQUIRE(ok);
      REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
      polymer_sp->crossLink(cross_link_sp);

      cross_link_sp = std::make_shared<CrossLink>(
        cross_linker_csp,
        polymer_sp->getCstSharedPtr(),
        "This one of the DisulfideBond cross-links");
      cross_link_sp->fillInMonomers(";255;276;", ok);
      REQUIRE(ok);
      REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
      polymer_sp->crossLink(cross_link_sp);

      cross_link_sp = std::make_shared<CrossLink>(
        cross_linker_csp,
        polymer_sp->getCstSharedPtr(),
        "This one of the DisulfideBond cross-links");
      cross_link_sp->fillInMonomers(";286;336;", ok);
      REQUIRE(ok);
      REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
      polymer_sp->crossLink(cross_link_sp);

      cross_link_sp = std::make_shared<CrossLink>(
        cross_linker_csp,
        polymer_sp->getCstSharedPtr(),
        "This one of the DisulfideBond cross-links");
      cross_link_sp->fillInMonomers(";295;319;", ok);
      REQUIRE(ok);
      REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);
      polymer_sp->crossLink(cross_link_sp);

      cross_link_sp = std::make_shared<CrossLink>(
        cross_linker_csp,
        polymer_sp->getCstSharedPtr(),
        "This one of the DisulfideBond cross-links");
      cross_link_sp->fillInMonomers(";311;332;", ok);
      REQUIRE(ok);
      REQUIRE(cross_link_sp->getMonomersCstRef().size() == 2);

      MonomerCstSPtr monomer_csp =
        polymer_sp->getSequenceCstRef().getMonomerCstSPtrAt(311);
      REQUIRE(ok);
      REQUIRE(monomer_csp != nullptr);
      REQUIRE(cross_link_sp->hasUuid(monomer_csp));
      QString uuid = cross_link_sp->getUuidForMonomer(monomer_csp);

      MonomerCstSPtr same_monomer_csp = cross_link_sp->getMonomerForUuid(uuid);
      REQUIRE(same_monomer_csp == monomer_csp);

      std::vector<QString> all_monomer_uuids =  cross_link_sp->getAllMonomerUuids();
      REQUIRE(all_monomer_uuids.size() == 2);

      polymer_sp->crossLink(cross_link_sp);

      WHEN("The polymer has been cross-linked with the CrossLink instances")
      {
        THEN("The CrossLink instances must be found in the Polymer")
        {
          REQUIRE(polymer_sp->getCrossLinksCstRef().size() == 7);

          std::size_t in_count;
          std::size_t out_count;

          AND_THEN("Encompassing logic can be tested")
          {

            //  THESE ARE POSITIONS,  NOT INDICES

            // DisulfideBond/;91;188
            // DisulfideBond/;231;281
            // DisulfideBond/;240;264
            // DisulfideBond/;256;277
            // DisulfideBond/;287;337
            // DisulfideBond/;296;320
            // DisulfideBond/;312;333

            std::vector<std::size_t> monomer_indices =
              polymer_sp->getCrossLinksCstRef()
                .at(0)
                ->continuumOfLocationsOfInclusiveSequenceMonomers(
                  Enums::LocationType::INDEX);

            REQUIRE(monomer_indices.size() == 188 - 91 + 1);
            REQUIRE(monomer_indices.at(0) == 90);
            REQUIRE(monomer_indices.at(97) == 187);

            REQUIRE(polymer_sp->getCrossLinksCstRef().at(0) != nullptr);

            REQUIRE(
              polymer_sp->getCrossLinksCstRef()
                .at(0)
                ->isEncompassedByIndexRange(90, 187, in_count, out_count) ==
              Enums::CrossLinkEncompassed::FULLY);

            REQUIRE(
              polymer_sp->getCrossLinksCstRef()
                .at(0)
                ->isEncompassedByIndexRange(91, 187, in_count, out_count) ==
              Enums::CrossLinkEncompassed::PARTIALLY);

            REQUIRE(
              polymer_sp->getCrossLinksCstRef()
                .at(0)
                ->isEncompassedByIndexRange(90, 186, in_count, out_count) ==
              Enums::CrossLinkEncompassed::PARTIALLY);

            REQUIRE(
              polymer_sp->getCrossLinksCstRef()
                .at(0)
                ->isEncompassedByIndexRange(92, 187, in_count, out_count) ==
              Enums::CrossLinkEncompassed::PARTIALLY);

            REQUIRE(
              polymer_sp->getCrossLinksCstRef()
                .at(0)
                ->isEncompassedByIndexRange(92, 186, in_count, out_count) ==
              Enums::CrossLinkEncompassed::NOT);
          }

          THEN(
            "The copy-constructor and assignment-based CrossLink creation can "
            "be tested thoroughly")
          {
            CrossLink another_cross_link(*cross_link_sp);
            CrossLink other_cross_link;
            other_cross_link = another_cross_link;
            REQUIRE(other_cross_link == *cross_link_sp);
            REQUIRE_FALSE(other_cross_link != *cross_link_sp);

            REQUIRE(other_cross_link.isValid());

            double mono = 0;
            double avg  = 0;
            REQUIRE(other_cross_link.calculateMasses(
              pol_chem_def_csp->getIsotopicDataCstSPtr(), mono, avg));
            REQUIRE_THAT(
              mono, Catch::Matchers::WithinAbs(-2.01565006453, 0.0000000001));

            QString expected_text =
              "\nCross-link:\n===============\nCross-linker name: "
              "DisulfideBond\nCross-link comment: This one of the "
              "DisulfideBond cross-links\nPartner 1: C at position: "
              "312\nPartner 2: C at position: 333\n";

            REQUIRE(cross_link_sp->prepareResultsTxtString().toStdString() ==
                    expected_text.toStdString());
          }
        }

        AND_WHEN(
          "Configuring calculation options NOT to account for cross-links")
        {
          CalcOptions calc_options(
            /*deep_calculation*/ false,
            /*mass_type*/ Enums::MassType::BOTH,
            /*capping*/ Enums::CapType::BOTH,
            /*monomer_entities*/ Enums::ChemicalEntity::NONE,
            /*polymer_entities*/ Enums::ChemicalEntity::NONE);
          calc_options.setIndexRange(IndexRange(0, polymer_sp->size() - 1));

          AND_WHEN("The Polymer is asked to compute its masses")
          {
            REQUIRE(polymer_sp->calculateMasses(calc_options, /*reset*/ true));

            THEN("The masses are checked and are as expected")
            {
              REQUIRE_THAT(
                polymer_sp->getMass(Enums::MassType::MONO),
                Catch::Matchers::WithinAbs(38973.9813242497, 0.0000000001));
              REQUIRE_THAT(
                polymer_sp->getMass(Enums::MassType::AVG),
                Catch::Matchers::WithinAbs(38999.3128988044, 0.0000000001));
            }

            AND_WHEN(
              "Configuring calculation options to account for cross-links")
            {
              CalcOptions calc_options(
                /*deep_calculation*/ false,
                /*mass_type*/ Enums::MassType::BOTH,
                /*capping*/ Enums::CapType::BOTH,
                /*monomer_entities*/ Enums::ChemicalEntity::CROSS_LINKER,
                /*polymer_entities*/ Enums::ChemicalEntity::NONE);
              calc_options.setIndexRange(IndexRange(0, polymer_sp->size() - 1));

              AND_WHEN("The Polymer is asked to compute its masses")
              {
                REQUIRE(polymer_sp->calculateMasses(calc_options,
                                                    /*reset*/ true));

                THEN("The masses are checked and are as expected")
                {
                  REQUIRE_THAT(
                    polymer_sp->getMass(Enums::MassType::MONO),
                    Catch::Matchers::WithinAbs(38959.8717737979, 0.0000000001));
                  REQUIRE_THAT(
                    polymer_sp->getMass(Enums::MassType::AVG),
                    Catch::Matchers::WithinAbs(38985.2017182470, 0.0000000001));
                }
              }

              WHEN(
                "Configuring calculation options to account for "
                "cross-links "
                "with only one fully encompassed")
              {
                CalcOptions calc_options(
                  /*deep_calculation*/ false,
                  /*mass_type*/ Enums::MassType::BOTH,
                  /*capping*/ Enums::CapType::BOTH,
                  /*monomer_entities*/ Enums::ChemicalEntity::CROSS_LINKER,
                  /*polymer_entities*/ Enums::ChemicalEntity::NONE);
                calc_options.setIndexRange(IndexRange(55, 216));

                AND_WHEN("The Polymer is asked to compute its masses")
                {
                  REQUIRE(polymer_sp->calculateMasses(calc_options,
                                                      /*reset*/ true));

                  THEN("The masses are checked and are as expected")
                  {
                    REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::MONO),
                                 Catch::Matchers::WithinAbs(18241.1791940081,
                                                            0.0000000001));
                    REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::AVG),
                                 Catch::Matchers::WithinAbs(18252.6111982139,
                                                            0.0000000001));
                  }
                }
              }

              WHEN(
                "Configuring calculation options to account for "
                "cross-links "
                "with more than one fully encompassed")
              {
                CalcOptions calc_options(
                  /*deep_calculation*/ false,
                  /*mass_type*/ Enums::MassType::BOTH,
                  /*capping*/ Enums::CapType::BOTH,
                  /*monomer_entities*/ Enums::ChemicalEntity::CROSS_LINKER,
                  /*polymer_entities*/ Enums::ChemicalEntity::NONE);
                calc_options.setIndexRange(IndexRange(72, 284));

                AND_WHEN("The Polymer is asked to compute its masses")
                {
                  REQUIRE(polymer_sp->calculateMasses(calc_options,
                                                      /*reset*/ true));

                  THEN("The masses are checked and are as expected")
                  {
                    REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::MONO),
                                 Catch::Matchers::WithinAbs(23696.2700807047,
                                                            0.0000000001));
                    REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::AVG),
                                 Catch::Matchers::WithinAbs(23711.5168000099,
                                                            0.0000000001));
                  }
                }

                WHEN(
                  "Configuring calculation options to account for "
                  "cross-links "
                  "with more than one fully encompassed")
                {
                  CalcOptions calc_options(
                    /*deep_calculation*/ false,
                    /*mass_type*/ Enums::MassType::BOTH,
                    /*capping*/ Enums::CapType::BOTH,
                    /*monomer_entities*/ Enums::ChemicalEntity::CROSS_LINKER,
                    /*polymer_entities*/ Enums::ChemicalEntity::NONE);
                  calc_options.setIndexRange(IndexRange(196, 283));

                  AND_WHEN("The Polymer is asked to compute its masses")
                  {
                    REQUIRE(polymer_sp->calculateMasses(calc_options,
                                                        /*reset*/ true));

                    THEN("The masses are checked and are as expected")
                    {
                      REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::MONO),
                                   Catch::Matchers::WithinAbs(9571.3824122666,
                                                              0.0000000001));
                      REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::AVG),
                                   Catch::Matchers::WithinAbs(9577.7946954897,
                                                              0.0000000001));
                    }
                  }
                }

                WHEN(
                  "Configuring calculation options to account for "
                  "cross-links "
                  "with two incomplete cross-links")
                {
                  CalcOptions calc_options(
                    /*deep_calculation*/ false,
                    /*mass_type*/ Enums::MassType::BOTH,
                    /*capping*/ Enums::CapType::BOTH,
                    /*monomer_entities*/ Enums::ChemicalEntity::CROSS_LINKER,
                    /*polymer_entities*/ Enums::ChemicalEntity::NONE);
                  calc_options.setIndexRange(IndexRange(155, 286));

                  AND_WHEN("The Polymer is asked to compute its masses")
                  {
                    REQUIRE(polymer_sp->calculateMasses(calc_options,
                                                        /*reset*/ true));

                    THEN("The masses are checked and are as expected")
                    {
                      REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::MONO),
                                   Catch::Matchers::WithinAbs(14356.6660883098,
                                                              0.0000000001));
                      REQUIRE_THAT(polymer_sp->getMass(Enums::MassType::AVG),
                                   Catch::Matchers::WithinAbs(14366.1307048399,
                                                              0.0000000001));
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
