/////////////////////// 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/FragmentationPathway.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{

TestUtils test_utils_fragmentation_pathway_1_letter("protein-1-letter", 1);
TestUtils test_utils_fragmentation_pathway_3_letters("protein-3-letters", 1);

ErrorList error_list_fragmentation_pathway;

SCENARIO(
  "FragmentationPathway objects can be constructed empty and then initialized "
  "piecemeal "
  "until they are valid",
  "[FragmentationPathway]")
{
  test_utils_fragmentation_pathway_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_fragmentation_pathway_1_letter.msp_polChemDef;

  GIVEN("A FragmentationPathway constructed with no parameter at all")
  {
    FragmentationPathway fragmentation_pathway;

    REQUIRE_FALSE(fragmentation_pathway.isValid());
    REQUIRE_FALSE(
      fragmentation_pathway.validate(&error_list_fragmentation_pathway));

    // <fgs>
    //   <name>a</name>
    //   <end>LE</end>
    //   <formula>-C1O1</formula>
    //   <comment>opt_comment</comment>
    //   <fgr>
    //     <name>one_rule</name>
    //     <formula>+H2O</formula>
    //     <prev-mnm-code>M</prev-mnm-code>
    //     <this-mnm-code>Y</this-mnm-code>
    //     <next-mnm-code>T</next-mnm-code>
    //     <comment>opt_comment</comment>
    //   </fgr>
    //   other fgr allowed, none possible also
    // </fgs>

    WHEN("The PolChemDef is set with the setter")
    {
      fragmentation_pathway.setPolchemDefCstSPtr(pol_chem_def_csp);

      THEN(
        "The FragmentationPathway is still invalid and does not validate "
        "successfully")
      {
        REQUIRE_FALSE(fragmentation_pathway.isValid());
        REQUIRE_FALSE(
          fragmentation_pathway.validate(&error_list_fragmentation_pathway));
      }

      AND_WHEN("The name is set with the setter")
      {
        fragmentation_pathway.setName("a");

        THEN(
          "The FragmentationPathway is still invalid and does not validate "
          "successfully")
        {
          REQUIRE_FALSE(fragmentation_pathway.isValid());
          REQUIRE_FALSE(
            fragmentation_pathway.validate(&error_list_fragmentation_pathway));

          AND_THEN("A FragmentationPathway can be gotten from the PolChemDef")
          {
            FragmentationPathwayCstSPtr fragmentation_pathway_csp =
              fragmentation_pathway.getFromPolChemDefByName();
            REQUIRE(fragmentation_pathway_csp != nullptr);

            REQUIRE(fragmentation_pathway.isKnownByNameInPolChemDef() ==
                    Enums::PolChemDefEntityStatus::ENTITY_KNOWN);
          }
        }

        AND_WHEN("The end is set with the setter")
        {
          fragmentation_pathway.setFragEnd(Enums::FragEnd::LE);

          THEN(
            "The FragmentationPathway is still invalid and does not validate "
            "successfully")
          {
            REQUIRE_FALSE(fragmentation_pathway.isValid());
            REQUIRE_FALSE(
              fragmentation_pathway.validate(&error_list_fragmentation_pathway));
          }

          AND_WHEN("The formula is set with one of the setters")
          {
            fragmentation_pathway.setFormula("-C1O1");

            THEN(
              "The FragmentationPathway is  valid and does  validate "
              "successfully")
            {
              REQUIRE(fragmentation_pathway.isValid());
              REQUIRE(fragmentation_pathway.validate(
                &error_list_fragmentation_pathway));
            }
            AND_WHEN("The formula is set with other of the setters")
            {
              fragmentation_pathway.setFormula(Formula("-C1O1"));

              THEN(
                "The FragmentationPathway is  valid and does  validate "
                "successfully")
              {
                REQUIRE(fragmentation_pathway.isValid());
                REQUIRE(fragmentation_pathway.validate(
                  &error_list_fragmentation_pathway));
              }

              AND_WHEN("The comment is set with the setter")
              {
                fragmentation_pathway.setComment("opt_comment");

                THEN(
                  "The FragmentationPathway is still valid and does validate "
                  "successfully")
                {
                  REQUIRE(fragmentation_pathway.isValid());
                  REQUIRE(fragmentation_pathway.validate(
                    &error_list_fragmentation_pathway));
                }

                AND_WHEN("A correct FragmentationRule is added")
                {
                  //   <fgr>
                  //     <name>one_rule</name>
                  //     <formula>+H2O</formula>
                  //     <prev-mnm-code>M</prev-mnm-code>
                  //     <this-mnm-code>Y</this-mnm-code>
                  //     <next-mnm-code>T</next-mnm-code>
                  //     <comment>opt_comment</comment>
                  //   </fgr>

                  FragmentationRuleSPtr frag_rule_sp =
                    std::make_shared<FragmentationRule>(pol_chem_def_csp,
                                                        "one_rule",
                                                        "F",
                                                        "D",
                                                        "E",
                                                        "opt_comment",
                                                        "+H2O");

                  fragmentation_pathway.addRule(frag_rule_sp);

                  THEN(
                    "The FragmentationPathway is still valid and does validate "
                    "successfully")
                  {
                    REQUIRE(fragmentation_pathway.isValid());
                    REQUIRE(fragmentation_pathway.validate(
                      &error_list_fragmentation_pathway));

                    REQUIRE(fragmentation_pathway.getName().toStdString() ==
                            "a");
                    REQUIRE(fragmentation_pathway.getFragEnd() == Enums::FragEnd::LE);
                    REQUIRE(fragmentation_pathway.getFormulaCstRef()
                              .getActionFormula()
                              .toStdString() == "-C1O1");
                    REQUIRE(fragmentation_pathway.getRulesCstRef().size() == 1);
                    REQUIRE(fragmentation_pathway.getRulesCstRef()
                              .at(0)
                              ->getName()
                              .toStdString() == "one_rule");
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

SCENARIO(
  "FragmentationPathway objects can get as many FragmentationRule instances "
  "as "
  "necessary",
  "[FragmentationPathway]")
{
  test_utils_fragmentation_pathway_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_fragmentation_pathway_1_letter.msp_polChemDef;

  GIVEN(
    "A FragmentationPathway constructed with full parameters (with "
    "FragmentationRules)")
  {
    FragmentationPathway fragmentation_pathway(
      pol_chem_def_csp, "a", "-C1O1", Enums::FragEnd::LE, "opt_comment");

    FragmentationRuleSPtr frag_rule_1_sp = std::make_shared<FragmentationRule>(
      pol_chem_def_csp, "one_rule", "F", "D", "E", "opt1_comment", "+H2O");

    FragmentationRuleSPtr frag_rule_2_sp = std::make_shared<FragmentationRule>(
      pol_chem_def_csp, "two_rule", "G", "E", "F", "opt2_comment", "+H4O2");

    FragmentationRuleSPtr frag_rule_3_sp = std::make_shared<FragmentationRule>(
      pol_chem_def_csp, "three_rule", "H", "F", "G", "opt3_comment", "+H6O9");

    fragmentation_pathway.addRule(frag_rule_1_sp);
    fragmentation_pathway.addRule(frag_rule_2_sp);
    fragmentation_pathway.insertRuleAt(frag_rule_3_sp, 1);

    THEN("The FragmentationPathway is valid and does validate successfully")
    {
      REQUIRE(fragmentation_pathway.isValid());
      REQUIRE(fragmentation_pathway.validate(&error_list_fragmentation_pathway));

      REQUIRE(fragmentation_pathway.getName().toStdString() == "a");
      REQUIRE(fragmentation_pathway.getFragEnd() == Enums::FragEnd::LE);
      REQUIRE(fragmentation_pathway.getFormulaCstRef()
                .getActionFormula()
                .toStdString() == "-C1O1");
      REQUIRE(fragmentation_pathway.getRulesCstRef().size() == 3);
      REQUIRE(
        fragmentation_pathway.getRulesCstRef().at(0)->getName().toStdString() ==
        "one_rule");
      REQUIRE(
        fragmentation_pathway.getRulesCstRef().at(1)->getName().toStdString() ==
        "three_rule");
      REQUIRE(
        fragmentation_pathway.getRulesCstRef().at(2)->getName().toStdString() ==
        "two_rule");
    }

    WHEN("Copy-constructing a new FragmentationPathway")
    {
      FragmentationPathway new_fragmentation_pathway(fragmentation_pathway);

      THEN(
        "The new FragmentationPathway is valid, does validate successfully "
        "and operator==() returns proper result")
      {
        REQUIRE(new_fragmentation_pathway.isValid());
        REQUIRE(
          new_fragmentation_pathway.validate(&error_list_fragmentation_pathway));

        REQUIRE(new_fragmentation_pathway.getName().toStdString() == "a");
        REQUIRE(new_fragmentation_pathway.getFragEnd() == Enums::FragEnd::LE);
        REQUIRE(new_fragmentation_pathway.getFormulaCstRef()
                  .getActionFormula()
                  .toStdString() == "-C1O1");
        REQUIRE(new_fragmentation_pathway.getRulesCstRef().size() == 3);
        REQUIRE(new_fragmentation_pathway.getRulesCstRef()
                  .at(0)
                  ->getName()
                  .toStdString() == "one_rule");
        REQUIRE(new_fragmentation_pathway.getRulesCstRef()
                  .at(1)
                  ->getName()
                  .toStdString() == "three_rule");
        REQUIRE(new_fragmentation_pathway.getRulesCstRef()
                  .at(2)
                  ->getName()
                  .toStdString() == "two_rule");

        REQUIRE(new_fragmentation_pathway == fragmentation_pathway);
      }
    }

    WHEN("One FragmentationRule is removed")
    {
      fragmentation_pathway.removeRuleAt(0);

      THEN("The container changes")
      {
        REQUIRE(fragmentation_pathway.getRulesCstRef().size() == 2);
        REQUIRE(fragmentation_pathway.getRulesCstRef()
                  .at(0)
                  ->getName()
                  .toStdString() == "three_rule");
        REQUIRE(fragmentation_pathway.getRulesCstRef()
                  .at(1)
                  ->getName()
                  .toStdString() == "two_rule");
      }

      WHEN("One FragmentationRule is removed")
      {
        fragmentation_pathway.removeRuleAt(1);

        THEN("The container changes")
        {
          REQUIRE(fragmentation_pathway.getRulesCstRef().size() == 1);
          REQUIRE(fragmentation_pathway.getRulesCstRef()
                    .at(0)
                    ->getName()
                    .toStdString() == "three_rule");
        }
      }
    }
  }
}

SCENARIO(
  "FragmentationPathway objects can be copy- or assignment-constructed and "
  "compared",
  "[FragmentationPathway]")
{
  test_utils_fragmentation_pathway_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_fragmentation_pathway_1_letter.msp_polChemDef;

  GIVEN(
    "A FragmentationPathway constructed with full parameters (with "
    "FragmentationRules)")
  {
    FragmentationPathway fragmentation_pathway(
      pol_chem_def_csp, "a", "-C1O1", Enums::FragEnd::LE, "opt_comment");

    FragmentationRuleSPtr frag_rule_sp = std::make_shared<FragmentationRule>(
      pol_chem_def_csp, "one_rule", "F", "D", "E", "opt_comment", "+H2O");

    fragmentation_pathway.addRule(frag_rule_sp);

    THEN("The FragmentationPathway is valid and does validate successfully")
    {
      REQUIRE(fragmentation_pathway.isValid());
      REQUIRE(fragmentation_pathway.validate(&error_list_fragmentation_pathway));

      REQUIRE(fragmentation_pathway.getName().toStdString() == "a");
      REQUIRE(fragmentation_pathway.getFragEnd() == Enums::FragEnd::LE);
      REQUIRE(fragmentation_pathway.getFormulaCstRef()
                .getActionFormula()
                .toStdString() == "-C1O1");
      REQUIRE(fragmentation_pathway.getRulesCstRef().size() == 1);
      REQUIRE(
        fragmentation_pathway.getRulesCstRef().at(0)->getName().toStdString() ==
        "one_rule");
    }

    WHEN("Another FragmentationPathway is copy-constructed")
    {
      FragmentationPathway new_fragmentation_pathway(fragmentation_pathway);

      THEN("It is identical to the original one")
      {
        REQUIRE(new_fragmentation_pathway.isValid());
        REQUIRE(
          new_fragmentation_pathway.validate(&error_list_fragmentation_pathway));

        REQUIRE(new_fragmentation_pathway.getName().toStdString() == "a");
        REQUIRE(new_fragmentation_pathway.getFragEnd() == Enums::FragEnd::LE);
        REQUIRE(new_fragmentation_pathway.getFormulaCstRef()
                  .getActionFormula()
                  .toStdString() == "-C1O1");
        REQUIRE(new_fragmentation_pathway.getRulesCstRef().size() == 1);
        REQUIRE(new_fragmentation_pathway.getRulesCstRef()
                  .at(0)
                  ->getName()
                  .toStdString() == "one_rule");
      }

      THEN("The comparison operators work")
      {
        REQUIRE(new_fragmentation_pathway == fragmentation_pathway);
        REQUIRE_FALSE(new_fragmentation_pathway != fragmentation_pathway);
        REQUIRE(new_fragmentation_pathway == new_fragmentation_pathway);
        REQUIRE_FALSE(new_fragmentation_pathway != new_fragmentation_pathway);
      }
    }

    WHEN("Another FragmentationPathway is assignment-initialized")
    {
      FragmentationPathway new_fragmentation_pathway;
      new_fragmentation_pathway = fragmentation_pathway;

      THEN("It is identical to the original one")
      {
        REQUIRE(new_fragmentation_pathway.isValid());
        REQUIRE(
          new_fragmentation_pathway.validate(&error_list_fragmentation_pathway));

        REQUIRE(new_fragmentation_pathway.getName().toStdString() == "a");
        REQUIRE(new_fragmentation_pathway.getFragEnd() == Enums::FragEnd::LE);
        REQUIRE(new_fragmentation_pathway.getFormulaCstRef()
                  .getActionFormula()
                  .toStdString() == "-C1O1");
        REQUIRE(new_fragmentation_pathway.getRulesCstRef().size() == 1);
        REQUIRE(new_fragmentation_pathway.getRulesCstRef()
                  .at(0)
                  ->getName()
                  .toStdString() == "one_rule");
      }

      THEN("The comparison operators work")
      {
        REQUIRE(new_fragmentation_pathway == fragmentation_pathway);
        REQUIRE_FALSE(new_fragmentation_pathway != fragmentation_pathway);
        REQUIRE(new_fragmentation_pathway == new_fragmentation_pathway);
        REQUIRE_FALSE(new_fragmentation_pathway != new_fragmentation_pathway);
      }
    }
  }
}

SCENARIO(
  "FragmentationPathway objects from version 1 XML element  can be "
  "initialized "
  "from an <fgs> XML element and can export themselves as such also",
  "[FragmentationPathway]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_fragmentation_pathway_1_letter.msp_polChemDef;

  // int fgs_element_index = 0;
  // //
  // int name_element_index     = 1;
  // int name_text_index        = 2;
  // int frag_end_element_index = 3;
  // int frag_end_text_index    = 4;
  // int formula_element_index  = 5;
  // int formula_text_index     = 6;
  // int side_chain_element_index = 7;
  // int side_chain_text_index = 8;
  // int comment_element_index  = 9;
  // int comment_text_index     = 10;
  // //
  // int fgr_element_index = 11;
  // //
  // int fgr_name_element_index     = 12;
  // int fgr_name_text_index        = 13;
  // int fgr_formula_element_index  = 14;
  // int fgr_formula_text_index     = 15;
  // int prev_code_element_index    = 16;
  // int prev_code_text_index       = 17;
  // int curr_code_element_index = 18;
  // int curr_code_text_index    = 19;
  // int next_code_element_index    = 20;
  // int next_code_text_index       = 21;
  // int fgr_comment_element_index  = 22;
  // int fgr_comment_text_index     = 23;

  // <fgs>
  //   <name>a</name>
  //   <end>LE</end>
  //   <formula>-C1O1</formula>
  //   <sidechaincontrib>0</sidechaincontrib>
  //   <comment>opt_comment</comment>
  //   <fgr>
  //     <name>one_rule</name>
  //     <formula>+H2O</formula>
  //     <prev-mnm-code>M</prev-mnm-code>
  //     <curr-mnm-code>Y</curr-mnm-code>
  //     <next-mnm-code>T</next-mnm-code>
  //     <comment>opt_comment</comment>
  //   </fgr>
  //   other fgr allowed, none possible also
  // </fgs>

  QStringList dom_strings{"fgs",              // 0
                          "name",             // 1
                          "a",                // 2
                          "end",              // 3
                          "LE",               // 4
                          "formula",          // 5
                          "-C1O1",            // 6
                          "sidechaincontrib", // 7
                          "0",                // 8
                          "comment",          // 9
                          "opt_comment",      // 10
                          "fgr",              // 11
                          "name",             // 12
                          "one_rule",         // 13
                          "formula",          // 14
                          "+H2O",             // 15
                          "prev-mnm-code",    // 16
                          "M",                // 17
                          "curr-mnm-code",    // 18
                          "Y",                // 19
                          "next-mnm-code",    // 20
                          "T",                // 21
                          "comment",          // 22
                          "opt_comment"};     // 23

  QDomDocument document =
    test_utils_fragmentation_pathway_1_letter.craftFgsDomDocument(dom_strings);
  QDomElement fragmentation_pathway_element =
    document.elementsByTagName(dom_strings[0]).item(0).toElement();

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

  //  We need to remove all spaces from the strings to be able to compare
  //  them. There must be a bug somewhere because with the original output, at
  //  the screen everything seems correct,  but test FAILED.

  QString document_string = document.toString(/*indentation*/ 0);
  document_string         = Utils::unspacify(document_string);

  QString expected_fgs_string =
    "<fgs><name>a</name><end>LE</end><formula>-C1O1</formula>"
    "<sidechaincontrib>0</sidechaincontrib><comment>opt_comment</comment>"
    "<fgr><name>one_rule</name><formula>+H2O</formula>"
    "<prev-mnm-code>M</prev-mnm-code><curr-mnm-code>Y</curr-mnm-code>"
    "<next-mnm-code>T</next-mnm-code><comment>opt_comment</comment>"
    "</fgr></fgs>";
  expected_fgs_string = Utils::unspacify(expected_fgs_string);

  QString expected_fgp_string =
    "<fgp><name>a</name><end>LE</end><formula>-C1O1</formula>"
    "<sidechaincontrib>0</sidechaincontrib><comment>opt_comment</comment>"
    "<fgr><name>one_rule</name><formula>+H2O</formula>"
    "<prev-mnm-code>M</prev-mnm-code><curr-mnm-code>Y</curr-mnm-code>"
    "<next-mnm-code>T</next-mnm-code><comment>opt_comment</comment>"
    "</fgr></fgp>";
  expected_fgp_string = Utils::unspacify(expected_fgp_string);

  REQUIRE(document_string.toStdString() == expected_fgs_string.toStdString());

  WHEN("Creating a FragmentationPathway only with PolChemDef")
  {
    FragmentationPathway fragmentation_pathway(pol_chem_def_csp);

    AND_WHEN("Initializing it with an XML <fgs> element")
    {
      REQUIRE(fragmentation_pathway.renderXmlFgsElement(
        fragmentation_pathway_element, /*version*/ 1));

      THEN(
        "The initalized FragmentationPathway is valid and validates "
        "successfully")
      {

        THEN("The FragmentationPathway is valid and validates successfully")
        {
          REQUIRE(fragmentation_pathway.isValid());
          REQUIRE(
            fragmentation_pathway.validate(&error_list_fragmentation_pathway));

          REQUIRE(fragmentation_pathway.getName().toStdString() == "a");
          REQUIRE(fragmentation_pathway.getFragEnd() == Enums::FragEnd::LE);
          REQUIRE(fragmentation_pathway.getFormulaCstRef()
                    .getActionFormula()
                    .toStdString() == "-C1O1");
          REQUIRE(fragmentation_pathway.getRulesCstRef().size() == 1);
          REQUIRE(fragmentation_pathway.getRulesCstRef()
                    .at(0)
                    ->getName()
                    .toStdString() == "one_rule");
        }
      }

      AND_WHEN("Exporting itself as a  <fgp> XML element")
      {
        QString xml_text =
          fragmentation_pathway.formatXmlFgpElement(/*offset*/ 1);
        xml_text = Utils::unspacify(xml_text);

        THEN("The text must be identical to the initial XML <fgs> string")
        {
          REQUIRE(xml_text.toStdString() == expected_fgp_string.toStdString());
        }
      }
    }
  }
}


} // namespace libXpertMassCore
} // namespace MsXpS
