// ./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>


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

namespace MsXpS
{
namespace libXpertMassCore
{

TestUtils test_utils_1_letter_monomer("protein-1-letter", 1);
TestUtils test_utils_3_letters_monomer("protein-3-letters", 1);

ErrorList error_list_monomer;

SCENARIO(
  "Monomer instances can be constructed piecemeal starting from totally "
  "unconfigured",
  "[Monomer]")
{
  test_utils_1_letter_monomer.initializeXpertmassLibrary();

  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN("An allocated polymer chemistry definition")
  {

    WHEN("A Monomer is allocated totally unconfigured")
    {
      Monomer monomer;

      THEN("The Monomer is not valid and does not validate successfully")
      {
        REQUIRE_FALSE(monomer.isValid());

        REQUIRE(monomer.getName().toStdString() == "");

        REQUIRE(monomer.getCode().toStdString() == "");
        REQUIRE_FALSE(monomer.checkCodeSyntax());

        REQUIRE(monomer.getFormula().toStdString() == "");

        REQUIRE(monomer.isKnownByNameInPolChemDef() ==
                Enums::PolChemDefEntityStatus::POL_CHEM_DEF_NOT_AVAILABLE);
        REQUIRE_FALSE(monomer.validate(&error_list_monomer));
      }

      AND_WHEN("The PolChemDef is set")
      {
        monomer.setPolChemDefCstSPtr(pol_chem_def_csp);

        THEN(
          "The monomer is still not valid and does not validate successfully")
        {
          REQUIRE_FALSE(monomer.isValid());

          REQUIRE(monomer.isKnownByNameInPolChemDef() ==
                  Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN);

          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }

        AND_WHEN("The name is set")
        {
          monomer.setName("Tryptophan");

          THEN(
            "The monomer is still not valid and does not validate successfully")
          {
            REQUIRE_FALSE(monomer.isValid());

            REQUIRE(monomer.isKnownByNameInPolChemDef() ==
                    Enums::PolChemDefEntityStatus::ENTITY_KNOWN);

            REQUIRE_FALSE(monomer.validate(&error_list_monomer));
          }

          AND_WHEN("The code is set")
          {
            monomer.setCode("W");

            THEN(
              "The monomer is still not valid and does not validate "
              "successfully")
            {
              REQUIRE_FALSE(monomer.isValid());

              REQUIRE(monomer.isKnownByNameInPolChemDef() ==
                      Enums::PolChemDefEntityStatus::ENTITY_KNOWN);

              REQUIRE(monomer.isKnownByCodeInPolChemDef() ==
                      Enums::PolChemDefEntityStatus::ENTITY_KNOWN);

              REQUIRE_FALSE(monomer.validate(&error_list_monomer));
            }

            AND_WHEN("The formula is set")
            {
              monomer.setFormula(
                test_utils_1_letter_monomer.m_tryptophanFormulaString);

              THEN("The monomer is valid and does validate successfully")
              {
                REQUIRE(monomer.isValid());

                REQUIRE(monomer.isKnownByNameInPolChemDef() ==
                        Enums::PolChemDefEntityStatus::ENTITY_KNOWN);

                REQUIRE(monomer.isKnownByCodeInPolChemDef() ==
                        Enums::PolChemDefEntityStatus::ENTITY_KNOWN);

                REQUIRE(monomer.validate(&error_list_monomer));
              }
            }
          }
        }
      }
    }
  }
}

SCENARIO("Monomers can be constructed using a valid XML mnm element",
         "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  QStringList dom_strings{
    "mnm", "name", "Glycine", "code", "G", "formula", "C2H3N1O1"};


  GIVEN("A proper set of strings, a proper XML element can be crafted")
  {
    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<mnm>\n <name>Glycine</name>\n <code>G</code>\n "
            "<formula>C2H3N1O1</formula>\n</mnm>\n");

    WHEN(
      "A Monomer instance is allocated using that string but without "
      "PolChemDef")
    {
      Monomer monomer(nullptr, mnm_element, 1);

      THEN("The Monomer instance is initialized but not the PolChemDef")
      {
        REQUIRE(monomer.getName().toStdString() == "Glycine");
        REQUIRE(monomer.getCode().toStdString() == "G");
        REQUIRE(monomer.getFormula().toStdString() == "C2H3N1O1");

        AND_THEN(
          "The Monomer instance is not valid because cannot "
          "thoroughly validate successfully because of no PolChemDef")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("A new Monomer is copy-constructed using it")
      {
        Monomer new_monomer(monomer);

        THEN("The new Monomer instance is not valid and does not validate")
        {
          REQUIRE_FALSE(new_monomer.isValid());
          REQUIRE_FALSE(new_monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("A new Monomer is assignment-copied using it")
      {
        Monomer new_monomer;
        new_monomer = monomer;

        THEN("The new Monomer instance is not valid and does not validate")
        {
          REQUIRE_FALSE(new_monomer.isValid());
          REQUIRE_FALSE(new_monomer.validate(&error_list_monomer));
        }
      }
    }

    WHEN("A Monomer instance is allocated with PolChemDef, using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has proper member data")
      {
        REQUIRE(monomer.getName().toStdString() == "Glycine");
        REQUIRE(monomer.getCode().toStdString() == "G");
        REQUIRE(monomer.getFormula().toStdString() == "C2H3N1O1");

        AND_THEN("The Monomer instance must validate successfully")
        {
          REQUIRE(monomer.isValid());
          REQUIRE(monomer.validate(&error_list_monomer));
        }
      }
    }
  }
}

SCENARIO(
  "Monomers can be constructed using an invalid XML mnm element but are not "
  "valid Monomers",
  "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  QStringList dom_strings{
    "mnm", "name", "Glycine", "code", "G", "formula", "C2H3N1O1"};

  int mnm_element_index     = 0;
  int name_element_index    = 1;
  int name_text_index       = 2;
  int code_element_index    = 3;
  int code_text_index       = 4;
  int formula_element_index = 5;
  int formula_text_index    = 6;

  GIVEN("A set of strings with erroneous <mnm> element")
  {
    dom_strings[mnm_element_index] = "not_mnm";

    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<not_mnm>\n <name>Glycine</name>\n <code>G</code>\n "
            "<formula>C2H3N1O1</formula>\n</not_mnm>\n");

    WHEN("A Monomer instance is allocated using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has an invalid status")
      {
        REQUIRE_FALSE(monomer.isValid());
      }
    }
  }

