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

#include <iostream>
#include <fstream>

#include "ctor.h"
#include "execute.h"
#include "util.h"
#include "tools.h"

TaskLD::TaskLD(const ctor::build_configuration& config,
               const ctor::settings& settings,
               const std::string& target,
               const std::vector<std::string>& objects,
               const std::string& sourceDir)
	: Task(config, settings, sourceDir)
	, config(config)
	, settings(settings)
	, sourceDir(sourceDir)
{
	target_type = config.type;
	if(target_type == ctor::target_type::automatic)
	{
		target_type = ctor::target_type::executable;
	}

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

	_targetFile = target;
	auto toolchain = getToolChain(config.system);
	_targetFile = extension(toolchain, target_type, config.system, _targetFile);
	for(const auto& object : objects)
	{
		std::filesystem::path objectFile = object;
		objectFiles.push_back(objectFile);
		dependsStr.push_back(objectFile.string());
	}

	for(const auto& dep : config.depends)
	{
		depFiles.push_back(dep);
	}

	flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();
	flagsFile += ".flags";

	source_language = ctor::language::c;
	for(const auto& source : config.sources)
	{
		std::filesystem::path sourceFile(source.file);
		if(sourceFile.extension().string() != ".c")
		{
			source_language = ctor::language::cpp;
		}
	}
}

bool TaskLD::dirtyInner()
{
	if(!std::filesystem::exists(targetFile()))
	{
		return true;
	}

	if(!std::filesystem::exists(flagsFile))
	{
		return true;
	}

	{
		auto lastFlags = readFile(flagsFile.string());
		if(flagsString() != lastFlags)
		{
			//std::cout << "The compiler flags changed\n";
			return true;
		}
	}

	return false;
}

int TaskLD::runInner()
{
	auto toolchain = getToolChain(config.system);

	std::vector<std::string> args;
	for(const auto& dep : getDependsTasks())
	{
		auto depFile = dep->targetFile();
		auto dep_type = target_type_from_extension(toolchain, depFile);
		if(dep_type == ctor::target_type::dynamic_library)
		{
			append(args, ld_option(toolchain, ctor::ld_opt::library_path,
			                       targetFile().parent_path().string()));
			auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix
			append(args, ld_option(toolchain, ctor::ld_opt::link, lib));
		}
		else if(dep_type == ctor::target_type::static_library ||
		        dep_type == ctor::target_type::object)
		{
			args.push_back(depFile.string());
		}
	}

	for(const auto& flag : config.flags.ldflags)
	{
		append(args, to_strings(toolchain, flag));
	}

	append(args, ld_option(toolchain, ctor::ld_opt::output, targetFile().string()));

	{ // Write flags to file.
		std::ofstream flagsStream(flagsFile);
		flagsStream << flagsString();
	}

	if(settings.verbose == 0)
	{
		std::cout << "LD => " << targetFile().string() << std::endl;
	}

	auto tool = compiler();
	return execute(tool, args, {}, settings.verbose > 0);
}

int TaskLD::clean()
{
	if(std::filesystem::exists(targetFile()))
	{
		std::cout << "Removing " << targetFile().string() << "\n";
		std::filesystem::remove(targetFile());
	}

	if(std::filesystem::exists(flagsFile))
	{
		std::cout << "Removing " << flagsFile.string() << "\n";
		std::filesystem::remove(flagsFile);
	}

	return 0;
}

std::vector<std::string> TaskLD::depends() const
{
	std::vector<std::string> deps;
	for(const auto& objectFile : objectFiles)
	{
		deps.push_back(objectFile.string());
	}

	for(const auto& depFile : depFiles)
	{
		deps.push_back(depFile.string());
	}

	return deps;
}

std::string TaskLD::target() const
{
	return _targetFile.string();
}

std::filesystem::path TaskLD::targetFile() const
{
	return std::filesystem::path(settings.builddir) / sourceDir / _targetFile;
}

bool TaskLD::derived() const
{
	return false;
}

std::string TaskLD::flagsString() const
{
	auto toolchain = getToolChain(config.system);
	std::string flagsStr;
	bool first{true};
	for(const auto& flag : config.flags.ldflags)
	{
		for(const auto& str : to_strings(toolchain, flag))
		{
			if(first)
			{
				flagsStr += " ";
				first = false;
			}
			flagsStr += str;
		}
	}
	flagsStr += "\n";

	for(const auto& dep : config.depends)
	{
		if(dep != config.depends[0])
		{
			flagsStr += " ";
		}
		flagsStr += dep;
	}

	auto deps = depends();
	for(const auto& dep : deps)
	{
		flagsStr += " ";
		flagsStr += dep;
	}

	return flagsStr;
}