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


/////////////////////// Qt includes
#include <QDebug>
#include <QString>
#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>


/////////////////////// libXpertMassCore includes
#include "MsXpS/libXpertMassCore/Utils.hpp"
#include "MsXpS/libXpertMassCore/Isotope.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataLibraryHandler.hpp"
#include "MsXpS/libXpertMassCore/Formula.hpp"


/////////////////////// Local includes
#include "tests-config.h"
#include "TestUtils.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{
TestUtils test_utils_formula;
ErrorList error_list_formula;

SCENARIO("Construction of Formula with no action-formula string", "[Formula]")
{
  test_utils_formula.initializeXpertmassLibrary();

  IsotopicDataLibraryHandler isotopic_data_library_handler;

  qsizetype non_isotope_skipped_items = 0;
  qsizetype loaded_isotope_count =
    isotopic_data_library_handler.loadData(non_isotope_skipped_items);

  IsotopicDataCstSPtr isotopic_data_csp = nullptr;

  REQUIRE(loaded_isotope_count + non_isotope_skipped_items ==
          static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries));

  isotopic_data_csp = isotopic_data_library_handler.getIsotopicData();
  REQUIRE(isotopic_data_csp->size() == loaded_isotope_count);

  WHEN("Constructed without action-formula")
  {
    Formula formula;

    THEN("The formula is invalid")
    {
      REQUIRE(formula.getActionFormula().toStdString() == "");
      REQUIRE_FALSE(formula.isValid());
    }

    WHEN("Explicitely validated with no arguments but the IsotopicData")
    {

      THEN("The formula cannot validate succesfully")
      {
        REQUIRE_FALSE(formula.validate(isotopic_data_csp, &error_list_formula));
        REQUIRE_FALSE(formula.isValid());
      }
    }

    WHEN("Copy-constructed")
    {
      Formula new_formula(formula);

      THEN("The new Formula is also invalid")
      {
        REQUIRE(new_formula.getActionFormula().toStdString() == "");
        REQUIRE_FALSE(new_formula.isValid());
      }
    }

    AND_WHEN("Assignment operator copied")
    {
      Formula new_formula;
      new_formula = formula;

      THEN("The new Formula is also invalid")
      {

        REQUIRE(new_formula.getActionFormula().toStdString() == "");
        REQUIRE_FALSE(new_formula.isValid());
      }
    }

    WHEN("Setting the titled action-formula with the setter and a string")
    {
      formula.setActionFormula(
        test_utils_formula.m_actionFormulaStringMetAlaDipeptidylTitled);

      // qDebug() <<  "The action-formula:" << formula.getActionFormula();
      // qDebug() <<  "The action-formula with title:" <<
      // formula.getActionFormula(true);

      THEN(
        "The formula is invalid (not validated yet), the title has been "
        "removed "
        "and is now in m_title")
      {
        REQUIRE(formula.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(formula.checkSyntax());
        REQUIRE(formula.getTitle().toStdString() ==
                test_utils_formula.m_formulaTitle.toStdString());
        REQUIRE_FALSE(formula.isValid());
      }

      AND_WHEN("Appending to the action-formula an empty string")
      {
        formula.appendActionFormula("");

        // qDebug() <<  "The action-formula:" << formula.getActionFormula();
        // qDebug() <<  "The action-formula with title:" <<
        // formula.getActionFormula(true);

        THEN("The formula is not changed whatsoever")
        {
          REQUIRE(formula.getActionFormula().toStdString() ==
                  test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                    .toStdString());
          REQUIRE(formula.checkSyntax());
          REQUIRE(formula.getTitle().toStdString() ==
                  test_utils_formula.m_formulaTitle.toStdString());
          REQUIRE_FALSE(formula.isValid());
        }

        AND_WHEN("The masses are accounted")
        {
          double mono;
          double avg;
          bool ok = false;

          formula.accountMasses(ok, isotopic_data_csp, mono, avg, /*times*/ 1);
          REQUIRE(ok);
        }
      }

      AND_WHEN(
        "Appending to the action-formula a non-empty string but with "
        "erroneous "
        "syntax")
      {
        bool result = formula.appendActionFormula("+h2O");

        THEN("The formula is not changed whatsoever")
        {
          REQUIRE_FALSE(result);
          REQUIRE(formula.getActionFormula().toStdString() ==
                  test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                    .toStdString());
          REQUIRE(formula.checkSyntax());
          REQUIRE(formula.getTitle().toStdString() ==
                  test_utils_formula.m_formulaTitle.toStdString());
          REQUIRE_FALSE(formula.isValid());
        }
      }

      AND_WHEN(
        "Appending to the action-formula a non-empty string correct-syntax but "
        "with "
        "failing chemistry")
      {
        QString new_action_formula_string =
          test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl;
        new_action_formula_string += "+Hh2O";
        bool result                = formula.appendActionFormula("+Hh2O");

        // qDebug() << "The action-formula:" << formula.getActionFormula();
        // qDebug() << "The action-formula with title:"
        //          << formula.getActionFormula(true);

        THEN("The formula is indeed changed")
        {
          REQUIRE(result);
          REQUIRE(formula.getActionFormula().toStdString() ==
                  new_action_formula_string.toStdString());
          REQUIRE(formula.checkSyntax());
          REQUIRE(formula.getTitle().toStdString() ==
                  test_utils_formula.m_formulaTitle.toStdString());
          REQUIRE_FALSE(formula.isValid());
        }

        AND_WHEN("That changed formula is validated")
        {
          THEN("The validation fails")
          {
            REQUIRE_FALSE(
              formula.validate(isotopic_data_csp, &error_list_formula));
          }

          AND_WHEN("The masses are accounted")
          {
            double mono;
            double avg;
            bool ok = false;

            formula.accountMasses(
              ok, isotopic_data_csp, mono, avg, /*times*/ 1);
            REQUIRE_FALSE(ok);
          }
        }
      }

      AND_WHEN(
        "Appending to the action-formula a valid non-empty string without "
        "title")
      {
        QString new_action_formula_string =
          test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl;
        new_action_formula_string += "+H2O";
        bool result                = formula.appendActionFormula("+H2O");

        THEN("The formula is changed")
        {
          REQUIRE(result);
          REQUIRE(formula.getActionFormula().toStdString() ==
                  new_action_formula_string.toStdString());
          REQUIRE(formula.checkSyntax());
          REQUIRE(formula.getTitle().toStdString() ==
                  test_utils_formula.m_formulaTitle.toStdString());
          REQUIRE_FALSE(formula.isValid());
        }
      }

      AND_WHEN(
        "Appending to the action-formula a valid non-empty string with title")
      {
        QString new_action_formula_string =
          test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl;
        new_action_formula_string += "+H2O";
        bool result = formula.appendActionFormula("\"Add water\"+H2O");

        THEN("The formula is changed")
        {
          REQUIRE(result);
          REQUIRE(formula.getActionFormula().toStdString() ==
                  new_action_formula_string.toStdString());
          REQUIRE(formula.checkSyntax());
          REQUIRE(formula.getTitle().toStdString() ==
                  test_utils_formula.m_formulaTitle.toStdString());
          REQUIRE_FALSE(formula.isValid());
        }
      }

      AND_WHEN(
        "Resetting the titled action-formula with the setter to the empty "
        "string")
      {
        formula.setActionFormula("");

        THEN(
          "The formula is invalid (not validated yet), the title has been "
          "removed "
          "and is now in m_title")
        {
          REQUIRE(formula.getActionFormula().toStdString() == "");
          REQUIRE_FALSE(formula.checkSyntax());
          REQUIRE(formula.getTitle().toStdString() == "");
          REQUIRE_FALSE(formula.isValid());
        }
      }
    }

    WHEN("Setting the titled action-formula with the setter and a Formula")
    {
      Formula new_formula(
        test_utils_formula.m_actionFormulaStringMetAlaDipeptidylTitled);

      formula.setActionFormula(new_formula);

      THEN(
        "The formula is invalid (not validated yet), the title has to be "
        "empty "
        "(was removed upon construction)")
      {
        REQUIRE(formula.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(formula.checkSyntax());
        REQUIRE(formula.getTitle().toStdString() == "");
        REQUIRE_FALSE(formula.isValid());
      }

      AND_WHEN(
        "Resetting the titled action-formula with the setter to the empty "
        "string via a Formula")
      {
        new_formula.setActionFormula("");
        formula.setActionFormula(new_formula);

        THEN(
          "The formula is invalid (not validated yet), the title has been "
          "removed "
          "and is now in m_title")
        {
          REQUIRE(formula.getActionFormula().toStdString() == "");
          REQUIRE_FALSE(formula.checkSyntax());
          REQUIRE(formula.getTitle().toStdString() == "");
          REQUIRE_FALSE(formula.isValid());
        }
      }
    }
  }
}

SCENARIO(
  "Construction and copying of Formula instances with titled action-formula "
  "string",
  "[Formula]")
{
  WHEN("Constructed with a title action-formula")
  {
    Formula formula(
      test_utils_formula.m_actionFormulaStringMetAlaDipeptidylTitled);

    THEN(
      "The formula is invalid (not validated yet), the title has been "
      "removed "
      "and is now in m_title")
    {
      REQUIRE(
        formula.getActionFormula().toStdString() ==
        test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl.toStdString());
      REQUIRE(formula.checkSyntax());
      REQUIRE(formula.getTitle().toStdString() ==
              test_utils_formula.m_formulaTitle.toStdString());
      REQUIRE_FALSE(formula.isValid());
    }

    WHEN("Explicitely validated with no arguments but the IsotopicData")
    {
      IsotopicDataLibraryHandler isotopic_data_library_handler;

      qsizetype non_isotope_skipped_items = 0;
      qsizetype loaded_isotope_count =
        isotopic_data_library_handler.loadData(non_isotope_skipped_items);

      IsotopicDataCstSPtr isotopic_data_csp = nullptr;

      REQUIRE(
        loaded_isotope_count + non_isotope_skipped_items ==
        static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries));

      isotopic_data_csp = isotopic_data_library_handler.getIsotopicData();
      REQUIRE(isotopic_data_csp->size() == loaded_isotope_count);

      THEN("The formula validates succesfully")
      {
        REQUIRE(formula.validate(isotopic_data_csp, &error_list_formula));
        REQUIRE(formula.isValid());
      }
    }

    AND_WHEN("Copy-constructed to a new Formula")
    {
      Formula new_formula(formula);

      THEN("All relevant members need to be identical")
      {
        REQUIRE(formula.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(formula.checkSyntax());
        REQUIRE(formula.getTitle().toStdString() ==
                test_utils_formula.m_formulaTitle.toStdString());
        REQUIRE_FALSE(formula.isValid());

        REQUIRE(new_formula.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(new_formula.checkSyntax());
        REQUIRE(new_formula.getTitle().toStdString() ==
                test_utils_formula.m_formulaTitle.toStdString());
        REQUIRE_FALSE(new_formula.isValid());
      }
    }

    AND_WHEN("Assignment-operator copied to a new Formula")
    {
      Formula new_formula;
      new_formula = formula;

      THEN("All relevant members need to be identical")
      {
        REQUIRE(formula.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(formula.checkSyntax());
        REQUIRE(formula.getTitle().toStdString() ==
                test_utils_formula.m_formulaTitle.toStdString());
        REQUIRE_FALSE(formula.isValid());

        REQUIRE(new_formula.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(new_formula.checkSyntax());
        REQUIRE(new_formula.getTitle().toStdString() ==
                test_utils_formula.m_formulaTitle.toStdString());
        REQUIRE_FALSE(new_formula.isValid());
      }
    }
  }
}

SCENARIO("Construction of Formula_s with a simple no-action string",
         "[Formula]")
{
  WHEN("Constructed, using the constructor and the copy constructor")
  {
    Formula formula_c6h12O6(test_utils_formula.m_glucoseFormulaString);
    Formula formula_c6h12O6_copy(formula_c6h12O6);

    THEN("The the formula string is set to the member datum")
    {
      REQUIRE(formula_c6h12O6.getActionFormula().toStdString() ==
              test_utils_formula.m_glucoseFormulaString.toStdString());
      REQUIRE(formula_c6h12O6_copy.getActionFormula().toStdString() ==
              test_utils_formula.m_glucoseFormulaString.toStdString());
    }
  }

  AND_WHEN("Setting another formula string manually")
  {
    Formula formula_c6h12O6(test_utils_formula.m_glucoseFormulaString);
    formula_c6h12O6.setActionFormula(
      test_utils_formula.m_saccharoseFormulaString);

    THEN("The new formula string is set accordingly to the member datum")
    {
      REQUIRE(formula_c6h12O6.getActionFormula().toStdString() ==
              test_utils_formula.m_saccharoseFormulaString.toStdString());
    }
  }

  AND_WHEN("Appending another formula string")
  {
    Formula formula_c6h12O6(test_utils_formula.m_glucoseFormulaString);
    bool result = formula_c6h12O6.appendActionFormula(
      test_utils_formula.m_saccharoseFormulaString);
    QString after_appending_fomula(test_utils_formula.m_glucoseFormulaString);
    after_appending_fomula += test_utils_formula.m_saccharoseFormulaString;

    THEN("The member formula string is updated accordingly")
    {
      REQUIRE(result);
      REQUIRE(formula_c6h12O6.getActionFormula().toStdString() ==
              after_appending_fomula.toStdString());
    }
  }
}

SCENARIO("Construction of Formula_s with another Formula instance", "[Formula]")
{
  Formula formula_c6h12O6(test_utils_formula.m_glucoseFormulaString);

  WHEN("Constructed with another Formula instance")
  {
    Formula formula_c6h12O6_copy(formula_c6h12O6);

    THEN("The Formula string is set to the member datum")
    {
      REQUIRE(formula_c6h12O6.getActionFormula().toStdString() ==
              test_utils_formula.m_glucoseFormulaString.toStdString());
      REQUIRE(formula_c6h12O6_copy.getActionFormula().toStdString() ==
              test_utils_formula.m_glucoseFormulaString.toStdString());
    }
  }
}

SCENARIO("Until validated, a Formula is invalid", "[Formula]")
{
  WHEN(
    "Constructed, using the constructor, the copy constructor and the "
    "assignment operator")
  {
    Formula formula_c6h12O6(test_utils_formula.m_glucoseFormulaString);
    Formula formula_c6h12O6_copy(formula_c6h12O6);
    Formula formula_c6h12O6_assigned;
    formula_c6h12O6_assigned = formula_c6h12O6;

    THEN("The the formula string is set to the member datum")
    {
      REQUIRE(formula_c6h12O6.getActionFormula().toStdString() ==
              test_utils_formula.m_glucoseFormulaString.toStdString());
      REQUIRE(formula_c6h12O6_copy.getActionFormula().toStdString() ==
              test_utils_formula.m_glucoseFormulaString.toStdString());
      REQUIRE(formula_c6h12O6_assigned.getActionFormula().toStdString() ==
              test_utils_formula.m_glucoseFormulaString.toStdString());
    }

    THEN("Because not explicitely validated,  the formulas are invalid")
    {
      REQUIRE_FALSE(formula_c6h12O6.isValid());
      REQUIRE_FALSE(formula_c6h12O6_copy.isValid());
      REQUIRE_FALSE(formula_c6h12O6_assigned.isValid());
    }
  }
}

SCENARIO(
  "Syntax checking of an action Formula with or without the title is not a "
  "validation",
  "[Formula]")
{
  Formula formula_MA_dipeptidyl(
    test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl);
  Formula titled_formula_MA_dipeptidyl(
    test_utils_formula.m_actionFormulaStringMetAlaDipeptidylTitled);

  WHEN("Checked")
  {
    THEN("Checking succeeds")
    {
      REQUIRE(formula_MA_dipeptidyl.checkSyntax());
      REQUIRE(titled_formula_MA_dipeptidyl.checkSyntax());
    }

    THEN("The formula is not valid")
    {
      REQUIRE_FALSE(formula_MA_dipeptidyl.isValid());
      REQUIRE_FALSE(titled_formula_MA_dipeptidyl.isValid());
    }
  }
  AND_WHEN("Checking an invalid Formula starting with a cipher")
  {
    Formula formula("3Cz3H12O6N14L2");

    THEN("Checking fails")
    {
      REQUIRE(formula.checkSyntax() == false);
    }

    THEN("The formula is not valid")
    {
      REQUIRE_FALSE(formula_MA_dipeptidyl.isValid());
      REQUIRE_FALSE(titled_formula_MA_dipeptidyl.isValid());
    }
  }
  AND_WHEN(
    "Checking an invalid Formula ending not with an alphabetic character or "
    "a "
    "number")
  {
    Formula formula("Cz3H12O6N14L2§");

    THEN("Checking fails")
    {
      REQUIRE(formula.checkSyntax() == false);
    }

    THEN("The formula is not valid")
    {
      REQUIRE_FALSE(formula_MA_dipeptidyl.isValid());
      REQUIRE_FALSE(titled_formula_MA_dipeptidyl.isValid());
    }
  }
}

SCENARIO(
  "Only an explicit validation of a Formula establishes its valid/unvalid "
  "status",
  "[Formula]")
{
  IsotopicDataLibraryHandler isotopic_data_library_handler;

  qsizetype non_isotope_skipped_items = 0;
  qsizetype loaded_isotope_count =
    isotopic_data_library_handler.loadData(non_isotope_skipped_items);

  IsotopicDataCstSPtr isotopic_data_csp = nullptr;

  REQUIRE(loaded_isotope_count + non_isotope_skipped_items ==
          static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries));

  isotopic_data_csp = isotopic_data_library_handler.getIsotopicData();
  REQUIRE(isotopic_data_csp->size() == loaded_isotope_count);

  GIVEN(
    "Two Formula instances constructed with valid actionformula strings "
    "either "
    "titled or not")
  {
    Formula formula_MA_dipeptidyl(
      test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl);

    Formula titled_formula_MA_dipeptidyl(
      test_utils_formula.m_actionFormulaStringMetAlaDipeptidylTitled);

    THEN("These have proper action-formulas stripped of title")
    {
      REQUIRE(
        formula_MA_dipeptidyl.getActionFormula().toStdString() ==
        test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl.toStdString());

      REQUIRE(
        titled_formula_MA_dipeptidyl.getActionFormula().toStdString() ==
        test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl.toStdString());
    }

    WHEN(
      "Explicitely validated with no arguments but the IsotopicData (const "
      "function)")
    {
      REQUIRE(
        formula_MA_dipeptidyl.validate(isotopic_data_csp, &error_list_formula));
      REQUIRE(formula_MA_dipeptidyl.isValid());

      REQUIRE(titled_formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                                    &error_list_formula));
      REQUIRE(titled_formula_MA_dipeptidyl.isValid());

      THEN(
        "The + and - sub formulas are not set because we did not ask for "
        "storing")
      {
        // "+C5H11N1O2S1-H20+C3H7N1O2-H2O"
        REQUIRE(formula_MA_dipeptidyl.getPlusFormula().toStdString() == "");
        REQUIRE(formula_MA_dipeptidyl.getMinusFormula().toStdString() == "");

        REQUIRE(titled_formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                "");
        REQUIRE(titled_formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
                "");
      }

      THEN(
        "The symbol counts cannot be used to craft a new integrated "
        "elemental composition string formula because we did not store them")
      {
        REQUIRE(formula_MA_dipeptidyl.getSymbolCountMapCstRef().size() == 0);

        std::vector<std::pair<QString, double>> symbol_count_pairs;
        QString elemental_composition =
          formula_MA_dipeptidyl.elementalComposition(&symbol_count_pairs);
        REQUIRE(elemental_composition.toStdString() == "");

        elemental_composition =
          titled_formula_MA_dipeptidyl.elementalComposition(
            &symbol_count_pairs);
        REQUIRE(elemental_composition.toStdString() == "");

        const std::map<QString, double> &formula_symbol_count_map =
          formula_MA_dipeptidyl.getSymbolCountMapCstRef();

        REQUIRE(formula_symbol_count_map.size() == 0);
      }
    }

    WHEN(
      "Explicitely validated with reset and store set to true, the Formula "
      "creates + and - subformulas")
    {
      REQUIRE(formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                             /*store*/ true,
                                             /*reset*/ true,
                                             &error_list_formula));
      REQUIRE(formula_MA_dipeptidyl.isValid());

      REQUIRE(titled_formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                                    /*store*/ true,
                                                    /*reset*/ true,
                                                    &error_list_formula));
      REQUIRE(titled_formula_MA_dipeptidyl.isValid());

      THEN("The + and - sub formulas are checked")
      {
        // "+C5H11N1O2S1-H20+C3H7N1O2-H2O"
        REQUIRE(formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                "C5H11N1O2S1C3H7N1O2");
        REQUIRE(formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
                "H2O1H2O1");

        REQUIRE(titled_formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                "C5H11N1O2S1C3H7N1O2");
        REQUIRE(titled_formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
                "H2O1H2O1");

        AND_THEN(
          "The symbol counts can be used to craft a new integrated "
          "elemental composition string formula")
        {
          // Expecting result of "C5H11N1O2S1C3H7N1O2" - "H2O1H2O1"
          // which is C8H18N2O4S1 - H4O2
          // which is C8H14N2O2S1
          std::vector<std::pair<QString, double>> symbol_count_pairs;
          QString elemental_composition =
            formula_MA_dipeptidyl.elementalComposition(&symbol_count_pairs);
          REQUIRE(elemental_composition.toStdString() == "C8H14N2O2S1");

          elemental_composition =
            titled_formula_MA_dipeptidyl.elementalComposition(
              &symbol_count_pairs);
          REQUIRE(elemental_composition.toStdString() == "C8H14N2O2S1");

          const std::map<QString, double> &formula_symbol_count_map =
            formula_MA_dipeptidyl.getSymbolCountMapCstRef();

          auto map_iterator = formula_symbol_count_map.find("C");
          REQUIRE(map_iterator->second == 8);
          map_iterator = formula_symbol_count_map.find("H");
          REQUIRE(map_iterator->second == 14);
          map_iterator = formula_symbol_count_map.find("N");
          REQUIRE(map_iterator->second == 2);
          map_iterator = formula_symbol_count_map.find("O");
          REQUIRE(map_iterator->second == 2);
          map_iterator = formula_symbol_count_map.find("S");
          REQUIRE(map_iterator->second == 1);
        }
      }

      AND_WHEN(
        "Explicitely validated with reset set to false and store set to "
        "true, "
        "the action-formula increments but not the + and - subformulas")
      {
        REQUIRE(formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                               /*store*/ true,
                                               /*reset*/ false,
                                               &error_list_formula));
        REQUIRE(formula_MA_dipeptidyl.isValid());

        REQUIRE(titled_formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                                      /*store*/ true,
                                                      /*reset*/ false,
                                                      &error_list_formula));
        REQUIRE(titled_formula_MA_dipeptidyl.isValid());

        THEN("The + and - sub formulas are checked")
        {
          // "+C5H11N1O2S1-H20+C3H7N1O2-H2O"
          REQUIRE(formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                  "C5H11N1O2S1C3H7N1O2");
          REQUIRE(formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
                  "H2O1H2O1");

          REQUIRE(titled_formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                  "C5H11N1O2S1C3H7N1O2");
          REQUIRE(
            titled_formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
            "H2O1H2O1");
        }

        AND_THEN(
          "The symbol counts can be used to craft a new integrated "
          "elemental composition string formula")
        {
          // Expecting result of "C5H11N1O2S1C3H7N1O2" - "H2O1H2O1"
          // which is C8H18N2O4S1 - H4O2
          // which is C8H14N2O2S1
          std::vector<std::pair<QString, double>> symbol_count_pairs;
          QString elemental_composition =
            formula_MA_dipeptidyl.elementalComposition(&symbol_count_pairs);
          REQUIRE(elemental_composition.toStdString() == "C16H28N4O4S2");

          elemental_composition =
            titled_formula_MA_dipeptidyl.elementalComposition(
              &symbol_count_pairs);
          REQUIRE(elemental_composition.toStdString() == "C16H28N4O4S2");

          const std::map<QString, double> &formula_symbol_count_map =
            formula_MA_dipeptidyl.getSymbolCountMapCstRef();

          auto map_iterator = formula_symbol_count_map.find("C");
          REQUIRE(map_iterator->second == 16);
          map_iterator = formula_symbol_count_map.find("H");
          REQUIRE(map_iterator->second == 28);
          map_iterator = formula_symbol_count_map.find("N");
          REQUIRE(map_iterator->second == 4);
          map_iterator = formula_symbol_count_map.find("O");
          REQUIRE(map_iterator->second == 4);
          map_iterator = formula_symbol_count_map.find("S");
          REQUIRE(map_iterator->second == 2);
        }
      }
    }
  }
}

