#include "CppUnitTest.h"
#include "helper.h"
#include "../Hazelnupp/CmdArgsInterface.h"
#include "../Hazelnupp/HazelnuppException.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace Hazelnp;

namespace TestHazelnupp
{
	TEST_CLASS(_Constraints)
	{
	public:

		// Tests that default values get added
		TEST_METHOD(DefaultValues_GetAdded)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"123"
				});

			// Exercise
			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint("--elenor-int", ParamConstraint::Require({ "5994" }));
			cmdArgsI.RegisterConstraint("--federich-float", ParamConstraint::Require({ "420.69" }));
			cmdArgsI.RegisterConstraint("--siegbert-string", ParamConstraint::Require({ "banana" }));
			cmdArgsI.RegisterConstraint("--lieber-liste", ParamConstraint::Require({ "banana", "apple", "59" }));

			cmdArgsI.Parse(C_Ify(args));

			// Verify
			Assert::IsTrue(cmdArgsI.HasParam("--elenor-int"));
			Assert::IsTrue(cmdArgsI["--elenor-int"].GetDataType() == DATA_TYPE::INT);
			Assert::AreEqual(cmdArgsI["--elenor-int"].GetInt32(), 5994);

			Assert::IsTrue(cmdArgsI.HasParam("--federich-float"));
			Assert::IsTrue(cmdArgsI["--federich-float"].GetDataType() == DATA_TYPE::FLOAT);
			Assert::AreEqual(cmdArgsI["--federich-float"].GetFloat32(), 420.69);

			Assert::IsTrue(cmdArgsI.HasParam("--siegbert-string"));
			Assert::IsTrue(cmdArgsI["--siegbert-string"].GetDataType() == DATA_TYPE::STRING);
			Assert::AreEqual(cmdArgsI["--siegbert-string"].GetString(), std::string("banana"));

			Assert::IsTrue(cmdArgsI.HasParam("--lieber-liste"));
			Assert::IsTrue(cmdArgsI["--lieber-liste"].GetDataType() == DATA_TYPE::LIST);
			Assert::AreEqual(cmdArgsI["--lieber-liste"].GetList()[0]->GetString(), std::string("banana"));
			Assert::AreEqual(cmdArgsI["--lieber-liste"].GetList()[1]->GetString(), std::string("apple"));
			Assert::AreEqual(cmdArgsI["--lieber-liste"].GetList()[2]->GetInt32(), 59);

			return;
		}

		// Tests that the default values do not override actually set values
		TEST_METHOD(DefaultValues_DefaultDoesntOverride)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--elenor-int",
				"5994",
				"--federich-float",
				"420.69",
				"--siegbert-string",
				"banana",
				"--lieber-liste",
				"banana",
				"apple",
				"59"
				});

			// Exercise
			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint("--elenor-int", ParamConstraint::Require({ "6871" }));
			cmdArgsI.RegisterConstraint("--federich-float", ParamConstraint::Require({ "-199.44" }));
			cmdArgsI.RegisterConstraint("--siegbert-string", ParamConstraint::Require({ "bornana" }));
			cmdArgsI.RegisterConstraint("--lieber-liste", ParamConstraint::Require({ "bornana", "ollpe", "5" }));

			cmdArgsI.Parse(C_Ify(args));

			// Verify
			Assert::IsTrue(cmdArgsI.HasParam("--elenor-int"));
			Assert::IsTrue(cmdArgsI["--elenor-int"].GetDataType() == DATA_TYPE::INT);
			Assert::AreEqual(cmdArgsI["--elenor-int"].GetInt32(), 5994);

			Assert::IsTrue(cmdArgsI.HasParam("--federich-float"));
			Assert::IsTrue(cmdArgsI["--federich-float"].GetDataType() == DATA_TYPE::FLOAT);
			Assert::AreEqual(cmdArgsI["--federich-float"].GetFloat32(), 420.69);

			Assert::IsTrue(cmdArgsI.HasParam("--siegbert-string"));
			Assert::IsTrue(cmdArgsI["--siegbert-string"].GetDataType() == DATA_TYPE::STRING);
			Assert::AreEqual(cmdArgsI["--siegbert-string"].GetString(), std::string("banana"));

			Assert::IsTrue(cmdArgsI.HasParam("--lieber-liste"));
			Assert::IsTrue(cmdArgsI["--lieber-liste"].GetDataType() == DATA_TYPE::LIST);
			Assert::AreEqual(cmdArgsI["--lieber-liste"].GetList()[0]->GetString(), std::string("banana"));
			Assert::AreEqual(cmdArgsI["--lieber-liste"].GetList()[1]->GetString(), std::string("apple"));
			Assert::AreEqual(cmdArgsI["--lieber-liste"].GetList()[2]->GetInt32(), 59);

			return;
		}

		// Tests that data types get forced according to the constraints
		TEST_METHOD(ForceTypes)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--num-apples",
				"39.75",
				"--table-height",
				"400",
				"--license-plate",
				"193273",
				"--fav-fruits",
				"apple",
				"--indices",
				"9",
				"--force",
				"plsdontuseme"
				});

			// Exercise
			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint("--num-apples", ParamConstraint::TypeSafety(DATA_TYPE::INT));
			cmdArgsI.RegisterConstraint("--table-height", ParamConstraint::TypeSafety(DATA_TYPE::FLOAT));
			cmdArgsI.RegisterConstraint("--license-plate", ParamConstraint::TypeSafety(DATA_TYPE::STRING));
			cmdArgsI.RegisterConstraint("--fav-fruits", ParamConstraint::TypeSafety(DATA_TYPE::LIST));
			cmdArgsI.RegisterConstraint("--indices", ParamConstraint::TypeSafety(DATA_TYPE::LIST));
			cmdArgsI.RegisterConstraint("--force", ParamConstraint::TypeSafety(DATA_TYPE::VOID));

			cmdArgsI.Parse(C_Ify(args));

			// Verify
			Assert::IsTrue(cmdArgsI.HasParam("--num-apples"));
			Assert::IsTrue(cmdArgsI["--num-apples"].GetDataType() == DATA_TYPE::INT);
			Assert::AreEqual(cmdArgsI["--num-apples"].GetInt32(), 39);

			Assert::IsTrue(cmdArgsI.HasParam("--table-height"));
			Assert::IsTrue(cmdArgsI["--table-height"].GetDataType() == DATA_TYPE::FLOAT);
			Assert::AreEqual(cmdArgsI["--table-height"].GetFloat32(), 400.0);

			Assert::IsTrue(cmdArgsI.HasParam("--license-plate"));
			Assert::IsTrue(cmdArgsI["--license-plate"].GetDataType() == DATA_TYPE::STRING);
			Assert::AreEqual(cmdArgsI["--license-plate"].GetString(), std::string("193273"));

			Assert::IsTrue(cmdArgsI.HasParam("--fav-fruits"));
			Assert::IsTrue(cmdArgsI["--fav-fruits"].GetDataType() == DATA_TYPE::LIST);
			Assert::AreEqual(cmdArgsI["--fav-fruits"].GetList()[0]->GetString(), std::string("apple"));

			Assert::IsTrue(cmdArgsI.HasParam("--indices"));
			Assert::IsTrue(cmdArgsI["--indices"].GetDataType() == DATA_TYPE::LIST);
			Assert::AreEqual(cmdArgsI["--indices"].GetList()[0]->GetInt32(), 9);

			Assert::IsTrue(cmdArgsI.HasParam("--force"));
			Assert::IsTrue(cmdArgsI["--force"].GetDataType() == DATA_TYPE::VOID);

			return;
		}

		// Tests that an HazelnuppConstraintMissingValue gets raised if a required parameter
		// is missing and does not have a default parameter
		TEST_METHOD(Exception_MissingImportant_Parameter_WithoutDefault)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--federich-float",
				"420.69",
				"--siegbert-string",
				"banana",
				"--lieber-liste",
				"banana",
				"apple",
				"59"
				});

			Assert::ExpectException<HazelnuppConstraintMissingValue>(
				[args]
				{
					CmdArgsInterface cmdArgsI;
					cmdArgsI.SetCrashOnFail(false);

					cmdArgsI.RegisterConstraint(
						"--elenor-int",
						ParamConstraint::Require()
					);

					cmdArgsI.Parse(C_Ify(args));
				}
				);

			return;
		}

		// Tests that an HazelnuppConstraintTypeMissmatch gets raised if a required parameter
		// is missing of the wrong type and cannot be converted
		TEST_METHOD(Exception_TypeMismatch_Parameter_NotConvertable)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--elenor-int",
				"hello"
				"--federich-float",
				"420.69",
				"--siegbert-string",
				"banana",
				"--lieber-liste",
				"banana",
				"apple",
				"59"
				});

			Assert::ExpectException<HazelnuppConstraintTypeMissmatch>(
				[args]
				{
					CmdArgsInterface cmdArgsI;
					cmdArgsI.SetCrashOnFail(false);

					cmdArgsI.RegisterConstraint(
						"--elenor-int",
						ParamConstraint::TypeSafety(DATA_TYPE::INT)
					);

					cmdArgsI.Parse(C_Ify(args));
				}
				);

			return;
		}

		// Tests that everything can be converted to void
		TEST_METHOD(Weird_Load_Conversions_ToVoid)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--void1",
				"--void2",
				"12",
				"--void3",
				"9.5",
				"--void4",
				"hello",
				"--void5",
				"foo",
				"baz"
				});

			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint("--void1", ParamConstraint::TypeSafety(DATA_TYPE::VOID));
			cmdArgsI.RegisterConstraint("--void2", ParamConstraint::TypeSafety(DATA_TYPE::VOID));
			cmdArgsI.RegisterConstraint("--void3", ParamConstraint::TypeSafety(DATA_TYPE::VOID));
			cmdArgsI.RegisterConstraint("--void4", ParamConstraint::TypeSafety(DATA_TYPE::VOID));
			cmdArgsI.RegisterConstraint("--void5", ParamConstraint::TypeSafety(DATA_TYPE::VOID));


			// Exercise
			cmdArgsI.Parse(C_Ify(args));

			// Verify
			Assert::IsTrue(cmdArgsI["--void1"].GetDataType() == DATA_TYPE::VOID);
			Assert::IsTrue(cmdArgsI["--void2"].GetDataType() == DATA_TYPE::VOID);
			Assert::IsTrue(cmdArgsI["--void3"].GetDataType() == DATA_TYPE::VOID);
			Assert::IsTrue(cmdArgsI["--void4"].GetDataType() == DATA_TYPE::VOID);
			Assert::IsTrue(cmdArgsI["--void5"].GetDataType() == DATA_TYPE::VOID);

			return;
		}

		// Tests that a void can be converted to an empty list
		TEST_METHOD(Weird_Load_Conversions_VoidToEmptyList)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--empty-list",
				});

			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint(
				"--empty-list",
				ParamConstraint::TypeSafety(DATA_TYPE::LIST)
			);


			// Exercise
			cmdArgsI.Parse(C_Ify(args));

			// Verify
			Assert::IsTrue(cmdArgsI["--empty-list"].GetDataType() == DATA_TYPE::LIST, L"Wrong datatype");
			Assert::AreEqual(std::size_t(0), cmdArgsI["--empty-list"].GetList().size(), L"Wrong size");

			return;
		}

		// Tests that a void can be converted to an empty string
		TEST_METHOD(Weird_Load_Conversions_VoidToEmptyString)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--empty-string",
				});

			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint(
				"--empty-string",
				ParamConstraint::TypeSafety(DATA_TYPE::STRING)
			);


			// Exercise
			cmdArgsI.Parse(C_Ify(args));

			// Verify
			Assert::IsTrue(cmdArgsI["--empty-string"].GetDataType() == DATA_TYPE::STRING, L"Wrong datatype");
			Assert::AreEqual(std::size_t(0), cmdArgsI["--empty-string"].GetString().length(), L"Wrong size");

			return;
		}

		// Tests that parsing a void as int fails
		TEST_METHOD(Weird_Load_Conversions_VoidFailsAsInt)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--thisisvoid",
				});

			// Test section: INT
			Assert::ExpectException<HazelnuppConstraintTypeMissmatch>([args]
				{
					CmdArgsInterface cmdArgsI;
					cmdArgsI.SetCrashOnFail(false);

					cmdArgsI.RegisterConstraint(
						"--thisisvoid",
						ParamConstraint::TypeSafety(DATA_TYPE::INT)
					);

					// Exercise
					cmdArgsI.Parse(C_Ify(args));

				}, L"Failed with int");

			return;
		}

		// Tests that parsing a void as float fails
		TEST_METHOD(Weird_Load_Conversions_VoidFailsAsFloat)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--thisisvoid",
				});

			// Test section: FLOAT
			Assert::ExpectException<HazelnuppConstraintTypeMissmatch>([args]
				{
					CmdArgsInterface cmdArgsI;
					cmdArgsI.SetCrashOnFail(false);

					cmdArgsI.RegisterConstraint(
						"--thisisvoid",
						ParamConstraint::TypeSafety(DATA_TYPE::FLOAT)
					);

					// Exercise
					cmdArgsI.Parse(C_Ify(args));

				}, L"Failed with float");

			return;
		}

		// Tests that constraints can be cleared invidivually
		TEST_METHOD(Can_Clear_Constraints_Individually)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--empty-list",
				});

			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint(
				"--default-val",
				ParamConstraint::Require({ "32" }, true)
			);

			cmdArgsI.RegisterConstraint(
				"--not-there",
				ParamConstraint::Require({}, true)
			);

			// Exercise
			cmdArgsI.ClearConstraint("--not-there");


			// Verify
			cmdArgsI.Parse(C_Ify(args));

			// Also verifies that parse does not throw an exception for --not-there

			Assert::IsTrue(cmdArgsI.HasParam("--default-val"), L"Default value is missing");
			Assert::AreEqual(32, cmdArgsI["--default-val"].GetInt32(), L"Default value has wrong value");

			return;
		}

		// Tests that constraints can be cleared invidivually
		TEST_METHOD(Can_Clear_All_Constraints)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--empty-list",
				});

			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint(
				"--not-there",
				ParamConstraint::Require({}, true)
			);

			// Exercise
			cmdArgsI.ClearConstraints();

			// Verify
			cmdArgsI.Parse(C_Ify(args));

			return;
		}

		// Tests that setting a constraint for a parameter again will overwrite the existing one
		TEST_METHOD(Can_Override_Constraints)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--empty-list",
				});

			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			cmdArgsI.RegisterConstraint(
				"--not-there",
				ParamConstraint::Require({}, true)
			);

			//Exercise
			cmdArgsI.RegisterConstraint(
				"--not-there",
				ParamConstraint::Require({}, false)
			);

			// Verify
			cmdArgsI.Parse(C_Ify(args));

			return;
		}

		// Tests that the GetConstraint method returns the correct constraint information
		TEST_METHOD(Get_Constraint)
		{
			// Setup
			ArgList args({
				"/my/fake/path/wahoo.out",
				"--dummy",
				"--empty-list",
				});

			CmdArgsInterface cmdArgsI;
			cmdArgsI.SetCrashOnFail(false);

			ParamConstraint dftvalConst_expected =
				ParamConstraint::Require({ "32" }, true);

			cmdArgsI.RegisterConstraint("--default-val", dftvalConst_expected);
			cmdArgsI.RegisterConstraint("--not-there", ParamConstraint::Require({}, true));
			cmdArgsI.RegisterConstraint("--another-one", ParamConstraint::Require({ "bites" }, true));

			// Exercise
			ParamConstraint dftvalConst = cmdArgsI.GetConstraint("--default-val");

			// Verify
			Assert::IsTrue(dftvalConst_expected.required == dftvalConst.required, L"required");
			Assert::IsTrue(dftvalConst_expected.defaultValue == dftvalConst.defaultValue, L"defaultValue");
			Assert::IsTrue(dftvalConst_expected.requiredType == dftvalConst.requiredType, L"requiredType");
			Assert::IsTrue(dftvalConst_expected.constrainType == dftvalConst.constrainType, L"constrainType");

			return;
		}
	};
}
