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


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


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


/////////////////////// Local includes
#include "TestUtils.hpp"
#include "MsXpS/libXpertMassCore/Sequence.hpp"
#include "MsXpS/libXpertMassCore/Monomer.hpp"

namespace MsXpS
{

namespace libXpertMassCore
{

TestUtils test_utils_sequence_1_letter("protein-1-letter", 1);
ErrorList error_list_sequence;

SCENARIO("Sequence object can be constructed empty", "[Sequence]")
{
  test_utils_sequence_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  WHEN("Constructing an empty Sequence")
  {
    qDebug() << "Allocate empty sequence.";

    Sequence sequence;

    THEN("The object is invalid")
    {
      REQUIRE_FALSE(sequence.isValid());
    }

    AND_WHEN("Setting the PolChemDef and then a sequence as 1-letter code text")
    {
      qDebug() << "Will set the pol chem def.";

      sequence.setPolChemDefCstSPtr(pol_chem_def_csp);
      REQUIRE(sequence.getPolChemDef() != nullptr);
      REQUIRE(sequence.getPolChemDef().get() != nullptr);

      std::vector<std::size_t> failing_indices;

      qDebug() << "Will set the sequence.";

      sequence.setSequence(
        test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter,
        failing_indices);

      THEN(
        "The sequence is valid because both PolChemDef and a proper sequence "
        "are defined.")
      {
        REQUIRE_FALSE(failing_indices.size());
        REQUIRE(sequence.isValid());
      }
    }
  }
}

SCENARIO("Sequence objects can be constructed in one go", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A fully qualified Sequence object")
  {
    Sequence sequence(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);

    THEN("The Sequence is valid")
    {
      REQUIRE(sequence.isValid());
    }
  }
}

SCENARIO("Sequence objects can be copy-constructed", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A fully qualified Sequence object")
  {
    Sequence sequence(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);

    THEN("The Sequence is valid")
    {
      REQUIRE(sequence.validate(&error_list_sequence));
      REQUIRE(sequence.isValid());
      REQUIRE(sequence.size() == 157);
    }

    WHEN("A new Sequence is copy-constructed")
    {
      REQUIRE(sequence.size() == 157);
      Sequence new_sequence(sequence);
      REQUIRE(new_sequence.size() == 157);

      THEN("The new Sequence is valid and is identical to the initial one")
      {
        REQUIRE(new_sequence.isValid());
        REQUIRE(new_sequence == sequence);
      }
    }
  }
}

SCENARIO("Sequence objects can be assignment-configured", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A fully qualified Sequence instance")
  {
    Sequence sequence(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);

    THEN("The Sequence is valid")
    {
      REQUIRE(sequence.validate(&error_list_sequence));
      REQUIRE(sequence.isValid());
    }

    WHEN("Instantiating an empty sequence and assigning to it the previous one")
    {
      Sequence new_sequence;
      new_sequence = sequence;

      REQUIRE((sequence = sequence) == sequence);
      REQUIRE((new_sequence = new_sequence) == new_sequence);

      THEN("The new Sequence is valid and is identical to the initial one")
      {
        REQUIRE(new_sequence.validate(&error_list_sequence));
        REQUIRE(new_sequence.isValid());
        REQUIRE(new_sequence == sequence);
      }

      THEN("The sequence can be gotten back ")
      {
        REQUIRE(sequence.getSequence().toStdString() ==
                test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter
                  .toStdString());
        REQUIRE(new_sequence.getSequence().toStdString() ==
                test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter
                  .toStdString());
      }
    }
  }
}

SCENARIO("Sequence objects can be tested for (in)equality", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A fully qualified Sequence object and a copy of it")
  {
    Sequence sequence(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);
    Sequence new_sequence(sequence);

    THEN("The sequences are identical and can be tested for (in)equality")
    {
      REQUIRE(new_sequence == sequence);
      REQUIRE_FALSE(new_sequence != sequence);
    }

    THEN(
      "If the sequence is compared to itself the proper result must be "
      "returned")
    {
      REQUIRE(sequence == sequence);
      REQUIRE_FALSE(sequence != sequence);
    }

    AND_WHEN("A copy of that sequence is made using the assignment operator")
    {
      Sequence new_sequence;
      new_sequence = sequence;

      THEN("Both sequences can be compared")
      {
        REQUIRE(new_sequence == sequence);
        REQUIRE_FALSE(new_sequence != sequence);
      }
    }
  }
}

SCENARIO(
  "Sequence instances can be iterated into code by code (1-letter-code case)",
  "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A newly empty allocated sequence")
  {
    Sequence sequence_1(pol_chem_def_csp, "");

    WHEN("Setting sequence as 1-letter code text")
    {

      std::vector<std::size_t> failing_indices;
      sequence_1.setSequence(
        test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter,
        failing_indices);

      THEN(
        "The sequence cannot be gotten back because makeMonomerList() must be "
        "called first")
      {
        REQUIRE(sequence_1.getSequence().toStdString() ==
                test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter
                  .toStdString());
      }

      THEN("Asking for next code starting at index 0 should work")
      {
        QString sequence = sequence_1.getSequence();
        QString next_code;
        std::size_t last_parsed_code_char_index = 0;
        QString error;
        // All the monomer codes are 1-letter-long.
        std::size_t parsed_code_length = sequence_1.nextCode(
          sequence, next_code, last_parsed_code_char_index, error);

        REQUIRE(next_code.toStdString() == "M");
        // The very first index will be returned because we have a 1-letter
        // code string and the very first code is at index 0..
        REQUIRE(parsed_code_length == 1);
        REQUIRE(last_parsed_code_char_index == 0);
        REQUIRE(!error.size());

        AND_THEN(
          "Asking for next code using and incremented index, should work")
        {
          int parsed_code_length = sequence_1.nextCode(
            sequence, next_code, ++last_parsed_code_char_index, error);

          REQUIRE(next_code.toStdString() == "A");
          REQUIRE(parsed_code_length == 1);
          REQUIRE(last_parsed_code_char_index == 1);
          REQUIRE(!error.size());
        }
      }
    }
  }