SCENARIO("Formula validation can fail in a number of ways", "[Formula]")
{
  IsotopicDataLibraryHandler isotopic_data_library_handler;

  qsizetype non_isotope_skipped_items = 0;
  qsizetype loaded_isotope_count =
    isotopic_data_library_handler.loadData(non_isotope_skipped_items);

  IsotopicDataCstSPtr isotopic_data_csp = nullptr;

  REQUIRE(loaded_isotope_count + non_isotope_skipped_items ==
          static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries));

  isotopic_data_csp = isotopic_data_library_handler.getIsotopicData();
  REQUIRE(isotopic_data_csp->size() == loaded_isotope_count);

  GIVEN("Failing formula strings and failing formulas constructed with these")
  {
    QString invalid_formula_string            = "+h20";
    QString invalid_formula_string_with_title = "\"invalid\"+h20";

    Formula invalid_formula(invalid_formula_string);
    Formula invalid_formula_with_title(invalid_formula_string_with_title);

    REQUIRE_FALSE(invalid_formula.isValid());
    REQUIRE_FALSE(invalid_formula_with_title.isValid());

    WHEN("Explicitely validated against reference isotopic data")
    {
      REQUIRE_FALSE(
        invalid_formula.validate(isotopic_data_csp, &error_list_formula));

      REQUIRE_FALSE(invalid_formula_with_title.validate(isotopic_data_csp,
                                                        &error_list_formula));

      THEN("They persit to be invalid")
      {
        REQUIRE_FALSE(invalid_formula.isValid());
        REQUIRE_FALSE(invalid_formula_with_title.isValid());
      }

      AND_WHEN(
        "The failing formulas are modified with valid string formulas using "
        "setFormula()")
      {
        invalid_formula.setActionFormula("+H2O");
        invalid_formula_with_title.setActionFormula("\"Fixed\"+H2O");

        THEN(
          "The formulas are still invalid because it was not explicitely "
          "validated")
        {
          REQUIRE_FALSE(invalid_formula.isValid());
          REQUIRE_FALSE(invalid_formula_with_title.isValid());
        }

        AND_WHEN("The formula is explicitely validated")
        {
          REQUIRE(
            invalid_formula.validate(isotopic_data_csp, &error_list_formula));
          REQUIRE(invalid_formula_with_title.validate(isotopic_data_csp,
                                                      &error_list_formula));

          THEN("It becomes valid because the new formula was correct")
          {
            REQUIRE(invalid_formula.isValid());
            REQUIRE(invalid_formula_with_title.isValid());
          }

          AND_WHEN("The formula is explicitely cleared")
          {
            invalid_formula.clear();

            THEN("All the member data are reset")
            {
              REQUIRE(invalid_formula.getTitle().toStdString() == "");
              REQUIRE(invalid_formula.getActionFormula().toStdString() == "");
              REQUIRE(invalid_formula.getPlusFormula().toStdString() == "");
              REQUIRE(invalid_formula.getMinusFormula().toStdString() == "");
              REQUIRE(invalid_formula.getSymbolCountMapCstRef().size() == 0);
            }
          }
        }
      }
    }
  }
}

