/////////////////////// 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/FragmentationRule.hpp"
#include "MsXpS/libXpertMassCore/Sequence.hpp"
#include "MsXpS/libXpertMassCore/Monomer.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{

TestUtils test_utils_frag_rule_1_letter("protein-1-letter", 1);
TestUtils test_utils_frag_rule_3_letters("protein-3-letters", 1);

ErrorList error_list_frag_rule;

SCENARIO(
  "FragmentationRule objects can be constructed fully qualified in one go",
  "[FragmentationRule]")
{
  test_utils_frag_rule_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_frag_rule_1_letter.msp_polChemDef;

  WHEN("Constructing a FragmentationRule with all valid parameters")
  {
    FragmentationRule frag_rule(
      pol_chem_def_csp, "a-fgr-2", "F", "D", "E", "thecomment", "+H100");

    THEN("The FragmentationRule is valid and does validate successfully")
    {
      REQUIRE(frag_rule.isValid());
      REQUIRE(frag_rule.validate(&error_list_frag_rule));
    }
    AND_THEN("The FragmentationRule is valid and does validate successfully")
    {
      REQUIRE(frag_rule.getName().toStdString() == "a-fgr-2");
      REQUIRE(frag_rule.getPrevCode().toStdString() == "F");
      REQUIRE(frag_rule.getCurrCode().toStdString() == "D");
      REQUIRE(frag_rule.getNextCode().toStdString() == "E");
      REQUIRE(frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
              "+H100");
      REQUIRE(frag_rule.getComment().toStdString() == "thecomment");
    }
  }
}

SCENARIO(
  "FragmentationRule objects can be constructed from a <fgr> XML element",
  "[FragmentationRule]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_frag_rule_1_letter.msp_polChemDef;

  WHEN("Constructing a FragmentationRule with a valid XML element")
  {
    //  Totally fake fgr !!!
    // <fgr>
    // <name>a-fgr-2</name>
    // <formula>+H100</formula>
    // <prev-mnm-code>F</prev-mnm-code>
    // <curr-mnm-code>D</curr-mnm-code>
    // <next-mnm-code>E</next-mnm-code>
    // <comment>thecomment</comment>
    // </fgr>


    QStringList dom_strings{"fgr", // 0
                            "name",
                            "a-fgr-2",
                            "formula",
                            "+H100",
                            "prev-mnm-code",
                            "F",
                            "curr-mnm-code",
                            "D",
                            "next-mnm-code",
                            "E",
                            "comment",
                            "thecomment"};


    QDomDocument document =
      test_utils_frag_rule_1_letter.craftFgrDomDocument(dom_strings);
    QDomElement frag_rule_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_string =
      "<fgr><name>a-fgr-2</name><formula>+H100</formula><prev-mnm-code>F</"
      "prev-mnm-code><curr-mnm-code>D</curr-mnm-code><next-mnm-code>E</"
      "next-mnm-code><comment>thecomment</comment></fgr>";
    expected_string = Utils::unspacify(expected_string);

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

    FragmentationRule frag_rule(pol_chem_def_csp, frag_rule_element, 1);

    THEN("The FragmentationRule is valid and does validate successfully")
    {
      REQUIRE(frag_rule.isValid());
      REQUIRE(frag_rule.validate(&error_list_frag_rule));
    }
    AND_THEN("The FragmentationRule is valid and does validate successfully")
    {
      REQUIRE(frag_rule.getName().toStdString() == "a-fgr-2");
      REQUIRE(frag_rule.getPrevCode().toStdString() == "F");
      REQUIRE(frag_rule.getCurrCode().toStdString() == "D");
      REQUIRE(frag_rule.getNextCode().toStdString() == "E");
      REQUIRE(frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
              "+H100");
      REQUIRE(frag_rule.getComment().toStdString() == "thecomment");
    }
  }
}

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

  WHEN("Constructing a FragmentationRule with no valid parameter")
  {
    FragmentationRule frag_rule;

    THEN("The FragmentationRule is invalid and does not validate successfully")
    {
      REQUIRE_FALSE(frag_rule.isValid());
      REQUIRE_FALSE(frag_rule.validate(&error_list_frag_rule));
    }

    AND_WHEN(
      "Setting PolChemDef, a name, correct prev/curr/next codes, formula and "
      "comment")
    {
      frag_rule.setPolchemDefCstSPtr(pol_chem_def_csp);
      frag_rule.setName("a-fgr-2");
      frag_rule.setPrevCode("F");
      frag_rule.setCurrCode("D");
      frag_rule.setNextCode("E");
      frag_rule.setFormula(Formula("+H100"));
      frag_rule.setComment("thecomment");

      THEN("The FragmentationRule is valid and does validate successfully")
      {
        REQUIRE(frag_rule.isValid());
        REQUIRE(frag_rule.validate(&error_list_frag_rule));

        AND_THEN("All the members are set correctly")
        {
          REQUIRE(frag_rule.getName().toStdString() == "a-fgr-2");
          REQUIRE(frag_rule.getPrevCode().toStdString() == "F");
          REQUIRE(frag_rule.getCurrCode().toStdString() == "D");
          REQUIRE(frag_rule.getNextCode().toStdString() == "E");
          REQUIRE(
            frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
            "+H100");
          REQUIRE(frag_rule.getComment().toStdString() == "thecomment");
        }
      }
    }
  }
}

