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


/////////////////////// Stdlib includes


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


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


/////////////////////// libXpertMassCore includes
#include "MsXpS/libXpertMassCore/Isotope.hpp"


/////////////////////// Local includes
#include "TestUtils.hpp"


namespace MsXpS
{

namespace libXpertMassCore
{

SCENARIO("Isotope construction with correct arguments", "[Isotope]")
{

  TestUtils test_utils;
  QVector<QString> error_list;

  test_utils.initializeXpertmassLibrary();

  WHEN("An isotope is constructed with fully descriptive arguments")
  {
    Isotope isotope_1(
      "carbon",
      "C",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

    THEN("All the member data are set accordingly")
    {
      // qDebug()  << "The isotope string is: " <<
      // isotope_1.toString().toStdString();

      REQUIRE(isotope_1.getName().toStdString() == "carbon");
      REQUIRE(isotope_1.getSymbol().toStdString() == "C");
      REQUIRE(isotope_1.getMass() == 12.0);
      REQUIRE(isotope_1.getProbability() ==
              0.989211941850466902614869013632414862513542175292968750000000);

      AND_THEN("The Isotope should both be valid and validate successfully")
      {
        REQUIRE(isotope_1.isValid());

        REQUIRE(isotope_1.validate(&error_list));
        REQUIRE(error_list.size() == 0);
      }

      WHEN("A new Isotope is assignment operator constructed")
      {
        Isotope new_isotope(isotope_1, Q_NULLPTR);

        THEN("The new instance should be identical to the first one")
        {
          REQUIRE(new_isotope == isotope_1);
          REQUIRE(new_isotope.validate(&error_list));
          REQUIRE(error_list.size() == 0);
        }

        THEN("An Isotope instance == compared to itself should return true")
        {
          REQUIRE((isotope_1 == isotope_1));
        }

        THEN("An Isotope instance != compared to itself should return false")
        {
          REQUIRE_FALSE((isotope_1 != isotope_1));
        }
      }
    }

    AND_WHEN(
      "An identical isotope is constructed with an identical textual "
      "representation")
    {
      QString isotope_string(" carbon, C, 12.0, ");
      isotope_string +=
        "0.989211941850466902614869013632414862513542175292968750000000";

      // qDebug() << "The isotope_string:" << isotope_string.toStdString();

      Isotope isotope_2(isotope_string);

      THEN("All the member data are set accordingly")
      {
        // qDebug()  << "The isotope string is: " <<
        // isotope_1.toString().toStdString();

        REQUIRE(isotope_2.getName() == "carbon");
        REQUIRE(isotope_2.getSymbol() == "C");
        REQUIRE(isotope_2.getMass() == 12.0);
        REQUIRE(isotope_2.getProbability() ==
                0.989211941850466902614869013632414862513542175292968750000000);

        AND_THEN("The Isotope should both be valid and validate successfully")
        {
          REQUIRE(isotope_2.isValid());

          QVector<QString> error_list;

          REQUIRE(isotope_2.validate(&error_list));
          REQUIRE(error_list.size() == 0);
        }

        AND_THEN("Both isotopes are identical")
        {
          REQUIRE(isotope_2 == isotope_1);
        }
      }
    }

    AND_WHEN("A new isotope is copy-constructed")
    {
      Isotope isotope_3(isotope_1);

      THEN("All the member data are set accordingly")
      {
        REQUIRE(isotope_3.getName().toStdString() == "carbon");
        REQUIRE(isotope_3.getSymbol().toStdString() == "C");
        REQUIRE(isotope_3.getMass() == 12.0);
        REQUIRE(isotope_3.getProbability() ==
                0.989211941850466902614869013632414862513542175292968750000000);

        AND_THEN("The Isotope should both be valid and validate successfully")
        {
          REQUIRE(isotope_3.isValid());

          QVector<QString> error_list;

          REQUIRE(isotope_3.validate(&error_list));
          REQUIRE(error_list.size() == 0);
        }

        AND_THEN("Both isotopes are identical")
        {
          REQUIRE(isotope_3 == isotope_1);
        }
      }
    }

    AND_WHEN("A new isotope is assignment-operator-constructed")
    {
      Isotope isotope_4 = isotope_1;

      THEN("All the member data are set accordingly")
      {
        REQUIRE(isotope_4.getName().toStdString() == "carbon");
        REQUIRE(isotope_4.getSymbol().toStdString() == "C");
        REQUIRE(isotope_4.getMass() == 12.0);
        REQUIRE(isotope_4.getProbability() ==
                0.989211941850466902614869013632414862513542175292968750000000);

        AND_THEN("The Isotope should both be valid and validate successfully")
        {
          REQUIRE(isotope_4.isValid());

          QVector<QString> error_list;

          REQUIRE(isotope_4.validate(&error_list));
          REQUIRE(error_list.size() == 0);
        }

        AND_THEN("Both isotopes are identical")
        {
          REQUIRE(isotope_4 == isotope_1);
        }
      }
    }

    AND_WHEN(
      "An absurd isotope is constructed with a failing descriptive string")
    {
      QString isotope_string = " nothing, N, 600, 1.3";

      Isotope isotope_5(isotope_string);

      THEN("The isotope has to be different than the other correct ones")
      {
        REQUIRE(isotope_5 != isotope_1);
      }
    }
  }
}

SCENARIO("Isotope construction with incorrect arguments", "[Isotope]")
{
  TestUtils test_utils;
  QVector<QString> error_list;

  WHEN("Constructing Isotope with failing element's name")
  {
    Isotope isotope(
      "Carbon",
      "C",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

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

    AND_WHEN("Copy-constructing a new Isotope using it")
    {
      Isotope isotope_new(isotope);

      THEN("It is also invalid")
      {
        REQUIRE_FALSE(isotope_new.isValid());
      }
    }
  }

  WHEN("Constructing Isotope with all failing members")
  {
    Isotope isotope("Carbon", "c", -1, 1.1);

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

    AND_WHEN("Copy-constructing a new Isotope using it")
    {
      Isotope isotope_new(isotope);

      THEN("It is also invalid")
      {
        REQUIRE_FALSE(isotope_new.isValid());
      }
    }
  }

  WHEN("Constructing Isotope with failing element's name")
  {
    Isotope isotope(
      "1Carbon",
      "C",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

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

  WHEN("Constructing Isotope with failing element's symbol")
  {
    Isotope isotope(
      "carbon",
      "c",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

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

  WHEN("Constructing Isotope with failing element's symbol")
  {
    Isotope isotope(
      "carbon",
      "1c",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

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

  WHEN("Constructing Isotope with failing mass")
  {
    Isotope isotope(
      "carbon",
      "C",
      -0.00001,
      0.989211941850466902614869013632414862513542175292968750000000);

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

  WHEN("Constructing Isotope with failing probability")
  {
    Isotope isotope("carbon", "C", 12.0, -0.00000001);

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

  WHEN("Constructing Isotope with failing probability")
  {
    Isotope isotope("carbon", "C", 12.0, 1.0000001);

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


SCENARIO("Isotope instances can be initialized with the four parameters",
         "[Isotope]")
{
  TestUtils test_utils;
  QVector<QString> error_list;

  GIVEN("An empty Isotope")
  {
    Isotope isotope;

    WHEN("Initialized with all proper arguments")
    {
      isotope.initialize("carbon", "C", 12.0, 0.9892119418504669026);

      THEN("The Isotope should be valid")
      {
        REQUIRE(isotope.isValid());
        REQUIRE(isotope.validate(&error_list));
      }
    }

    WHEN("Intialized with failing name")
    {
      isotope.initialize("Carbon", "C", 12.0, 0.9892119418504669026);

      THEN("The Isotope should not be valid")
      {
        REQUIRE_FALSE(isotope.isValid());
      }
    }

    WHEN("Initialized with failing name")
    {
      isotope.initialize("", "C", 12.0, 0.9892119418504669026);

      THEN("The Isotope should not be valid")
      {
        REQUIRE_FALSE(isotope.isValid());
      }
    }

    WHEN("Initialized with failing symbol")
    {
      isotope.initialize("carbon", "c", 12.0, 0.9892119418504669026);

      THEN("The Isotope should not be valid")
      {
        REQUIRE_FALSE(isotope.isValid());
      }
    }

    WHEN("Initialized with failing symbol")
    {
      isotope.initialize("carbon", "", 12.0, 0.9892119418504669026);

      THEN("The Isotope should not be valid")
      {
        REQUIRE_FALSE(isotope.isValid());
      }
    }

    WHEN("Initialized with failing mass")
    {
      isotope.initialize("carbon", "C", -0.00000120, 0.9892119418504669026);

      THEN("The Isotope should not be valid")
      {
        REQUIRE_FALSE(isotope.isValid());
      }
    }

    WHEN("Initialized with failing probability")
    {
      isotope.initialize("carbon", "C", 12.0, -0.00001);

      THEN("The Isotope should not be valid")
      {
        REQUIRE_FALSE(isotope.isValid());
      }
    }

    WHEN("Initialized with failing probability")
    {
      isotope.initialize("carbon", "C", 12.0, 1.00001);

      THEN("The Isotope should not be valid")
      {
        REQUIRE_FALSE(isotope.isValid());
      }
    }
  }
}

SCENARIO("Isotope instances can be initialized piecemeal with setter functions",
         "[Isotope]")
{
  QVector<QString> error_list;

  GIVEN("An empty Isotope instance")
  {
    Isotope isotope;

    THEN("The instance is invalid")
    {
      REQUIRE_FALSE(isotope.isValid());
    }

    WHEN("Failing name is set")
    {
      isotope.setName("carbon");

      THEN("The Isotope is invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }
    }

    WHEN("Failing name is set")
    {
      isotope.setName("");

      THEN("The Isotope is invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }
    }

    WHEN("Failing symbol is set")
    {
      isotope.setSymbol("carbon");

      THEN("The Isotope is invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }
    }

    WHEN("Failing symbol is set")
    {
      isotope.setSymbol("");

      THEN("The Isotope is invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }
    }

    WHEN("Failing mass is set")
    {
      isotope.setMass(-0.00001);

      THEN("The Isotope is invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }
    }

    WHEN("Failing probability is set")
    {
      isotope.setProbability(-0.00001);

      THEN("The Isotope is invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }
    }

    WHEN("Failing probability is set")
    {
      isotope.setProbability(1.000001);

      THEN("The Isotope is invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }
    }

    WHEN("Proper name is set")
    {
      isotope.setName("carbon");

      THEN("The Isotope is still invalid")
      {
        REQUIRE_FALSE(isotope.validate(&error_list));
      }

      AND_WHEN("Proper symbol is set")
      {
        isotope.setSymbol("C");

        THEN("The Isotope is still invalid")
        {
          REQUIRE_FALSE(isotope.validate(&error_list));
        }

        AND_WHEN("Proper mass is set")
        {
          isotope.setMass(12.00000);

          THEN("The Isotope is still invalid")
          {
            REQUIRE_FALSE(isotope.validate(&error_list));
          }

          AND_WHEN("Proper probability is set")
          {
            isotope.setProbability(0.988);

            THEN("The Isotope is finally valid")
            {
              REQUIRE(isotope.validate(&error_list));
            }
          }
        }
      }
    }
  }
}

SCENARIO("Isotope instances can be initialized with a descriptive text",
         "[Isotope]")
{
  TestUtils test_utils;
  QVector<QString> error_list;

  // Easy means to craft various lengths of descriptive text strings.
  QStringList isotope_descriptive_strings;
  isotope_descriptive_strings
    << "carbon"
    << "C"
    << "12.000000000000000000000000000000000000000000000000000000000000"
    << "0.989211941850466902614869013632414862513542175292968750000000";

  QString isotope_descriptive_string = isotope_descriptive_strings.join(",");

  GIVEN("Two correct and identical Isotope instances")
  {
    Isotope isotope_1(
      "carbon",
      "C",
      12.000000000000000000000000000000000000000000000000000000000000,
      0.989211941850466902614869013632414862513542175292968750000000);

    REQUIRE(isotope_1.isValid());
    REQUIRE(isotope_1.validate(&error_list));

    Isotope isotope_2(
      "carbon",
      "C",
      12.000000000000000000000000000000000000000000000000000000000000,
      0.989211941850466902614869013632414862513542175292968750000000);

    REQUIRE(isotope_2.isValid());
    REQUIRE(isotope_2.validate(&error_list));

    REQUIRE(isotope_1 == isotope_2);

    WHEN("Self-documenting one of them to a string")
    {
      QString isotope_1_string = isotope_1.toString();

      THEN("The string has to be correct")
      {
        REQUIRE(isotope_1_string.toStdString() ==
                isotope_descriptive_string.toStdString());
      }

      AND_WHEN("The descriptive text is cleared")
      {
        isotope_descriptive_string.clear();

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(isotope_descriptive_string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The descriptive text is incomplete")
      {
        QStringList strings = isotope_descriptive_strings;
        strings.removeLast();
        QString string = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The element name in the descriptive text is incorrect")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[0]          = "1234";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The element name in the descriptive text starts uppercase")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[0]          = "Carbon";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The symbol in the descriptive text is incorrect")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[1]          = "1234";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The symbol in the descriptive text is lowercase")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[1]          = "c";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The mass in the descriptive text is  < 0")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[2]          = "-0.0000001";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The mass in the descriptive text is not double")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[2]          = "waouw";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The probability in the descriptive text is  < 0")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[3]          = "-0.0000001";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The probability in the descriptive text is  > 1")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[3]          = "1.0000001";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }

      AND_WHEN("The probability in the descriptive text is not double")
      {
        QStringList strings = isotope_descriptive_strings;
        strings[3]          = "a1254";
        QString string      = strings.join(",");

        THEN("Initialization of an Isotope instance using it should fail")
        {
          REQUIRE(isotope_1.initialize(string) == false);

          AND_THEN("The isotope should become invalid")
          {
            REQUIRE_FALSE(isotope_1.isValid());
          }
        }
      }
    }
  }
}

SCENARIO("All Isotope member data can be set using setters", "[Isotope]")
{
  TestUtils test_utils;
  QVector<QString> error_list;

  WHEN("An absurd Isotope is created starting for a failing descriptive string")
  {
    QString isotope_string = " 0, nothing, N, 600, 1200, 1200, 1,";
    isotope_string += "1.3,";
    isotope_string += "-2,";
    isotope_string += "0 ";

    Isotope isotope_1(isotope_string);

    AND_WHEN("Using setters to configure it and make it a correct Isotope")
    {
      // Now test all the set functions.
      isotope_1.setName("carbon");
      isotope_1.setSymbol("C");
      isotope_1.setMass(12.0);
      isotope_1.setProbability(
        0.989211941850466902614869013632414862513542175292968750000000);
    }

    AND_WHEN(
      "Another Isotope is constructed to be identical to the configured one")
    {
      Isotope isotope_2(
        "carbon",
        "C",
        12.0,
        0.989211941850466902614869013632414862513542175292968750000000);

      THEN("Both isotopes need to turn out identical")
      {
        REQUIRE("isotope_1 == isotope_2");
      }
    }
  }
}

SCENARIO("Isotope instances can be compared with operators ==() and !=()",
         "[Isotope]")
{
  TestUtils test_utils;
  QVector<QString> error_list;

  WHEN("Two identical Isotope instances are created")
  {
    Isotope isotope_1(
      "carbon",
      "C",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

    Isotope isotope_2(
      "carbon",
      "C",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

    THEN("Operators ==() and !=()  should return correct results")
    {
      REQUIRE(isotope_1 == isotope_2);
      REQUIRE_FALSE(isotope_1 != isotope_2);
    }

    AND_WHEN("One of the isotopes is modified")
    {
      isotope_1.setSymbol("N");

      THEN("Operators ==() and !=()  should return correct results")
      {
        REQUIRE(isotope_1 != isotope_2);
        REQUIRE_FALSE(isotope_1 == isotope_2);
      }
    }
  }
}

SCENARIO("Failed validation of Isotope instances report error strings",
         "[Isotope]")
{
  TestUtils test_utils;
  QVector<QString> error_list;

  WHEN("A correct Isotope is instanciated")
  {
    Isotope isotope_1(
      "carbon",
      "C",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

    THEN("Validation should be successful with no error strings")
    {
      REQUIRE(isotope_1.isValid());
      REQUIRE(isotope_1.validate(&error_list));
      REQUIRE(!error_list.size());
    }

    AND_WHEN("The symbol is crippled")
    {
      isotope_1.setSymbol("");

      THEN("Validation should return an informative string")
      {
        REQUIRE_FALSE(isotope_1.validate(&error_list));
        REQUIRE(error_list.size() == 1);
        REQUIRE(error_list.front().toStdString() ==
                "Failed to validate the element's symbol.");
      }
    }

    AND_WHEN("The mass is also crippled")
    {
      isotope_1.setSymbol("");
      isotope_1.setMass(-10);

      THEN("Validation should return an informative string")
      {
        error_list.clear();

        REQUIRE_FALSE(isotope_1.validate(&error_list));
        REQUIRE(error_list.size() == 2);
        REQUIRE(error_list.front().toStdString() ==
                "Failed to validate the element's symbol.");
        REQUIRE(error_list.back().toStdString() ==
                "Failed to validate the isotope's mass.");
      }
    }

    AND_WHEN("The probability is also crippled")
    {
      isotope_1.setSymbol("");
      isotope_1.setMass(-10);
      isotope_1.setProbability(-0.10);

      THEN("Validation should return an informative string")
      {
        error_list.clear();

        REQUIRE_FALSE(isotope_1.validate(&error_list));

        REQUIRE(error_list.front().toStdString() ==
                "Failed to validate the element's symbol.");
        REQUIRE(error_list.back().toStdString() ==
                "Failed to validate the isotope's probability.");
      }
    }
  }
}

SCENARIO("Isotope instances can be self-documenting with as descriptive string",
         "[Isotope]")
{
  TestUtils test_utils;
  QVector<QString> error_list;

  WHEN("Instantiating a correct Isotope")
  {
    Isotope isotope_1(
      "carbon",
      "C",
      12.0,
      0.989211941850466902614869013632414862513542175292968750000000);

    THEN("That Isotope instance can produce a descriptive string")
    {
      QString isotope_string = isotope_1.toString();

      REQUIRE(isotope_string.toStdString() ==
              "carbon,C,"
              "12.000000000000000000000000000000000000000000000000000000000000,"
              "0.989211941850466902614869013632414862513542175292968750000000");


      AND_WHEN("That descriptive string is used to instantiate a new Isotope")
      {
        Isotope isotope_2(isotope_string);

        THEN("Both isotopes need to be identical")
        {
          REQUIRE(isotope_1 == isotope_2);
          REQUIRE_FALSE(isotope_1 != isotope_2);
        }
      }
    }
  }
}


} // namespace libXpertMassCore
} // namespace MsXpS
