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


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


/////////////////////// IsoSpec
#include <IsoSpec++/isoSpec++.h>
#include <IsoSpec++/element_tables.h>


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


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

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

ErrorList error_list_cross_linker;

SCENARIO("Construction of a CrossLinker with a proper XML element",
         "[CrossLinker]")
{
  // <clk>
  //  <name>CFP-chromophore</name>
  //  <formula></formula>
  //  <modifname>Chromo-O</modifname>
  //  <modifname>Chromo-H3</modifname>
  //  <modifname>Chromo-H</modifname>
  // </clk>

  QStringList dom_strings{"clk",
                          "name",
                          "CFP-chromophore",
                          "formula",
                          "",
                          "modifname",
                          "Chromo-O",
                          "modifname",
                          "Chromo-H3",
                          "modifname",
                          "Chromo-H"};

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  WHEN(
    "A CrossLinker is constructed with PolChemDef and a valid XML clk element")
  {
    QDomDocument document =
      test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
    QDomElement clk_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

    //  Use indentation 1 to mimick what happens in XpertMass.
    qDebug() << "The document:" << document.toString(/*indentation*/ 1);

    REQUIRE(
      document.toString(/*indentation*/ 1).toStdString() ==
      "<clk>\n <name>CFP-chromophore</name>\n <formula></formula>\n "
      "<modifname>Chromo-O</modifname>\n <modifname>Chromo-H3</modifname>\n "
      "<modifname>Chromo-H</modifname>\n</clk>\n");

    CrossLinker cross_linker(pol_chem_def_csp, clk_element, /*version*/ 1);

    THEN(
      "It is valid, does validate successfully and has all proper member data")
    {
      REQUIRE(cross_linker.getPolChemDefCstSPtr() == pol_chem_def_csp);

      error_list_cross_linker.clear();
      REQUIRE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE(cross_linker.isValid());
      REQUIRE(cross_linker.getName().toStdString() == "CFP-chromophore");
      REQUIRE(cross_linker.getFormula().toStdString() == "");
      REQUIRE(cross_linker.getModifsCstRef().size() == 3);
      REQUIRE(cross_linker.getModifsRef().size() == 3);
      REQUIRE(
        (*cross_linker.getModifsCstRef().cbegin())->getName().toStdString() ==
        "Chromo-O");
      REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 1))
                ->getName()
                .toStdString() == "Chromo-H3");
      REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 2))
                ->getName()
                .toStdString() == "Chromo-H");
    }
  }
}

SCENARIO(
  "Construction of a CrossLinker with nullptr PolChemDefCstSPtr and a proper "
  "XML element",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  QStringList dom_strings{"clk",
                          "name",
                          "CFP-chromophore",
                          "formula",
                          "",
                          "modifname",
                          "Chromo-O",
                          "modifname",
                          "Chromo-H3",
                          "modifname",
                          "Chromo-H"};

  QDomDocument document =
    test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
  QDomElement clk_element =
    document.elementsByTagName(dom_strings[0]).item(0).toElement();

  //  Use indentation 1 to mimick what happens in XpertMass.
  qDebug() << "The document:" << document.toString(/*indentation*/ 1);

  REQUIRE(
    document.toString(/*indentation*/ 1).toStdString() ==
    "<clk>\n <name>CFP-chromophore</name>\n <formula></formula>\n "
    "<modifname>Chromo-O</modifname>\n <modifname>Chromo-H3</modifname>\n "
    "<modifname>Chromo-H</modifname>\n</clk>\n");

  WHEN(
    "Allocating a CrossLinker with null PolChemDefCstSPtr and a valid XML "
    "element")
  {
    CrossLinker cross_linker(nullptr, clk_element);

    THEN("The CrossLinker fails to validate.")
    {
      REQUIRE_FALSE(cross_linker.isValid());
    }
  }
}

