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

namespace MsXpS
{
namespace libXpertMassCore
{

TestUtils test_utils_cleavage_rule_1_letter("protein-1-letter", 1);
TestUtils test_utils_cleavage_rule_3_letters("protein-3-letters", 1);

ErrorList error_list_cleavage_rule;

SCENARIO("Construction of a CleavageRule instances", "[CleavageRule]")
{
  test_utils_cleavage_rule_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_cleavage_rule_1_letter.msp_polChemDef;

  WHEN("Constructing a CleavageRule with only the polymer chemistry definition")
  {
    CleavageRule cleavage_rule(pol_chem_def_csp);

    THEN("The CleavageRule is invalid and does not validate successfully")
    {
      REQUIRE_FALSE(cleavage_rule.isValid());
      REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
    }

    AND_WHEN("Setting a name, correct left/right codes and formulas")
    {
      cleavage_rule.setName("Homoseryl");
      cleavage_rule.setLeftCode("A");
      cleavage_rule.setLeftFormula(Formula("+CH3COOH"));
      cleavage_rule.setRightCode("M");
      cleavage_rule.setRightFormula(Formula("-CH2S+O"));

      THEN("The CleavageRule is valid and does validate successfully")
      {
        REQUIRE(cleavage_rule.isValid());
        REQUIRE(cleavage_rule.validate(&error_list_cleavage_rule));

        AND_THEN("All the members are set correctly")
        {
          REQUIRE(cleavage_rule.getName().toStdString() == "Homoseryl");

          REQUIRE(cleavage_rule.getLeftCode().toStdString() == "A");
          REQUIRE(
            cleavage_rule.getLeftFormula().getActionFormula().toStdString() ==
            "+CH3COOH");

          REQUIRE(cleavage_rule.getRightCode().toStdString() == "M");
          REQUIRE(
            cleavage_rule.getRightFormula().getActionFormula().toStdString() ==
            "-CH2S+O");
        }
      }
    }
  }

  WHEN("Constructing a CleavageRule with all the parameters")
  {
    CleavageRule cleavage_rule(
      pol_chem_def_csp, "Homoseryl", "A", "+CH3COOH", "M", "-CH2S+O");

    THEN("The CleavageRule is valid and validates successfully")
    {
      REQUIRE(cleavage_rule.isValid());
      REQUIRE(cleavage_rule.validate(&error_list_cleavage_rule));
    }
  }
}

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

  //  Totally fake clr !!!
  // <clr>
  //   <name>Homoseryl</name>
  //   <le-mnm-code>A</le-mnm-code>
  //   <le-formula>+CH3COOH</le-formula>
  //   <re-mnm-code>M</re-mnm-code>
  //   <re-formula>-CH2S+O</re-formula>
  // </clr>

  // int clr_element_index         = 0;
  // int name_element_index        = 1;
  // int name_text_index           = 2;
  // int le_mnm_code_element_index = 3;
  // int le_mnm_code_text_index    = 4;
  // int le_formula_element_index  = 5;
  // int le_formula_text_index     = 6;
  // int re_mnm_code_element_index = 7;
  // int re_mnm_code_text_index    = 8;
  // int re_formula_element_index  = 9;
  // int re_formula_text_index     = 10;

  QStringList dom_strings{"clr",
                          "name",
                          "Homoseryl",
                          "le-mnm-code",
                          "A",
                          "le-formula",
                          "+CH3COOH",
                          "re-mnm-code",
                          "M",
                          "re-formula",
                          "-CH2S+O"};

  QDomDocument document =
    test_utils_cleavage_rule_1_letter.craftClrDomDocument(dom_strings);
  QDomElement cleavage_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 =
    "<clr>\n"
    " <name>Homoseryl</name>\n"
    " <le-mnm-code>A</le-mnm-code>\n"
    " <le-formula>+CH3COOH</le-formula>\n"
    " <re-mnm-code>M</re-mnm-code>\n"
    " <re-formula>-CH2S+O</re-formula>\n"
    "</clr> \n";
  expected_string = Utils::unspacify(expected_string);

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

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

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

