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


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


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


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

namespace MsXpS
{
namespace libXpertMassCore
{

TestUtils test_utils_chemical_group_rule_1_letter("protein-1-letter", 1);
ErrorList error_list_chemical_group_rule;

SCENARIO("ChemicalGroup object can be constructed empty", "[ChemicalGroup]")
{
  WHEN("Constructing an empty ChemicalGroup")
  {
    ChemicalGroup chemical_group;

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

    AND_WHEN(
      "Setting the name, the pKa, the charge bool, the Enums::ChemicalGroupTrapped")
    {
      chemical_group.setName("N-term NH2");
      chemical_group.setPka(9.6);
      chemical_group.setAcidCharged(true);
      chemical_group.setPolRule(Enums::ChemicalGroupTrapped::LEFT);

      THEN("The ChemicalGroup is valid.")
      {
        REQUIRE(chemical_group.isValid());
      }

      AND_WHEN("Constructing two fully initialized ChemicalGroupRule")
      {
        ChemicalGroupRule chemical_group_rule_1;
        chemical_group_rule_1.setName("Acetylation");
        chemical_group_rule_1.setEntity("LE_PLM_MODIF");
        chemical_group_rule_1.setFate(Enums::ChemicalGroupFate::LOST);

        ChemicalGroupRule chemical_group_rule_2;
        chemical_group_rule_2.setName("Formylation");
        chemical_group_rule_2.setEntity("LE_PLM_MODIF");
        chemical_group_rule_2.setFate(Enums::ChemicalGroupFate::LOST);

        AND_WHEN("Adding that rule to the chemical group")
        {
          chemical_group.getRulesRef().push_back(
            std::make_shared<ChemicalGroupRule>(chemical_group_rule_1));
          chemical_group.getRulesRef().push_back(
            std::make_shared<ChemicalGroupRule>(chemical_group_rule_2));

          REQUIRE(chemical_group.getRulesRef().size() == 2);

          std::size_t index = 0;

          THEN(
            "The ChemicalGroupRule instances are findable in the "
            "ChemicalGroup.")
          {
            REQUIRE(chemical_group.getRulesRef().size() == 2);
            REQUIRE(chemical_group.getRulesCstRef().size() == 2);

            ChemicalGroupRuleSPtr chemical_group_rule_1_sp =
              chemical_group.getRulesCstRef().at(0);
            REQUIRE(chemical_group_rule_1_sp != nullptr);
            REQUIRE(chemical_group_rule_1_sp->getEntity().toStdString() ==
                    "LE_PLM_MODIF");

            // Begin erroneous cases
            REQUIRE(nullptr ==
                    chemical_group.findRuleByEntity("Not_existent", index));

            std::size_t out_of_bounds_index = 10;
            REQUIRE(nullptr == chemical_group.findRuleByEntity(
                                 "LE_PLM_MODIF", out_of_bounds_index));

            REQUIRE(nullptr ==
                    chemical_group.findRuleByName("Not_existent", index));
            REQUIRE(nullptr == chemical_group.findRuleByName(
                                 "Acetylation", out_of_bounds_index));
            // End erroneous cases

            chemical_group_rule_1_sp =
              chemical_group.findRuleByEntity("LE_PLM_MODIF", index);
            REQUIRE(chemical_group_rule_1_sp->getEntity().toStdString() ==
                    "LE_PLM_MODIF");
            REQUIRE(index == 0);

            chemical_group_rule_1_sp =
              chemical_group.findRuleByName("Acetylation", index);
            REQUIRE(chemical_group_rule_1_sp->getName().toStdString() ==
                    "Acetylation");
            REQUIRE(index == 0);

            chemical_group_rule_1_sp = chemical_group.findRuleByEntityAndName(
              "LE_PLM_MODIF", "Acetylation", index);
            REQUIRE(chemical_group_rule_1_sp->getEntity().toStdString() ==
                    "LE_PLM_MODIF");
            REQUIRE(chemical_group_rule_1_sp->getName().toStdString() ==
                    "Acetylation");
            REQUIRE(index == 0);


            ChemicalGroupRuleSPtr chemical_group_rule_2_sp =
              chemical_group.getRulesRef().at(1);
            REQUIRE(chemical_group_rule_2_sp != nullptr);
            REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                    "LE_PLM_MODIF");

            index = 0;
            chemical_group_rule_2_sp =
              chemical_group.findRuleByEntity("LE_PLM_MODIF", index);
            REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                    "LE_PLM_MODIF");
            REQUIRE(index == 0);
            index = 1;
            chemical_group_rule_2_sp =
              chemical_group.findRuleByEntity("LE_PLM_MODIF", index);
            REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                    "LE_PLM_MODIF");
            REQUIRE(index == 1);

            index = 0;
            chemical_group_rule_2_sp =
              chemical_group.findRuleByName("Formylation", index);
            REQUIRE(chemical_group_rule_2_sp->getName().toStdString() ==
                    "Formylation");
            REQUIRE(index == 1);

            index                    = 0;
            chemical_group_rule_2_sp = chemical_group.findRuleByEntityAndName(
              "LE_PLM_MODIF", "Formylation", index);
            REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                    "LE_PLM_MODIF");
            REQUIRE(chemical_group_rule_2_sp->getName().toStdString() ==
                    "Formylation");
            REQUIRE(index == 1);
          }

          AND_WHEN(
            "An new ChemicalGroup is allocated and initialized with "
            "operator=() or allocated with the copy constructor")
          {
            ChemicalGroup other_chemical_group;
            other_chemical_group = chemical_group;

            ChemicalGroup another_chemical_group(other_chemical_group);

            THEN("The new ChemicalGroup is identical to the first one.")
            {
              REQUIRE(another_chemical_group.isValid());

              REQUIRE(another_chemical_group.getName().toStdString() ==
                      "N-term NH2");
              REQUIRE_THAT(chemical_group.getPka(),
                           Catch::Matchers::WithinAbs(9.6, 0.0001));
              REQUIRE(chemical_group.isAcidCharged());
              REQUIRE(chemical_group.getPolRule() ==
                      Enums::ChemicalGroupTrapped::LEFT);

              ChemicalGroupRuleSPtr chemical_group_rule_1_sp =
                another_chemical_group.getRulesRef().at(0);
              REQUIRE(chemical_group_rule_1_sp != nullptr);
              REQUIRE(chemical_group_rule_1_sp->getEntity().toStdString() ==
                      "LE_PLM_MODIF");

              chemical_group_rule_1_sp =
                another_chemical_group.findRuleByEntity("LE_PLM_MODIF", index);
              REQUIRE(chemical_group_rule_1_sp->getEntity().toStdString() ==
                      "LE_PLM_MODIF");
              REQUIRE(index == 0);

              chemical_group_rule_1_sp =
                another_chemical_group.findRuleByName("Acetylation", index);
              REQUIRE(chemical_group_rule_1_sp->getName().toStdString() ==
                      "Acetylation");
              REQUIRE(index == 0);

              chemical_group_rule_1_sp =
                another_chemical_group.findRuleByEntityAndName(
                  "LE_PLM_MODIF", "Acetylation", index);
              REQUIRE(chemical_group_rule_1_sp->getEntity().toStdString() ==
                      "LE_PLM_MODIF");
              REQUIRE(chemical_group_rule_1_sp->getName().toStdString() ==
                      "Acetylation");
              REQUIRE(index == 0);


              ChemicalGroupRuleSPtr chemical_group_rule_2_sp =
                another_chemical_group.getRulesRef().at(1);
              REQUIRE(chemical_group_rule_2_sp != nullptr);
              REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                      "LE_PLM_MODIF");

              index = 0;
              chemical_group_rule_2_sp =
                another_chemical_group.findRuleByEntity("LE_PLM_MODIF", index);
              REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                      "LE_PLM_MODIF");
              REQUIRE(index == 0);
              index = 1;
              chemical_group_rule_2_sp =
                another_chemical_group.findRuleByEntity("LE_PLM_MODIF", index);
              REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                      "LE_PLM_MODIF");
              REQUIRE(index == 1);

