#include "task_ld.h"

#include <iostream>
#include <fstream>

#include "libcppbuild.h"
#include "settings.h"
#include "execute.h"

namespace
{
std::string readFile(const std::string &fileName)
{
	std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate);

	std::ifstream::pos_type fileSize = ifs.tellg();
	ifs.seekg(0, std::ios::beg);

	std::vector<char> bytes(fileSize);
	ifs.read(bytes.data(), fileSize);

	return std::string(bytes.data(), fileSize);
}

std::vector<std::string> addPrefix(const std::vector<std::string>& lst,
                                   const Settings& settings)
{
	std::vector<std::string> out;
	for(const auto& item : lst)
	{
		std::filesystem::path file = settings.builddir;
		file /= item;
		out.push_back(file.string());
	}
	return out;
}
} // namespace ::

TaskLD::TaskLD(const BuildConfiguration& config,
               const Settings& settings,
               const std::string& target,
               const std::vector<std::string>& objects)
	: Task(addPrefix(config.depends, settings))
	, config(config)
	, settings(settings)
{
	targetFile = settings.builddir;
	targetFile /= target;
	for(const auto& object : objects)
	{
		std::filesystem::path objectFile = object;
		objectFiles.push_back(objectFile);
		dependsStr.push_back(objectFile);
	}

	for(const auto& dep : config.depends)
	{
		std::filesystem::path depFile = settings.builddir;
		depFile /= dep;
		depFiles.push_back(depFile);
	}

	flagsFile = settings.builddir / targetFile.stem();
	flagsFile += ".flags";
}

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

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

	for(const auto& objectFile : objectFiles)
	{
		if(std::filesystem::last_write_time(targetFile) <=
		   std::filesystem::last_write_time(objectFile))
		{
			return true;
		}
	}

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

	return false;
}

int TaskLD::runInner()
{
	std::string objectlist;
	for(const auto& objectFile : objectFiles)
	{
		if(!objectlist.empty())
		{
			objectlist += " ";
		}
		objectlist += std::string(objectFile);
	}

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

	for(const auto& depFile : depFiles)
	{
		if(depFile.extension() == ".so")
		{
			args.push_back(std::string("-L") + settings.builddir);
			auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix
			args.push_back(std::string("-l") + lib);
		}
		else if(depFile.extension() == ".a")
		{
			args.push_back(depFile.string());
		}
	}

	for(const auto& flag : config.ldflags)
	{
		args.push_back(flag);
	}
	args.push_back("-o");
	args.push_back(std::string(targetFile));

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

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

	return execute("/usr/bin/g++", args, settings.verbose > 0);
}

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

	if(std::filesystem::exists(flagsFile))
	{
		std::cout << "Removing " << std::string(flagsFile) << "\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 std::string(targetFile);
}

std::string TaskLD::flagsString() const
{
	std::string flagsStr;
	for(const auto& flag : config.ldflags)
	{
		if(!flagsStr.empty())
		{
			flagsStr += " ";
		}
		flagsStr += flag;
	}
	return flagsStr;
}