SCENARIO(
  "Construction of a CrossLinker with proper PolChemDefCstSPtr and a proper "
  "XML element",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  QStringList dom_strings{"clk",
                          "name",
                          "CFP-chromophore",
                          "formula",
                          "",
                          "modifname",
                          "Chromo-O",
                          "modifname",
                          "Chromo-H3",
                          "modifname",
                          "Chromo-H"};

  // Failing <clk> tag
  dom_strings.replace(0, "erroneous_clk_tag");

  QDomDocument document =
    test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
  QDomElement clk_element =
    document.elementsByTagName(dom_strings[0]).item(0).toElement();

  //  Use indentation 1 to mimick what happens in XpertMass.
  qDebug() << "The document:" << document.toString(/*indentation*/ 1);

  REQUIRE(
    document.toString(/*indentation*/ 1).toStdString() ==
    "<erroneous_clk_tag>\n <name>CFP-chromophore</name>\n "
    "<formula></formula>\n "
    "<modifname>Chromo-O</modifname>\n <modifname>Chromo-H3</modifname>\n "
    "<modifname>Chromo-H</modifname>\n</erroneous_clk_tag>\n");

  WHEN(
    "Allocating a CrossLinker with null PolChemDefCstSPtr and a valid XML "
    "element")
  {
    CrossLinker cross_linker(pol_chem_def_csp, clk_element);

    THEN("The CrossLinker fails to validate.")
    {
      REQUIRE_FALSE(cross_linker.isValid());
    }
  }
}

SCENARIO(
  "Construction of a CrossLinker lacking PolChemDefCstSPtr or not with a "
  "proper XML element",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  QStringList dom_strings{"clk",
                          "name",
                          "CFP-chromophore",
                          "formula",
                          "",
                          "modifname",
                          "Chromo-O",
                          "modifname",
                          "Chromo-H3",
                          "modifname",
                          "Chromo-H"};

  WHEN("A CrossLinker is created empty")
  {
    CrossLinker cross_linker;

    THEN("It is invalid and does not validate successfully")
    {
      error_list_cross_linker.clear();
      REQUIRE_FALSE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE_FALSE(cross_linker.isValid());
    }

    AND_WHEN(
      "The CrossLinker lacking PolChemDefCstSPtr is rendered as an XML "
      "element")
    {
      QDomDocument document =
        test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
      QDomElement clk_element =
        document.elementsByTagName(dom_strings[0]).item(0).toElement();

      //  Use indentation 1 to mimick what happens in XpertMass.
      qDebug() << "The document:" << document.toString(/*indentation*/ 1);

      REQUIRE(document.toString(/*indentation*/ 1).toStdString() ==
              "<clk>\n <name>CFP-chromophore</name>\n <formula></formula>\n "
              "<modifname>Chromo-O</modifname>\n "
              "<modifname>Chromo-H3</modifname>\n "
              "<modifname>Chromo-H</modifname>\n</clk>\n");

      REQUIRE_FALSE(
        cross_linker.renderXmlClkElement(clk_element, /*version*/ 1));

      THEN("Some data are set,  other not, the CrossLinker is not valid")
      {
        REQUIRE(cross_linker.getName().toStdString() == "CFP-chromophore");
        REQUIRE(cross_linker.getFormula().toStdString() == "");
        REQUIRE(cross_linker.getModifsCstRef().size() == 0);

        error_list_cross_linker.clear();
        REQUIRE_FALSE(cross_linker.validate(&error_list_cross_linker));
        REQUIRE_FALSE(cross_linker.isValid());
      }
    }
  }

  WHEN(
    "A CrossLinker is created empty and then the PolChemDef is set with the "
    "setter")
  {
    CrossLinker cross_linker;
    cross_linker.setPolChemDefCstSPtr(pol_chem_def_csp);

    THEN("It is invalid and does not validate successfully")
    {
      error_list_cross_linker.clear();
      REQUIRE_FALSE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE_FALSE(cross_linker.isValid());
    }

    AND_WHEN("The CrossLinker is rendered as an XML element")
    {
      QDomDocument document =
        test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
      QDomElement clk_element =
        document.elementsByTagName(dom_strings[0]).item(0).toElement();

      //  Use indentation 1 to mimick what happens in XpertMass.
      qDebug() << "The document:" << document.toString(/*indentation*/ 1);

      REQUIRE(document.toString(/*indentation*/ 1).toStdString() ==
              "<clk>\n <name>CFP-chromophore</name>\n <formula></formula>\n "
              "<modifname>Chromo-O</modifname>\n "
              "<modifname>Chromo-H3</modifname>\n "
              "<modifname>Chromo-H</modifname>\n</clk>\n");

      REQUIRE(cross_linker.renderXmlClkElement(clk_element, /*version*/ 1));

      THEN(
        "It is valid, does validate successfully and has all proper member "
        "data")
      {
        error_list_cross_linker.clear();
        REQUIRE(cross_linker.validate(&error_list_cross_linker));
        REQUIRE(cross_linker.isValid());
        REQUIRE(cross_linker.getName().toStdString() == "CFP-chromophore");
        REQUIRE(cross_linker.getFormula().toStdString() == "");
        REQUIRE(cross_linker.getModifsCstRef().size() == 3);
        REQUIRE(
          (*cross_linker.getModifsCstRef().cbegin())->getName().toStdString() ==
          "Chromo-O");
        REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 1))
                  ->getName()
                  .toStdString() == "Chromo-H3");
        REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 2))
                  ->getName()
                  .toStdString() == "Chromo-H");
      }
    }
  }
}