  GIVEN("A set of strings with erroneous <name> element")
  {
    dom_strings[name_element_index] = "not_mame";

    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<mnm>\n <not_mame>Glycine</not_mame>\n <code>G</code>\n "
            "<formula>C2H3N1O1</formula>\n</mnm>\n");

    WHEN("A Monomer instance is allocated using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has an invalid status")
      {
        REQUIRE_FALSE(monomer.isValid());
      }
    }
  }

  GIVEN("A set of strings with erroneous (empty) <name> element's text")
  {
    dom_strings[name_text_index] = "";

    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<mnm>\n <name></name>\n <code>G</code>\n "
            "<formula>C2H3N1O1</formula>\n</mnm>\n");

    WHEN("A Monomer instance is allocated using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has an invalid status")
      {
        REQUIRE_FALSE(monomer.isValid());
      }
    }
  }

  GIVEN("A set of strings with erroneous <code> element")
  {
    dom_strings[code_element_index] = "not_code";

    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<mnm>\n <name>Glycine</name>\n <not_code>G</not_code>\n "
            "<formula>C2H3N1O1</formula>\n</mnm>\n");

    WHEN("A Monomer instance is allocated using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has an invalid status")
      {
        REQUIRE_FALSE(monomer.isValid());
      }
    }
  }

  GIVEN("A set of strings with erroneous <code> element's text")
  {
    dom_strings[code_text_index] = "g";

    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<mnm>\n <name>Glycine</name>\n <code>g</code>\n "
            "<formula>C2H3N1O1</formula>\n</mnm>\n");

    WHEN("A Monomer instance is allocated using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has an invalid status")
      {
        REQUIRE_FALSE(monomer.isValid());
      }
    }
  }

  GIVEN("A set of strings with erroneous <formula> element")
  {
    dom_strings[formula_element_index] = "not_formula";

    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<mnm>\n <name>Glycine</name>\n <code>G</code>\n "
            "<not_formula>C2H3N1O1</not_formula>\n</mnm>\n");

    WHEN("A Monomer instance is allocated using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has an invalid status")
      {
        REQUIRE_FALSE(monomer.isValid());
      }
    }
  }

  GIVEN("A set of strings with erroneous <formula> element's text")
  {
    dom_strings[formula_text_index] = "h2O";

    QDomDocument document =
      test_utils_1_letter_monomer.craftMnmDomDocument(dom_strings);
    QDomElement mnm_element =
      document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

    REQUIRE(document.toString().toStdString() ==
            "<mnm>\n <name>Glycine</name>\n <code>G</code>\n "
            "<formula>h2O</formula>\n</mnm>\n");

    WHEN("A Monomer instance is allocated using that string")
    {
      Monomer monomer(pol_chem_def_csp, mnm_element, 1);

      THEN("The Monomer instance has an invalid status")
      {
        REQUIRE_FALSE(monomer.isValid());
      }
    }
  }
}

SCENARIO(
  "Monomers can be fully initialized using a <monomer> XML element out of a "
  "polymer sequence file",
  "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  // <monomer>
  // <code>S</code>
  // <mdf>
  // <name>Phosphorylation</name>
  // <formula>H1O3P1</formula>
  // <targets>*</targets>
  // <maxcount>1</maxcount>
  // </mdf>
  // </monomer>

  QStringList dom_strings{"monomer",
                          "code",
                          "S",
                          "mdf",
                          "name",
                          "Phosphorylation",
                          "formula",
                          "H1O3P1",
                          "targets",
                          "*",
                          "maxcount",
                          "1"};

  // int monomer_element_index  = 0;
  // int code_element_index     = 1;
  // int code_text_index        = 2;
  // int mdf_element_index      = 3;
  // int name_element_index     = 4;
  // int name_text_index        = 5;
  // int formula_element_index  = 6;
  // int formula_text_index     = 7;
  // int targets_element_index  = 8;
  // int targets_text_index     = 9;
  // int maxcount_element_index = 10;
  // int maxcount_text_index    = 11;

  GIVEN(
    "A proper set of strings, a proper <monomer> XML element can be crafted")
  {
    QDomDocument document =
      test_utils_1_letter_monomer.craftMonomerDomDocument(dom_strings);
    QDomElement monomer_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() ==
      "<monomer>\n <code>S</code>\n <mdf>\n  <name>Phosphorylation</name>\n  "
      "<formula>H1O3P1</formula>\n  <targets>*</targets>\n  "
      "<maxcount>1</maxcount>\n </mdf>\n</monomer>\n");

    WHEN(
      "A Monomer instance is allocated entirely free of data (only the polymer "
      "chemistry definition is provided to the constructor)")
    {
      Monomer monomer(pol_chem_def_csp);

      THEN("The Monomer instance is not valid and cannot validate")
      {
        REQUIRE(monomer.getName().toStdString() == "");
        REQUIRE(monomer.getCode().toStdString() == "");
        REQUIRE(monomer.getFormula().toStdString() == "");

        REQUIRE_FALSE(monomer.isValid());
        REQUIRE_FALSE(monomer.validate(&error_list_monomer));

        AND_WHEN(
          "The monomer instance is asked to render in itself the <monomer> XML "
          "element")
        {
          REQUIRE(
            monomer.renderXmlMonomerElement(monomer_element, /*version*/ 1));

          THEN(
            "The Monomer is set correctly, is valid and validates "
            "successfully.")
          {
            REQUIRE(monomer.getName().toStdString() == "Serine");
            REQUIRE(monomer.getCode().toStdString() == "S");
            REQUIRE(monomer.getFormula().toStdString() == "C3H5N1O2");

            REQUIRE(monomer.isValid());

            REQUIRE(monomer.validate(&error_list_monomer));

            AND_THEN(
              "Calculating the Monomer masses should succeed (not accounting "
              "modif)")
            {
              bool ok = false;
              REQUIRE(
                monomer.calculateMasses(
                  ok,
                  monomer.getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
                  Enums::ChemicalEntity::NONE) == monomer);

              REQUIRE_THAT(
                monomer.getMass(Enums::MassType::MONO),
                Catch::Matchers::WithinAbs(87.0320284060, 0.0000000001));
              REQUIRE_THAT(
                monomer.getMass(Enums::MassType::AVG),
                Catch::Matchers::WithinAbs(87.0777027179, 0.0000000001));

              AND_THEN("Accounting the Monomer masses should succeed")
              {
                double mono = 0;
                double avg  = 0;

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

                REQUIRE_THAT(
                  mono,
                  Catch::Matchers::WithinAbs(87.0320284060, 0.0000000001));
                REQUIRE_THAT(
                  avg, Catch::Matchers::WithinAbs(87.0777027179, 0.0000000001));
              }
            }

            AND_THEN(
              "Calculating the Monomer masses should succeed (accounting "
              "modif)")
            {
              bool ok = false;
              REQUIRE(
                monomer.calculateMasses(
                  ok,
                  monomer.getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(),
                  Enums::ChemicalEntity::MODIF) == monomer);

              REQUIRE_THAT(
                monomer.getMass(Enums::MassType::MONO),
                Catch::Matchers::WithinAbs(166.9983592974, 0.0000000001));
              REQUIRE_THAT(
                monomer.getMass(Enums::MassType::AVG),
                Catch::Matchers::WithinAbs(167.0576323363, 0.0000000001));

              AND_THEN("Accounting the Monomer masses should succeed")
              {
                double mono = 0;
                double avg  = 0;

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

                REQUIRE_THAT(
                  mono,
                  Catch::Matchers::WithinAbs(166.9983592974, 0.0000000001));
                REQUIRE_THAT(
                  avg,
                  Catch::Matchers::WithinAbs(167.0576323363, 0.0000000001));
              }
            }
          }

          AND_WHEN(
            "The newly initialized monomer instance prints itself as a "
            "<monomer> XML element string")
          {
            QString monomer_xml_string = monomer.formatXmlMonomerElement(
              0, test_utils_1_letter_monomer.xml_format_indent_string);

            THEN(
              "The initial XML <monomer> element and this one should be "
              "identical")
            {
              //  Use indentation 1 to mimick what happens in XpertMass.
              REQUIRE(monomer_xml_string.toStdString() ==
                      document.toString(/*indentation*/ 1).toStdString());
            }
          }
        }
      }
    }
  }
}

