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

namespace MsXpS
{

namespace libXpertMassCore
{

TestUtils test_utils_cleavage_motif_1_letter("protein-1-letter", 1);
TestUtils test_utils_cleavage_motif_3_letters("protein-3-letters", 1);

ErrorList error_list_cleavage_motif;

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

  WHEN("Constructing a CleavageMotif with no valid parameter")
  {
    CleavageMotif cleavage_motif(nullptr);

    THEN("The CleavageMotif is invalid and does not validate successfully")
    {
      REQUIRE_FALSE(cleavage_motif.isValid());
      REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
    }

    AND_WHEN("Setting the PolChemDef")
    {
      cleavage_motif.setPolChemDefCstSPtr(pol_chem_def_csp);

      THEN("The CleavageMotif is still invalid")
      {
        REQUIRE_FALSE(cleavage_motif.isValid());
        REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
      }

      AND_WHEN("Setting the Motif")
      {
        cleavage_motif.setMotif("KP");

        THEN(
          "The CleavageMotif is still invalid although with proper monomer "
          "container")
        {
          REQUIRE_FALSE(cleavage_motif.isValid());
          REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
          REQUIRE(cleavage_motif.getMonomersCstRef().size() == 2);
          REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() == "K");
          REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() == "P");
        }

        AND_WHEN("Setting the offset")
        {
          cleavage_motif.setOffset(1);

          THEN(
            "The CleavageMotif is still invalid without proper "
            "Enums::CleavageAction "
            "set")
          {
            REQUIRE_FALSE(cleavage_motif.isValid());
            REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
            REQUIRE(cleavage_motif.getMonomersCstRef().size() == 2);
            REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() ==
                    "K");
            REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() ==
                    "P");
            REQUIRE(cleavage_motif.getOffset() == 1);
          }

          AND_WHEN("Setting the Enums::CleavageAction")
          {
            cleavage_motif.setCleavageAction(Enums::CleavageAction::NO_CLEAVE);

            THEN("The CleavageMotif is valid with proper partial member data")
            {
              REQUIRE(cleavage_motif.isValid());
              REQUIRE(cleavage_motif.validate(&error_list_cleavage_motif));
              REQUIRE(cleavage_motif.getMonomersCstRef().size() == 2);
              REQUIRE(cleavage_motif.getOffset() == 1);
            }
          }
        }
      }
    }
  }
}

SCENARIO(
  "CleavageMotif objects can be constructed with parameters to fully "
  "initialize "
  "them in one go",
  "[CleavageMotif]")
{
  test_utils_cleavage_motif_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_cleavage_motif_1_letter.msp_polChemDef;

  WHEN("Constructing a CleavageMotif with all valid parameters")
  {
    CleavageMotif cleavage_motif(
      pol_chem_def_csp, "KP", 1, Enums::CleavageAction::NO_CLEAVE);

    THEN("The CleavageMotif must be valid and validates successfully")
    {

      REQUIRE(cleavage_motif.isValid());
      error_list_cleavage_motif.clear();
      REQUIRE(cleavage_motif.validate(&error_list_cleavage_motif));
      REQUIRE(error_list_cleavage_motif.size() == 0);
      REQUIRE(cleavage_motif.getMonomersCstRef().size() == 2);
      REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() == "K");
      REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() == "P");
      REQUIRE(cleavage_motif.getOffset() == 1);
      REQUIRE(cleavage_motif.getCleavageAction() ==
              Enums::CleavageAction::NO_CLEAVE);
    }
  }
}

SCENARIO("CleavageMotif objects can be initialized with a cleavage site",
         "[CleavageMotif]")
{
  test_utils_cleavage_motif_1_letter.initializeXpertmassLibrary();
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_cleavage_motif_1_letter.msp_polChemDef;

  WHEN("Constructing a CleavageMotif with only PolChemDef parameter")
  {
    CleavageMotif cleavage_motif(pol_chem_def_csp);

    AND_WHEN("Setting a cleavage site")
    {
      REQUIRE(cleavage_motif.parseSite("K/P") == 2);

      THEN(
        "The CleavageMotif is not valid because the cleavage operation is not "
        "defined")
      {
        REQUIRE_FALSE(cleavage_motif.isValid());
        REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
        REQUIRE(cleavage_motif.getMonomersCstRef().size() == 2);
        REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() == "K");
        REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() == "P");
        REQUIRE(cleavage_motif.getOffset() == 1);
        REQUIRE(cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::NOT_SET);
      }

      AND_WHEN("The is-for-cleavage bit is set")
      {
        cleavage_motif.setCleavageAction(Enums::CleavageAction::NO_CLEAVE);

        THEN(
          "The CleavageMotif is fully initialized,  valid and validates "
          "successfully")
        {
          REQUIRE(cleavage_motif.isValid());
          REQUIRE(cleavage_motif.validate(&error_list_cleavage_motif));
        }
      }
    }
  }
}

