#include <catch2/catch.hpp>

#include <Models/InputModelBox.hpp>
#include <Models/OutputModelBox.hpp>
#include <Models/AlgorithmModelBox.hpp>
#include <Algorithm/Registry.hpp>
#include <Utils.hpp>

TEST_CASE ( "ModelBox", "[unit][gui]" ) {
	Registry::initialize();

	auto input1 = std::make_unique<InputModelBox>();
	auto input2 = std::make_unique<InputModelBox>();
	auto output1 = std::make_unique<OutputModelBox>();
	auto output2 = std::make_unique<OutputModelBox>();
	auto alg = Registry::getAlgorithm("automaton::determinize::Determinize");
	auto algo = std::make_unique<AlgorithmModelBox>(alg);

	SECTION ( "Basic Properties" ) {
		CHECK(input1->getMaxInputCount() == 0);
		CHECK(input1->canHaveOutput());

		CHECK(output1->getMaxInputCount() == 1);
		CHECK(output1->canHaveOutput() == false);

		CHECK(algo->getMaxInputCount() == 1);
		CHECK(algo->canHaveOutput());

		CHECK(input1->getType() == ModelType::Input);
		CHECK(input1->getName() == "INPUT");

		CHECK(output1->getType() == ModelType::Output);
		CHECK(output1->getName() == "OUTPUT");

		CHECK(algo->getType() == ModelType::Algorithm);
		CHECK(algo->getName() == "Determinize");

		CHECK(algo->getAlgorithm() == Registry::getAlgorithm("automaton::determinize::Determinize"));
	}

	SECTION ( "Connect Disconnect Basic" ) {
		REQUIRE(output1->evaluate() == nullptr);

		input1->setAutomaton(Utils::generateRandomAutomaton());

		REQUIRE(output1->evaluate() == nullptr);

		ModelBox::connect(input1.get(), output1.get(), 0);

		CHECK(output1->evaluate() == input1->getAutomaton());

		ModelBox::disconnect(input1.get(), output1.get(), 0);

		REQUIRE(output1->evaluate() == nullptr);
	}

	SECTION ( "Connect Disconnect Multiple Outputs" ) {
		REQUIRE(output1->evaluate() == nullptr);
		REQUIRE(output2->evaluate() == nullptr);

		input1->setAutomaton(Utils::generateRandomAutomaton());

		CHECK(output1->evaluate() == nullptr);
		CHECK(output2->evaluate() == nullptr);

		ModelBox::connect(input1.get(), output1.get(), 0);

		CHECK(output1->evaluate() == input1->getAutomaton());
		CHECK(output2->evaluate() == nullptr);

		ModelBox::connect(input1.get(), output2.get(), 0);

		CHECK(output1->evaluate() == input1->getAutomaton());
		CHECK(output2->evaluate() == input1->getAutomaton());

		ModelBox::disconnect(input1.get(), output1.get(), 0);

		CHECK(output1->evaluate() == nullptr);
		CHECK(output2->evaluate() == input1->getAutomaton());
	}

	SECTION ( "Connect Disconnect Multiple Inputs" ) {
		REQUIRE(output1->evaluate() == nullptr);

		ModelBox::connect(input1.get(), output1.get(), 0);
		CHECK_THROWS_AS(ModelBox::connect(input2.get(), output1.get(), 0), std::runtime_error);
	}

	SECTION ( "Connect Disconnect Multiple Existing" ) {
		ModelBox::connect(input1.get(), output1.get(), 0);
		ModelBox::disconnect(input1.get(), output1.get(), 0);
		CHECK_NOTHROW(ModelBox::connect(input2.get(), output1.get(), 0));
	}

	SECTION ( "Connect Disconnect Nonexistent Slot" ) {
		CHECK_THROWS_AS(ModelBox::connect(input1.get(), output1.get(), 1), std::runtime_error);
	}

	SECTION ( "Connect Disconnect Nonexistent Connection" ) {
		CHECK_THROWS_AS(ModelBox::disconnect(input1.get(), output1.get(), 0), std::runtime_error);
	}

	Registry::deinitialize();
}
