// -*- c++ -*-
// Distributed under the BSD 2-Clause License.
// See accompanying file LICENSE for details.
#pragma once

#include <source_location>
#include <string>
#include <vector>
#include <map>
#include <variant>
#include <cstddef>
#include <functional>

namespace ctor {

enum class target_type
{
	automatic, // Default - deduce from target name and sources extensions

	executable,
	static_library,
	dynamic_library,
	object,
	unit_test,
	unit_test_library,
	function,
};

enum class language
{
	automatic, // Default - deduce language from source extensions

	c,
	cpp,
	assembler,
};

enum class output_system
{
	host, // Output for the target system
	build, // Internal tool during cross-compilation
};

struct source
{
	source(const char* file) : file(file) {}
	source(const std::string& file) : file(file) {}
	source(const char* file, ctor::language lang) : file(file), language(lang) {}
	source(const std::string& file, ctor::language lang) : file(file), language(lang) {}

	source(const char* file, const char* output) : file(file), output(output) {}
	source(const std::string& file, const std::string& output) : file(file), output(output) {}
	source(const char* file, ctor::language lang, const char* output) : file(file), language(lang), output(output) {}
	source(const std::string& file, ctor::language lang, const std::string& output) : file(file), language(lang), output(output) {}

	std::string file;
	ctor::language language{ctor::language::automatic};
	std::string output{};
};

struct flags
{
	std::vector<std::string> cxxflags; // flags for c++ compiler
	std::vector<std::string> cflags; // flags for c compiler
	std::vector<std::string> ldflags; // flags for linker
	std::vector<std::string> asmflags; // flags for asm translator
};

struct settings
{
	std::string builddir{"build"};
	std::size_t parallel_processes{1};
	int verbose{0}; // -1: completely silent, 0: normal, 1: verbose, ...
};

struct build_configuration;
using GeneratorCb = std::function<int(const std::string& input,
                                      const std::string& output,
                                      const build_configuration& config,
                                      const ctor::settings& settings)>;

struct build_configuration
{
	std::string name; // Name - used for referring in other configurations.
	ctor::target_type type{ctor::target_type::automatic};
	ctor::output_system system{ctor::output_system::host};
	std::string target; // Output target file for this configuration
	std::vector<ctor::source> sources; // source list
	std::vector<std::string> depends; // internal target dependencies
	ctor::flags flags;
	std::vector<std::string> externals; // externals used by this configuration
	GeneratorCb function;
};

using build_configurations = std::vector<build_configuration>;

int reg(ctor::build_configurations (*cb)(const ctor::settings&),
        const std::source_location location = std::source_location::current());

// This type will use flags verbatim
struct external_manual
{
	ctor::flags flags;
};


struct external_configuration
{
	std::string name; // Name for configuration
	ctor::output_system system{ctor::output_system::host};
	std::variant<ctor::external_manual> external;
};

using external_configurations = std::vector<ctor::external_configuration>;

int reg(ctor::external_configurations (*cb)(const ctor::settings&),
        const std::source_location location = std::source_location::current());

// Convenience macro - ugly but keeps things simple(r)
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define CONCAT_INNER(a, b) a ## b
#define UNIQUE_NAME(base) CONCAT(base, __LINE__)
#define REG(cb) namespace { int UNIQUE_NAME(unique) = reg(cb); }

// Predefined configuration keys
namespace cfg
{
constexpr auto builddir = "builddir";

constexpr auto host_cc = "host-cc";
constexpr auto host_cxx = "host-cpp";
constexpr auto host_ar = "host-ar";
constexpr auto host_ld = "host-ld";

constexpr auto build_cc = "build-cc";
constexpr auto build_cxx = "build-cpp";
constexpr auto build_ar = "build-ar";
constexpr auto build_ld = "build-ld";

constexpr auto ctor_includedir = "ctor-includedir";
constexpr auto ctor_libdir = "ctor-libdir";
}

struct Configuration
{
	std::vector<std::string> args; // vector of arguments used when last calling configure
	std::map<std::string, std::string> env; // env used when last calling configure

	std::map<std::string, std::string> tools; // tools
	std::map<std::string, ctor::flags> externals;
};

const Configuration& configuration();
bool hasConfiguration(const std::string& key);
const std::string& getConfiguration(const std::string& key,
                                    const std::string& defaultValue = {});

} // namespace ctor::