SCENARIO("CleavageMotif objects are checked for validity", "[CleavageMotif]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_cleavage_motif_3_letters.msp_polChemDef;

  WHEN("Constructing a CleavageMotif with only PolChemDef parameter")
  {
    CleavageMotif cleavage_motif(pol_chem_def_csp);

    AND_WHEN("Setting a cleavage site with 3-letters code")
    {
      REQUIRE(cleavage_motif.parseSite("Lys/Pro") == 2);

      THEN(
        "The CleavageMotif is not valid because the cleavage operation is not "
        "defined")
      {
        REQUIRE_FALSE(cleavage_motif.isValid());
        REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
        REQUIRE(cleavage_motif.getMonomersCstRef().size() == 2);
        REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() == "Lys");
        REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() == "Pro");
        REQUIRE(cleavage_motif.getOffset() == 1);
        REQUIRE(cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::NOT_SET);
      }

      AND_WHEN("The is-for-cleavage bit is set")
      {
        cleavage_motif.setCleavageAction(Enums::CleavageAction::NO_CLEAVE);

        THEN(
          "The CleavageMotif is fully initialized,  valid and validates "
          "successfully")
        {
          REQUIRE(cleavage_motif.isValid());
          REQUIRE(cleavage_motif.validate(&error_list_cleavage_motif));
        }
      }
    }

    AND_WHEN("Setting a cleavage site with 3-letters code")
    {
      REQUIRE(cleavage_motif.parseSite("LysArgMet/") == 3);

      THEN(
        "The CleavageMotif is not valid because the cleavage operation is not "
        "defined")
      {
        REQUIRE_FALSE(cleavage_motif.isValid());
        REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
        REQUIRE(cleavage_motif.getMonomersCstRef().size() == 3);
        REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() == "Lys");
        REQUIRE(cleavage_motif.getMonomersCstRef().at(1)->getCode() == "Arg");
        REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() == "Met");
        REQUIRE(cleavage_motif.getOffset() == 3);
        REQUIRE(cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::NOT_SET);
      }

      AND_WHEN("The is-for-cleavage bit is set")
      {
        cleavage_motif.setCleavageAction(Enums::CleavageAction::CLEAVE);
        REQUIRE(cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::CLEAVE);
        THEN(
          "The CleavageMotif is fully initialized,  valid and validates "
          "successfully")
        {
          REQUIRE(cleavage_motif.isValid());
          REQUIRE(cleavage_motif.validate(&error_list_cleavage_motif));
        }
      }
    }

    AND_WHEN("Setting a cleavage site with 3-letters code")
    {
      REQUIRE(cleavage_motif.parseSite("/AspGluTrpLysArgMet") == 6);

      THEN(
        "The CleavageMotif is not valid because the cleavage operation is not "
        "defined")
      {
        REQUIRE_FALSE(cleavage_motif.isValid());
        REQUIRE_FALSE(cleavage_motif.validate(&error_list_cleavage_motif));
        REQUIRE(cleavage_motif.getMonomersCstRef().size() == 6);
        REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() == "Asp");
        REQUIRE(cleavage_motif.getMonomersCstRef().at(1)->getCode() == "Glu");
        REQUIRE(cleavage_motif.getMonomersCstRef().at(2)->getCode() == "Trp");
        REQUIRE(cleavage_motif.getMonomersCstRef().at(3)->getCode() == "Lys");
        REQUIRE(cleavage_motif.getMonomersCstRef().at(4)->getCode() == "Arg");
        REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() == "Met");
        REQUIRE(cleavage_motif.getOffset() == 0);
        REQUIRE(cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::NOT_SET);
      }

      AND_WHEN("The is-for-cleavage bit is set")
      {
        cleavage_motif.setCleavageAction(Enums::CleavageAction::CLEAVE);
        REQUIRE(cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::CLEAVE);
        THEN(
          "The CleavageMotif is fully initialized,  valid and validates "
          "successfully")
        {
          REQUIRE(cleavage_motif.isValid());
          REQUIRE(cleavage_motif.validate(&error_list_cleavage_motif));
        }
      }
    }
  }
}

