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

#include <iostream>
#include <filesystem>
#include <algorithm>
#include <source_location>
#include <cstring>

#include "configure.h"
#include "ctor.h"
#include "tasks.h"
#include "build.h"
#include "execute.h"
#include "tools.h"
#include "util.h"

using namespace ctor;

std::array<BuildConfigurationEntry, 1024> configFiles;
std::size_t numConfigFiles{0};

namespace ctor {
int reg(ctor::build_configurations (*cb)(const ctor::settings&),
        const std::source_location location)
{
	// NOTE: std::cout cannot be used here
	if(numConfigFiles >= configFiles.size())
	{
		fprintf(stderr, "Max %d build configurations currently supported.\n",
		        (int)configFiles.size());
		exit(1);
	}

	auto loc = std::filesystem::path(location.file_name());
	if(loc.is_absolute())
	{
		auto pwd = std::filesystem::current_path();
		auto rel = std::filesystem::relative(loc, pwd);
		configFiles[numConfigFiles].file = strdup(rel.string().data()); // NOTE: This intentionally leaks memory
	}
	else
	{
		configFiles[numConfigFiles].file = location.file_name();
	}
	configFiles[numConfigFiles].cb = cb;
	++numConfigFiles;

	return 0;
}
} // namespace ctor::

int reg(const char* location)
{
	// NOTE: std::cout cannot be used here
	if(numConfigFiles >= configFiles.size())
	{
		fprintf(stderr, "Max %d build configurations currently supported.\n",
		        (int)configFiles.size());
		exit(1);
	}

	configFiles[numConfigFiles].file = location;
	configFiles[numConfigFiles].cb =
		[](const ctor::settings&){ return std::vector<ctor::build_configuration>{}; };
	++numConfigFiles;

	return 0;
}

int unreg(const char* location)
{
	std::size_t found{0};
	for(std::size_t i = 0; i < numConfigFiles;)
	{
		if(std::string(location) == configFiles[i].file)
		{
			++found;
			for(std::size_t j = i; j < numConfigFiles; ++j)
			{
				configFiles[j] = configFiles[j + 1];
			}
			--numConfigFiles;
		}
		else
		{
			++i;
		}
	}

	for(std::size_t i = 0; i < numExternalConfigFiles;)
	{
		if(std::string(location) == externalConfigFiles[i].file)
		{
			++found;
			for(std::size_t j = i; j < numExternalConfigFiles; ++j)
			{
				externalConfigFiles[j] = externalConfigFiles[j + 1];
			}
			--numExternalConfigFiles;
		}
		else
		{
			++i;
		}
	}

	return found;
}

std::array<ExternalConfigurationEntry, 1024> externalConfigFiles;
std::size_t numExternalConfigFiles{0};

namespace ctor {
int reg(ctor::external_configurations (*cb)(const ctor::settings&),
        const std::source_location location)
{
	// NOTE: std::cout cannot be used here
	if(numExternalConfigFiles >= externalConfigFiles.size())
	{
		fprintf(stderr, "Max %d external configurations currently supported.\n",
		        (int)externalConfigFiles.size());
		exit(1);
	}

	externalConfigFiles[numExternalConfigFiles].file = location.file_name();
	externalConfigFiles[numExternalConfigFiles].cb = cb;
	++numExternalConfigFiles;

	return 0;
}
} // namespace ctor::

namespace
{
bool contains(const std::vector<ctor::source>& sources, const std::string& file)
{
	for(const auto& source : sources)
	{
		if(source.file == file)
		{
			return true;
		}
	}

	return false;
}
}

bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[],
                    bool relaunch_allowed)
{
	using namespace std::string_literals;

	if(global_settings.verbose > 1)
	{
		std::cout << "Recompile check (" << numConfigFiles << "):\n";
	}

	ctor::build_configuration config;

	config.name = "ctor";
	config.system = ctor::output_system::build;

	auto tool_chain = getToolChain(config.system);

	append(config.flags.cxxflags,
	       getOption(tool_chain, opt::optimization, "3"));
	append(config.flags.cxxflags,
	       getOption(tool_chain, opt::cpp_std, "c++20"));
	if(hasConfiguration(cfg::ctor_includedir))
	{
		append(config.flags.cxxflags,
		       getOption(tool_chain, opt::include_path,
		                 getConfiguration(cfg::ctor_includedir)));
	}
	if(hasConfiguration(cfg::ctor_libdir))
	{
		append(config.flags.ldflags,
		       getOption(tool_chain, opt::library_path,
		                 getConfiguration(cfg::ctor_libdir)));
	}
	append(config.flags.ldflags, getOption(tool_chain, opt::link, "ctor"));
	append(config.flags.ldflags, getOption(tool_chain, opt::threads));

	ctor::settings settings{global_settings};
	settings.verbose = -1; // Make check completely silent.
	settings.builddir += "/ctor"; // override builddir to use ctor subdir

	{
		std::filesystem::path buildfile  = settings.builddir;
		std::filesystem::path currentfile = argv[0];
		config.target = std::filesystem::relative(currentfile, buildfile).string();
	}

	if(std::filesystem::exists(configurationFile))
	{
		config.sources.push_back(configurationFile.string());
	}

	for(std::size_t i = 0; i < numConfigFiles; ++i)
	{
		std::string location = configFiles[i].file;
		if(global_settings.verbose > 1)
		{
			std::cout << " - " << location << "\n";
		}

		// Ensure that files containing multiple configurations are only added once.
		if(!contains(config.sources, location))
		{
			config.sources.push_back(location);
		}
	}

	for(std::size_t i = 0; i < numExternalConfigFiles; ++i)
	{
		std::string location = externalConfigFiles[i].file;
		if(global_settings.verbose > 1)
		{
			std::cout << " - " << location << "\n";
		}

		// Ensure that files containing multiple configurations are only added once.
		if(!contains(config.sources, location))
		{
			config.sources.push_back(location);
		}
	}

	auto tasks = taskFactory({config}, settings, {});

	for(auto task : tasks)
	{
		if(task->registerDepTasks(tasks))
		{
			return false;
		}
	}

	// Find out if reconfigure is needed
	bool reconfigure{false};
	for(auto task : tasks)
	{
		if(task->dirty() &&
		   task->source() != "" && // only look at source files
		   task->source() != "configuration.cc") // don't reconfigure if only configuration.cc is changed.
		{
			reconfigure |= true;
		}
	}

	auto dirty_tasks = build(settings, "ctor", tasks, true); // dryrun
	if(dirty_tasks)
	{
		std::cout << "Rebuilding config.\n";
		auto ret = build(settings, "ctor", tasks); // run for real
		if(ret != 0)
		{
			return ret;
		}
	}

	if(reconfigure)
	{
		std::vector<std::string> args;
		args.push_back("reconfigure");
		if(!relaunch_allowed)
		{
			args.push_back("--no-rerun");
		}
		for(int i = 1; i < argc; ++i)
		{
			args.push_back(argv[i]);
		}
		auto ret = execute(argv[0], args);
		//if(ret != 0)
		{
			exit(ret);
		}

	}

	return dirty_tasks;
}