SCENARIO(
  "Construction of a CrossLinker with PolChemDefCstSPtr but with a "
  "failing XML element",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  QStringList dom_strings{"clk",
                          "name",
                          "CFP-chromophore",
                          "formula",
                          "",
                          "modifname",
                          "Chromo-O",
                          "modifname",
                          "Chromo-H3",
                          "modifname",
                          "Chromo-H"};

  GIVEN("An empty CrossLinker set with a proper PolChemDefCstSPtr")
  {
    CrossLinker cross_linker;
    cross_linker.setPolChemDefCstSPtr(pol_chem_def_csp);

    THEN("It is invalid and does not validate successfully")
    {
      error_list_cross_linker.clear();
      REQUIRE_FALSE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE_FALSE(cross_linker.isValid());
    }

    AND_WHEN("The CrossLinker is rendered as a failing XML element <clk> tag")
    {
      // Failing <clk> tag.
      dom_strings.replace(0, "clw");

      QDomDocument document =
        test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
      QDomElement clk_element =
        document.elementsByTagName(dom_strings[0]).item(0).toElement();

      //  Use indentation 1 to mimick what happens in XpertMass.
      qDebug() << "The document:" << document.toString(/*indentation*/ 1);

      REQUIRE(document.toString(/*indentation*/ 1).toStdString() ==
              "<clw>\n <name>CFP-chromophore</name>\n <formula></formula>\n "
              "<modifname>Chromo-O</modifname>\n "
              "<modifname>Chromo-H3</modifname>\n "
              "<modifname>Chromo-H</modifname>\n</clw>\n");

      REQUIRE_FALSE(
        cross_linker.renderXmlClkElement(clk_element, /*version*/ 1));

      THEN("It is not valid because the XML element is failing")
      {
        error_list_cross_linker.clear();
        REQUIRE_FALSE(cross_linker.validate(&error_list_cross_linker));
        REQUIRE_FALSE(cross_linker.isValid());
      }
    }

    AND_WHEN("The CrossLinker is rendered as a failing XML element <name> tag")
    {
      dom_strings.replace(0, "clk");
      dom_strings.replace(1, "erroneous_name_tag");

      QDomDocument document =
        test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
      QDomElement clk_element =
        document.elementsByTagName(dom_strings[0]).item(0).toElement();

      //  Use indentation 1 to mimick what happens in XpertMass.
      qDebug() << "The document:" << document.toString(/*indentation*/ 1);

      REQUIRE(
        document.toString(/*indentation*/ 1).toStdString() ==
        "<clk>\n <erroneous_name_tag>CFP-chromophore</erroneous_name_tag>\n "
        "<formula></formula>\n "
        "<modifname>Chromo-O</modifname>\n "
        "<modifname>Chromo-H3</modifname>\n "
        "<modifname>Chromo-H</modifname>\n</clk>\n");

      REQUIRE_FALSE(
        cross_linker.renderXmlClkElement(clk_element, /*version*/ 1));

      THEN("It is not valid because the XML element is failing")
      {
        error_list_cross_linker.clear();
        REQUIRE_FALSE(cross_linker.validate(&error_list_cross_linker));
        REQUIRE_FALSE(cross_linker.isValid());
      }
    }

    AND_WHEN(
      "The CrossLinker is rendered as a failing XML element <formula> tag")
    {
      dom_strings.replace(1, "name");
      dom_strings.replace(3, "erroneous_formula_tag");

      QDomDocument document =
        test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
      QDomElement clk_element =
        document.elementsByTagName(dom_strings[0]).item(0).toElement();

      //  Use indentation 1 to mimick what happens in XpertMass.
      qDebug() << "The document:" << document.toString(/*indentation*/ 1);

      REQUIRE(document.toString(/*indentation*/ 1).toStdString() ==
              "<clk>\n <name>CFP-chromophore</name>\n "
              "<erroneous_formula_tag></erroneous_formula_tag>\n "
              "<modifname>Chromo-O</modifname>\n "
              "<modifname>Chromo-H3</modifname>\n "
              "<modifname>Chromo-H</modifname>\n</clk>\n");

      THEN("It is not valid because the XML element is failing")
      {
        REQUIRE_FALSE(
          cross_linker.renderXmlClkElement(clk_element, /*version*/ 1));
      }
    }

    AND_WHEN(
      "The CrossLinker is rendered as a failing XML element <formula> text")
    {
      dom_strings.replace(3, "formula");
      dom_strings.replace(4, "+HWKPTR");

      QDomDocument document =
        test_utils_1_letter_cross_linker.craftClkDomDocument(dom_strings);
      QDomElement clk_element =
        document.elementsByTagName(dom_strings[0]).item(0).toElement();

      //  Use indentation 1 to mimick what happens in XpertMass.
      qDebug() << "The document:" << document.toString(/*indentation*/ 1);

      REQUIRE(
        document.toString(/*indentation*/ 1).toStdString() ==
        "<clk>\n <name>CFP-chromophore</name>\n <formula>+HWKPTR</formula>\n "
        "<modifname>Chromo-O</modifname>\n "
        "<modifname>Chromo-H3</modifname>\n "
        "<modifname>Chromo-H</modifname>\n</clk>\n");

      REQUIRE_FALSE(
        cross_linker.renderXmlClkElement(clk_element, /*version*/ 1));
    }
  }
}