SCENARIO(
  "FragmentationRule objects can be initialized from an <fgr> XML element",
  "[FragmentationRule]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_frag_rule_1_letter.msp_polChemDef;

  //  Totally fake fgr !!!
  // <fgr>
  // <name>a-fgr-2</name>
  // <formula>+H100</formula>
  // <prev-mnm-code>F</prev-mnm-code>
  // <curr-mnm-code>D</curr-mnm-code>
  // <next-mnm-code>E</next-mnm-code>
  // <comment>thecomment</comment>
  // </fgr>


  QStringList dom_strings{"fgr", // 0
                          "name",
                          "a-fgr-2",
                          "formula",
                          "+H100",
                          "prev-mnm-code",
                          "F",
                          "curr-mnm-code",
                          "D",
                          "next-mnm-code",
                          "E",
                          "comment",
                          "thecomment"};


  QDomDocument document =
    test_utils_frag_rule_1_letter.craftFgrDomDocument(dom_strings);
  QDomElement frag_rule_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_string =
    "<fgr><name>a-fgr-2</name><formula>+H100</formula><prev-mnm-code>F</"
    "prev-mnm-code><curr-mnm-code>D</curr-mnm-code><next-mnm-code>E</"
    "next-mnm-code><comment>thecomment</comment></fgr>";
  expected_string = Utils::unspacify(expected_string);

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

  FragmentationRule frag_rule(pol_chem_def_csp, frag_rule_element, 1);

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

    THEN("The FragmentationRule instance is not valid and cannot validate")
    {
      REQUIRE(frag_rule.getName().toStdString() == "");

      REQUIRE_FALSE(frag_rule.isValid());
      REQUIRE_FALSE(frag_rule.validate(&error_list_frag_rule));

      AND_WHEN(
        "The FragmentationRule instance is asked to render in itself the <fgr> "
        "XML "
        "element")
      {
        REQUIRE(frag_rule.renderXmlFgrElement(frag_rule_element));

        AND_THEN("All the members are set correctly")
        {
          REQUIRE(frag_rule.getName().toStdString() == "a-fgr-2");
          REQUIRE(frag_rule.getPrevCode().toStdString() == "F");
          REQUIRE(frag_rule.getCurrCode().toStdString() == "D");
          REQUIRE(frag_rule.getNextCode().toStdString() == "E");
          REQUIRE(
            frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
            "+H100");
          REQUIRE(frag_rule.getComment().toStdString() == "thecomment");
        }
      }
    }
  }
}

