#include <uunit.h>

#include <libctor.h>
#include <tasks.h>
#include <settings.h>

namespace
{
BuildConfigurations ctorTestConfigs1()
{
	return
	{
		{
			.target = "target1",
			.sources = {"foo.cc", "bar.c"},
		},
		{
			.target = "target2",
		},
	};
}

BuildConfigurations ctorTestConfigs2()
{
	return
	{
		{
			.target = "target3",
		},
		{
			.target = "target4",
		},
	};
}
}

REG(ctorTestConfigs1);
REG(ctorTestConfigs2);


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

	std::string name() const override { return task_name; }
	int clean() override { return 0; }
	std::vector<std::string> depends() const override { return task_deps; }
	std::string target() const override { return task_name; }
	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:
	TasksTest()
	{
		uTEST(TasksTest::getTargets_test);
		uTEST(TasksTest::getTasks_test);
		uTEST(TasksTest::getNextTask_test);
	}

	void getTargets_test()
	{
		using namespace std::string_literals;
		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()
	{
		using namespace std::string_literals;
		Settings settings{ .builddir = "foo" };
		{
			auto tasks = getTasks(settings);
			uASSERT_EQUAL(6u, tasks.size());
			auto task = tasks.begin();
			uASSERT_EQUAL("foo/test/target1-foo_cc.o"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target1-bar_c.o"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target1"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target2"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target3"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target4"s, (*task)->target());
		}
		{
			auto tasks = getTasks(settings, {"target1", "target3"});
			uASSERT_EQUAL(4u, tasks.size());
			auto task = tasks.begin();
			uASSERT_EQUAL("foo/test/target1-foo_cc.o"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target1-bar_c.o"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target1"s, (*task)->target());
			task++;
			uASSERT_EQUAL("foo/test/target3"s, (*task)->target());
		}
		{
			auto tasks = getTasks(settings, {"no-such-target"});
			uASSERT_EQUAL(0u, tasks.size());
		}
	}

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

		{ // Zero (Empty)
			std::list<std::shared_ptr<Task>> allTasks;
			std::list<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)
			auto task1 = std::make_shared<TestTask>("task1", false);

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

			std::list<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)
			auto task1 = std::make_shared<TestTask>("task1", true);

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

			std::list<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)
			auto task1 = std::make_shared<TestTask>("task1", false);
			auto task2 = std::make_shared<TestTask>("task2", true);

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

			std::list<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)
			auto task1 = std::make_shared<TestTask>("task1", false);

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

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

			std::list<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)
			auto task1 = std::make_shared<TestTask>("task1", true);

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

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

			std::list<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());
		}

	}
};

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