SCENARIO(
  "It is possible to account an action-formula string into an existing "
  "Formula instance",
  "[Formula]")
{
  IsotopicDataLibraryHandler isotopic_data_library_handler;

  qsizetype non_isotope_skipped_items = 0;
  qsizetype loaded_isotope_count =
    isotopic_data_library_handler.loadData(non_isotope_skipped_items);

  IsotopicDataCstSPtr isotopic_data_csp = nullptr;

  REQUIRE(loaded_isotope_count + non_isotope_skipped_items ==
          static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries));

  isotopic_data_csp = isotopic_data_library_handler.getIsotopicData();
  REQUIRE(isotopic_data_csp->size() == loaded_isotope_count);

  GIVEN("A Formula created from a valid formula string")
  {
    Formula formula_MA_dipeptidyl(
      test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl);

    WHEN("That formula is validated succesfully with store set to true")
    {
      REQUIRE(formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                             /*store*/ true,
                                             /*reset*/ true,
                                             &error_list_formula));
      REQUIRE(formula_MA_dipeptidyl.isValid());

      THEN("The sub-formulas need to be correct")
      {
        REQUIRE(formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                "C5H11N1O2S1C3H7N1O2");
        REQUIRE(formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
                "H2O1H2O1");
      }

      AND_WHEN("An invalid string action-formula is accounted for")
      {
        QString invalid_action_formula_string("+h2O");
        bool ok   = false;
        int times = 2;

        formula_MA_dipeptidyl.accountFormula(
          invalid_action_formula_string, isotopic_data_csp, times, ok);

        THEN("The new action formula has to be unchanged")
        {
          REQUIRE(formula_MA_dipeptidyl.getActionFormula().toStdString() ==
                  test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                    .toStdString());
        }

        AND_WHEN("A valid string action-formula is accounted for")
        {
          QString invalid_action_formula_string("+H2O");
          bool ok   = false;
          int times = 2;

          formula_MA_dipeptidyl.accountFormula(
            invalid_action_formula_string, isotopic_data_csp, times, ok);

          THEN("The new action formula has to be unchanged")
          {
            REQUIRE(formula_MA_dipeptidyl.getActionFormula().toStdString() ==
                    "C8H18N2O4S1");
          }
        }
      }
    }
  }
}

