// -*- c++ -*-
// Distributed under the BSD 2-Clause License.
// See accompanying file LICENSE for details.
#include "build.h"

#include <future>
#include <iostream>
#include <chrono>
#include <thread>
#include <list>
#include <algorithm>

#include "ctor.h"

using namespace std::chrono_literals;

int build(const ctor::settings& settings,
          const std::string& name,
          const std::vector<std::shared_ptr<Task>>& tasks,
          const std::vector<std::shared_ptr<Task>>& all_tasks,
          bool dryrun)
{
	if(settings.verbose > 1)
	{
		std::cout << "Building '" << name << "'\n";
	}

	std::vector<std::shared_ptr<Task>> dirtyTasks;
	for(auto task : tasks)
	{
		if(task->dirty() &&
		   std::find(dirtyTasks.begin(), dirtyTasks.end(), task) == dirtyTasks.end())
		{
			dirtyTasks.push_back(task);
		}
	}

	// Dry-run returns number of dirty tasks but otherwise does nothing.
	if(dryrun)
	{
		return static_cast<int>(dirtyTasks.size());
	}

	if(dirtyTasks.empty())
	{
		if(settings.verbose > -1)
		{
			std::cout << "Nothing to be done for '"<< name << "'\n";
		}
		return 0;
	}

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

	// Start all tasks
	bool done{false};
	while(!done)
	{
		bool started_one{false};
		while(processes.size() < settings.parallel_processes)
		{
			if(dirtyTasks.empty())
			{
				done = true;
				break;
			}

			auto task = getNextTask(all_tasks, dirtyTasks);
			if(task == nullptr)
			{
				if(processes.empty() && !dirtyTasks.empty())
				{
					// No running processes, yet no process to run. This is a dead-lock...
					std::cout << "Dead-lock detected.\n";
					return 1;
				}
				break;
			}

			processes.emplace_back(
				std::async(std::launch::async,
				           [task]()
				           {
					           return task->run();
				           }));
			started_one = true;
			// Make sure we don't start tasks on top of each other to prevent
			// straining the disk.
			std::this_thread::sleep_for(50ms);
		}

		for(auto process = processes.begin();
		    process != processes.end();
		    ++process)
		{
			if(process->valid() == false)
			{
				continue;
			}

			auto ret = process->get();
			if(ret != 0)
			{
				// NOTE Wait for other processes to finish before returning
				return ret;
			}
			processes.erase(process);
			break;
		}

		if(!started_one) // prevent polling too fast if no task is yet ready
		{
			std::this_thread::sleep_for(10ms);
		}
	}

	for(auto& process : processes)
	{
		if(process.valid() == false)
		{
			continue;
		}
		process.wait();
		auto ret = process.get();
		if (ret != 0)
		{
			return ret;
		}
	}

	return 0;
}

namespace
{
std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task)
{
	std::vector<std::shared_ptr<Task>> tasks;
	tasks.push_back(task);

	auto deps = task->getDependsTasks();
	for(const auto& dep : deps)
	{
		auto depSet = getDepTasks(dep);
		for(const auto& dep : depSet)
		{
			if(std::find(tasks.begin(), tasks.end(), dep) == tasks.end())
			{
				tasks.push_back(dep);
			}
		}
	}

	return tasks;
}
}

int build(const ctor::settings& settings,
          const std::string& name,
          const std::vector<std::shared_ptr<Task>>& all_tasks,
          bool dryrun)
{
	bool task_found{false};
	for(auto task : all_tasks)
	{
		if(*task == name)
		{
			task_found = true;

			auto depSet = getDepTasks(task);
			std::vector<std::shared_ptr<Task>> ts;
			for(const auto& task : depSet)
			{
				if(std::find(ts.begin(), ts.end(), task) == ts.end())
				{
					ts.push_back(task);
				}
			}

			auto ret = build(settings, name, ts, all_tasks, dryrun);
			if(ret != 0)
			{
				return ret;
			}

			break;
		}
	}

	if(!task_found)
	{
		std::cerr << "*** No rule to make target '" << name << "'.  Stop.\n";
		return 1;
	}

	return 0;
}

int build(const ctor::settings& settings,
          const std::string& name,
          const std::vector<Target>& targets,
          const std::vector<std::shared_ptr<Task>>& all_tasks,
          bool dryrun)
{
	bool task_found{false};
	std::vector<std::shared_ptr<Task>> ts;

	for(const auto& target : targets)
	{
		for(auto task : all_tasks)
		{
			if(!task->derived() && // only consider non-derived tasks
			   task->buildConfig().target == target.config.target)
			{
				task_found = true;

				auto depSet = getDepTasks(task);
				for(const auto& task : depSet)
				{
					if(std::find(ts.begin(), ts.end(), task) == ts.end())
					{
						ts.push_back(task);
					}
				}
			}
		}
	}

	if(!task_found)
	{
		std::cerr << "*** No rule to make target '" << name << "'.  Stop.\n";
		return 1;
	}

	return build(settings, name, ts, all_tasks, dryrun);
}