              index = 0;
              chemical_group_rule_2_sp =
                another_chemical_group.findRuleByName("Formylation", index);
              REQUIRE(chemical_group_rule_2_sp->getName().toStdString() ==
                      "Formylation");
              REQUIRE(index == 1);

              index = 0;
              chemical_group_rule_2_sp =
                another_chemical_group.findRuleByEntityAndName(
                  "LE_PLM_MODIF", "Formylation", index);
              REQUIRE(chemical_group_rule_2_sp->getEntity().toStdString() ==
                      "LE_PLM_MODIF");
              REQUIRE(chemical_group_rule_2_sp->getName().toStdString() ==
                      "Formylation");
              REQUIRE(index == 1);
            }
          }
        }
      }
    }
  }
}

SCENARIO(
  "ChemicalGroup objects can be created by reading a mnmchemgroup XML element",
  "[ChemicalGroup]")
{
  WHEN("Constructing an empty ChemicalGroup")
  {
    ChemicalGroup chemical_group;

    AND_WHEN("Initializing it with a <mnmchemgroup> XML element")
    {
      // QString dom_string = "<mnmchemgroup><name>N-term
      // NH2</name><pka>9.6</pka><acidcharged>TRUE</acidcharged><polrule>left_trapped</polrule><chemgrouprule><entity>LE_PLM_MODIF</entity><name>Acetylation</name><outcome>LOST</outcome></chemgrouprule></mnmchemgroup>";

      QDomDocument document;

      QDomElement mnmchemgroup_element = document.createElement("mnmchemgroup");

      QDomElement name_element = document.createElement("name");
      QDomText name_text       = document.createTextNode("N-term NH2");
      name_element.appendChild(name_text);

      QDomElement pka_element = document.createElement("pka");
      QDomText pka_text       = document.createTextNode("9.6");
      pka_element.appendChild(pka_text);

      QDomElement acidcharged_element = document.createElement("acidcharged");
      QDomText acidcharged_text       = document.createTextNode("TRUE");
      acidcharged_element.appendChild(acidcharged_text);

      QDomElement polrule_element = document.createElement("polrule");
      QDomText polrule_text       = document.createTextNode("left_trapped");
      polrule_element.appendChild(polrule_text);

      QDomElement chemgrouprule_element =
        document.createElement("chemgrouprule");

      QDomElement entity_element = document.createElement("entity");
      QDomText entity_text       = document.createTextNode("LE_PLM_MODIF");
      entity_element.appendChild(entity_text);

      QDomElement rule_name_element = document.createElement("name");
      QDomText rule_name_text       = document.createTextNode("Acetylation");
      rule_name_element.appendChild(rule_name_text);

      QDomElement outcome_element = document.createElement("outcome");
      QDomText outcome_text       = document.createTextNode("LOST");
      outcome_element.appendChild(outcome_text);
      chemgrouprule_element.appendChild(entity_element);
      chemgrouprule_element.appendChild(rule_name_element);
      chemgrouprule_element.appendChild(outcome_element);

      mnmchemgroup_element.appendChild(name_element);
      mnmchemgroup_element.appendChild(pka_element);
      mnmchemgroup_element.appendChild(acidcharged_element);
      mnmchemgroup_element.appendChild(polrule_element);
      mnmchemgroup_element.appendChild(chemgrouprule_element);

      document.appendChild(mnmchemgroup_element);

      REQUIRE(chemical_group.renderXmlMnmElement(mnmchemgroup_element));

      THEN("The object is valid and the member data are set correctly")
      {
        REQUIRE(chemical_group.isValid());

        REQUIRE(chemical_group.getName().toStdString() == "N-term NH2");
        REQUIRE_THAT(chemical_group.getPka(),
                     Catch::Matchers::WithinAbs(9.6, 0.001));
        REQUIRE(chemical_group.isAcidCharged());
        REQUIRE(chemical_group.getPolRule() == Enums::ChemicalGroupTrapped::LEFT);

        REQUIRE(
          chemical_group.getRulesCstRef().front()->getName().toStdString() ==
          "Acetylation");

        REQUIRE(
          chemical_group.getRulesCstRef().front()->getEntity().toStdString() ==
          "LE_PLM_MODIF");

        REQUIRE(chemical_group.getRulesCstRef().front()->getFate() ==
                Enums::ChemicalGroupFate::LOST);
      }
    }
  }
}


