#include <uunit.h>

#include <ctor.h>
#include <tasks.h>

namespace
{
ctor::build_configurations ctorTestConfigs1(const ctor::settings&)
{
	return
	{
		{
			.name = "Target1",
			.target = "target1",
			.sources = {"foo.cc", "bar.c"},
		},
		{
			.target = "target2",
		},
	};
}

ctor::build_configurations ctorTestConfigs2(const ctor::settings&)
{
	return
	{
		{
			.target = "target3",
		},
		{
			.target = "target4",
		},
	};
}
}

namespace test_global {
ctor::toolchain toolchain{};
ctor::arch arch{};
}
const ctor::configuration& ctor::get_configuration()
{
	static ctor::configuration cfg{};
	cfg.build_toolchain = test_global::toolchain;
	cfg.build_arch = test_global::arch;
	return cfg;
}


REG(ctorTestConfigs1);
REG(ctorTestConfigs2);

std::size_t count(const std::vector<std::shared_ptr<Task>>& tasks,
                  const std::filesystem::path& name)
{
	auto cnt{0u};
	for(const auto& task : tasks)
	{
		if(task->target() == name.string())
		{
			cnt++;
		}
	}
	return cnt;
}

class TestTask
	: public Task
{
public:
	TestTask(const ctor::build_configuration& config,
	         const ctor::settings& settings,
	         const std::string& name, bool dirty,
	         const std::vector<std::string>& deps = {})
		: Task(config, settings, {})
		, task_name(name)
		, task_dirty(dirty)
		, task_deps(deps)
	{
	}

	int clean() override { return 0; }
	std::vector<std::string> depends() const override { return task_deps; }
	std::string target() const override { return task_name; }
	std::filesystem::path targetFile() const override { return {}; }
	bool derived() const override { return false; }
	bool dirtyInner() override { return task_dirty; }

private:
	std::string task_name;
	bool task_dirty;
	std::vector<std::string> task_deps;
};