SCENARIO(
  "The monomer code needs syntax checking depending on the polymer chemistry "
  "definition",
  "[Monomer]")
{
  GIVEN("A one-letter polymer chemistry definition")
  {
    PolChemDefCstSPtr pol_chem_def_csp =
      test_utils_1_letter_monomer.msp_polChemDef;

    WHEN(
      "A valid monomer is instantiated with full description (name,  code,  "
      "formula)")
    {
      Monomer monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

      THEN("The monomer should be valid and should validate successfully")
      {
        REQUIRE(monomer.isValid());
        REQUIRE(monomer.validate(&error_list_monomer));
      }

      AND_WHEN("An invalid code is set with setCode()")
      {
        monomer.setCode("w");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("Another invalid code is set with setCode()")
      {
        monomer.setCode("Ww");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }
    }
  }

  GIVEN("A three-letter polymer chemistry definition")
  {
    PolChemDefCstSPtr pol_chem_def_csp =
      test_utils_3_letters_monomer.msp_polChemDef;

    ErrorList error_list_monomer;

    REQUIRE(pol_chem_def_csp->getCodeLength() == 3);
    REQUIRE(pol_chem_def_csp->getMonomersCstRef().size() == 21);
    REQUIRE(pol_chem_def_csp->getModifsCstRef().size() == 26);
    // REQUIRE(pol_chem_def_csp->crossLinkerList().size() == 2);
    // REQUIRE(pol_chem_def_csp->cleaveSpecList().size() == 8);
    // REQUIRE(pol_chem_def_csp->fragSpecList().size() == 7);

    WHEN(
      "A valid monomer is instantiated with full description (name,  code,  "
      "formula)")
    {
      Monomer monomer(pol_chem_def_csp, "Tryptophan", "Trp", "C11H10N2O1");

      THEN("The monomer should be valid and should validate successfully")
      {
        REQUIRE(monomer.isValid());
        REQUIRE(monomer.validate(&error_list_monomer));
      }

      AND_WHEN("An empty code is set with setCode()")
      {
        monomer.setCode("");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("A syntactically valid shorter code is set with setCode()")
      {
        monomer.setCode("Tr");

        THEN("The monomer becomes valid and does validate successfully")
        {
          REQUIRE(monomer.isValid());
          REQUIRE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN(
        "Another syntactically valid even shorter code is set with setCode()")
      {
        monomer.setCode("T");

        THEN("The monomer is still valid and does validate successfully")
        {
          REQUIRE(monomer.isValid());
          REQUIRE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("An invalid code is set with setCode()")
      {
        monomer.setCode("t");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("Another invalid code is set with setCode()")
      {
        monomer.setCode("t");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("Another invalid code is set with setCode()")
      {
        monomer.setCode("tt");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("Another invalid code is set with setCode()")
      {
        monomer.setCode("ttt");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("Another invalid code is set with setCode()")
      {
        monomer.setCode("tttt");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }

      AND_WHEN("Another invalid code is set with setCode()")
      {
        monomer.setCode("Trpp");

        THEN(
          "The monomer is not valid and does not validate successfully "
          "anymore")
        {
          REQUIRE_FALSE(monomer.isValid());
          REQUIRE_FALSE(monomer.validate(&error_list_monomer));
        }
      }
    }
  }
}

SCENARIO(
  "A monomer can be modified multiple times and modifications can be removed "
  "piecemeal",
  "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  Monomer monomer = Monomer(pol_chem_def_csp, "Lysine", "K", "C6H12N2O1");

  Modif methylation(pol_chem_def_csp, "Methylation", "+C1H2");
  methylation.setTargets("K");
  methylation.setMaxCount(3);

  REQUIRE(monomer.isModifTarget(methylation));

  //  We need to store the QUuid strings for the Modif instances.
  QString first_uuid;
  QString second_uuid;
  QString third_uuid;
  QString fourth_uuid;

  GIVEN(
    "A Monomer instance and a Modif that targets that monomer at most three "
    "times")
  {
    WHEN("The monomer is modified once")
    {
      qDebug() << "20250428 - Now calling modify with:"
               << methylation.getName();

      first_uuid = monomer.modify(methylation,
                                  /* override modif count */ false,
                                  &error_list_monomer);

      THEN("The modification should be successful")
      {
        REQUIRE_FALSE(first_uuid.isEmpty());
        ModifSPtr modif_sp = monomer.getModifForUuid(first_uuid);
        REQUIRE(modif_sp != nullptr);
        REQUIRE(modif_sp->getName() == "Methylation");
        REQUIRE(*monomer.getModifForUuid(first_uuid) == methylation);
        REQUIRE(monomer.countModifsByName("Methylation") == 1);
      }
      AND_WHEN("The monomer is modified twice")
      {
        second_uuid = monomer.modify(methylation,
                                     /* override modif count */ false,
                                     &error_list_monomer);

        THEN("The modification should be successful")
        {
          REQUIRE_FALSE(second_uuid.isEmpty());
          REQUIRE(*monomer.getModifForUuid(second_uuid) == methylation);
          REQUIRE(monomer.countModifsByName("Methylation") == 2);
        }
        AND_WHEN("The monomer is modified thrice")
        {
          third_uuid = monomer.modify(methylation,
                                      /* override modif count */ false,
                                      &error_list_monomer);

          THEN("The modification should be successful")
          {
            REQUIRE_FALSE(third_uuid.isEmpty());
            REQUIRE(*monomer.getModifForUuid(third_uuid) == methylation);
            REQUIRE(monomer.countModifsByName("Methylation") == 3);
          }
          AND_WHEN("The monomer is modified quadrice")
          {
            fourth_uuid = monomer.modify(methylation,
                                         /* override modif count */ false,
                                         &error_list_monomer);

            THEN("The modification should not be successful (> max count)")
            {
              REQUIRE(fourth_uuid.isEmpty());
              REQUIRE(monomer.getModifForUuid(fourth_uuid) == nullptr);
              REQUIRE(monomer.countModifsByName("Methylation") == 3);
            }
            AND_WHEN(
              "The monomer is modified quadrice with the override bit set")
            {
              fourth_uuid = monomer.modify(methylation,
                                           /* override modif count */ true,
                                           &error_list_monomer);

              THEN(
                "The modification should be successful (> max count "
                "overridden)")
              {
                REQUIRE_FALSE(fourth_uuid.isEmpty());
                REQUIRE(*monomer.getModifForUuid(fourth_uuid) == methylation);
                REQUIRE(monomer.countModifsByName("Methylation") == 4);
              }
              AND_WHEN("The monomer is unmodified once")
              {
                REQUIRE(monomer.unmodify(first_uuid));

                THEN("There should remain only three modifications")
                {
                  REQUIRE(monomer.countModifsByName("Methylation") == 3);
                }
                AND_WHEN("The monomer is unmodified once more")
                {
                  REQUIRE(monomer.unmodify(second_uuid));

                  THEN("There should remain only two modifications")
                  {
                    REQUIRE(monomer.countModifsByName("Methylation") == 2);
                  }
                  AND_WHEN("The monomer is unmodified once more")
                  {
                    REQUIRE(monomer.unmodify(third_uuid));

                    THEN("There should remain only one modification")
                    {
                      REQUIRE(monomer.countModifsByName("Methylation") == 1);
                    }
                    AND_WHEN("The monomer is unmodified once more")
                    {
                      REQUIRE(monomer.unmodify(fourth_uuid));

                      THEN("There should remain no modification")
                      {
                        REQUIRE(monomer.countModifsByName("Methylation") == 0);
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

SCENARIO(
  "A modified Monomer instance can be copied deeply into a new Monomer "
  "instance",
  "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN(
    "A Monomer instance and a heap-allocated Modif that targets that monomer")
  {
    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    std::unique_ptr<Modif> oxidation_up =
      std::make_unique<Modif>(pol_chem_def_csp, "Oxidation", "+O");
    oxidation_up->setTargets("M;Y;W");
    oxidation_up->setMaxCount(1);

    REQUIRE(monomer.isModifTarget(*oxidation_up.get()) == true);

    WHEN("That monomer is modified with that modif that targets the monomer")
    {
      QString uuid = monomer.modify(*oxidation_up.get(),
                                    /* override modif count */ false,
                                    &error_list_monomer);

      THEN("The monomer modification process should succeed")
      {
        REQUIRE_FALSE(uuid.isEmpty());

        REQUIRE(monomer.getModifsCstRef().size() == 1);
        REQUIRE(monomer.getModifsCstRef()
                  .front()

                  ->getName()
                  .toStdString() == "Oxidation");
      }

      AND_WHEN("A second Monomer is copy-constructed")
      {
        Monomer monomer_2(monomer);

        THEN(
          "The second monomer should have the same Modif instances as those of "
          "the first one")
        {
          REQUIRE(monomer_2.getPolChemDefCstSPtr() ==
                  monomer.getPolChemDefCstSPtr());

          REQUIRE(monomer_2.getName().toStdString() ==
                  monomer.getName().toStdString());
          REQUIRE(monomer_2.getCode().toStdString() ==
                  monomer.getCode().toStdString());

          REQUIRE(monomer_2.getModifsCstRef().size() == 1);
          REQUIRE(monomer_2.getModifsCstRef()
                    .front()

                    ->getName()
                    .toStdString() == "Oxidation");
        }
      }

      AND_WHEN(
        "A third Monomer is allocated uninitialized and then the first monomer "
        "is copied using the "
        "assignment operator")
      {
        Monomer monomer_3(pol_chem_def_csp, "NOT_SET", "NOT_SET", "NOT_SET");
        monomer_3 = monomer;

        THEN(
          "The third monomer should have the same Modif instances as those of "
          "the first one")
        {
          REQUIRE(monomer_3.getPolChemDefCstSPtr() ==
                  monomer_3.getPolChemDefCstSPtr());

          REQUIRE(monomer_3.getName().toStdString() ==
                  monomer.getName().toStdString());
          REQUIRE(monomer_3.getCode().toStdString() ==
                  monomer.getCode().toStdString());

          REQUIRE(monomer_3.getModifsCstRef().size() == 1);
          REQUIRE(monomer_3.getModifsCstRef()
                    .front()

                    ->getName()
                    .toStdString() == "Oxidation");
        }
      }

      AND_WHEN(
        "A fourth Monomer is allocated initialized, and modified with a "
        "different Modif")
      {
        Monomer monomer_4(pol_chem_def_csp, "Serine", "S", "C3H5N1O2");

        std::unique_ptr<Modif> phosphorylation_up = std::make_unique<Modif>(
          pol_chem_def_csp, "Phosphorylation", "H1O3P1");
        phosphorylation_up->setTargets("S");
        phosphorylation_up->setMaxCount(1);

        // qDebug() <<  "The modif:" <<  phosphorylation_up->toString();

        REQUIRE(monomer_4.isModifTarget(*phosphorylation_up.get()) == true);

        REQUIRE(monomer_4.modify(*phosphorylation_up.get(),
                                 /* override modif count */ false,
                                 &error_list_monomer) != nullptr);

        AND_WHEN("The first monomer is assigned to the fourth monomer")
        {
          monomer_4 = monomer;

          THEN(
            "The fourth monomer should have the same Modif instances as those "
            "of the first one")
          {
            REQUIRE(monomer_4.getPolChemDefCstSPtr() ==
                    monomer_4.getPolChemDefCstSPtr());

            REQUIRE(monomer_4.getName().toStdString() ==
                    monomer.getName().toStdString());
            REQUIRE(monomer_4.getCode().toStdString() ==
                    monomer.getCode().toStdString());

            REQUIRE(monomer_4.getModifsCstRef().size() == 1);
            REQUIRE(monomer_4.getModifsCstRef()
                      .front()

                      ->getName()
                      .toStdString() == "Oxidation");
            REQUIRE(monomer_4.getModifsCstRef()
                      .front()

                      ->getName()
                      .toStdString() == monomer.getModifsCstRef()
                                          .front()

                                          ->getName()
                                          .toStdString());
          }
        }
      }
    }
  }
}

SCENARIO(
  "Monomer instances can be searched into the polymer chemistry definition in "
  "various ways",
  "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN("A fully configured inexisting Monomer")
  {
    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophanooo", "Z", "C11H10N2O1");

    Enums::PolChemDefEntityStatus pol_chem_def_entity_status =
      Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;

    THEN(
      "Checks can be performed with the PolChemDef by using either name or "
      "code")
    {
      pol_chem_def_entity_status = monomer.isKnownByNameInPolChemDef();

      REQUIRE(pol_chem_def_entity_status ==
              Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN);

      MonomerSPtr monomer_csp = monomer.getFromPolChemDefByName();
      REQUIRE(monomer_csp == nullptr);

      monomer_csp = monomer.getPolChemDefCstSPtr()->getMonomerCstSPtrByName(
        monomer.getName());
      REQUIRE(monomer_csp == nullptr);

      pol_chem_def_entity_status = monomer.isKnownByCodeInPolChemDef();

      REQUIRE(pol_chem_def_entity_status ==
              Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN);

      monomer_csp = monomer.getFromPolChemDefByCode();
      REQUIRE(monomer_csp == nullptr);

      monomer_csp = monomer.getPolChemDefCstSPtr()->getMonomerCstSPtrByCode(
        monomer.getCode());
      REQUIRE(monomer_csp == nullptr);
    }
  }

  GIVEN("A fully configured existing Monomer")
  {
    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    Enums::PolChemDefEntityStatus pol_chem_def_entity_status =
      Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;

    THEN(
      "Checks can be performed with the PolChemDef by using either name or "
      "code")
    {
      pol_chem_def_entity_status = monomer.isKnownByNameInPolChemDef();

      REQUIRE(pol_chem_def_entity_status ==
              Enums::PolChemDefEntityStatus::ENTITY_KNOWN);

      MonomerSPtr monomer_csp = monomer.getFromPolChemDefByName();
      REQUIRE(monomer_csp != nullptr);
      REQUIRE(monomer_csp->getName().toStdString() ==
              monomer.getName().toStdString());

      monomer_csp = monomer.getPolChemDefCstSPtr()->getMonomerCstSPtrByName(
        monomer.getName());
      REQUIRE(monomer_csp != nullptr);
      REQUIRE(monomer_csp->getName().toStdString() ==
              monomer.getName().toStdString());

      pol_chem_def_entity_status = monomer.isKnownByCodeInPolChemDef();

      REQUIRE(pol_chem_def_entity_status ==
              Enums::PolChemDefEntityStatus::ENTITY_KNOWN);

      monomer_csp = monomer.getFromPolChemDefByCode();
      REQUIRE(monomer_csp != nullptr);
      REQUIRE(monomer_csp->getCode().toStdString() ==
              monomer.getCode().toStdString());

      monomer_csp = monomer.getPolChemDefCstSPtr()->getMonomerCstSPtrByCode(
        monomer.getCode());
      REQUIRE(monomer_csp != nullptr);
      REQUIRE(monomer_csp->getCode().toStdString() ==
              monomer.getCode().toStdString());
    }
  }
}

SCENARIO(
  "Monomer instances are properly compared with the ==() and !=() operators",
  "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN("A modified Monomer instance")
  {

    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    std::unique_ptr<Modif> oxidation_up =
      std::make_unique<Modif>(pol_chem_def_csp, "Oxidation", "+O");
    oxidation_up->setTargets("W");

    //  We'll have to modify one monomer twice for the sake of the tests.
    oxidation_up->setMaxCount(2);

    REQUIRE(monomer.isModifTarget(*oxidation_up.get()) == true);

    REQUIRE(monomer.modify(*oxidation_up.get(),
                           /* override modif count */ false,
                           &error_list_monomer) != nullptr);

    THEN("That Monomer instance is identical to itself")
    {
      REQUIRE(monomer == monomer);
      REQUIRE_FALSE(monomer != monomer);
    }

    WHEN("A second Monomer is copy-constructed")
    {
      Monomer monomer_2(monomer);

      THEN("The second monomer should be equal to the first one")
      {
        REQUIRE(monomer_2.getPolChemDefCstSPtr() ==
                monomer.getPolChemDefCstSPtr());

        REQUIRE(monomer_2.getName().toStdString() ==
                monomer.getName().toStdString());
        REQUIRE(monomer_2.getCode().toStdString() ==
                monomer.getCode().toStdString());

        REQUIRE(monomer_2.getModifsCstRef().size() ==
                monomer.getModifsCstRef().size());
        REQUIRE(monomer_2.getModifsCstRef().front()->getName().toStdString() ==
                "Oxidation");
        REQUIRE(monomer_2.getModifsCstRef().front()->getName().toStdString() ==
                monomer.getModifsCstRef().front()->getName().toStdString());
      }

      AND_THEN("The comparison operators should return correct results")
      {
        REQUIRE(monomer_2 == monomer);
        REQUIRE_FALSE(monomer_2 != monomer);
      }

      AND_WHEN("The second monomer's name is changed")
      {
        REQUIRE(monomer_2.getName().toStdString() == "Tryptophan");
        monomer_2.setName("Serine");

        THEN("The second and the first monomers are no more equal")
        {
          REQUIRE(monomer_2 != monomer);
          REQUIRE_FALSE(monomer_2 == monomer);
        }
      }

      AND_WHEN("The second monomer's code is changed")
      {
        REQUIRE(monomer_2.getName().toStdString() == "Tryptophan");
        monomer_2.setCode("S");

        THEN("The second and the first monomers are no more equal")
        {
          REQUIRE(monomer_2 != monomer);
          REQUIRE_FALSE(monomer_2 == monomer);
        }
      }

      AND_WHEN("The second monomer's formula is changed")
      {
        REQUIRE(monomer_2.getName().toStdString() == "Tryptophan");
        monomer_2.setFormula("H2O");

        THEN("The second and the first monomers are no more equal")
        {
          REQUIRE(monomer_2 != monomer);
          REQUIRE_FALSE(monomer_2 == monomer);
        }
      }

      AND_WHEN("The second monomer is unmodified")
      {
        REQUIRE(monomer_2.getName().toStdString() == "Tryptophan");
        monomer_2.unmodify();

        THEN("The second and the first monomers are no more equal")
        {
          REQUIRE(monomer_2 != monomer);
          REQUIRE_FALSE(monomer_2 == monomer);
        }
      }

      AND_WHEN("The second monomer is modified once more with same modif")
      {
        REQUIRE(monomer_2.getName().toStdString() == "Tryptophan");

        REQUIRE(monomer_2.modify(*oxidation_up.get(),
                                 /* override modif count */ false,
                                 &error_list_monomer) != nullptr);

        THEN("The second and the first monomers are no more equal")
        {
          REQUIRE(monomer_2 != monomer);
          REQUIRE_FALSE(monomer_2 == monomer);
          REQUIRE(monomer_2.getModifsCstRef().size() !=
                  monomer.getModifsCstRef().size());
        }
      }

      AND_WHEN(
        "The second monomer is first unmodified and then modified with "
        "different modif")
      {
        REQUIRE(monomer_2.getName().toStdString() == "Tryptophan");

        monomer_2.unmodify();

        //  Absolute crap,  from a chemical standpoint
        std::unique_ptr<Modif> formylation_up = std::make_unique<Modif>(
          pol_chem_def_csp, "Formylation", "+HCOOH-H2O");
        formylation_up->setTargets("W");
        formylation_up->setMaxCount(1);

        REQUIRE(monomer_2.isModifTarget(*formylation_up.get()) == true);

        REQUIRE(monomer_2.modify(*formylation_up.get(),
                                 /* override modif count */ false,
                                 &error_list_monomer) != nullptr);

        THEN("The second and the first monomers are no more equal")
        {
          REQUIRE(monomer_2.getModifsCstRef().size() ==
                  monomer.getModifsCstRef().size());
          REQUIRE(monomer_2 != monomer);
          REQUIRE_FALSE(monomer_2 == monomer);
        }
      }
    }
  }
}

SCENARIO("Monomer instances can be modified using Modif objects", "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN("A Monomer instance")
  {
    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    AND_GIVEN(
      "A Modif instance (heap) that targets that monomer only once (maxCount)")
    {
      std::unique_ptr<Modif> oxidation_up =
        std::make_unique<Modif>(pol_chem_def_csp, "Oxidation", "+O");
      oxidation_up->setTargets("W");
      oxidation_up->setMaxCount(1);

      REQUIRE(monomer.isModifTarget(*oxidation_up.get()) == true);

      WHEN("That monomer is modified with that modif")
      {
        QString uuid = monomer.modify(*oxidation_up.get(),
                                      /* override modif count */ false,
                                      &error_list_monomer);

        THEN("The monomer modification process should succeed")
        {
          REQUIRE_FALSE(uuid.isEmpty());
        }

        AND_THEN(
          "The new Modif in Monomer is equal to that used for the modification "
          "call")
        {
          // Modif modif_a(*oxidation_up.get());
          // Modif modif_b(*monomer.getModifsCstRef().front().get());
          // REQUIRE(modif_a.getName().toStdString() ==
          // modif_b.getName().toStdString());
          REQUIRE(*oxidation_up.get() ==
                  *monomer.getModifsCstRef().front().get());
        }

        AND_WHEN(
          "That monomer is modified again with that modif (maxCount = 1)")
        {
          QString uuid = monomer.modify(*oxidation_up.get(),
                                        /* override modif count */ false,
                                        &error_list_monomer);

          THEN("The monomer modification process should fail")
          {
            REQUIRE(uuid.isEmpty());
          }
        }

        AND_WHEN("The Modif is changed so as not to target the monomer anymore")
        {
          oxidation_up->setTargets("S;T,Y");

          THEN("The monomer should not be a target of that Modif anymore")
          {
            REQUIRE(monomer.isModifTarget(*oxidation_up.get()) == false);
          }

          AND_WHEN("The modification of the monomer is tried again")
          {
            QString uuid = monomer.modify(*oxidation_up.get(),
                                          /* override modif count */ false,
                                          &error_list_monomer);

            THEN("The modification process should fail")
            {
              REQUIRE(uuid.isEmpty());
            }
          }
        }
      }
    }
  }

  GIVEN("A Monomer instance")
  {
    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    AND_GIVEN(
      "A Modif instance (stack) that targets that monomer only once (maxCount)")
    {
      Modif modif(pol_chem_def_csp, "Oxidation", "+O");
      modif.setTargets("W");
      modif.setMaxCount(1);

      REQUIRE(monomer.isModifTarget(modif) == true);

      WHEN("That monomer is modified with that modif")
      {
        QString uuid = monomer.modify(modif,
                                      /* override modif count */ false,
                                      &error_list_monomer);

        THEN("The monomer modification process should succeed")
        {
          REQUIRE_FALSE(uuid.isEmpty());
        }


        AND_WHEN(
          "That monomer is modified again with that modif (maxCount = 1)")
        {
          QString uuid = monomer.modify(modif,
                                        /* override modif count */ false,
                                        &error_list_monomer);

          THEN("The monomer modification process should fail")
          {
            REQUIRE(uuid.isEmpty());
          }
        }

        AND_WHEN("The Modif is changed so as not to target the monomer anymore")
        {
          modif.setTargets("S;T,Y");

          THEN("The monomer should not be a target of that Modif anymore")
          {
            REQUIRE(monomer.isModifTarget(modif) == false);
          }

          AND_WHEN("The modification of the monomer is tried again")
          {
            QString uuid = monomer.modify(modif,
                                          /* override modif count */ false,
                                          &error_list_monomer);

            THEN("The modification process should fail")
            {
              REQUIRE(uuid.isEmpty());
            }
          }
        }
      }
    }
  }
}

SCENARIO("A Monomer that is modified with a Modif has its masses that change",
         "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN("An allocated Monomer and a Modif object that targets that monomer")
  {
    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    std::unique_ptr<Modif> oxidation_up =
      std::make_unique<Modif>(pol_chem_def_csp, "Oxidation", "+O");
    oxidation_up->setTargets("M;W");
    oxidation_up->setMaxCount(1);

    REQUIRE(oxidation_up->calculateMasses(nullptr) == true);

    WHEN(
      "That Monomer is modified with that Modif and the masses are "
      "recalculated")
    {
      QString uuid = monomer.modify(*oxidation_up.get(),
                                    /* override modif count */ false,
                                    &error_list_monomer);
      REQUIRE_FALSE(uuid.isEmpty());
      REQUIRE(monomer.getModifsCstRef().size() == 1);
      REQUIRE(monomer.calculateMasses(nullptr, Enums::ChemicalEntity::MODIF) ==
              true);

      THEN("The masses of the Monomer do change")
      {
        double mono = 0;
        double avg  = 0;

        REQUIRE_THAT(monomer.getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(202.0742275715, 0.0000000001));
        REQUIRE_THAT(monomer.getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(202.2107092530, 0.0000000001));

        REQUIRE(monomer.accountMasses(&mono, &avg) == monomer);
        REQUIRE_THAT(mono,
                     Catch::Matchers::WithinAbs(202.0742275715, 0.0000000001));
        REQUIRE_THAT(avg,
                     Catch::Matchers::WithinAbs(202.2107092530, 0.0000000001));
      }

      THEN("The monomer should validate the modification also")
      {
        REQUIRE(monomer.validate(&error_list_monomer));
      }

      AND_WHEN("The monomer is unmodified and the masses are recalculated")
      {
        REQUIRE(monomer.isModified());

        REQUIRE(monomer.unmodify(uuid));

        REQUIRE(monomer.calculateMasses(nullptr, Enums::ChemicalEntity::MODIF));

        THEN("The recalculated masses should update")
        {
          double mono = 0;
          double avg  = 0;

          REQUIRE_THAT(
            monomer.getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
          REQUIRE_THAT(
            monomer.getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));

          REQUIRE(monomer.accountMasses(&mono, &avg) == monomer);
          REQUIRE_THAT(
            mono, Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
          REQUIRE_THAT(
            avg, Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));
        }
      }
    }
  }
}

SCENARIO("A Monomer cannot be correctly modified with an invalid Modif",
         "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN(
    "An allocated validated Monomer and an invalid Modif object that targets "
    "that monomer")
  {
    Monomer monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    REQUIRE(monomer.validate(&error_list_monomer));
    REQUIRE(monomer.isValid());

    std::unique_ptr<Modif> fake_oxidation_up =
      std::make_unique<Modif>(pol_chem_def_csp, "FAkeOxidation", "+oO");
    fake_oxidation_up->setTargets("M;W");
    fake_oxidation_up->setMaxCount(1);

    REQUIRE(fake_oxidation_up->isValid() == false);
    REQUIRE_FALSE(fake_oxidation_up->calculateMasses(nullptr));

    WHEN(
      "That Monomer is modified with that Modif and the masses a "
      "recalculated")
    {
      QString uuid = monomer.modify(*fake_oxidation_up.get(),
                                    /* override modif count */ false,
                                    &error_list_monomer);

      REQUIRE_FALSE(uuid.isEmpty());
      REQUIRE(monomer.getModifsCstRef().size() == 1);
      REQUIRE(monomer.calculateMasses(nullptr, Enums::ChemicalEntity::MODIF));

      THEN(
        "The masses of the Monomer do not change because an invalid formula "
        "does not translate into masses")
      {
        double mono = 0;
        double avg  = 0;

        REQUIRE_THAT(monomer.getMass(Enums::MassType::MONO),
                     Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
        REQUIRE_THAT(monomer.getMass(Enums::MassType::AVG),
                     Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));

        REQUIRE(monomer.accountMasses(&mono, &avg) == monomer);
        REQUIRE_THAT(mono,
                     Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
        REQUIRE_THAT(avg,
                     Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));
      }

      THEN("The monomer modified with an incorrect formula should not validate")
      {
        REQUIRE_FALSE(monomer.validate(&error_list_monomer));
      }

      AND_WHEN("The monomer is unmodified and the masses a recalculated")
      {
        REQUIRE(monomer.isModified());

        REQUIRE(monomer.unmodify(uuid));

        REQUIRE(monomer.calculateMasses(nullptr, Enums::ChemicalEntity::MODIF));

        THEN("The recalculated masses should update")
        {
          double mono = 0;
          double avg  = 0;

          REQUIRE_THAT(
            monomer.getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
          REQUIRE_THAT(
            monomer.getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));

          REQUIRE(monomer.accountMasses(&mono, &avg) == monomer);
          REQUIRE_THAT(
            mono, Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
          REQUIRE_THAT(
            avg, Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));
        }

        AND_THEN(
          "The monomer should validate, since it is no more modified with an "
          "incorrect formula")
        {
          REQUIRE(monomer.validate(&error_list_monomer));
          REQUIRE(monomer.isValid());
        }
      }
    }
  }
}

SCENARIO(
  "Monomers can be asked to craft an elemental composition formula describing "
  "them fully",
  "[Monomer]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_1_letter_monomer.msp_polChemDef;

  GIVEN("A fully qualified valid Monomer")
  {
    Monomer monomer =
      Monomer(pol_chem_def_csp, "Tryptophan", "W", "C11H10N2O1");

    REQUIRE(monomer.validate(&error_list_monomer));
    REQUIRE(monomer.isValid());

    WHEN("Asked to compute masses and the elemental composition formula")
    {
      REQUIRE(monomer.calculateMasses(nullptr));

      QString elem_composition =
        monomer.calculateFormula(Enums::ChemicalEntity::NONE);

      // qDebug() <<  "The elemental composition:" <<  elem_composition;

      Formula the_formula(elem_composition);
      REQUIRE_FALSE(the_formula.isValid());
      REQUIRE(the_formula.validate(pol_chem_def_csp->getIsotopicDataCstSPtr(),
                                   &error_list_monomer));
      REQUIRE(the_formula.isValid());
      REQUIRE(the_formula.getActionFormula().toStdString() == "C11H10N2O1");

      AND_WHEN("When calculating masses for that returned formula")
      {
        double mono = 0;
        double avg  = 0;
        bool ok     = false;

        Formula(elem_composition)
          .accountMasses(
            ok, pol_chem_def_csp->getIsotopicDataCstSPtr(), mono, avg, 1);
        REQUIRE(ok);

        THEN(
          "All the masses in the monomer and for the chemical composition "
          "formula should be identical")
        {
          double mono = 0;
          double avg  = 0;

          REQUIRE_THAT(
            monomer.getMass(Enums::MassType::MONO),
            Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
          REQUIRE_THAT(
            monomer.getMass(Enums::MassType::AVG),
            Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));

          REQUIRE(monomer.accountMasses(&mono, &avg) == monomer);
          REQUIRE_THAT(
            mono, Catch::Matchers::WithinAbs(186.0793129513, 0.0000000001));
          REQUIRE_THAT(
            avg, Catch::Matchers::WithinAbs(186.2113005359, 0.0000000001));
        }
      }
    }

    AND_GIVEN("A fully qualified valid Modif object")
    {
      std::unique_ptr<Modif> oxidation_up =
        std::make_unique<Modif>(pol_chem_def_csp, "Oxidation", "+O");
      oxidation_up->setTargets("M;Y;W");
      oxidation_up->setMaxCount(1);

      REQUIRE(oxidation_up->isValid());

      REQUIRE(oxidation_up->calculateMasses(nullptr) == true);

      REQUIRE_THAT(oxidation_up->getMass(Enums::MassType::MONO),
                   Catch::Matchers::WithinAbs(15.9949146202, 0.0000000001));
      REQUIRE_THAT(oxidation_up->getMass(Enums::MassType::AVG),
                   Catch::Matchers::WithinAbs(15.9994087171, 0.0000000001));

      WHEN("The Monomer is modified with that Modif")
      {
        QString uuid = monomer.modify(*oxidation_up.get(),
                                      /* override modif count */ false,
                                      &error_list_monomer);
        REQUIRE_FALSE(uuid.isEmpty());
        REQUIRE(*monomer.getModifForUuid(uuid) == *oxidation_up.get());

        THEN("The monomer reports that it is modified")
        {
          REQUIRE(monomer.getModifsCstRef().size() == 1);
        }
        AND_WHEN("The new masses are calculated")
        {
          REQUIRE(
            monomer.calculateMasses(nullptr, Enums::ChemicalEntity::MODIF));

          THEN("These masses are reported correctly")
          {

            double mono = 0;
            double avg  = 0;

            REQUIRE_THAT(
              monomer.getMass(Enums::MassType::MONO),
              Catch::Matchers::WithinAbs(202.0742275715, 0.0000000001));
            REQUIRE_THAT(
              monomer.getMass(Enums::MassType::AVG),
              Catch::Matchers::WithinAbs(202.2107092530, 0.0000000001));

            REQUIRE(monomer.accountMasses(&mono, &avg) == monomer);
            REQUIRE_THAT(
              mono, Catch::Matchers::WithinAbs(202.0742275715, 0.0000000001));
            REQUIRE_THAT(
              avg, Catch::Matchers::WithinAbs(202.2107092530, 0.0000000001));
          }
        }
        AND_WHEN(
          "The Monomer's elemental composition is asked for without accounting "
          "for modifications")
        {
          QString elem_composition =
            monomer.calculateFormula(Enums::ChemicalEntity::NONE);

          // qDebug() <<  "The elemental composition:" <<  elem_composition;

          Formula the_formula(elem_composition);
          REQUIRE_FALSE(the_formula.isValid());
          REQUIRE(
            the_formula.validate(pol_chem_def_csp->getIsotopicDataCstSPtr(),
                                 /*store*/ true,
                                 /*reset*/ false,
                                 &error_list_monomer));
          REQUIRE(the_formula.isValid());

          THEN(
            "The formulas for the modified Monomer and one formula matching "
            "the modification net chemical should be equal because we did not "
            "account for Modif")
          {
            QString reduced_tryptophan_formula("C11H10N2O1");

            REQUIRE(elem_composition.toStdString() ==
                    reduced_tryptophan_formula.toStdString());
          }
        }

        AND_WHEN(
          "The Monomer's elemental composition is asked for with accounting "
          "for modifications")
        {
          QString elem_composition =
            monomer.calculateFormula(Enums::ChemicalEntity::MODIF);

          // qDebug() <<  "The elemental composition:" <<  elem_composition;

          Formula the_formula(elem_composition);
          REQUIRE_FALSE(the_formula.isValid());
          REQUIRE(
            the_formula.validate(pol_chem_def_csp->getIsotopicDataCstSPtr(),
                                 /*store*/ true,
                                 /*reset*/ false,
                                 &error_list_monomer));
          REQUIRE(the_formula.isValid());

          THEN(
            "The formulas for the modified Monomer and one formula matching "
            "the modification result should be equal")
          {
            QString oxidized_tryptophan_formula("C11H10N2O2");

            REQUIRE(elem_composition.toStdString() ==
                    oxidized_tryptophan_formula.toStdString());
          }
        }

        AND_WHEN("Crippling the Monomer's formula with setFormula(\"+h2o\")")
        {
          monomer.setFormula("+h2o");

          THEN(
            "That formula cannot validate and computing the elemental "
            "composition should fail")
          {

            QString elem_composition =
              monomer.calculateFormula(Enums::ChemicalEntity::NONE);

            REQUIRE_FALSE(Formula(elem_composition).isValid());
            REQUIRE_FALSE(
              Formula(elem_composition)
                .validate(pol_chem_def_csp->getIsotopicDataCstSPtr(),
                          &error_list_monomer));
          }
        }
      }
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