SCENARIO("Construction of a CrossLinker starting from empty with the setters",
         "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;
  WHEN("A CrossLinker is created empty")
  {
    CrossLinker cross_linker;

    THEN("It is invalid and does not validate successfully")
    {
      error_list_cross_linker.clear();
      REQUIRE_FALSE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE_FALSE(cross_linker.isValid());
    }

    AND_WHEN("The CrossLinker is configured piecemeal with setters")
    {
      cross_linker.setPolChemDefCstSPtr(pol_chem_def_csp);
      cross_linker.setName("CFP-chromophore");
      cross_linker.setFormula("");

      // Get Modifs from the PolChemDef.
      ModifCstSPtr modif_csp =
        pol_chem_def_csp->getModifCstSPtrByName("Chromo-O");
      REQUIRE(modif_csp != nullptr);
      cross_linker.appendModif(modif_csp);

      modif_csp = pol_chem_def_csp->getModifCstSPtrByName("Chromo-H");
      REQUIRE(modif_csp != nullptr);
      cross_linker.appendModif(modif_csp);

      modif_csp = pol_chem_def_csp->getModifCstSPtrByName("Chromo-H3");
      REQUIRE(modif_csp != nullptr);
      cross_linker.insertModifAt(modif_csp, 1);

      THEN(
        "It is valid, does validate successfully and has all proper member "
        "data")
      {
        error_list_cross_linker.clear();
        REQUIRE(cross_linker.validate(&error_list_cross_linker));
        REQUIRE(cross_linker.isValid());
        REQUIRE(cross_linker.getName().toStdString() == "CFP-chromophore");
        REQUIRE(cross_linker.getFormula().toStdString() == "");
        REQUIRE(cross_linker.getModifsCstRef().size() == 3);
        REQUIRE(
          (*cross_linker.getModifsCstRef().cbegin())->getName().toStdString() ==
          "Chromo-O");
        REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 1))
                  ->getName()
                  .toStdString() == "Chromo-H3");
        REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 2))
                  ->getName()
                  .toStdString() == "Chromo-H");

        REQUIRE(cross_linker.getModifAt(0) ==
                (*cross_linker.getModifsCstRef().cbegin()));
        REQUIRE(cross_linker.getModifAt(2) ==
                (*(std::prev(cross_linker.getModifsCstRef().cend()))));

        AND_THEN("The masses should be calculated fine")
        {
          REQUIRE(cross_linker.calculateMasses(nullptr));
          REQUIRE_THAT(
            cross_linker.getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(-20.0262147493, 0.0000000001));
          REQUIRE_THAT(
            cross_linker.getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(-20.0311745907, 0.0000000001));
        }
      }
    }
  }
}