class TasksTest
	: public uUnit
{
public:
	using fs = std::filesystem::path;

	TasksTest()
	{
		uTEST(TasksTest::getTargets_test);
		uTEST(TasksTest::getTasks_test);
		uTEST(TasksTest::getNextTask_test);
		uTEST(TasksTest::comparison_test);
	}

	void getTargets_test()
	{
		using namespace std::string_literals;
		ctor::settings settings{};
		const auto& targets = getTargets(settings);
		uASSERT_EQUAL(4u, targets.size());

		uASSERT_EQUAL("target1"s, targets[0].config.target);
		uASSERT_EQUAL("target2"s, targets[1].config.target);
		uASSERT_EQUAL("target3"s, targets[2].config.target);
		uASSERT_EQUAL("target4"s, targets[3].config.target);

		uASSERT_EQUAL("test"s, targets[0].path);
		uASSERT_EQUAL("test"s, targets[1].path);
		uASSERT_EQUAL("test"s, targets[2].path);
		uASSERT_EQUAL("test"s, targets[3].path);
	}

	void getTasks_test()
	{
		test_global::toolchain = ctor::toolchain::gcc;
		test_global::arch = ctor::arch::unix;

		using namespace std::string_literals;
		ctor::settings settings{ .builddir = "foo" };
		{
			auto tasks = getTasks(settings);
			uASSERT_EQUAL(6u, tasks.size());
			// Note: count() is used here because the order doesn't matter
			uASSERT_EQUAL(1u, count(tasks, fs("target1")));
			uASSERT_EQUAL(1u, count(tasks, fs("target2")));
			uASSERT_EQUAL(1u, count(tasks, fs("target3")));
			uASSERT_EQUAL(1u, count(tasks, fs("target4")));
			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o"));
			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));
		}
		{
			auto tasks = getTasks(settings, {"target1", "target3"});
			uASSERT_EQUAL(4u, tasks.size());
			uASSERT_EQUAL(1u, count(tasks, fs("target1")));
			uASSERT_EQUAL(1u, count(tasks, fs("target3")));
			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o"));
			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));
		}
		{
			auto tasks = getTasks(settings, {"no-such-target"});
			uASSERT_EQUAL(0u, tasks.size());
		}
	}

	void getNextTask_test()
	{
		using namespace std::string_literals;
		ctor::settings settings{};

		{ // Zero (Empty)
			std::vector<std::shared_ptr<Task>> allTasks;
			std::vector<std::shared_ptr<Task>> dirtyTasks;

			for(auto& task : dirtyTasks)
			{
				uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
			}

			uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks));
		}

		{ // Zero (One task, no dirty)
			ctor::build_configuration config;
			auto task1 = std::make_shared<TestTask>(config, settings, "task1", false);

			std::vector<std::shared_ptr<Task>> allTasks;
			allTasks.push_back(task1);

			std::vector<std::shared_ptr<Task>> dirtyTasks;

			for(auto& task : dirtyTasks)
			{
				uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
			}

			uASSERT_EQUAL(nullptr, getNextTask({}, allTasks, dirtyTasks));
		}

		{ // One (One task, one dirty)
			ctor::build_configuration config;
			auto task1 = std::make_shared<TestTask>(config, settings, "task1", true);

			std::vector<std::shared_ptr<Task>> allTasks;
			allTasks.push_back(task1);

			std::vector<std::shared_ptr<Task>> dirtyTasks;
			dirtyTasks.push_back(task1);

			for(auto& task : dirtyTasks)
			{
				uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
			}

			uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks));
			uASSERT_EQUAL(0u, dirtyTasks.size());
		}

		{ // One (Two tasks, one dirty)
			ctor::build_configuration config;
			auto task1 = std::make_shared<TestTask>(config, settings, "task1", false);
			auto task2 = std::make_shared<TestTask>(config, settings, "task2", true);

			std::vector<std::shared_ptr<Task>> allTasks;
			allTasks.push_back(task1);
			allTasks.push_back(task2);

			std::vector<std::shared_ptr<Task>> dirtyTasks;
			dirtyTasks.push_back(task2);

			for(auto& task : dirtyTasks)
			{
				uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
			}

			uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks));
			uASSERT_EQUAL(0u, dirtyTasks.size());
		}

		{ // One (Two tasks, one dirty which depends on the other)
			ctor::build_configuration config;
			auto task1 = std::make_shared<TestTask>(config, settings, "task1", false);

			std::vector<std::string> deps = {"task1"};
			auto task2 =
				std::make_shared<TestTask>(config, settings, "task2", true, deps);

			std::vector<std::shared_ptr<Task>> allTasks;
			allTasks.push_back(task1);
			allTasks.push_back(task2);

			std::vector<std::shared_ptr<Task>> dirtyTasks;
			dirtyTasks.push_back(task2);

			for(auto& task : dirtyTasks)
			{
				uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
			}

			uASSERT_EQUAL(task2, getNextTask({}, allTasks, dirtyTasks));
			uASSERT_EQUAL(0u, dirtyTasks.size());
		}

		{ // One (Two tasks, Both dirty, one depends on the other)
			ctor::build_configuration config{};
			auto task1 = std::make_shared<TestTask>(config, settings, "task1", true);

			std::vector<std::string> deps = {"task1"};
			auto task2 =
				std::make_shared<TestTask>(config, settings, "task2", true, deps);

			std::vector<std::shared_ptr<Task>> allTasks;
			allTasks.push_back(task2);
			allTasks.push_back(task1);

			std::vector<std::shared_ptr<Task>> dirtyTasks;
			dirtyTasks.push_back(task2);
			dirtyTasks.push_back(task1);

			for(auto& task : dirtyTasks)
			{
				uASSERT_EQUAL(0, task->registerDepTasks(allTasks));
			}

			uASSERT_EQUAL(task1, getNextTask({}, allTasks, dirtyTasks));
			uASSERT_EQUAL(1u, dirtyTasks.size());
		}
	}

	void comparison_test()
	{
		test_global::toolchain = ctor::toolchain::gcc;
		test_global::arch = ctor::arch::unix;

		using namespace std::string_literals;
		ctor::settings settings{ .builddir = "foo" };
		{ // Test Task::operator==
			auto tasks = getTasks(settings, {"target1"});
			uASSERT_EQUAL(3u, tasks.size());
			uASSERT_EQUAL(1u, count(tasks, fs("target1")));
			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o"));
			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));

			int cnt1{};
			int cnt2{};
			int cnt3{};
			for(const auto& task : tasks)
			{
				if(task->target() == fs("target1"))
				{
					++cnt1;
					uASSERT(*task == "target1");
					uASSERT(*task == "Target1");
				}
				if(task->target() == fs("test")/"target1-foo_cc.o")
				{
					++cnt2;
					uASSERT(*task != "target1");
					uASSERT(*task != "Target1");
				}
				if(task->target() == fs("test")/"target1-bar_c.o")
				{
					++cnt3;
					uASSERT(*task != "target1");
					uASSERT(*task != "Target1");
				}
			}
			// Assert that we did actually perform all three tests exactly once
			uASSERT_EQUAL(1, cnt1);
			uASSERT_EQUAL(1, cnt2);
			uASSERT_EQUAL(1, cnt3);
		}
	}
};

// Registers the fixture into the 'registry'
static TasksTest test;