SCENARIO("Various ways to account a Formula for its masses (mono, avg)",
         "[Formula]")
{
  IsotopicDataLibraryHandler isotopic_data_library_handler;

  qsizetype non_isotope_skipped_items = 0;
  qsizetype loaded_isotope_count =
    isotopic_data_library_handler.loadData(non_isotope_skipped_items);

  IsotopicDataCstSPtr isotopic_data_csp = nullptr;

  REQUIRE(loaded_isotope_count + non_isotope_skipped_items ==
          static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries));

  isotopic_data_csp = isotopic_data_library_handler.getIsotopicData();
  REQUIRE(isotopic_data_csp->size() == loaded_isotope_count);

  GIVEN("A Formula created from a valid formula string")
  {
    Formula formula_MA_dipeptidyl(
      test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl);

    WHEN("That formula is validated succesfully")
    {
      REQUIRE(formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                             /*store*/ true,
                                             /*reset*/ false,
                                             &error_list_formula));

      THEN("It becomes valid")
      {
        REQUIRE(formula_MA_dipeptidyl.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(formula_MA_dipeptidyl.isValid());
      }

      AND_WHEN(
        "That formula is validated with adequate options, its other member "
        "data are updated")
      {
        formula_MA_dipeptidyl.validate(isotopic_data_csp, &error_list_formula);

        THEN("The + and - sub formulas are checked")
        {
          // "+C5H11N1O2S1-H20+C3H7N1O2-H2O"
          REQUIRE(formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                  "C5H11N1O2S1C3H7N1O2");
          REQUIRE(formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
                  "H2O1H2O1");

          AND_THEN("The formula becomes valid")
          {
            REQUIRE(formula_MA_dipeptidyl.isValid());
          }
        }
        AND_WHEN("Masses are accounted for the action-formula")
        {

          THEN("Mono and avg masses are updated in ony possible way")
          {
            double mono = 0;
            double avg  = 0;
            bool ok;

            formula_MA_dipeptidyl.accountMasses(
              ok, isotopic_data_csp, mono, avg, 1);
            REQUIRE(ok == true);

            REQUIRE_THAT(
              mono, Catch::Matchers::WithinAbs(202.07759887468, 0.0000000001));
            REQUIRE_THAT(
              avg,
              Catch::Matchers::WithinAbs(202.274890194285717, 0.0000000001));
          }

          AND_THEN("In another possible way")
          {
            double mono = 0;
            double avg  = 0;
            bool ok;

            formula_MA_dipeptidyl.accountMasses(
              ok, isotopic_data_csp, mono, avg, 10);
            REQUIRE(ok == true);

            REQUIRE_THAT(
              mono,
              Catch::Matchers::WithinAbs(202.07759887468 * 10, 0.0000000001));
            REQUIRE_THAT(avg,
                         Catch::Matchers::WithinAbs(202.274890194285717 * 10,
                                                    0.0000000001));
          }

          AND_WHEN("The formula is compared to itself")
          {
            THEN("True is returned for ==()")
            {
              REQUIRE(formula_MA_dipeptidyl == formula_MA_dipeptidyl);
            }
            THEN("False is returned for !=()")
            {
              REQUIRE_FALSE(formula_MA_dipeptidyl != formula_MA_dipeptidyl);
            }
          }

          AND_WHEN("Another formula is copy-constructed")
          {
            const Formula another_formula(formula_MA_dipeptidyl);

            THEN("The two Formula instances should compare positively")
            {
              REQUIRE(another_formula == formula_MA_dipeptidyl);
              REQUIRE_FALSE(another_formula != formula_MA_dipeptidyl);

              REQUIRE(another_formula.getSymbolCountMapCstRef().size() ==
                      formula_MA_dipeptidyl.getSymbolCountMapCstRef().size());
              REQUIRE(another_formula.getSymbolCountMapCstRef() ==
                      formula_MA_dipeptidyl.getSymbolCountMapCstRef());

              //  This is the formula:
              // "+C5H11N1O2S1-H2O+C3H7N1O2-H2O"
              //  equivalent to:
              // "+C5H11N1O2S1+C3H7N1O2-H4O2"
              //  equivalent to:
              // "+C8H18N2O4S1-H4O2"
              //  that is:
              //  "C8H14N2O2S1"
              // So we can check the atom symbol count map.

              qDebug() << "Compare atom symbol count for C";
              auto iter =
                formula_MA_dipeptidyl.getSymbolCountMapCstRef().find("C");
              auto another_iter =
                another_formula.getSymbolCountMapCstRef().find("C");

              REQUIRE(iter !=
                      formula_MA_dipeptidyl.getSymbolCountMapCstRef().end());
              REQUIRE(another_iter !=
                      another_formula.getSymbolCountMapCstRef().end());

              REQUIRE((*iter).second == (*another_iter).second);

              qDebug() << "Compare atom symbol count for H";
              iter = formula_MA_dipeptidyl.getSymbolCountMapCstRef().find("H");
              another_iter =
                another_formula.getSymbolCountMapCstRef().find("H");

              REQUIRE(iter !=
                      formula_MA_dipeptidyl.getSymbolCountMapCstRef().end());
              REQUIRE(another_iter !=
                      another_formula.getSymbolCountMapCstRef().end());

              REQUIRE((*iter).second == (*another_iter).second);

              qDebug() << "Compare atom symbol count for N";
              iter = formula_MA_dipeptidyl.getSymbolCountMapCstRef().find("N");
              another_iter =
                another_formula.getSymbolCountMapCstRef().find("N");

              REQUIRE(iter !=
                      formula_MA_dipeptidyl.getSymbolCountMapCstRef().end());
              REQUIRE(another_iter !=
                      another_formula.getSymbolCountMapCstRef().end());

              REQUIRE((*iter).second == (*another_iter).second);

              qDebug() << "Compare atom symbol count for O";
              iter = formula_MA_dipeptidyl.getSymbolCountMapCstRef().find("O");
              another_iter =
                another_formula.getSymbolCountMapCstRef().find("O");

              REQUIRE(iter !=
                      formula_MA_dipeptidyl.getSymbolCountMapCstRef().end());
              REQUIRE(another_iter !=
                      another_formula.getSymbolCountMapCstRef().end());

              REQUIRE((*iter).second == (*another_iter).second);

              qDebug() << "Compare atom symbol count for S";
              iter = formula_MA_dipeptidyl.getSymbolCountMapCstRef().find("S");
              another_iter =
                another_formula.getSymbolCountMapCstRef().find("S");

              REQUIRE(iter !=
                      formula_MA_dipeptidyl.getSymbolCountMapCstRef().end());
              REQUIRE(another_iter !=
                      another_formula.getSymbolCountMapCstRef().end());

              REQUIRE((*iter).second == (*another_iter).second);
            }
          }

          AND_WHEN("Another formula is assignment-operator copied")
          {
            Formula another_formula;
            another_formula = formula_MA_dipeptidyl;

            THEN("The two Formula instances should compare positively")
            {
              REQUIRE(another_formula == formula_MA_dipeptidyl);
              REQUIRE_FALSE(another_formula != formula_MA_dipeptidyl);
            }
          }

          AND_WHEN(
            "Another formula is copy-constructed,  but later modified on title")
          {
            Formula another_formula(formula_MA_dipeptidyl);
            another_formula.setTitle("Different");

            THEN("The two Formula instances should not compare positively")
            {
              REQUIRE(another_formula != formula_MA_dipeptidyl);
              REQUIRE_FALSE(another_formula == formula_MA_dipeptidyl);
            }
          }

          AND_WHEN(
            "Another formula is assignment-operator copied,  but later "
            "modified "
            "on title")
          {
            Formula another_formula;
            another_formula = formula_MA_dipeptidyl;
            another_formula.setTitle("Different");

            THEN("The two Formula instances should not compare positively")
            {
              REQUIRE(another_formula != formula_MA_dipeptidyl);
              REQUIRE_FALSE(another_formula == formula_MA_dipeptidyl);
            }
          }

          AND_WHEN(
            "Another formula is copy-constructed,  but later modified on "
            "action-formula")
          {
            Formula another_formula(formula_MA_dipeptidyl);
            another_formula.setActionFormula("-H2O");

            THEN("The two Formula instances should not compare positively")
            {
              REQUIRE(another_formula != formula_MA_dipeptidyl);
              REQUIRE_FALSE(another_formula == formula_MA_dipeptidyl);
            }
          }

          AND_WHEN(
            "Another formula is assignment-operator copied,  but later "
            "modified "
            "on action-formula")
          {
            Formula another_formula;
            another_formula = formula_MA_dipeptidyl;
            another_formula.setActionFormula("-H2O");

            THEN("The two Formula instances should not compare positively")
            {
              REQUIRE(another_formula != formula_MA_dipeptidyl);
              REQUIRE_FALSE(another_formula == formula_MA_dipeptidyl);
            }
          }
        }
      }
    }
  }
}