SCENARIO(
  "CleavageMotif objects can be copied by construction or assignment and then "
  "compared",
  "[CleavageMotif]")
{
  PolChemDefCstSPtr pol_chem_def_csp =
    test_utils_cleavage_motif_3_letters.msp_polChemDef;

  WHEN("Constructing a fully initialized CleavageMotif")
  {
    CleavageMotif cleavage_motif(
      pol_chem_def_csp, "/AspGluTrpLysArgMet", 0, Enums::CleavageAction::CLEAVE);

    THEN("The CleavageMotif is valid and fully initialized")
    {
      REQUIRE(cleavage_motif.isValid());
      REQUIRE(cleavage_motif.validate(&error_list_cleavage_motif));
      REQUIRE(cleavage_motif.getMonomersCstRef().size() == 6);
      REQUIRE(cleavage_motif.getMonomersCstRef().front()->getCode() == "Asp");
      REQUIRE(cleavage_motif.getMonomersCstRef().at(1)->getCode() == "Glu");
      REQUIRE(cleavage_motif.getMonomersCstRef().at(2)->getCode() == "Trp");
      REQUIRE(cleavage_motif.getMonomersCstRef().at(3)->getCode() == "Lys");
      REQUIRE(cleavage_motif.getMonomersCstRef().at(4)->getCode() == "Arg");
      REQUIRE(cleavage_motif.getMonomersCstRef().back()->getCode() == "Met");
      REQUIRE(cleavage_motif.getOffset() == 0);
      REQUIRE(cleavage_motif.getCleavageAction() ==
              Enums::CleavageAction::CLEAVE);
    }

    AND_WHEN("A new CleavageMotif is allocated by copy-construction")
    {
      CleavageMotif new_cleavage_motif(cleavage_motif);

      THEN("The CleavageMotif is valid and fully initialized")
      {
        REQUIRE(new_cleavage_motif.isValid());
        REQUIRE(new_cleavage_motif.validate(&error_list_cleavage_motif));
        REQUIRE(new_cleavage_motif.getMonomersCstRef().size() == 6);
        REQUIRE(new_cleavage_motif.getMonomersCstRef().front()->getCode() ==
                "Asp");
        REQUIRE(new_cleavage_motif.getMonomersCstRef().at(1)->getCode() ==
                "Glu");
        REQUIRE(new_cleavage_motif.getMonomersCstRef().at(2)->getCode() ==
                "Trp");
        REQUIRE(new_cleavage_motif.getMonomersCstRef().at(3)->getCode() ==
                "Lys");
        REQUIRE(new_cleavage_motif.getMonomersCstRef().at(4)->getCode() ==
                "Arg");
        REQUIRE(new_cleavage_motif.getMonomersCstRef().back()->getCode() ==
                "Met");
        REQUIRE(new_cleavage_motif.getOffset() == 0);
        REQUIRE(new_cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::CLEAVE);

        AND_THEN("The comparisons yield expected results")
        {
          REQUIRE(new_cleavage_motif == cleavage_motif);
          REQUIRE_FALSE(new_cleavage_motif != cleavage_motif);
        }
      }
    }

    AND_WHEN("A new CleavageMotif is allocated by assignment")
    {
      CleavageMotif new_new_cleavage_motif(nullptr);
      new_new_cleavage_motif = cleavage_motif;

      THEN("The CleavageMotif is valid and fully initialized")
      {
        REQUIRE(new_new_cleavage_motif.isValid());
        REQUIRE(new_new_cleavage_motif.validate(&error_list_cleavage_motif));
        REQUIRE(new_new_cleavage_motif.getMonomersCstRef().size() == 6);
        REQUIRE(new_new_cleavage_motif.getMonomersCstRef().front()->getCode() ==
                "Asp");
        REQUIRE(new_new_cleavage_motif.getMonomersCstRef().at(1)->getCode() ==
                "Glu");
        REQUIRE(new_new_cleavage_motif.getMonomersCstRef().at(2)->getCode() ==
                "Trp");
        REQUIRE(new_new_cleavage_motif.getMonomersCstRef().at(3)->getCode() ==
                "Lys");
        REQUIRE(new_new_cleavage_motif.getMonomersCstRef().at(4)->getCode() ==
                "Arg");
        REQUIRE(new_new_cleavage_motif.getMonomersCstRef().back()->getCode() ==
                "Met");
        REQUIRE(new_new_cleavage_motif.getOffset() == 0);
        REQUIRE(new_new_cleavage_motif.getCleavageAction() ==
                Enums::CleavageAction::CLEAVE);

        AND_THEN("The comparisons yield expected results")
        {
          REQUIRE(new_new_cleavage_motif == cleavage_motif);
          REQUIRE_FALSE(new_new_cleavage_motif != cleavage_motif);
        }
      }
    }
  }
}

} // namespace libXpertMassCore
} // namespace MsXpS
