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

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,

	unknown,
};

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
};

enum class arch
{
	unix,    //!< Target platform architecture is unix-based (ie. linux, bsd, etc)
	apple,   //!< Target platform architecture is macos
	windows, //!< Target platform architecture is windows

	unknown, //!< Target platform architecture has not yet detected or was not possible to detect
};

enum class toolchain
{
	any,
	none,
	gcc,
	clang,
};

struct source
{
	source(const char* file_) : file(file_) {} // convenience ctor

	source(std::string_view file_) : source(file_, ctor::language::automatic) {}
	source(std::string_view file_, ctor::language lang_) : file(file_), language(lang_) {}

	source(std::string_view file_, std::string_view output_) : file(file_), output(output_) {}
	source(std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), language(lang_), output(output_) {}

	source(ctor::toolchain toolchain_, std::string_view file_) : file(file_), toolchain(toolchain_) {}
	source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_) : file(file_), toolchain(toolchain_), language(lang_) {}

	source(ctor::toolchain toolchain_, std::string_view file_, std::string_view output_) : file(file_), toolchain(toolchain_), output(output_) {}

	source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), toolchain(toolchain_), language(lang_), output(output_) {}

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

enum class cxx_opt
{
	//                                  gcc/clang
	output,                          //  -o
	debug,                           //  -g
	warn_all,                        //  -Wall
	warn_conversion,                 //  -Wconversion
	warn_shadow,                     //  -Wshadow
	warn_extra,                      //  -Wextra
	warnings_as_errors,              //  -Werror
	generate_dep_tree,               //  -MMD
	no_link,                         //  -c
	include_path,                    //  -I<arg>
	cpp_std,                         //  -std=<arg>
	optimization,                    //  -O<arg>
	position_independent_code,       //  -fPIC
	position_independent_executable, //  -fPIE
	define,                          //  -D<arg>[=<arg2>]
	custom,                          // entire option taken verbatim from <arg>
};

enum class c_opt
{
	//                                  gcc/clang
	output,                          //  -o
	debug,                           //  -g
	warn_all,                        //  -Wall
	warn_conversion,                 //  -Wconversion
	warn_shadow,                     //  -Wshadow
	warn_extra,                      //  -Wextra
	warnings_as_errors,              //  -Werror
	generate_dep_tree,               //  -MMD
	no_link,                         //  -c
	include_path,                    //  -I<arg>
	c_std,                           //  -std=<arg>
	optimization,                    //  -O<arg>
	position_independent_code,       //  -fPIC
	position_independent_executable, //  -fPIE
	define,                          //  -D<arg>[=<arg2>]
	custom,                          // entire option taken verbatim from <arg>
};

enum class ld_opt
{
	//                                  gcc/clang
	output,                          //  -o
	warn_all,                        //  -Wall
	warnings_as_errors,              //  -Werror
	library_path,                    //  -L<arg>
	link,                            //  -l<arg>
	cpp_std,                         //  -std=<arg>
	build_shared,                    //  -shared
	threads,                         //  -pthread
	position_independent_code,       //  -fPIC
	position_independent_executable, //  -fPIE
	custom,                          // entire option taken verbatim from <arg>
};

enum class ar_opt
{
	//                                  gcc/clang
	replace,                         // -r
	add_index,                       // -s
	create,                          // -c
	output,                          // <arg>

	custom,                          // entire option taken verbatim from <arg>
};

enum class asm_opt
{
	//                                  gcc/clang
	custom,                          // entire option taken verbatim from <arg>
};

template<typename T>
class flag
{
public:
	flag(std::string_view str);
	flag(const char* str);
	flag(T opt_) : opt(opt_) {}
	flag(T opt_, std::string_view arg_, std::string_view arg2_ = "")
		: opt(opt_), arg(arg_), arg2(arg2_) {}
	flag(T opt_, const char* arg_, const char* arg2_ = "")
		: opt(opt_), arg(arg_), arg2(arg2_) {}
	flag(ctor::toolchain toolchain_, T opt_)
		: toolchain(toolchain_), opt(opt_) {}
	flag(ctor::toolchain toolchain_, T opt_, const char* arg_, const char* arg2_ = "")
		: toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {}
	flag(ctor::toolchain toolchain_, T opt_, std::string_view arg_, std::string_view arg2_ = "")
		: toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {}

	ctor::toolchain toolchain{ctor::toolchain::any};
	T opt{};
	std::string arg;
	std::string arg2;
};

using c_flag = ctor::flag<ctor::c_opt>;
using cxx_flag = ctor::flag<ctor::cxx_opt>;
using ld_flag = ctor::flag<ctor::ld_opt>;
using ar_flag = ctor::flag<ctor::ar_opt>;
using asm_flag = ctor::flag<ctor::asm_opt>;

using c_flags = std::vector<ctor::c_flag>;
using cxx_flags = std::vector<ctor::cxx_flag>;
using ld_flags = std::vector<ctor::ld_flag>;
using ar_flags = std::vector<ctor::ar_flag>;
using asm_flags = std::vector<ctor::asm_flag>;

struct flags
{
	ctor::c_flags cflags; // flags for c compiler
	ctor::cxx_flags cxxflags; // flags for c++ compiler
	ctor::ld_flags ldflags; // flags for linker
	ctor::ar_flags arflags; // flags for archiver
	ctor::asm_flags 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, ...
	bool dry_run{false};
};

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::build};
	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(std::function<ctor::build_configurations (const ctor::settings&)> cb,
        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::build};
	std::variant<ctor::external_manual> external;
};

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

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

// Convenience macro - ugly but keeps things simple(r)
#define CTOR_CONCAT(a, b) CTOR_CONCAT_INNER(a, b)
#define CTOR_CONCAT_INNER(a, b) a ## b
#define CTOR_UNIQUE_NAME(base) CTOR_CONCAT(base, __LINE__)
#define REG(cb) namespace { int CTOR_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-cxx";
constexpr auto host_ar = "host-ar";
constexpr auto host_ld = "host-ld";

constexpr auto build_cc = "build-cc";
constexpr auto build_cxx = "build-cxx";
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
{
	bool has(const std::string& key) const;
	std::string get(const std::string& key, const std::string& default_value = {}) const;

	ctor::toolchain host_toolchain{ctor::toolchain::none};
	ctor::arch host_arch{ctor::arch::unknown};

	ctor::toolchain build_toolchain{ctor::toolchain::none};
	ctor::arch build_arch{ctor::arch::unknown};

	std::vector<std::string> args; // vector of arguments used when last calling configure

	std::string getenv(const std::string& key) const;
	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 ctor::configuration& get_configuration();

} // ctor::