SCENARIO("Formula instances can be scrutinized in various ways", "[Formula]")
{
  IsotopicDataLibraryHandler isotopic_data_library_handler;

  qsizetype non_isotope_skipped_items = 0;
  qsizetype loaded_isotope_count =
    isotopic_data_library_handler.loadData(non_isotope_skipped_items);

  IsotopicDataCstSPtr isotopic_data_csp = nullptr;

  REQUIRE(loaded_isotope_count + non_isotope_skipped_items ==
          static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries));

  isotopic_data_csp = isotopic_data_library_handler.getIsotopicData();
  REQUIRE(isotopic_data_csp->size() == loaded_isotope_count);

  GIVEN("A Formula created from a valid formula string")
  {
    Formula formula_MA_dipeptidyl(
      test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl);

    WHEN("That formula is validated succesfully")
    {
      REQUIRE(formula_MA_dipeptidyl.validate(isotopic_data_csp,
                                             /*store*/ true,
                                             /*reset*/ false,
                                             &error_list_formula));

      THEN("It becomes valid")
      {
        REQUIRE(formula_MA_dipeptidyl.getActionFormula().toStdString() ==
                test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl
                  .toStdString());
        REQUIRE(formula_MA_dipeptidyl.isValid());
      }

      THEN("The sub-formulas can be checked")
      {
        REQUIRE(formula_MA_dipeptidyl.getPlusFormula().toStdString() ==
                "C5H11N1O2S1C3H7N1O2");
        REQUIRE(formula_MA_dipeptidyl.getMinusFormula().toStdString() ==
                "H2O1H2O1");
      }

      THEN("The elemental composition can be checked")
      {
        REQUIRE(formula_MA_dipeptidyl.elementalComposition() == "C8H14N2O2S1");
      }

      THEN("The actions can be checked")
      {
        REQUIRE(formula_MA_dipeptidyl.actions() == '-');
      }
    }
  }
}