SCENARIO(
  "ChemicalGroup objects can be created by reading a mdfchemgroup XML element",
  "[ChemicalGroup]")
{
  WHEN("Constructing an empty ChemicalGroup")
  {
    ChemicalGroup chemical_group;

    AND_WHEN("Initializing it with a <mdfchemgroup> XML element")
    {
      // QString dom_string =
      // "<mdfchemgroup><name>Phosphorylation</name><pka>1.2</pka><acidcharged>FALSE</acidcharged></mdfchemgroup>";

      QDomDocument document;

      QDomElement mdfchemgroup_element = document.createElement("mdfchemgroup");

      QDomElement name_element = document.createElement("name");
      QDomText name_text       = document.createTextNode("Phosphorylation");
      name_element.appendChild(name_text);

      QDomElement pka_element = document.createElement("pka");
      QDomText pka_text       = document.createTextNode("1.2");
      pka_element.appendChild(pka_text);

      QDomElement acidcharged_element = document.createElement("acidcharged");
      QDomText acidcharged_text       = document.createTextNode("FALSE");
      acidcharged_element.appendChild(acidcharged_text);

      mdfchemgroup_element.appendChild(name_element);
      mdfchemgroup_element.appendChild(pka_element);
      mdfchemgroup_element.appendChild(acidcharged_element);

      document.appendChild(mdfchemgroup_element);

      REQUIRE(chemical_group.renderXmlMdfElement(mdfchemgroup_element));

      THEN("The object is valid and the member data are set correctly")
      {
        REQUIRE(chemical_group.isValid());

        REQUIRE(chemical_group.getName().toStdString() == "Phosphorylation");
        REQUIRE_THAT(chemical_group.getPka(),
                     Catch::Matchers::WithinAbs(1.2, 0.001));
        REQUIRE_FALSE(chemical_group.isAcidCharged());
      }
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
