// -*- 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 <span>
#include <vector>

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

std::vector<BuildConfigurationEntry>& getConfigFileList()
{
	static std::vector<BuildConfigurationEntry> configFiles;
	return configFiles;
}

std::vector<ExternalConfigurationEntry>& getExternalConfigFileList()
{
	static std::vector<ExternalConfigurationEntry> externalConfigFiles;
	return externalConfigFiles;
}

namespace ctor {
int reg(std::function<ctor::build_configurations (const ctor::settings&)> cb,
        const std::source_location location)
{
	BuildConfigurationEntry entry;

	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);
		entry.file = rel.string();
	}
	else
	{
		entry.file = location.file_name();
	}
	entry.cb = cb;
	auto& configFiles = getConfigFileList();
	configFiles.push_back(entry);

	return 0;
}
} // ctor::

int reg(const char* location)
{
	BuildConfigurationEntry entry;

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

	return 0;
}

int unreg(const char* location)
{
	auto& configFiles = getConfigFileList();
	auto erasedConfigs =
		std::erase_if(configFiles,
		              [&](const BuildConfigurationEntry& entry)
		              {
			              return entry.file == location;
		              });

	auto& externalConfigFiles = getExternalConfigFileList();
	auto erasedExternals =
		std::erase_if(externalConfigFiles,
		              [&](const ExternalConfigurationEntry& entry)
		              {
			              return entry.file == location;
		              });

	return static_cast<int>(erasedConfigs) + static_cast<int>(erasedExternals);
}

namespace ctor {
int reg(std::function<ctor::external_configurations (const ctor::settings&)> cb,
        const std::source_location location)
{
	ExternalConfigurationEntry entry;
	entry.file = location.file_name();
	entry.cb = cb;
	auto& externalConfigFiles = getExternalConfigFileList();
	externalConfigFiles.push_back(entry);

	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)
{
	auto args_span = std::span(argv, static_cast<std::size_t>(argc));

	using namespace std::string_literals;

	const auto& configFiles = getConfigFileList();
	if(global_settings.verbose > 1)
	{
		std::cout << "Recompile check (" << configFiles.size() << "):\n";
	}

	ctor::build_configuration config;

	config.type = ctor::target_type::executable;
	config.name = "ctor";
	config.system = ctor::output_system::build;

	config.flags.cxxflags.emplace_back(ctor::cxx_opt::optimization, "3");
	config.flags.cxxflags.emplace_back(ctor::cxx_opt::cpp_std, "c++20");

	const auto& c = ctor::get_configuration();
	if(c.has(ctor::cfg::ctor_includedir))
	{
		config.flags.cxxflags.emplace_back(ctor::cxx_opt::include_path,
		                                   c.get(ctor::cfg::ctor_includedir));
	}
	if(c.has(ctor::cfg::ctor_libdir))
	{
		config.flags.ldflags.emplace_back(ctor::ld_opt::library_path,
		                                  c.get(ctor::cfg::ctor_libdir));
	}
	config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor");
	config.flags.ldflags.emplace_back(ctor::ld_opt::threads);

	ctor::settings settings{global_settings};
	settings.verbose = -1; // Make check completely silent.

	// override builddir to use ctor subdir
	auto ctor_builddir = std::filesystem::path(settings.builddir) / "ctor";
	settings.builddir = ctor_builddir.string();

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

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

	for(const auto& configFile : configFiles)
	{
		std::string location = configFile.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.emplace_back(location);
		}
	}

	const auto& externalConfigFiles = getExternalConfigFileList();
	for(const auto& externalConfigFile : externalConfigFiles)
	{
		std::string location = externalConfigFile.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.emplace_back(location);
		}
	}

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

	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.emplace_back("reconfigure");
		if(!relaunch_allowed)
		{
			args.emplace_back("--no-rerun");
		}
		for(std::size_t i = 1; i < args_span.size(); ++i)
		{
			args.emplace_back(args_span[i]);
		}

		auto ret = execute(settings, args_span[0], args);
		//if(ret != 0)
		{
			exit(ret);
		}

	}

	return dirty_tasks;
}