SCENARIO("A Formula instance can initialize itself using an XML element")
{
  // int formula_element_index    = 0;
  // int formula_text_index       = 1;

  QStringList dom_strings{"formula", "\"Protonation\"+H"};

  QDomDocument document =
    test_utils_formula.craftFormulaDomDocument(dom_strings);

  QDomElement formula_element =
    document.elementsByTagName(dom_strings[0]).item(0).toElement();

  // qDebug() << "The document:" << document.toString();

  WHEN(
    "An empty Formula instance is initialized by rendering in it the XML "
    "element")
  {
    Formula formula;

    REQUIRE(formula.renderXmlFormulaElement(formula_element) == true);

    THEN("The formula shoul have its members correctly set")
    {
      REQUIRE(formula.getActionFormula().toStdString() == "+H");
      REQUIRE(formula.getTitle().toStdString() == "Protonation");
    }
  }
}

SCENARIO("A Formula instance can document itself in an XML element")
{
  Formula titled_formula(
    test_utils_formula.m_actionFormulaStringMetAlaDipeptidylTitled);

  WHEN("Documenting itself in an XML element")
  {
    QString xml_element =
      titled_formula.formatXmlFormulaElement(0, Utils::xmlIndentationToken);

    THEN("The string should match the expected")
    {

      REQUIRE(xml_element.toStdString() ==
              QString("<formula>\"%1\"%2</formula>\n")
                .arg(test_utils_formula.m_formulaTitle)
                .arg(test_utils_formula.m_actionFormulaStringMetAlaDipeptidyl)
                .toStdString());
    }
  }
}


} // namespace libXpertMassCore
} // namespace MsXpS
