#include <vector>
#include <string>
#include <filesystem>
#include <iostream>
#include <utility>
#include <list>
#include <chrono>
#include <thread>
#include <memory>

#include "libcppbuild.h"
#include "task_cc.h"
#include "task_ld.h"
#include "settings.h"

#include <unistd.h>

using namespace std::chrono_literals;

int main(int argc, const char* argv[])
{
	Settings settings;

	// TODO: Set from commandline
	settings.builddir = "build/foo";

	settings.parallel_processes =
		std::max(1u, std::thread::hardware_concurrency() * 2 - 1);

	std::filesystem::path builddir(settings.builddir);
	std::filesystem::create_directories(builddir);

	auto build_configs = configs();
	for(const auto& build_config : build_configs)
	{
		std::vector<std::string> objects;
		std::vector<std::unique_ptr<Task>> tasks;
		for(const auto& file : build_config.sources)
		{
			tasks.emplace_back(std::make_unique<TaskCC>(build_config, settings, file));
			objects.push_back(tasks.back()->target());
		}

		TaskLD task_ld(build_config, settings, build_config.target, objects);

		if(argc == 2 && std::string(argv[1]) == "clean")
		{
			std::cout << "Cleaning\n";
			//std::filesystem::remove_all(builddir);
			for(auto& task : tasks)
			{
				if(task->clean() != 0)
				{
					return 1;
				}
			}

			if(task_ld.clean() != 0)
			{
				return 1;
			}

			return 0;
		}

		std::cout << "Building\n";

		std::list<std::future<int>> processes;

		// Start all tasks
		auto task = tasks.begin();
		while(task != tasks.end())
		{
			while(processes.size() < settings.parallel_processes &&
			      task != tasks.end())
			{
				if(!(*task)->dirty())
				{
					++task;
					continue;
				}

				processes.emplace_back(
					std::async(std::launch::async,
					           [&t = *task]()
					           {
						           return t->run();
					           }));
				++task;
				std::this_thread::sleep_for(2ms);
			}

			for(auto process = processes.begin();
			    process != processes.end();
			    ++process)
			{
				if(process->valid())
				{
					if(process->get() != 0)
					{
						return 1;
					}
					processes.erase(process);
					break;
				}
			}

			std::this_thread::sleep_for(2ms);
		}

		for(auto process = processes.begin();
		    process != processes.end();
		    ++process)
		{
			process->wait();
			auto ret = process->get();
			if(ret != 0)
			{
				return 1;
			}
		}

		std::cout << "Linking\n";
		if(task_ld.dirty())
		{
			return task_ld.run();
		}
	}

	return 0;
}