  GIVEN("A newly empty allocated sequence")
  {
    Sequence sequence_1(pol_chem_def_csp, "");

    WHEN("Setting sequence as 1-letter code text")
    {
      std::vector<std::size_t> failing_indices;

      sequence_1.setSequence(
        test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter,
        failing_indices);

      THEN("The sequence can be gotten back")
      {
        REQUIRE(sequence_1.getSequence().toStdString() ==
                test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter
                  .toStdString());

        AND_THEN("Asking for next code starting at index 0 should work")
        {
          QString sequence = sequence_1.getSequence();
          QString next_code;
          std::size_t last_parsed_code_char_index = 0;
          QString error;
          // All the monomer codes are 1-letter-long.
          int parsed_code_length = sequence_1.nextCode(
            sequence, next_code, last_parsed_code_char_index, error);

          REQUIRE(next_code.toStdString() == "M");
          // The very first index will be returned because we have a 1-letter
          // code string and the very first code is at index 0..
          REQUIRE(parsed_code_length == 1);
          REQUIRE(last_parsed_code_char_index == 0);
          REQUIRE(!error.size());

          AND_THEN(
            "Asking for next code using an incremented index, should work")
          {
            int parsed_code_length = sequence_1.nextCode(
              sequence, next_code, ++last_parsed_code_char_index, error);

            REQUIRE(next_code.toStdString() == "A");
            REQUIRE(parsed_code_length == 1);
            REQUIRE(last_parsed_code_char_index == 1);
            REQUIRE(!error.size());
          }
        }
      }
    }
  }
}

SCENARIO(
  "Sequence instances can be iterated into code by code (3-letter-code case)",
  "[Sequence]")
{
  TestUtils test_utils_sequence_3_letter("protein-3-letters", 1);

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_3_letter.msp_polChemDef;

  GIVEN("A newly empty allocated sequence")
  {
    Sequence sequence_1(pol_chem_def_csp, "");

    WHEN("Setting sequence as 3-letter code text")
    {
      std::vector<std::size_t> failing_indices;

      sequence_1.setSequence(
        test_utils_sequence_3_letter.m_telokinAsMonomerText3Letters,
        failing_indices);

      THEN("The sequence can be checked")
      {
        REQUIRE(sequence_1.getSequence().toStdString() ==
                test_utils_sequence_3_letter.m_telokinAsMonomerText3Letters
                  .toStdString());

        AND_THEN("Asking for next code starting at index 0 should work")
        {
          QString sequence = sequence_1.getSequence();
          QString next_code;
          std::size_t last_parsed_code_char_index = 0;
          QString error;
          // All the monomer codes are 3-letter-long.
          int parsed_code_length = sequence_1.nextCode(
            sequence, next_code, last_parsed_code_char_index, error);

          REQUIRE(next_code.toStdString() == "Met");
          // The very first index will be returned because we have a 1-letter
          // code string and the very first code is at index 0..
          REQUIRE(parsed_code_length == 3);
          REQUIRE(last_parsed_code_char_index == 2);
          REQUIRE(!error.size());

          AND_THEN(
            "Asking for next code using and incremented index, should work")
          {
            int parsed_code_length = sequence_1.nextCode(
              sequence, next_code, ++last_parsed_code_char_index, error);

            REQUIRE(next_code.toStdString() == "Ala");
            REQUIRE(parsed_code_length == 3);
            REQUIRE(last_parsed_code_char_index == 5);
            REQUIRE(!error.size());
          }
        }
      }
    }
  }
}

SCENARIO("Sequence_s can be copied and compared", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("Newly allocated sequences with string sequences during construction")
  {
    Sequence sequence_1(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);
    Sequence sequence_2(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinNtermPeptideAsMonomerText1Letter);

    REQUIRE_FALSE(sequence_1 == sequence_2);
    REQUIRE(sequence_1 != sequence_2);

    WHEN("A new Sequence is either copy-constructed or assigned")
    {
      Sequence sequence_3(sequence_1);
      Sequence sequence_4 = sequence_1;

      THEN("The two Sequence objects should be identical (monomer text-wise)")
      {
        REQUIRE(sequence_3 == sequence_1);
        REQUIRE_FALSE(sequence_3 != sequence_1);

        REQUIRE(sequence_4 == sequence_1);
        REQUIRE_FALSE(sequence_4 != sequence_1);
      }

      AND_WHEN("A Sequence is either assigned a different sequence")
      {
        sequence_4 = sequence_2;

        THEN("The two Sequence objects should be identical (monomer text-wise)")
        {
          REQUIRE(sequence_4 == sequence_2);
          REQUIRE_FALSE(sequence_4 != sequence_2);
        }
      }
    }
  }

  GIVEN("Two Sequence instances differing by only one Monomer")
  {
    QString sequence_string_1 = "MAMISGMSGRKAS";
    Sequence sequence_1(pol_chem_def_csp, sequence_string_1);
    QString sequence_string_2 = "MAMISGWSGRKAS";
    Sequence sequence_2(pol_chem_def_csp, sequence_string_2);

    REQUIRE_FALSE(sequence_1 == sequence_2);
    REQUIRE(sequence_1 != sequence_2);

    WHEN("A new Sequence is either copy-constructed or assigned")
    {
      Sequence sequence_3(sequence_1);
      Sequence sequence_4 = sequence_1;

      THEN(
        "The two Sequence objects should be identical (monomer "
        "text-wise)")
      {
        REQUIRE(sequence_3 == sequence_1);
        REQUIRE_FALSE(sequence_3 != sequence_1);

        REQUIRE(sequence_4 == sequence_1);
        REQUIRE_FALSE(sequence_4 != sequence_1);
      }

      AND_WHEN("A Sequence is either assigned a different sequence")
      {
        sequence_4 = sequence_2;

        THEN(
          "The two Sequence objects should be identical (monomer "
          "text-wise)")
        {
          REQUIRE(sequence_4 == sequence_2);
          REQUIRE_FALSE(sequence_4 != sequence_2);
        }
      }
    }
  }
}

SCENARIO("Monomers can be retrieved by pointer from a Sequence", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  Sequence sequence_1(
    pol_chem_def_csp,
    test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);

  GIVEN("A newly allocated sequence with a string sequence during construction")
  {
    WHEN("Retrieving the index of a inexistent Monomer fails")
    {
      MonomerSPtr monomer_sp =
        std::make_shared<Monomer>(pol_chem_def_csp, "NOT_SET", "NOT_SET");

      bool ok = false;
      REQUIRE(sequence_1.monomerIndex(monomer_sp, ok) == 0);
      REQUIRE_FALSE(ok);
    }

    WHEN("Retrieving the Monomer at a given index as MonomerCstSPtr")
    {
      MonomerSPtr monomer_csp = sequence_1.getMonomerCstSPtrAt(4);

      THEN("The identity of the monomer can be checked")
      {
        REQUIRE(monomer_csp->getCode().toStdString() == "S");

        AND_THEN("Retrieving the index of that Monomer using the raw pointer")
        {

          bool ok = false;
          REQUIRE(sequence_1.monomerIndex(monomer_csp.get(), ok) == 4);
          REQUIRE(ok);
        }
      }
    }
  }
}

SCENARIO("A motif can be searched in a Sequence", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A newly allocated sequence with a string sequence during construction")
  {
    Sequence sequence_1(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);
    QString monomer_text = sequence_1.getSequence();
    REQUIRE(sequence_1.size() == static_cast<std::size_t>(monomer_text.size()));

    std::size_t search_index_1 = 0;
    std::size_t search_index_2 = 0;
    std::size_t search_index_3 = 0;
    std::size_t search_index_4 = 0;
    std::size_t search_index_5 = 0;
    std::size_t search_index_6 = 0;
    std::size_t search_index_7 = 0;

    Sequence motif_sequence_1_occurrence(pol_chem_def_csp, "MAMISGM");
    Sequence motif_sequence_6_occurrences(pol_chem_def_csp, "EE");

    WHEN("A single-copy existing motif is searched at a bad index (index: -1)")
    {
      std::size_t bad_index = -1;
      int result_1 =
        sequence_1.findForwardMotif(motif_sequence_1_occurrence, bad_index);

      THEN("The motif cannot be found")
      {
        REQUIRE(result_1 == -1);
      }
    }

    WHEN(
      "A single-copy existing motif is searched at a bad index (index: >= "
      "size())")
    {
      std::size_t bad_index =
        test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter.size();
      int result_1 =
        sequence_1.findForwardMotif(motif_sequence_1_occurrence, bad_index);

      THEN("The motif cannot be found")
      {
        REQUIRE(result_1 == -1);
      }
    }

    WHEN("An empty motif is searched")
    {
      Sequence empty_motif(pol_chem_def_csp, "");
      int result_1 = sequence_1.findForwardMotif(empty_motif, search_index_1);

      THEN("The motif cannot be found")
      {
        REQUIRE(result_1 == 0);
      }
    }

    WHEN(
      "A single-copy existing motif is searched with an index that results "
      "out-of-bounds")
    {
      std::size_t bad_index =
        test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter.size() - 1;
      int result_1 =
        sequence_1.findForwardMotif(motif_sequence_6_occurrences, bad_index);

      THEN("The motif cannot be found")
      {
        REQUIRE(result_1 == 0);
      }
    }

    WHEN("A single-copy existing motif is searched")
    {
      int result_1 = sequence_1.findForwardMotif(motif_sequence_1_occurrence,
                                                 search_index_1);

      search_index_2 = search_index_1 + motif_sequence_1_occurrence.size();
      int result_2   = sequence_1.findForwardMotif(motif_sequence_1_occurrence,
                                                 search_index_2);

      THEN("The motif should be found only once")
      {
        REQUIRE(result_1 == 1);
        REQUIRE(search_index_1 == 0);
        REQUIRE(result_2 == 0);
      }

      AND_WHEN("A a seven-copy existing motif is searched")
      {
        search_index_1 = 0;

        // qDebug()  << "search_index_1: " << search_index_1 << std::endl;
        // qDebug()  << "search_index_2: " << search_index_2 << std::endl;

        int result_1 = sequence_1.findForwardMotif(motif_sequence_6_occurrences,
                                                   search_index_1);

        search_index_2 = search_index_1 + motif_sequence_6_occurrences.size();
        int result_2 = sequence_1.findForwardMotif(motif_sequence_6_occurrences,
                                                   search_index_2);

        search_index_3 = search_index_2 + motif_sequence_6_occurrences.size();
        int result_3 = sequence_1.findForwardMotif(motif_sequence_6_occurrences,
                                                   search_index_3);

        search_index_4 = search_index_3 + motif_sequence_6_occurrences.size();
        int result_4 = sequence_1.findForwardMotif(motif_sequence_6_occurrences,
                                                   search_index_4);

        search_index_5 = search_index_4 + motif_sequence_6_occurrences.size();
        int result_5 = sequence_1.findForwardMotif(motif_sequence_6_occurrences,
                                                   search_index_5);

        search_index_6 = search_index_5 + motif_sequence_6_occurrences.size();
        int result_6 = sequence_1.findForwardMotif(motif_sequence_6_occurrences,
                                                   search_index_6);

        search_index_7 = search_index_6 + motif_sequence_6_occurrences.size();
        int result_7 = sequence_1.findForwardMotif(motif_sequence_6_occurrences,
                                                   search_index_7);

        THEN("The motif should be found seven times")
        {
          REQUIRE(result_1 == 1);
          REQUIRE(search_index_1 == 33);

          REQUIRE(result_2 == 1);
          REQUIRE(search_index_2 == 37);

          REQUIRE(result_3 == 1);
          REQUIRE(search_index_3 == 96);

          REQUIRE(result_4 == 1);
          REQUIRE(search_index_4 == 148);

          REQUIRE(result_5 == 1);
          REQUIRE(search_index_5 == 151);

          REQUIRE(result_6 == 1);
          REQUIRE(search_index_6 == 153);

          REQUIRE(result_7 == 1);
          REQUIRE(search_index_7 == 155);
        }
      }
    }
  }
}

SCENARIO("A number of checks might be performed on a Sequence", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A newly allocated sequence with a string sequence during construction")
  {
    Sequence sequence_1(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);
    QString monomer_text = sequence_1.getSequence();
    REQUIRE(monomer_text.size() ==
            test_utils_sequence_1_letter.m_telokinSequenceMonomerCount);
    REQUIRE(static_cast<std::size_t>(monomer_text.size()) == sequence_1.size());

    std::size_t tested_index = 0;


    THEN("It is possible to check if an index is inbound or not")
    {
      REQUIRE(sequence_1.isInBound(tested_index) == true);

      tested_index = monomer_text.size() / 2;
      REQUIRE(sequence_1.isInBound(tested_index) == true);

      tested_index = monomer_text.size() - 1;
      REQUIRE(sequence_1.isInBound(tested_index) == true);

      tested_index = monomer_text.size();
      REQUIRE(sequence_1.isInBound(tested_index) == false);
    }
  }
}

SCENARIO("Monomers can be added or removed from a Sequence", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A newly allocated sequence with a string sequence during construction")
  {
    Sequence sequence_1(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);
    QString monomer_text = sequence_1.getSequence();
    REQUIRE(
      monomer_text.toStdString() ==
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter.toStdString());
    REQUIRE(monomer_text.size() ==
            test_utils_sequence_1_letter.m_telokinSequenceMonomerCount);
    REQUIRE(static_cast<std::size_t>(monomer_text.size()) == sequence_1.size());

    WHEN("A Monomer object is inserted at the beginining of the sequence")
    {
      Monomer monomer(pol_chem_def_csp, "Tryptophan", "W");

      sequence_1.insertMonomerAt(monomer, 0);

      THEN("That monomer has to be found there")
      {
        REQUIRE(sequence_1.getMonomerCstSPtrAt(0)->getCode().toStdString() ==
                "W");

        QString w_monomer_text = monomer_text;
        w_monomer_text.prepend("W");

        REQUIRE(sequence_1.getSequence().toStdString() ==
                w_monomer_text.toStdString());
      }

      AND_WHEN("When searching for a motif comprising that monomer")
      {
        Sequence motif_sequence_1_occurrence(pol_chem_def_csp, "WMAMISGMSGR");
        std::size_t search_index_1 = 0;

        int result_1 = sequence_1.findForwardMotif(motif_sequence_1_occurrence,
                                                   search_index_1);

        THEN("The search is successful")
        {
          REQUIRE(result_1 == true);
          REQUIRE(search_index_1 == 0);
        }
      }
    }

    WHEN("A monomer is inserted at the end of the sequence")
    {
      Monomer monomer(pol_chem_def_csp, "Tryptophan", "W");

      // qDebug() << "The size of the sequence:" << sequence_1.size()
      //          << "The count of monomers:"
      //          << test_utils_sequence_1_letter.m_telokinSequenceMonomerCount;

      sequence_1.insertMonomerAt(
        monomer, test_utils_sequence_1_letter.m_telokinSequenceMonomerCount);

      THEN("That monomer has to be found there")
      {
        REQUIRE(sequence_1
                  .getMonomerCstSPtrAt(
                    test_utils_sequence_1_letter.m_telokinSequenceMonomerCount)
                  ->getCode()
                  .toStdString() == "W");
      }

      AND_WHEN("When searching for a motif comprising that monomer")
      {
        Sequence motif_sequence_1_occurrence(pol_chem_def_csp, "EEEEEEW");
        std::size_t search_index_1 = 0;

        int result_1 = sequence_1.findForwardMotif(motif_sequence_1_occurrence,
                                                   search_index_1);

        THEN("The search is successful")
        {
          REQUIRE(result_1 == true);
          REQUIRE(search_index_1 == 151);
        }
      }
    }

    WHEN("A monomer is inserted in the middle of the sequence")
    {
      Monomer monomer(pol_chem_def_csp, "Tryptophan", "W");

      int floor = std::floor(sequence_1.size() / 2);
      // qDebug() << "The floor:" << floor;

      sequence_1.insertMonomerAt(monomer, floor);

      THEN("That monomer has to be found there")
      {
        REQUIRE(
          sequence_1.getMonomerCstSPtrAt(floor)->getCode().toStdString() ==
          "W");
      }

      AND_WHEN("When searching for a motif comprising that monomer")
      {
        Sequence motif_sequence_1_occurrence(pol_chem_def_csp, "DPEVMWWYKDDQ");
        std::size_t search_index_1 = 0;

        int result_1 = sequence_1.findForwardMotif(motif_sequence_1_occurrence,
                                                   search_index_1);

        THEN("The search is successful")
        {
          REQUIRE(result_1 == true);
          REQUIRE(search_index_1 == 72);
        }

        AND_WHEN("That monomer is removed back")
        {
          sequence_1.removeMonomerAt(floor);

          THEN("The sequence with the inserted monomer cannot be found anymore")
          {
            search_index_1 = 0;

            result_1 = sequence_1.findForwardMotif(motif_sequence_1_occurrence,
                                                   search_index_1);

            REQUIRE(result_1 == 0);
            REQUIRE(search_index_1 == 0);

            AND_THEN("The original sequence can be found again")
            {
              std::vector<std::size_t> failing_indices;

              motif_sequence_1_occurrence.setSequence("DPEVMWYKDDQ",
                                                      failing_indices);
              search_index_1 = 0;

              result_1 = sequence_1.findForwardMotif(
                motif_sequence_1_occurrence, search_index_1);

              REQUIRE(result_1 == 1);
              REQUIRE(search_index_1 == 72);
            }
          }

          AND_WHEN("An empty monomer sequence text is appended to the Sequence")
          {
            Sequence sequence_2 = sequence_1;

            std::vector<std::size_t> failing_indices;

            sequence_1.appendSequence("", failing_indices);

            THEN("The sequence should remain unchanged")
            {
              REQUIRE(sequence_1.size() == sequence_2.size());
              REQUIRE(sequence_1 == sequence_2);
            }

            AND_WHEN("A monomer sequence text is appended to the Sequence")
            {
              THEN("The original sequence can be found again")
              {
                // qDebug() << "The sequence:" << sequence_1.getSequence(70,
                // 85);
                std::vector<std::size_t> failing_indices;

                motif_sequence_1_occurrence.setSequence("DPEVMWYKDDQ",
                                                        failing_indices);

                search_index_1 = 0;

                result_1 = sequence_1.findForwardMotif(
                  motif_sequence_1_occurrence, search_index_1);

                // qDebug() << "The index at which the motif was found:"
                //          << search_index_1;

                REQUIRE(result_1 == 1);
                REQUIRE(search_index_1 == 72);
              }

              REQUIRE(sequence_1.getSequence().toStdString() ==
                      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter
                        .toStdString());

              std::vector<std::size_t> failing_indices;

              sequence_1.appendSequence("THISTEQST", failing_indices);

              THEN("Searching for it should return the right index")
              {
                Sequence motif_sequence_1_occurrence(pol_chem_def_csp,
                                                     "EEEEEETHISTEQST");
                std::size_t search_index_1 = 0;

                int result_1 = sequence_1.findForwardMotif(
                  motif_sequence_1_occurrence, search_index_1);

                REQUIRE(result_1 == true);
                REQUIRE(search_index_1 == 151);
              }
            }
          }
        }
      }
    }
  }
}

SCENARIO(
  "Ranges of Monomer instances can be extracted in various ways from a "
  "Sequence",
  "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  GIVEN("A newly allocated sequence with a string sequence during construction")
  {
    Sequence sequence_1(
      pol_chem_def_csp,
      test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);
    QString monomer_text = sequence_1.getSequence();
    REQUIRE(monomer_text.size() ==
            test_utils_sequence_1_letter.m_telokinSequenceMonomerCount);
    REQUIRE(static_cast<std::size_t>(monomer_text.size()) == sequence_1.size());

    IndexRange index_range(0, 12);
    IndexRangeCollection index_range_collection;
    index_range_collection.appendIndexRange(index_range);

    WHEN("Trying to get an index-based Monomer text")
    {
      QString text = sequence_1.getSequence(0, 12, /*with_modif*/ false);

      THEN("The returned string must the right size and sequence")
      {
        REQUIRE(text.size() == 13);
        REQUIRE(text.toStdString() == "MAMISGMSGRKAS");
      }
    }

    WHEN("Trying to get a sequence ranged-based Monomer text")
    {
      QString text = sequence_1.getSequence(index_range_collection,
                                            /*with_modif*/ false,
                                            /*delimited_regions*/ false);

      THEN("The returned string must the right size and sequence")
      {
        REQUIRE(text.size() == 13);
        REQUIRE(text.toStdString() == "MAMISGMSGRKAS");
      }
    }

    WHEN(
      "Modifying one monomer in the Sequence (using std::const_pointer_cast)")
    {
      std::unique_ptr<Modif> phospho_up =
        std::make_unique<Modif>(pol_chem_def_csp, "Phosphorylation", "H1O3P1");
      phospho_up->setTargets("S");
      phospho_up->setMaxCount(1);

      MonomerSPtr serine_7_csp = sequence_1.getMonomerCstSPtrAt(7);
      // qDebug() << "The monomer at index 7:" << serine_7_csp->toString();
      REQUIRE(serine_7_csp->getCode().toStdString() == "S");
      REQUIRE(serine_7_csp->isModifTarget(*phospho_up.get()) == true);

      //  Need to cast away constness of the MonomerCstSPtr.
      MonomerSPtr serine_7_sp = std::const_pointer_cast<Monomer>(serine_7_csp);
      REQUIRE(serine_7_sp->modify(*phospho_up.get(),
                                  /* override modif count */ false,
                                  &error_list_sequence) != nullptr);

      AND_WHEN(
        "Asking for the monomer index-based range without specifying that "
        "modifs are requested")
      {
        QString text = sequence_1.getSequence(0, 12, /*with_modif*/ false);

        THEN("The returned string must match the peptidic Monomer text")
        {
          REQUIRE(text.toStdString() == "MAMISGMSGRKAS");
        }
      }

      AND_WHEN(
        "Asking for the monomer coordinates-based range without specifying "
        "that modifs are requested")
      {
        QString text = sequence_1.getSequence(index_range_collection,
                                              /*with_modif*/ false,
                                              /*delimited_regions*/ false);

        THEN("The returned string must match the peptidic Monomer text")
        {
          REQUIRE(text.toStdString() == "MAMISGMSGRKAS");
        }
      }

      AND_WHEN(
        "Asking for the monomer index-based range with specifying that modifs "
        "are requested")
      {
        QString text = sequence_1.getSequence(0, 12, /*with_modif*/ true);

        THEN("The returned string must match the peptidic Monomer text")
        {
          REQUIRE(text.toStdString() == "MAMISGMS<Phosphorylation>GRKAS");
        }
      }

      AND_WHEN(
        "Asking for the monomer coordinates-based range with specifying that "
        "modifs "
        "are "
        "requested")
      {
        QString text = sequence_1.getSequence(index_range_collection,
                                              /*with_modif*/ true,
                                              /*delimited_regions*/ false);

        THEN("The returned string must match the peptidic Monomer text")
        {
          REQUIRE(text.toStdString() == "MAMISGMS<Phosphorylation>GRKAS");
        }
      }
      AND_WHEN(
        "Asking for the monomer coordinates-based range with specifying that "
        "modifs "
        "are "
        "requested and with delimited regions")
      {
        QString text = sequence_1.getSequence(index_range_collection,
                                              /*with_modif*/ true,
                                              /*delimited_regions*/ true);

        THEN("The returned string must match the peptidic Monomer text")
        {
          REQUIRE(text.toStdString() ==
                  "Region [1-13]: MAMISGMS<Phosphorylation>GRKAS\n");
        }
      }
    }
  }
}

SCENARIO("A sequence can compute a checksum", "[Sequence]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_sequence_1_letter.msp_polChemDef;

  Sequence sequence_1(
    pol_chem_def_csp,
    test_utils_sequence_1_letter.m_telokinAsMonomerText1Letter);

  quint16 check_sum_no_modifs = 0;
  quint16 check_sum_modifs    = 0;

  GIVEN(
    "A newly allocated non-modified sequence with a string sequence during "
    "construction")
  {
    WHEN("Computing the checksum")
    {
      qDebug() << "The sequence size is:"<< sequence_1.getSequence().size();

      quint16 check_sum =
        sequence_1.checksum(0, sequence_1.getSequence().size() - 1);

      THEN("The returned check sum is not zero")
      {
        REQUIRE(check_sum != 0);
      }
    }

    WHEN("Computing the checksum without modifs")
    {
      check_sum_no_modifs = sequence_1.checksum(
        0, sequence_1.getSequence().size() - 1, /*with_modifs*/ false);

      AND_WHEN("Computing the checksum with the modifs")
      {
        check_sum_modifs = sequence_1.checksum(
          0, sequence_1.getSequence().size() - 1, /*with_modifs*/ true);

        THEN(
          "Both check sums should be identical because the sequence is not "
          "modified")
        {
          REQUIRE(check_sum_no_modifs == check_sum_modifs);
        }

        AND_WHEN("The sequence is phosphorylated on a serine")
        {
          std::unique_ptr<Modif> phospho_up = std::make_unique<Modif>(
            pol_chem_def_csp, "Phosphorylation", "H1O3P1");
          phospho_up->setTargets("S");
          phospho_up->setMaxCount(1);

          MonomerSPtr serine_7_csp = sequence_1.getMonomerCstSPtrAt(7);
          REQUIRE(serine_7_csp->getCode().toStdString() == "S");
          REQUIRE(serine_7_csp->isModifTarget(*phospho_up.get()) == true);

          //  Need to cast away constness of the MonomerCstSPtr.
          MonomerSPtr serine_7_sp =
            std::const_pointer_cast<Monomer>(serine_7_csp);
          REQUIRE(serine_7_sp->modify(*phospho_up.get(),
                                      /* override modif count */ false,
                                      &error_list_sequence) != nullptr);

          THEN(
            "The new checksum with modifs must be different than the one "
            "calculated before")
          {
            quint16 new_check_sum = sequence_1.checksum(
              0, sequence_1.getSequence().size() - 1, /*with_modifs*/ true);
            REQUIRE(new_check_sum != 0);
            REQUIRE(new_check_sum != check_sum_modifs);
          }
        }
      }
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
