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


namespace MsXpS
{
namespace libXpertMassCore
{


SCENARIO("Construction of a SequenceRanges instance", "[SequenceRanges]")
{
  QString sequence_range_string_1("[50-150]");
  SequenceRange sequence_range_1(50, 150);

  QString sequence_range_string_2("[40-160]");
  SequenceRange sequence_range_2(40, 160);

  QString sequence_range_string_3("[30-170]");
  SequenceRange sequence_range_3(30, 170);

  QString all_three_sequence_range_strings = QString("%1%2%3")
                                               .arg(sequence_range_string_1)
                                               .arg(sequence_range_string_2)
                                               .arg(sequence_range_string_3);

  GIVEN("Construction with no argument")
  {
    SequenceRanges sequence_ranges;

    WHEN("A comment is set to it")
    {
      sequence_ranges.setComment("Comment");

      THEN("The comment should be set")
      {
        REQUIRE(sequence_ranges.getComment().toStdString() == "Comment");
      }
    }
  }

  GIVEN(
    "Construction of a SequenceRanges instance with a string representing "
    "three SequenceRange instances")
  {
    SequenceRanges sequence_ranges(all_three_sequence_range_strings);

    THEN("The list must have size 3")
    {
      REQUIRE(sequence_ranges.size() == 3);

      AND_THEN("All the members of the list should have proper values")
      {
        SequenceRange first_sequence_range =
          sequence_ranges.getCstReferenceAt(0);

        REQUIRE(first_sequence_range.first == sequence_range_1.first - 1);
        REQUIRE(first_sequence_range.second == sequence_range_1.second - 1);

        SequenceRange second_sequence_range =
          sequence_ranges.getCstReferenceAt(1);

        REQUIRE(second_sequence_range.first == sequence_range_2.first - 1);
        REQUIRE(second_sequence_range.second == sequence_range_2.second - 1);

        SequenceRange third_sequence_range =
          sequence_ranges.getCstReferenceAt(2);

        REQUIRE(third_sequence_range.first == sequence_range_3.first - 1);
        REQUIRE(third_sequence_range.second == sequence_range_3.second - 1);
      }
    }
  }

  GIVEN(
    "An incorrect string incorrectly representing three SequenceRange (missing "
    "1 "
    "'[')")
  {
    QString all_three_sequence_range_strings("50-150][40-160][30-170]");

    WHEN("Constructing a SequenceRanges instance")
    {
      SequenceRanges sequence_ranges(all_three_sequence_range_strings);

      THEN("The list is created empty")
      {
        REQUIRE(sequence_ranges.size() == 0);
      }
    }
  }

  GIVEN(
    "An incorrect string incorrectly representing three SequenceRange (missing "
    "1 "
    "']'")
  {
    QString all_three_sequence_range_strings("[50-150][40-160[30-170]");

    WHEN("Constructing a SequenceRanges instance")
    {
      SequenceRanges sequence_ranges(all_three_sequence_range_strings);

      THEN("The list is created empty")
      {
        REQUIRE(sequence_ranges.size() == 0);
      }
    }
  }

  GIVEN(
    "An incorrect string incorrectly representing three SequenceRange (missing "
    "1 "
    "']' and 1 '['")
  {
    QString all_three_sequence_range_strings("50-150][40-160[30-170]");

    WHEN("Constructing a SequenceRanges instance")
    {
      SequenceRanges sequence_ranges(all_three_sequence_range_strings);

      THEN("The list is created empty")
      {
        REQUIRE(sequence_ranges.size() == 0);
      }
    }
  }

  GIVEN(
    "An incorrect string incorrectly representing three SequenceRange (missing "
    "1 "
    "'-'")
  {
    QString all_three_sequence_range_strings("[50-150][40-160][30 170]");

    WHEN("Constructing a SequenceRanges instance")
    {
      SequenceRanges sequence_ranges(all_three_sequence_range_strings);

      THEN("The list is created empty")
      {
        REQUIRE(sequence_ranges.size() == 0);
      }
    }
  }

  GIVEN("Construction with a comment and no list; and SequenceRange objects")
  {
    SequenceRanges sequence_ranges;
    sequence_ranges.setComment("Comment");
    REQUIRE(sequence_ranges.getComment().toStdString() == "Comment");
    REQUIRE(sequence_ranges.size() == 0);

    WHEN("One SequenceRange object is appended to the list")
    {
      sequence_ranges.appendSequenceRange(sequence_range_1);

      THEN(
        "The list must have size 1, and that object has to be the one appended")
      {
        REQUIRE(sequence_ranges.size() == 1);
        REQUIRE(sequence_ranges.getCstReferenceAt(0) == sequence_range_1);
      }

      AND_WHEN("Another SequenceRange object is appended to the list")
      {
        sequence_ranges.appendSequenceRange(sequence_range_2);

        THEN(
          "The list must have size 2, and that object has to be the last of "
          "the list")
        {
          REQUIRE(sequence_ranges.size() == 2);
          REQUIRE(sequence_ranges.getCstReferenceAt(sequence_ranges.size() -
                                                    1) == sequence_range_2);
        }

        AND_WHEN(
          "That 2-sized SequenceRanges instance is copied (copy-constructor "
          "with reference)")
        {
          SequenceRanges new_sequence_ranges(sequence_ranges);

          THEN("The two SequenceRanges instances must have the same data")
          {
            REQUIRE(new_sequence_ranges.getComment().toStdString() ==
                    sequence_ranges.getComment().toStdString());
            REQUIRE(new_sequence_ranges.size() == sequence_ranges.size());
            REQUIRE(new_sequence_ranges.getCstReferenceAt(0) ==
                    sequence_ranges.getCstReferenceAt(0));
            REQUIRE(new_sequence_ranges.getCstReferenceAt(
                      new_sequence_ranges.size() - 1) ==
                    sequence_ranges.getCstReferenceAt(
                      new_sequence_ranges.size() - 1));
          }
        }

        AND_WHEN(
          "That 2-sized SequenceRanges instance is copied (assignment "
          "operator) to itself,  a reference to itself is returned")
        {
          REQUIRE((sequence_ranges = sequence_ranges) == sequence_ranges);
        }

        AND_WHEN(
          "That 2-sized SequenceRanges instance is copied (assignment "
          "operator)")
        {
          SequenceRanges new_sequence_ranges;
          new_sequence_ranges = sequence_ranges;

          THEN("The two SequenceRanges instances must have the same data")
          {
            REQUIRE(new_sequence_ranges.getComment().toStdString() ==
                    sequence_ranges.getComment().toStdString());
            REQUIRE(new_sequence_ranges.size() == sequence_ranges.size());
            REQUIRE(new_sequence_ranges.getCstReferenceAt(0) ==
                    sequence_ranges.getCstReferenceAt(0));
            REQUIRE(
              new_sequence_ranges.getCstReferenceAt(new_sequence_ranges.size() -
                                                    1) ==
              sequence_ranges.getCstReferenceAt(sequence_ranges.size() - 1));
          }
        }
      }
    }
  }
}

SCENARIO(
  "Empty SequenceRanges instances can be filled-in after construction with "
  "other SequenceRanges",
  "[SequenceRanges]")
{

  GIVEN(
    "Construction of a SequenceRanges instance with two SequenceRange "
    "instances")
  {
    SequenceRange sequence_range_1(50, 150);
    SequenceRange sequence_range_2(40, 160);
    SequenceRange sequence_range_3(30, 170);

    SequenceRanges sequence_ranges_0;
    sequence_ranges_0.setComment("Giver");
    sequence_ranges_0.appendSequenceRange(sequence_range_1);
    sequence_ranges_0.appendSequenceRange(sequence_range_2);

    REQUIRE(sequence_ranges_0.size() == 2);
    REQUIRE(sequence_ranges_0.getCstReferenceAt(0) == sequence_range_1);
    REQUIRE(sequence_ranges_0.getCstReferenceAt(sequence_ranges_0.size() - 1) ==
            sequence_range_2);

    AND_GIVEN("Construction of an empty SequenceRanges instance")
    {
      SequenceRanges sequence_ranges;
      sequence_ranges.setComment("Receiver");
      REQUIRE(sequence_ranges.getComment().toStdString() == "Receiver");
      REQUIRE(sequence_ranges.size() == 0);

      WHEN(
        "The first SequenceRanges instance with two items is appended to the "
        "the empty one")
      {
        sequence_ranges.appendSequenceRanges(sequence_ranges_0);

        THEN("Its size must become the same at the one that was appended")
        {
          REQUIRE(sequence_ranges.size() == sequence_ranges_0.size());
          REQUIRE(sequence_ranges.getCstReferenceAt(0) ==
                  sequence_ranges_0.getCstReferenceAt(0));
          REQUIRE(
            sequence_ranges.getCstReferenceAt(sequence_ranges.size() - 1) ==
            sequence_ranges_0.getCstReferenceAt(sequence_ranges_0.size() - 1));
        }

        AND_WHEN("A new SequenceRange instance is set (not appended)")
        {
          sequence_ranges.setSequenceRange(sequence_range_3);

          THEN("The single instance has to be this last")
          {
            REQUIRE(sequence_ranges.size() == 1);
            REQUIRE(sequence_ranges.getCstReferenceAt(0) == sequence_range_3);
          }

          AND_WHEN("A SequenceRanges instance is set (not appended)")
          {
            sequence_ranges.setSequenceRanges(sequence_ranges_0);

            THEN(
              "The previous single instance has to be lost and be replaced by "
              "the new ones")
            {
              REQUIRE(sequence_ranges.size() == 2);
              REQUIRE(sequence_ranges.getCstReferenceAt(0) ==
                      sequence_ranges_0.getCstReferenceAt(0));
              REQUIRE(
                sequence_ranges.getCstReferenceAt(sequence_ranges.size() - 1) ==
                sequence_ranges_0.getCstReferenceAt(sequence_ranges_0.size() -
                                                    1));
            }
          }
        }
      }
    }
  }

  SCENARIO(
    "Empty SequenceRanges instances can be filled in after construction with "
    "strings representative of SequenceRange instances (single or multiple)",
    "[SequenceRanges]")
  {
    QString sequence_range_string_1("[50-150]");
    SequenceRange sequence_range_1(50, 150);

    QString sequence_range_string_2("[40-160]");
    SequenceRange sequence_range_2(40, 160);

    QString sequence_range_string_3("[30-170]");
    SequenceRange sequence_range_3(30, 170);

    QString all_three_sequence_range_strings = QString("%1%2%3")
                                                 .arg(sequence_range_string_1)
                                                 .arg(sequence_range_string_2)
                                                 .arg(sequence_range_string_3);

    GIVEN("Construction with a comment and no list; and SequenceRange objects")
    {
      SequenceRanges sequence_ranges;
      sequence_ranges.setComment("Comment");
      REQUIRE(sequence_ranges.getComment().toStdString() == "Comment");
      REQUIRE(sequence_ranges.size() == 0);

      WHEN(
        "That SequenceRanges is set with one string representative of a single "
        "SequenceRange instance")
      {
        sequence_ranges.setSequenceRanges(sequence_range_string_1);

        THEN(
          "The list must have size 1, and that object has to be the one set,  "
          "with each first/second value decremented by one to convert "
          "positions to "
          "indices")
        {
          REQUIRE(sequence_ranges.size() == 1);
          REQUIRE(sequence_ranges.getCstReferenceAt(0).first ==
                  sequence_range_1.first - 1);
          REQUIRE(sequence_ranges.getCstReferenceAt(0).second ==
                  sequence_range_1.second - 1);
        }
      }

      WHEN(
        "That SequenceRanges is set with one string representative of three "
        "SequenceRange instances")
      {
        sequence_ranges.setSequenceRanges(all_three_sequence_range_strings);

        THEN("The list must have size 3")
        {
          REQUIRE(sequence_ranges.size() == 3);
        }

        AND_THEN("All the members of the list should have proper values")
        {
          REQUIRE(sequence_ranges.getCstReferenceAt(0).first ==
                  sequence_range_1.first - 1);
          REQUIRE(sequence_ranges.getCstReferenceAt(0).second ==
                  sequence_range_1.second - 1);

          REQUIRE(sequence_ranges.getCstReferenceAt(1).first ==
                  sequence_range_2.first - 1);
          REQUIRE(sequence_ranges.getCstReferenceAt(1).second ==
                  sequence_range_2.second - 1);

          REQUIRE(sequence_ranges.getCstReferenceAt(sequence_ranges.size() - 1)
                    .first == sequence_range_3.first - 1);
          REQUIRE(sequence_ranges.getCstReferenceAt(sequence_ranges.size() - 1)
                    .second == sequence_range_3.second - 1);
        }
      }
    }
  }

  SCENARIO("SequenceRanges instances can provide topological data",
           "[SequenceRanges]")
  {
    GIVEN("A SequenceRanges instance with a single SequenceRange instance")
    {
      SequenceRanges sequence_ranges;

      QString all_sequence_range_strings("[1-20]");

      sequence_ranges.setSequenceRanges(all_sequence_range_strings);

      REQUIRE(sequence_ranges.size() == 1);

      std::vector<std::size_t> left_most_indices;
      std::vector<std::size_t> right_most_indices;

      WHEN("Asking for the leftmost and the rightmost SequenceRange")
      {
        REQUIRE(sequence_ranges.leftMostSequenceRange(left_most_indices) == 1);
        REQUIRE(sequence_ranges.rightMostSequenceRange(right_most_indices) ==
                1);

        THEN(
          "The indices should actually match the correct SequenceRange "
          "instance")
        {
          REQUIRE(
            sequence_ranges.getCstReferenceAt(left_most_indices.at(0)).first ==
            0);
          REQUIRE(sequence_ranges.getCstReferenceAt(right_most_indices.at(0))
                    .second == 19);
        }
      }

      WHEN(
        "Checking if there are overlaps,  there cannot be because only one "
        "instance")
      {
        REQUIRE_FALSE(sequence_ranges.overlap());
      }

      WHEN("Checking for indices that are encompassed or not")
      {
        THEN("The results should be consistent")
        {
          REQUIRE(sequence_ranges.encompassIndex(0));
          REQUIRE(sequence_ranges.encompassIndex(19));
          REQUIRE(sequence_ranges.encompassIndex(10));
          REQUIRE_FALSE(sequence_ranges.encompassIndex(20));
        }
      }

      WHEN("Checking for indices left most SequenceRange")
      {
        THEN("The results should be consistent")
        {
          REQUIRE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(0)));
          REQUIRE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(0)));
        }
      }
    }

    GIVEN(
      "A SequenceRanges instance with a number of non-overlapping "
      "SequenceRange")
    {
      SequenceRanges sequence_ranges;

      QString all_sequence_range_strings("[1-20][25-45][55-100]");

      sequence_ranges.setSequenceRange(all_sequence_range_strings);

      REQUIRE(sequence_ranges.size() == 3);

      std::vector<std::size_t> left_most_indices;
      std::vector<std::size_t> right_most_indices;

      WHEN("Asking for the leftmost and the rightmost SequenceRange")
      {
        REQUIRE(sequence_ranges.leftMostSequenceRange(left_most_indices) == 1);
        REQUIRE(sequence_ranges.rightMostSequenceRange(right_most_indices) ==
                1);

        THEN(
          "The indices should actually match the right SequenceRange instances")
        {
          REQUIRE(
            sequence_ranges.getCstReferenceAt(left_most_indices.at(0)).first ==
            0);
          REQUIRE(sequence_ranges.getCstReferenceAt(right_most_indices.at(0))
                    .second == 99);
        }
      }

      WHEN("Checking if there are overlaps, there should not be")
      {
        REQUIRE_FALSE(sequence_ranges.overlap());
      }

      WHEN("Checking for indices that are encompassed or not")
      {
        THEN("The results should be consistent")
        {
          REQUIRE(sequence_ranges.encompassIndex(0));
          REQUIRE_FALSE(sequence_ranges.encompassIndex(50));
          REQUIRE(sequence_ranges.encompassIndex(50, /*globally*/ true));
          REQUIRE(sequence_ranges.encompassIndex(99));
          REQUIRE_FALSE(sequence_ranges.encompassIndex(100));
        }
      }

      WHEN("Checking for indices left most SequenceRange")
      {
        THEN("The results should be consistent")
        {
          // "[1-20][25-45][55-100]"

          REQUIRE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(0)));
          REQUIRE_FALSE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(0)));

          REQUIRE_FALSE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(1)));
          REQUIRE_FALSE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(1)));

          REQUIRE_FALSE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(2)));
          REQUIRE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(2)));
        }
      }
    }

    GIVEN(
      "A SequenceRanges instance with a number of SequenceRange with overlaps")
    {
      SequenceRanges sequence_ranges;

      QString all_sequence_range_strings(
        "[1-20][1-30][10-50][15-80][25-100][30-100]");

      sequence_ranges.setSequenceRange(all_sequence_range_strings);

      REQUIRE(sequence_ranges.size() == 6);

      std::vector<std::size_t> left_most_indices;
      std::vector<std::size_t> right_most_indices;

      WHEN("Asking for the leftmost and the rightmost SequenceRange")
      {
        REQUIRE(sequence_ranges.leftMostSequenceRange(left_most_indices) == 2);
        REQUIRE(sequence_ranges.rightMostSequenceRange(right_most_indices) ==
                2);

        THEN(
          "The indices should actually match the right SequenceRange instances")
        {
          REQUIRE(
            sequence_ranges.getCstReferenceAt(left_most_indices.at(0)).first ==
            0);
          REQUIRE(
            sequence_ranges.getCstReferenceAt(left_most_indices.back()).first ==
            0);
          REQUIRE(sequence_ranges.getCstReferenceAt(right_most_indices.at(0))
                    .second == 99);
          REQUIRE(sequence_ranges.getCstReferenceAt(right_most_indices.back())
                    .second == 99);
        }
      }

      WHEN("Checking if there are overlaps, there should  be")
      {
        REQUIRE(sequence_ranges.overlap());
      }

      WHEN("Checking for indices that are encompassed or not")
      {
        THEN("The results should be consistent")
        {
          REQUIRE(sequence_ranges.encompassIndex(0));
          REQUIRE(sequence_ranges.encompassIndex(49));
          REQUIRE(sequence_ranges.encompassIndex(50));
          REQUIRE(sequence_ranges.encompassIndex(99));
          REQUIRE_FALSE(sequence_ranges.encompassIndex(100));
        }
      }

      WHEN("Checking for indices left most SequenceRange")
      {
        THEN("The results should be consistent")
        {
          // "[1-20][1-30][10-50][15-80][25-100][30-100]"

          REQUIRE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(0)));
          REQUIRE_FALSE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(0)));

          REQUIRE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(1)));
          REQUIRE_FALSE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(1)));

          REQUIRE_FALSE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(2)));
          REQUIRE_FALSE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(2)));

          REQUIRE_FALSE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(3)));
          REQUIRE_FALSE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(3)));

          REQUIRE_FALSE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(4)));
          REQUIRE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(4)));

          REQUIRE_FALSE(sequence_ranges.isLeftMostSequenceRange(
            sequence_ranges.getCstReferenceAt(5)));
          REQUIRE(sequence_ranges.isRightMostSequenceRange(
            sequence_ranges.getCstReferenceAt(5)));
        }
      }
    }
  }

  SCENARIO("SequenceRanges instances can self-describe with text strings",
           "[SequenceRanges]")
  {
    QString all_three_sequence_range_strings("[50-150][40-160][30-170]");

    GIVEN("Construction with a string describing three SequenceRange instances")
    {
      SequenceRanges sequence_ranges(all_three_sequence_range_strings);

      WHEN("When asked to self-describe as indices")
      {
        QString text = sequence_ranges.indicesAsText();

        THEN("The string should be list indices and not positions")
        {
          REQUIRE(text.toStdString() == "[49-149][39-159][29-169]");
        }
      }

      WHEN("When asked to self-describe as positions")
      {
        QString text = sequence_ranges.positionsAsText();

        THEN("The string should be list positions and not indices")
        {
          REQUIRE(text.toStdString() ==
                  all_three_sequence_range_strings.toStdString());
        }
      }
    }
  }


} // namespace libXpertMassCore
} // namespace MsXpS