SCENARIO("CrossLinker instances can be set and removed Modif instances",
         "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  WHEN("A CrossLinker is created with name and formula but not Modif")
  {
    CrossLinker cross_linker(pol_chem_def_csp, "CFP-chromophore", "-H2O");

    THEN(
      "It is valid, does validate successfully and has all proper member "
      "data")
    {
      error_list_cross_linker.clear();
      REQUIRE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE(cross_linker.isValid());
      REQUIRE(cross_linker.getName().toStdString() == "CFP-chromophore");
      REQUIRE(cross_linker.getFormula().toStdString() == "-H2O");
      REQUIRE(cross_linker.getModifsCstRef().size() == 0);
    }

    AND_WHEN("Modifications are added to it")
    {
      // Get Modifs from the PolChemDef.
      ModifCstSPtr modif_csp =
        pol_chem_def_csp->getModifCstSPtrByName("Chromo-O");
      REQUIRE(modif_csp != nullptr);
      cross_linker.appendModif(modif_csp);

      modif_csp = pol_chem_def_csp->getModifCstSPtrByName("Chromo-H");
      REQUIRE(modif_csp != nullptr);
      cross_linker.appendModif(modif_csp);

      modif_csp = pol_chem_def_csp->getModifCstSPtrByName("Chromo-H3");
      REQUIRE(modif_csp != nullptr);
      cross_linker.insertModifAt(modif_csp, 1);

      THEN(
        "These are now available in the CrossLinker and can be checked fully")
      {
        REQUIRE(cross_linker.getModifsCstRef().size() == 3);
        REQUIRE(
          (*cross_linker.getModifsCstRef().cbegin())->getName().toStdString() ==
          "Chromo-O");
        REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 1))
                  ->getName()
                  .toStdString() == "Chromo-H3");
        REQUIRE((*(cross_linker.getModifsCstRef().cbegin() + 2))
                  ->getName()
                  .toStdString() == "Chromo-H");

        REQUIRE(cross_linker.hasModif("Chromo-O"));
        REQUIRE(cross_linker.modifIndex("Chromo-O") == 0);

        REQUIRE(cross_linker.hasModif("Chromo-H3"));
        REQUIRE(cross_linker.modifIndex("Chromo-H3") == 1);

        REQUIRE(cross_linker.hasModif("Chromo-H"));
        REQUIRE(cross_linker.modifIndex("Chromo-H") == 2);

        REQUIRE_FALSE(cross_linker.hasModif("Chromo-OOOO"));
        REQUIRE(cross_linker.modifIndex("Chromo-OOOO") == -1);

        REQUIRE(cross_linker.getModifAt(0) ==
                (*cross_linker.getModifsCstRef().cbegin()));
        REQUIRE(cross_linker.getModifAt(2) ==
                (*(std::prev(cross_linker.getModifsCstRef().cend()))));

        AND_THEN("The masses should be calculated fine")
        {
          REQUIRE(cross_linker.calculateMasses(nullptr));
          REQUIRE_THAT(cross_linker.getMass(Enums::MassType::MONO),
                       Catch::Matchers::WithinAbs(-38.036779434, 0.0000000001));
          REQUIRE_THAT(
            cross_linker.getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(-38.0464662446, 0.0000000001));
        }
      }

      AND_WHEN("Modifications are removed from it piecemeal")
      {
        cross_linker.removeModifAt(1);
        REQUIRE(cross_linker.getModifsCstRef().size() == 2);

        REQUIRE(cross_linker.getModifAt(0) ==
                (*cross_linker.getModifsCstRef().cbegin()));
        REQUIRE(cross_linker.getModifAt(1) ==
                (*(std::prev(cross_linker.getModifsCstRef().cend()))));

        REQUIRE(cross_linker.modifIndex("Chromo-O") == 0);
        REQUIRE_FALSE(cross_linker.hasModif("Chromo-H3"));
        REQUIRE(cross_linker.modifIndex("Chromo-H") == 1);

        cross_linker.removeModifAt(1);
        REQUIRE(cross_linker.getModifsCstRef().size() == 1);

        REQUIRE(cross_linker.getModifAt(0) ==
                (*cross_linker.getModifsCstRef().cbegin()));
        REQUIRE(cross_linker.modifIndex("Chromo-O") == 0);
        REQUIRE_FALSE(cross_linker.hasModif("Chromo-H"));

        cross_linker.removeModifAt(0);
        REQUIRE(cross_linker.getModifsCstRef().size() == 0);
      }
    }
  }
}