      REQUIRE_FALSE(cleavage_rule.isValid());
      REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));

      AND_WHEN(
        "The CleavageRule instance is asked to render in itself the <clr> XML "
        "element")
      {
        REQUIRE(cleavage_rule.renderXmlClrElement(cleavage_rule_element,
                                                  /*version*/ 1));

        THEN("All the members are set correctly")
        {
          REQUIRE(cleavage_rule.getName().toStdString() == "Homoseryl");

          REQUIRE(cleavage_rule.getLeftCode().toStdString() == "A");
          REQUIRE(
            cleavage_rule.getLeftFormula().getActionFormula().toStdString() ==
            "+CH3COOH");

          REQUIRE(cleavage_rule.getRightCode().toStdString() == "M");
          REQUIRE(
            cleavage_rule.getRightFormula().getActionFormula().toStdString() ==
            "-CH2S+O");
        }
      }
    }
  }
}

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

  QString expected_string =
    "<clr>\n"
    " <name>Homoseryl</name>\n"
    " <le-mnm-code>A</le-mnm-code>\n"
    " <le-formula>+CH3COOH</le-formula>\n"
    " <re-mnm-code>M</re-mnm-code>\n"
    " <re-formula>-CH2S+O</re-formula>\n"
    "</clr> \n";
  expected_string = Utils::unspacify(expected_string);

  GIVEN("Construction a CleavageRule fully initialized")
  {
    CleavageRule cleavage_rule(
      pol_chem_def_csp, "Homoseryl", "A", "+CH3COOH", "M", "-CH2S+O");

    THEN(
      "The CleavageRule is valid and does validate successfully and all the "
      "members are set correctly")
    {
      REQUIRE(cleavage_rule.isValid());
      REQUIRE(cleavage_rule.validate(&error_list_cleavage_rule));

      REQUIRE(cleavage_rule.getPolchemDefCstSPtr() == pol_chem_def_csp);
      REQUIRE(cleavage_rule.getName().toStdString() == "Homoseryl");
      REQUIRE(cleavage_rule.getLeftCode().toStdString() == "A");
      REQUIRE(cleavage_rule.getLeftFormula().getActionFormula().toStdString() ==
              "+CH3COOH");
      REQUIRE(cleavage_rule.getRightCode().toStdString() == "M");
      REQUIRE(
        cleavage_rule.getRightFormula().getActionFormula().toStdString() ==
        "-CH2S+O");
    }

    WHEN("The CleavageRule is exported as a <clr> XML element")
    {
      QString xml_text = cleavage_rule.formatXmlClrElement(/*offset*/ 1);
      xml_text         = Utils::unspacify(xml_text);

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

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

  GIVEN("Construction a CleavageRule fully initialized")
  {
    CleavageRule cleavage_rule(
      pol_chem_def_csp, "Homoseryl", "A", "+CH3COOH", "M", "-CH2S+O");

    THEN(
      "The CleavageRule is valid and does validate successfully and all the "
      "members are set correctly")
    {
      REQUIRE(cleavage_rule.isValid());
      REQUIRE(cleavage_rule.validate(&error_list_cleavage_rule));

      REQUIRE(cleavage_rule.getPolchemDefCstSPtr() == pol_chem_def_csp);
      REQUIRE(cleavage_rule.getName().toStdString() == "Homoseryl");
      REQUIRE(cleavage_rule.getLeftCode().toStdString() == "A");
      REQUIRE(cleavage_rule.getLeftFormula().getActionFormula().toStdString() ==
              "+CH3COOH");
      REQUIRE(cleavage_rule.getRightCode().toStdString() == "M");
      REQUIRE(
        cleavage_rule.getRightFormula().getActionFormula().toStdString() ==
        "-CH2S+O");
    }

    WHEN("A new CleavageRule is instantiated by copy-construction")
    {
      CleavageRule new_cleavage_rule(cleavage_rule);

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

        REQUIRE(cleavage_rule.getPolchemDefCstSPtr() == pol_chem_def_csp);
        REQUIRE(cleavage_rule.getName().toStdString() == "Homoseryl");
        REQUIRE(cleavage_rule.getLeftCode().toStdString() == "A");
        REQUIRE(
          cleavage_rule.getLeftFormula().getActionFormula().toStdString() ==
          "+CH3COOH");
        REQUIRE(cleavage_rule.getRightCode().toStdString() == "M");
        REQUIRE(
          cleavage_rule.getRightFormula().getActionFormula().toStdString() ==
          "-CH2S+O");
        AND_THEN("The comparison operator do work properly")
        {
          REQUIRE(new_cleavage_rule == cleavage_rule);
          REQUIRE_FALSE(new_cleavage_rule != cleavage_rule);
        }
      }
    }

    WHEN("A new CleavageRule is instantiated by assignment")
    {
      CleavageRule new_cleavage_rule = cleavage_rule;

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

        REQUIRE(cleavage_rule.getPolchemDefCstSPtr() == pol_chem_def_csp);
        REQUIRE(cleavage_rule.getName().toStdString() == "Homoseryl");
        REQUIRE(cleavage_rule.getLeftCode().toStdString() == "A");
        REQUIRE(
          cleavage_rule.getLeftFormula().getActionFormula().toStdString() ==
          "+CH3COOH");
        REQUIRE(cleavage_rule.getRightCode().toStdString() == "M");
        REQUIRE(
          cleavage_rule.getRightFormula().getActionFormula().toStdString() ==
          "-CH2S+O");

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

SCENARIO(
  "CleavageRule objects are checked for validity at each initialization step",
  "[CleavageRule]")
{
  test_utils_cleavage_rule_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_cleavage_rule_1_letter.msp_polChemDef;

  GIVEN("Construction a CleavageRule fully initialized with correct data")
  {
    CleavageRule cleavage_rule(
      pol_chem_def_csp, "Homoseryl", "A", "+CH3COOH", "M", "-CH2S+O");

    THEN(
      "The CleavageRule is valid and does validate successfully and all the "
      "members are set correctly")
    {
      REQUIRE(cleavage_rule.isValid());
      REQUIRE(cleavage_rule.validate(&error_list_cleavage_rule));

      REQUIRE(cleavage_rule.getPolchemDefCstSPtr() == pol_chem_def_csp);
      REQUIRE(cleavage_rule.getName().toStdString() == "Homoseryl");
      REQUIRE(cleavage_rule.getLeftCode().toStdString() == "A");
      REQUIRE(cleavage_rule.getLeftFormula().getActionFormula().toStdString() ==
              "+CH3COOH");
      REQUIRE(cleavage_rule.getRightCode().toStdString() == "M");
      REQUIRE(
        cleavage_rule.getRightFormula().getActionFormula().toStdString() ==
        "-CH2S+O");
    }

    WHEN("An empty name is set")
    {
      cleavage_rule.setName("");

      THEN(
        "The CleavageRule is no more valid and does not validate successfully")
      {
        REQUIRE_FALSE(cleavage_rule.isValid());
        REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
      }
      cleavage_rule.setName("Homoseryl");
    }

    WHEN("A failing left code is set")
    {
      cleavage_rule.setLeftCode("B");

      THEN(
        "The CleavageRule is no more valid and does not validate successfully")
      {
        REQUIRE_FALSE(cleavage_rule.isValid());
        REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
      }
      cleavage_rule.setLeftCode("A");
    }

    WHEN("A failing right code is set")
    {
      cleavage_rule.setRightCode("B");

      THEN(
        "The CleavageRule is no more valid and does not validate successfully")
      {
        REQUIRE_FALSE(cleavage_rule.isValid());
        REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
      }
      cleavage_rule.setRightCode("M");
    }

    WHEN("A failing left formula is set")
    {
      cleavage_rule.setLeftFormula(Formula("-h2O"));

      THEN(
        "The CleavageRule is no more valid and does not validate successfully")
      {
        REQUIRE_FALSE(cleavage_rule.isValid());
        REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
      }
      cleavage_rule.setLeftFormula(Formula("+CH3COOH"));
    }

    WHEN("A failing right formula is set")
    {
      cleavage_rule.setRightFormula(Formula("-h2O"));

      THEN(
        "The CleavageRule is no more valid and does not validate successfully")
      {
        REQUIRE_FALSE(cleavage_rule.isValid());
        REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
      }
      cleavage_rule.setRightFormula(Formula("-CH2S+O"));
    }

    WHEN("Having an empty left code and a correct formula")
    {
      cleavage_rule.setLeftCode("");

      THEN(
        "The CleavageRule is no more valid and does not validate successfully")
      {
        REQUIRE_FALSE(cleavage_rule.isValid());
        REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
      }
      cleavage_rule.setLeftCode("A");
    }

    WHEN("Having an empty right code and a correct formula")
    {
      cleavage_rule.setRightCode("");

      THEN(
        "The CleavageRule is no more valid and does not validate successfully")
      {
        REQUIRE_FALSE(cleavage_rule.isValid());
        REQUIRE_FALSE(cleavage_rule.validate(&error_list_cleavage_rule));
      }
      cleavage_rule.setRightCode("M");
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