SCENARIO(
  "FragmentationRule objects can output themselves to a <fgr> XML element",
  "[FragmentationRule]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_frag_rule_1_letter.msp_polChemDef;

  QString expected_string =
    "<fgr><name>a-fgr-2</name><formula>+H100</formula><prev-mnm-code>F</"
    "prev-mnm-code><curr-mnm-code>D</curr-mnm-code><next-mnm-code>E</"
    "next-mnm-code><comment>thecomment</comment></fgr>";
  expected_string = Utils::unspacify(expected_string);

  GIVEN("Construction a FragmentationRule fully initialized")
  {
    FragmentationRule frag_rule(
      pol_chem_def_csp, "a-fgr-2", "F", "D", "E", "thecomment", "+H100");

    THEN("The FragmentationRule is valid and does validate successfully")
    {
      REQUIRE(frag_rule.isValid());
      REQUIRE(frag_rule.validate(&error_list_frag_rule));

      REQUIRE(frag_rule.getName().toStdString() == "a-fgr-2");
      REQUIRE(frag_rule.getPrevCode().toStdString() == "F");
      REQUIRE(frag_rule.getCurrCode().toStdString() == "D");
      REQUIRE(frag_rule.getNextCode().toStdString() == "E");
      REQUIRE(frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
              "+H100");
      REQUIRE(frag_rule.getComment().toStdString() == "thecomment");
    }

    WHEN("The FragmentationRule is exported as a <fgr> XML element")
    {
      QString xml_text = frag_rule.formatXmlFgrElement(/*offset*/ 1);
      xml_text         = Utils::unspacify(xml_text);

      THEN("The obtained text is as expected")
      {
        REQUIRE(xml_text.toStdString() == expected_string.toStdString());
      }
    }
  }
}

SCENARIO(
  "FragmentationRule objects can be constructed by copy constructor or by "
  "assigment "
  "and then compared",
  "[FragmentationRule]")
{
  test_utils_frag_rule_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_frag_rule_1_letter.msp_polChemDef;

  GIVEN("Construction a FragmentationRule fully initialized")
  {
    FragmentationRule frag_rule(
      pol_chem_def_csp, "a-fgr-2", "F", "D", "E", "thecomment", "+H100");

    THEN("The FragmentationRule is valid and does validate successfully")
    {
      REQUIRE(frag_rule.isValid());
      REQUIRE(frag_rule.validate(&error_list_frag_rule));

      REQUIRE(frag_rule.getName().toStdString() == "a-fgr-2");
      REQUIRE(frag_rule.getPrevCode().toStdString() == "F");
      REQUIRE(frag_rule.getCurrCode().toStdString() == "D");
      REQUIRE(frag_rule.getNextCode().toStdString() == "E");
      REQUIRE(frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
              "+H100");
      REQUIRE(frag_rule.getComment().toStdString() == "thecomment");
    }

    WHEN("A new FragmentationRule is instantiated by copy-construction")
    {
      FragmentationRule new_frag_rule(frag_rule);

      THEN(
        "The new FragmentationRule is valid and does validate successfully and "
        "all "
        "the members are set correctly")
      {
        REQUIRE(new_frag_rule.isValid());
        REQUIRE(new_frag_rule.validate(&error_list_frag_rule));

        REQUIRE(new_frag_rule.getName().toStdString() == "a-fgr-2");
        REQUIRE(new_frag_rule.getPrevCode().toStdString() == "F");
        REQUIRE(new_frag_rule.getCurrCode().toStdString() == "D");
        REQUIRE(new_frag_rule.getNextCode().toStdString() == "E");
        REQUIRE(
          new_frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
          "+H100");
        REQUIRE(new_frag_rule.getComment().toStdString() == "thecomment");

        AND_THEN("The comparison operator do work properly")
        {
          REQUIRE(new_frag_rule == frag_rule);
          REQUIRE_FALSE(new_frag_rule != frag_rule);
        }
      }
    }

    WHEN("A new FragmentationRule is instantiated by assignment")
    {
      FragmentationRule new_frag_rule;
      new_frag_rule = frag_rule;

      THEN(
        "The new FragmentationRule is valid and does validate successfully and "
        "all "
        "the members are set correctly")
      {
        REQUIRE(new_frag_rule.isValid());
        REQUIRE(new_frag_rule.validate(&error_list_frag_rule));

        REQUIRE(new_frag_rule.getName().toStdString() == "a-fgr-2");
        REQUIRE(new_frag_rule.getPrevCode().toStdString() == "F");
        REQUIRE(new_frag_rule.getCurrCode().toStdString() == "D");
        REQUIRE(new_frag_rule.getNextCode().toStdString() == "E");
        REQUIRE(
          new_frag_rule.getFormulaCstRef().getActionFormula().toStdString() ==
          "+H100");
        REQUIRE(new_frag_rule.getComment().toStdString() == "thecomment");

        AND_THEN("The comparison operator do work properly")
        {
          REQUIRE(new_frag_rule == frag_rule);
          REQUIRE_FALSE(new_frag_rule != frag_rule);
        }
      }
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