SCENARIO(
  "CrossLinker instances can be copy-constructed or assigned and compared",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  WHEN(
    "A CrossLinker is created with name and formula and then Modif are added")
  {
    CrossLinker cross_linker(pol_chem_def_csp, "CFP-chromophore", "-H2O");

    // Get Modifs from the PolChemDef.

    ModifCstSPtr modif_csp =
      pol_chem_def_csp->getModifCstSPtrByName("Chromo-O");
    REQUIRE(modif_csp != nullptr);
    cross_linker.appendModif(modif_csp);

    modif_csp = pol_chem_def_csp->getModifCstSPtrByName("Chromo-H");
    REQUIRE(modif_csp != nullptr);
    cross_linker.appendModif(modif_csp);

    modif_csp = pol_chem_def_csp->getModifCstSPtrByName("Chromo-H3");
    cross_linker.insertModifAt(modif_csp, 1);

    THEN(
      "It is valid, does validate successfully and has all proper member "
      "data")
    {
      error_list_cross_linker.clear();
      REQUIRE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE(cross_linker.isValid());
      REQUIRE(cross_linker.getName().toStdString() == "CFP-chromophore");
      REQUIRE(cross_linker.getFormula().toStdString() == "-H2O");
      REQUIRE(cross_linker.getModifsCstRef().size() == 3);
    }

    AND_WHEN("A new CrossLinker is copy-constructed")
    {
      CrossLinker new_cross_linker(cross_linker);

      // qDebug() << "cross_linker:" << cross_linker.toString();
      // qDebug() << "new_cross_linker:" << new_cross_linker.toString();

      THEN("All the members should be identical in both instances")
      {
        REQUIRE(new_cross_linker.validate(&error_list_cross_linker));
        REQUIRE(new_cross_linker.isValid());
        REQUIRE(new_cross_linker.getName().toStdString() == "CFP-chromophore");
        REQUIRE(new_cross_linker.getFormula().toStdString() == "-H2O");

        REQUIRE(new_cross_linker.getModifsCstRef().size() == 3);
        REQUIRE(cross_linker.getModifAt(0) == new_cross_linker.getModifAt(0));
        REQUIRE(cross_linker.getModifAt(1) == new_cross_linker.getModifAt(1));
        REQUIRE(cross_linker.getModifAt(2) == new_cross_linker.getModifAt(2));

        AND_THEN("The comparison operators should provide correct results")
        {
          REQUIRE(new_cross_linker == cross_linker);
          REQUIRE_FALSE(new_cross_linker != cross_linker);
        }
      }
    }

    AND_WHEN("A new CrossLinker is assigned")
    {
      CrossLinker new_cross_linker;
      new_cross_linker = cross_linker;

      REQUIRE(cross_linker == (cross_linker = cross_linker));

      // qDebug() << "cross_linker:" << cross_linker.toString();
      // qDebug() << "new_cross_linker:" << new_cross_linker.toString();

      THEN("All the members should be identical in both instances")
      {
        REQUIRE(new_cross_linker.validate(&error_list_cross_linker));
        REQUIRE(new_cross_linker.isValid());
        REQUIRE(new_cross_linker.getName().toStdString() == "CFP-chromophore");
        REQUIRE(new_cross_linker.getFormula().toStdString() == "-H2O");

        REQUIRE(new_cross_linker.getModifsCstRef().size() == 3);
        REQUIRE(cross_linker.getModifAt(0) == new_cross_linker.getModifAt(0));
        REQUIRE(cross_linker.getModifAt(1) == new_cross_linker.getModifAt(1));
        REQUIRE(cross_linker.getModifAt(2) == new_cross_linker.getModifAt(2));

        AND_THEN("The comparison operators should provide correct results")
        {
          REQUIRE(new_cross_linker == cross_linker);
          REQUIRE_FALSE(new_cross_linker != cross_linker);
          REQUIRE(cross_linker == cross_linker);
          REQUIRE_FALSE(cross_linker != cross_linker);
        }
      }
    }

    AND_WHEN("A new CrossLinker is copy-constructed but its name modified")
    {
      CrossLinker new_cross_linker(cross_linker);

      // qDebug() << "cross_linker:" << cross_linker.toString();
      // qDebug() << "new_cross_linker:" << new_cross_linker.toString();

      new_cross_linker.setName("other");

      THEN("The comparison operators should provide correct results")
      {
        REQUIRE_FALSE(new_cross_linker == cross_linker);
        REQUIRE(new_cross_linker != cross_linker);
      }
    }

    AND_WHEN("A new CrossLinker is copy-constructed but its formula modified")
    {
      CrossLinker new_cross_linker(cross_linker);

      // qDebug() << "cross_linker:" << cross_linker.toString();
      // qDebug() << "new_cross_linker:" << new_cross_linker.toString();

      new_cross_linker.setFormula("");

      THEN("The comparison operators should provide correct results")
      {
        REQUIRE_FALSE(new_cross_linker == cross_linker);
        REQUIRE(new_cross_linker != cross_linker);
      }
    }

    AND_WHEN("A new CrossLinker is copy-constructed but one Modif is removed")
    {
      CrossLinker new_cross_linker(cross_linker);

      // qDebug() << "cross_linker:" << cross_linker.toString();
      // qDebug() << "new_cross_linker:" << new_cross_linker.toString();

      new_cross_linker.removeModifAt(0);

      THEN("The comparison operators should provide correct results")
      {
        REQUIRE_FALSE(new_cross_linker == cross_linker);
        REQUIRE(new_cross_linker != cross_linker);
      }
    }
  }
}

SCENARIO(
  "CrossLinker instances have masses accounting for formula and Modif "
  "instances",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  WHEN("A CrossLinker is created by looking into PolChemDef with name")
  {
    CrossLinkerCstSPtr cross_linker_csp =
      pol_chem_def_csp->getCrossLinkerCstSPtrByName("CFP-chromophore");
    REQUIRE(cross_linker_csp != nullptr);

    CrossLinker cross_linker(*cross_linker_csp);

    THEN(
      "It is valid, does validate successfully and has all proper member "
      "data")
    {
      error_list_cross_linker.clear();
      REQUIRE(cross_linker.validate(&error_list_cross_linker));
      REQUIRE(cross_linker.isValid());
      REQUIRE(cross_linker.getName().toStdString() == "CFP-chromophore");
      REQUIRE(cross_linker.getFormula().toStdString() == "");
      REQUIRE(cross_linker.getModifsCstRef().size() == 3);
    }

    AND_WHEN("The masses are calculated explicitely with bad params")
    {
      CrossLinker another_cross_linker(cross_linker);
      another_cross_linker.setPolChemDefCstSPtr(nullptr);
      REQUIRE_FALSE(another_cross_linker.calculateMasses(nullptr));

      another_cross_linker.setPolChemDefCstSPtr(pol_chem_def_csp);
      another_cross_linker.setFormula("%HWO");
      REQUIRE_FALSE(another_cross_linker.calculateMasses(nullptr));

      AND_WHEN("The masses are calculated explicitely with good params")
      {

        THEN("The masses are correct")
        {
          REQUIRE_THAT(
            cross_linker.getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(-20.0262147493, 0.0000000001));
          REQUIRE_THAT(
            cross_linker.getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(-20.0311745907, 0.0000000001));
        }

        AND_WHEN("The masses are accounted once (pointer version)")
        {
          double mono = 0;
          double avg  = 0;

          REQUIRE(cross_linker == cross_linker.accountMasses(&mono, &avg, 1));

          THEN("The masses are correct")
          {
            REQUIRE_THAT(
              mono, Catch::Matchers::WithinAbs(-20.0262147493, 0.0000000001));
            REQUIRE_THAT(
              avg, Catch::Matchers::WithinAbs(-20.0311745907, 0.0000000001));
          }
        }

        AND_WHEN("The masses are accounted twice (pointer version)")
        {
          double mono = 0;
          double avg  = 0;

          REQUIRE(cross_linker == cross_linker.accountMasses(&mono, &avg, 2));

          THEN("The masses are correct")
          {
            REQUIRE_THAT(
              mono,
              Catch::Matchers::WithinAbs(2 * -20.0262147493, 0.0000000001));
            REQUIRE_THAT(
              avg,
              Catch::Matchers::WithinAbs(2 * -20.0311745907, 0.0000000001));
          }
        }

        AND_WHEN("The masses are accounted once (reference version)")
        {
          double mono = 0;
          double avg  = 0;

          REQUIRE(cross_linker == cross_linker.accountMasses(mono, avg, 1));

          THEN("The masses are correct")
          {
            REQUIRE_THAT(
              mono, Catch::Matchers::WithinAbs(-20.0262147493, 0.0000000001));
            REQUIRE_THAT(
              avg, Catch::Matchers::WithinAbs(-20.0311745907, 0.0000000001));
          }
        }

        AND_WHEN("The masses are accounted twice (reference version)")
        {
          double mono = 0;
          double avg  = 0;

          REQUIRE(cross_linker == cross_linker.accountMasses(mono, avg, 2));

          THEN("The masses are correct")
          {
            REQUIRE_THAT(
              mono,
              Catch::Matchers::WithinAbs(2 * -20.0262147493, 0.0000000001));
            REQUIRE_THAT(
              avg,
              Catch::Matchers::WithinAbs(2 * -20.0311745907, 0.0000000001));
          }
        }
      }
    }
  }
}

SCENARIO(
  "CrossLinker instances can be tested for existence in the PolChemDef by "
  "name",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  WHEN("A CrossLinker is created by looking into PolChemDef with name")
  {
    CrossLinkerCstSPtr cross_linker_csp =
      pol_chem_def_csp->getCrossLinkerCstSPtrByName("CFP-chromophore");
    REQUIRE(cross_linker_csp != nullptr);

    CrossLinker cross_linker(*cross_linker_csp);

    THEN("Its existence in the PolChemDef can be checked by name")
    {
      REQUIRE(cross_linker.isKnownByNameInPolChemDef() ==
              Enums::PolChemDefEntityStatus::ENTITY_KNOWN);
    }
  }
}

SCENARIO(
  "CrossLinker instances can write themselves out to an XML <clk> element",
  "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  WHEN("A CrossLinker is created by looking into PolChemDef with name")
  {
    CrossLinkerCstSPtr cross_linker_csp =
      pol_chem_def_csp->getCrossLinkerCstSPtrByName("CFP-chromophore");
    REQUIRE(cross_linker_csp != nullptr);

    CrossLinker cross_linker(*cross_linker_csp);

    THEN("The XML element must be correct")
    {
      REQUIRE(cross_linker
                .formatXmlClkElement(/*offset*/ 0,
                                     /*indent*/ Utils::xmlIndentationToken)
                .toStdString() ==
              "<clk>\n <name>CFP-chromophore</name>\n <formula></formula>\n "
              "<modifname>Chromo-O</modifname>\n "
              "<modifname>Chromo-H3</modifname>\n "
              "<modifname>Chromo-H</modifname>\n</clk>\n");
    }
  }
}

SCENARIO("CrossLinker instances can check for existence in the PolChemDef",
         "[CrossLinker]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_cross_linker.msp_polChemDef;

  WHEN("A CrossLinker is created by looking into PolChemDef with name")
  {
    CrossLinkerCstSPtr cross_linker_csp =
      pol_chem_def_csp->getCrossLinkerCstSPtrByName("CFP-chromophore");
    REQUIRE(cross_linker_csp != nullptr);

    CrossLinker cross_linker(*cross_linker_csp);

    THEN("That CrossLinker should be found in the PolChemDef")
    {
      REQUIRE(*cross_linker.getFromPolChemDefByName() == cross_linker);
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
