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

#include <filesystem>
#include <iostream>
#include <sstream>

#include <cassert>
#include <cstdio>

#include "util.h"

std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt)
{
	// Adding to this enum should also imply adding to the unit-tests below
	switch(opt)
	{
	case ctor::c_opt::output: stream << "ctor::c_opt::output"; break;
	case ctor::c_opt::debug: stream << "ctor::c_opt::debug"; break;
	case ctor::c_opt::warn_all: stream << "ctor::c_opt::warn_all"; break;
	case ctor::c_opt::warn_conversion: stream << "ctor::c_opt::warn_conversion"; break;
	case ctor::c_opt::warn_shadow: stream << "ctor::c_opt::warn_shadow"; break;
	case ctor::c_opt::warn_extra: stream << "ctor::c_opt::warn_extra"; break;
	case ctor::c_opt::warnings_as_errors: stream << "ctor::c_opt::warnings_as_errors"; break;
	case ctor::c_opt::generate_dep_tree: stream << "ctor::c_opt::generate_dep_tree"; break;
	case ctor::c_opt::no_link: stream << "ctor::c_opt::no_link"; break;
	case ctor::c_opt::include_path: stream << "ctor::c_opt::include_path"; break;
	case ctor::c_opt::c_std: stream << "ctor::c_opt::c_std"; break;
	case ctor::c_opt::optimization: stream << "ctor::c_opt::optimization"; break;
	case ctor::c_opt::position_independent_code: stream << "ctor::c_opt::position_independent_code"; break;
	case ctor::c_opt::position_independent_executable: stream << "ctor::c_opt::position_independent_executable"; break;
	case ctor::c_opt::define: stream << "ctor::c_opt::define"; break;
	case ctor::c_opt::custom: stream << "ctor::c_opt::custom"; break;
	}

	return stream;
}

std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt)
{
	// Adding to this enum should also imply adding to the unit-tests below
	switch(opt)
	{
	case ctor::cxx_opt::output: stream << "ctor::cxx_opt::output"; break;
	case ctor::cxx_opt::debug: stream << "ctor::cxx_opt::debug"; break;
	case ctor::cxx_opt::warn_all: stream << "ctor::cxx_opt::warn_all"; break;
	case ctor::cxx_opt::warn_conversion: stream << "ctor::cxx_opt::warn_conversion"; break;
	case ctor::cxx_opt::warn_shadow: stream << "ctor::cxx_opt::warn_shadow"; break;
	case ctor::cxx_opt::warn_extra: stream << "ctor::cxx_opt::warn_extra"; break;
	case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break;
	case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break;
	case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break;
	case ctor::cxx_opt::include_path: stream << "ctor::cxx_opt::include_path"; break;
	case ctor::cxx_opt::cpp_std: stream << "ctor::cxx_opt::cpp_std"; break;
	case ctor::cxx_opt::optimization: stream << "ctor::cxx_opt::optimization"; break;
	case ctor::cxx_opt::position_independent_code: stream << "ctor::cxx_opt::position_independent_code"; break;
	case ctor::cxx_opt::position_independent_executable: stream << "ctor::cxx_opt::position_independent_executable"; break;
	case ctor::cxx_opt::define: stream << "ctor::cxx_opt::define"; break;
	case ctor::cxx_opt::custom: stream << "ctor::cxx_opt::custom"; break;
	}

	return stream;
}

std::ostream& operator<<(std::ostream& stream, const ctor::ld_opt& opt)
{
	// Adding to this enum should also imply adding to the unit-tests below
	switch(opt)
	{
	case ctor::ld_opt::output: stream << "ctor::ld_opt::output"; break;
	case ctor::ld_opt::strip: stream << "ctor::ld_opt::strip"; break;
	case ctor::ld_opt::warn_all: stream << "ctor::ld_opt::warn_all"; break;
	case ctor::ld_opt::warnings_as_errors: stream << "ctor::ld_opt::warnings_as_errors"; break;
	case ctor::ld_opt::library_path: stream << "ctor::ld_opt::library_path"; break;
	case ctor::ld_opt::link: stream << "ctor::ld_opt::link"; break;
	case ctor::ld_opt::cpp_std: stream << "ctor::ld_opt::cpp_std"; break;
	case ctor::ld_opt::build_shared: stream << "ctor::ld_opt::build_shared"; break;
	case ctor::ld_opt::threads: stream << "ctor::ld_opt::threads"; break;
	case ctor::ld_opt::position_independent_code: stream << "ctor::ld_opt::position_independent_code"; break;
	case ctor::ld_opt::position_independent_executable: stream << "ctor::ld_opt::position_independent_executable"; break;
	case ctor::ld_opt::custom: stream << "ctor::ld_opt::custom"; break;
	}

	return stream;
}

std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt)
{
	// Adding to this enum should also imply adding to the unit-tests below
	switch(opt)
	{
	case ctor::ar_opt::replace: stream << "ctor::ar_opt::replace"; break;
	case ctor::ar_opt::add_index: stream << "ctor::ar_opt::add_index"; break;
	case ctor::ar_opt::create: stream << "ctor::ar_opt::create"; break;
	case ctor::ar_opt::output: stream << "ctor::ar_opt::output"; break;
	case ctor::ar_opt::custom: stream << "ctor::ar_opt::custom"; break;
	}

	return stream;
}

std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt)
{
	// Adding to this enum should also imply adding to the unit-tests below
	switch(opt)
	{
	case ctor::asm_opt::custom: stream << "ctor::asm_opt::custom"; break;
	}

	return stream;
}

ctor::toolchain getToolChain(const std::string& compiler)
{
	std::filesystem::path cc(compiler);
	auto cc_cmd = cc.stem().string();

	// Note: "g++" is a substring of "clang++" so "clang++" must be tested first.
	if(cc_cmd.find("clang++") != std::string::npos ||
	   cc_cmd.find("clang") != std::string::npos)
	{
		return ctor::toolchain::clang;
	}
	else if(cc_cmd.find("g++") != std::string::npos ||
	        cc_cmd.find("gcc") != std::string::npos ||
	        cc_cmd.find("mingw") != std::string::npos)
	{
		return ctor::toolchain::gcc;
	}

	std::cerr << "Unsupported output system.\n";
	return ctor::toolchain::gcc;
}

ctor::toolchain getToolChain(ctor::output_system system)
{
	const auto& cfg = ctor::get_configuration();
	if(system == ctor::output_system::host)
	{
		if(cfg.host_toolchain != ctor::toolchain::none)
		{
			return cfg.host_toolchain;
		}
		return getToolChain(cfg.get(ctor::cfg::host_cxx, "g++"));
	}
	else
	{
		if(cfg.build_toolchain != ctor::toolchain::none)
		{
			return cfg.build_toolchain;
		}
		return getToolChain(cfg.get(ctor::cfg::build_cxx, "g++"));
	}
}

namespace gcc {
std::string get_arch(ctor::output_system system)
{
	std::string cmd;

	const auto& c = ctor::get_configuration();
	switch(system)
	{
	case ctor::output_system::host:
		cmd = c.get(ctor::cfg::host_cxx, "/usr/bin/g++");
		break;
	case ctor::output_system::build:
		cmd = c.get(ctor::cfg::build_cxx, "/usr/bin/g++");
		break;
	}

	cmd += " -v";
	cmd += " 2>&1";
	auto pipe = popen(cmd.data(), "r");
	if(!pipe)
	{
		std::cerr << "Could not run compiler " << cmd << "\n";
		return {};//ctor::arch::unknown;
	}

	std::string arch;
	while(!feof(pipe))
	{
		constexpr auto buffer_size{1024};
		std::array<char, buffer_size> buf{};
		if(fgets(buf.data(), buf.size(), pipe) != nullptr)
		{
			arch = buf.data();
			if(arch.starts_with("Target:"))
			{
				break;
			}
		}
	}

	pclose(pipe);

	// Remove trailing newline
	if(arch.ends_with("\n"))
	{
		arch = arch.substr(0, arch.length() - 1);
	}

	// Remove 'Target: ' prefix
	constexpr auto prefix_length{8};
	arch = arch.substr(prefix_length);
	return arch;
}

ctor::arch get_arch(const std::string& str)
{
	// gcc -v 2>&1 | grep Target
	// Target: x86_64-apple-darwin19.6.0
	// Target: x86_64-pc-linux-gnu
	// Target: arm-linux-gnueabihf
	// Target: x86_64-linux-gnu
	// Target: x86_64-unknown-freebsd13.1
	// # x86_64-w64-mingw32-c++-win32 -v 2>&1 | grep Target
	// Target: x86_64-w64-mingw32
	// # i686-w64-mingw32-c++-win32 -v 2>&1 | grep Target
	// Target: i686-w64-mingw32

	if(str.find("apple") != std::string::npos ||
	   str.find("darwin") != std::string::npos)
	{
		return ctor::arch::apple;
	}

	if(str.find("linux") != std::string::npos ||
	   str.find("bsd") != std::string::npos)
	{
		return ctor::arch::unix;
	}

	if(str.find("mingw") != std::string::npos)
	{
		return ctor::arch::windows;
	}

	std::cerr << "Could not deduce gcc arch from '" << str << "'" << std::endl;

	return ctor::arch::unknown;
}

ctor::c_flag c_option(const std::string& flag)
{
	if(flag.starts_with("-I"))
	{
		std::string path = flag.substr(2);
		path.erase(0, path.find_first_not_of(' '));
		return { ctor::c_opt::include_path, path };
	}

	if(flag.starts_with("-std="))
	{
		std::string std = flag.substr(5);
		return { ctor::c_opt::c_std, std };
	}

	if(flag.starts_with("-O"))
	{
		std::string opt = flag.substr(2, 1);
		return { ctor::c_opt::optimization, opt };
	}

	if(flag.starts_with("-Wall"))
	{
		return { ctor::c_opt::warn_all };
	}

	if(flag.starts_with("-Wconversion"))
	{
		return { ctor::c_opt::warn_conversion};
	}

	if(flag.starts_with("-Wshadow"))
	{
		return { ctor::c_opt::warn_shadow};
	}

	if(flag.starts_with("-Wextra"))
	{
		return { ctor::c_opt::warn_extra};
	}

	if(flag.starts_with("-Werror"))
	{
		return { ctor::c_opt::warnings_as_errors };
	}

	if(flag.starts_with("-g"))
	{
		return { ctor::c_opt::debug };
	}

	if(flag.starts_with("-D"))
	{
		std::string def = flag.substr(2);
		auto pos = def.find('=');
		if(pos != def.npos)
		{
			return { ctor::c_opt::define, def.substr(0, pos), def.substr(pos + 1) };
		}
		else
		{
			return { ctor::c_opt::define, def };
		}
	}
	return { ctor::c_opt::custom, flag };
}

ctor::cxx_flag cxx_option(const std::string& flag)
{
	if(flag.starts_with("-I"))
	{
		std::string path = flag.substr(2);
		path.erase(0, path.find_first_not_of(' '));
		return { ctor::cxx_opt::include_path, path };
	}

	if(flag.starts_with("-std="))
	{
		std::string std = flag.substr(5);
		return { ctor::cxx_opt::cpp_std, std };
	}

	if(flag.starts_with("-O"))
	{
		std::string opt = flag.substr(2, 1);
		return { ctor::cxx_opt::optimization, opt };
	}

	if(flag.starts_with("-Wall"))
	{
		return { ctor::cxx_opt::warn_all };
	}

	if(flag.starts_with("-Werror"))
	{
		return { ctor::cxx_opt::warnings_as_errors };
	}

	if(flag.starts_with("-Wconversion"))
	{
		return { ctor::cxx_opt::warn_conversion};
	}

	if(flag.starts_with("-Wshadow"))
	{
		return { ctor::cxx_opt::warn_shadow};
	}

	if(flag.starts_with("-Wextra"))
	{
		return { ctor::cxx_opt::warn_extra};
	}

	if(flag.starts_with("-g"))
	{
		return { ctor::cxx_opt::debug };
	}

	if(flag.starts_with("-D"))
	{
		std::string def = flag.substr(2);
		auto pos = def.find('=');
		if(pos != def.npos)
		{
			return { ctor::cxx_opt::define, def.substr(0, pos), def.substr(pos + 1) };
		}
		else
		{
			return { ctor::cxx_opt::define, def };
		}
	}

	return { ctor::cxx_opt::custom, flag };
}

ctor::ld_flag ld_option(const std::string& flag)
{
	if(flag.starts_with("-L"))
	{
		std::string path = flag.substr(2);
		path.erase(0, path.find_first_not_of(' '));
		return { ctor::ld_opt::library_path, path };
	}

	if(flag.starts_with("-pthread"))
	{
		return { ctor::ld_opt::threads };
	}

	return { ctor::ld_opt::custom, flag };
}

ctor::ar_flag ar_option(const std::string& flag)
{
	return { ctor::ar_opt::custom, flag };
}

std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg,
                                    const std::string& arg2)
{
	switch(opt)
	{
	case ctor::cxx_opt::output:
		return {"-o", arg};
	case ctor::cxx_opt::debug:
		return {"-g"};
	case ctor::cxx_opt::warn_all:
		return {"-Wall"};
	case ctor::cxx_opt::warn_conversion:
		return {"-Wconversion"};
	case ctor::cxx_opt::warn_shadow:
		return {"-Wshadow"};
	case ctor::cxx_opt::warn_extra:
		return {"-Wextra"};
	case ctor::cxx_opt::warnings_as_errors:
		return {"-Werror"};
	case ctor::cxx_opt::generate_dep_tree:
		return {"-MMD"};
	case ctor::cxx_opt::no_link:
		return {"-c"};
	case ctor::cxx_opt::include_path:
		return {"-I" + arg};
	case ctor::cxx_opt::cpp_std:
		return {"-std=" + arg};
	case ctor::cxx_opt::optimization:
		return {"-O" + arg};
	case ctor::cxx_opt::position_independent_code:
		return {"-fPIC"};
	case ctor::cxx_opt::position_independent_executable:
		return {"-fPIE"};
	case ctor::cxx_opt::define:
		if(!arg2.empty())
		{
			return {"-D" + arg + "=" + arg2};
		}
		return {"-D" + arg};
	case ctor::cxx_opt::custom:
		return {arg};
	}

	std::cerr << "Unsupported compiler option.\n";
	return {};
}

std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg,
                                  const std::string& arg2)
{
	switch(opt)
	{
	case ctor::c_opt::output:
		return {"-o", arg};
	case ctor::c_opt::debug:
		return {"-g"};
	case ctor::c_opt::warn_all:
		return {"-Wall"};
	case ctor::c_opt::warn_conversion:
		return {"-Wconversion"};
	case ctor::c_opt::warn_shadow:
		return {"-Wshadow"};
	case ctor::c_opt::warn_extra:
		return {"-Wextra"};
	case ctor::c_opt::warnings_as_errors:
		return {"-Werror"};
	case ctor::c_opt::generate_dep_tree:
		return {"-MMD"};
	case ctor::c_opt::no_link:
		return {"-c"};
	case ctor::c_opt::include_path:
		return {"-I" + arg};
	case ctor::c_opt::c_std:
		return {"-std=" + arg};
	case ctor::c_opt::optimization:
		return {"-O" + arg};
	case ctor::c_opt::position_independent_code:
		return {"-fPIC"};
	case ctor::c_opt::position_independent_executable:
		return {"-fPIE"};
	case ctor::c_opt::define:
		if(!arg2.empty())
		{
			return {"-D" + arg + "=" + arg2};
		}
		return {"-D" + arg};
	case ctor::c_opt::custom:
		return {arg};
	}

	std::cerr << "Unsupported compiler option.\n";
	return {};
}

std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg,
                                   [[maybe_unused]]const std::string& arg2)
{
	switch(opt)
	{
	case ctor::ld_opt::output:
		return {"-o", arg};
	case ctor::ld_opt::strip:
		return {"-s"};
	case ctor::ld_opt::warn_all:
		return {"-Wall"};
	case ctor::ld_opt::warnings_as_errors:
		return {"-Werror"};
	case ctor::ld_opt::library_path:
		return {"-L" + arg};
	case ctor::ld_opt::link:
		return {"-l" + arg};
	case ctor::ld_opt::cpp_std:
		return {"-std=" + arg};
	case ctor::ld_opt::build_shared:
		return {"-shared"};
	case ctor::ld_opt::threads:
		return {"-pthread"};
	case ctor::ld_opt::position_independent_code:
		return {"-fPIC"};
	case ctor::ld_opt::position_independent_executable:
		return {"-fPIE"};
	case ctor::ld_opt::custom:
		return {arg};
	}

	std::cerr << "Unsupported compiler option.\n";
	return {};
}

std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg,
                                   [[maybe_unused]]const std::string& arg2)
{
	switch(opt)
	{
	case ctor::ar_opt::replace:
		return {"-r"};
	case ctor::ar_opt::add_index:
		return {"-s"};
	case ctor::ar_opt::create:
		return {"-c"};
	case ctor::ar_opt::output:
		return {arg};
	case ctor::ar_opt::custom:
		return {arg};
	}

	std::cerr << "Unsupported compiler option.\n";
	return {};
}

std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg,
                                    [[maybe_unused]]const std::string& arg2)
{
	switch(opt)
	{
	case ctor::asm_opt::custom:
		return {arg};
	}

	std::cerr << "Unsupported compiler option.\n";
	return {};
}
} // gcc::

std::string get_arch(ctor::output_system system)
{
	auto toolchain = getToolChain(system);
	switch(toolchain)
	{
	case ctor::toolchain::clang:
	case ctor::toolchain::gcc:
		return gcc::get_arch(system);
	case ctor::toolchain::any:
	case ctor::toolchain::none:
		break;
	}
	return {};
}

ctor::arch get_arch(ctor::output_system system, const std::string& str)
{
	auto toolchain = getToolChain(system);
	switch(toolchain)
	{
	case ctor::toolchain::clang:
	case ctor::toolchain::gcc:
		return gcc::get_arch(str);
	case ctor::toolchain::any:
	case ctor::toolchain::none:
		break;
	}
	return ctor::arch::unknown;
}

std::vector<std::string> c_option(ctor::toolchain toolchain,
                                  ctor::c_opt opt,
                                  const std::string& arg,
                                  const std::string& arg2)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::c_option(opt, arg, arg2);
	case ctor::toolchain::any:
		{
			std::ostringstream ss;
			ss << "{" << opt;
			if(!arg.empty())
			{
				ss << ", \"" << arg << "\"";
			}
			if(!arg2.empty())
			{
				ss << ", \"" << arg2 << "\"";
			}
			ss << "}";
			return { ss.str() };
		}
	case ctor::toolchain::none:
		break;
	}

	std::cerr << "Unsupported tool-chain.\n";
	return {};
}

std::vector<std::string> cxx_option(ctor::toolchain toolchain,
                                    ctor::cxx_opt opt,
                                    const std::string& arg,
                                    const std::string& arg2)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::cxx_option(opt, arg, arg2);
	case ctor::toolchain::any:
		{
			std::ostringstream ss;
			ss << "{" << opt;
			if(!arg.empty())
			{
				ss << ", \"" << arg << "\"";
			}
			if(!arg2.empty())
			{
				ss << ", \"" << arg2 << "\"";
			}
			ss << "}";
			return { ss.str() };
		}
	case ctor::toolchain::none:
		break;
	}

	std::cerr << "Unsupported tool-chain.\n";
	return {};
}

std::vector<std::string> ld_option(ctor::toolchain toolchain,
                                   ctor::ld_opt opt,
                                   const std::string& arg,
                                   const std::string& arg2)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::ld_option(opt, arg, arg2);
	case ctor::toolchain::any:
		{
			std::ostringstream ss;
			ss << "{" << opt;
			if(!arg.empty())
			{
				ss << ", \"" << arg << "\"";
			}
			if(!arg2.empty())
			{
				ss << ", \"" << arg2 << "\"";
			}
			ss << "}";
			return { ss.str() };
		}
	case ctor::toolchain::none:
		break;
	}

	std::cerr << "Unsupported tool-chain.\n";
	return {};
}

std::vector<std::string> ar_option(ctor::toolchain toolchain,
                                   ctor::ar_opt opt,
                                   const std::string& arg,
                                   const std::string& arg2)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::ar_option(opt, arg, arg2);
	case ctor::toolchain::any:
		{
			std::ostringstream ss;
			ss << "{" << opt;
			if(!arg.empty())
			{
				ss << ", \"" << arg << "\"";
			}
			if(!arg2.empty())
			{
				ss << ", \"" << arg2 << "\"";
			}
			ss << "}";
			return { ss.str() };
		}
	case ctor::toolchain::none:
		break;
	}

	std::cerr << "Unsupported tool-chain.\n";
	return {};
}

std::vector<std::string> asm_option(ctor::toolchain toolchain,
                                    ctor::asm_opt opt,
                                    const std::string& arg,
                                    const std::string& arg2)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::asm_option(opt, arg, arg2);
	case ctor::toolchain::any:
		{
			std::ostringstream ss;
			ss << "{" << opt;
			if(!arg.empty())
			{
				ss << ", \"" << arg << "\"";
			}
			if(!arg2.empty())
			{
				ss << ", \"" << arg2 << "\"";
			}
			ss << "}";
			return { ss.str() };
		}
	case ctor::toolchain::none:
		break;
	}

	std::cerr << "Unsupported tool-chain.\n";
	return {};
}


ctor::c_flag c_option(const std::string& flag, ctor::toolchain toolchain)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::c_option(flag);
	case ctor::toolchain::any:
	case ctor::toolchain::none:
		break;
	}

	return { ctor::c_opt::custom, flag };
}

ctor::cxx_flag cxx_option(const std::string& flag, ctor::toolchain toolchain)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::cxx_option(flag);
	case ctor::toolchain::any:
	case ctor::toolchain::none:
		break;
	}

	return { ctor::cxx_opt::custom, flag };
}

ctor::ld_flag ld_option(const std::string& flag, ctor::toolchain toolchain)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::ld_option(flag);
	case ctor::toolchain::any:
	case ctor::toolchain::none:
		break;
	}

	return { ctor::ld_opt::custom, flag };
}

ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
		return gcc::ar_option(flag);
	case ctor::toolchain::any:
	case ctor::toolchain::none:
		break;
	}

	return { ctor::ar_opt::custom, flag };
}

ctor::asm_flag asm_option(const std::string& flag, ctor::toolchain toolchain)
{
	switch(toolchain)
	{
	case ctor::toolchain::gcc:
	case ctor::toolchain::clang:
	case ctor::toolchain::any:
	case ctor::toolchain::none:
		break;
	}

	return { ctor::asm_opt::custom, flag };
}

// Flag to string coversions

std::vector<std::string> to_strings(ctor::toolchain toolchain,
                                    const ctor::c_flag& flag)
{
	if(flag.toolchain == ctor::toolchain::any ||
	   flag.toolchain == toolchain)
	{
		return c_option(toolchain, flag.opt, flag.arg, flag.arg2);
	}

	return {};
}

std::vector<std::string> to_strings(ctor::toolchain toolchain,
                                    const ctor::cxx_flag& flag)
{
	if(flag.toolchain == ctor::toolchain::any ||
	   flag.toolchain == toolchain)
	{
		return cxx_option(toolchain, flag.opt, flag.arg, flag.arg2);
	}

	return {};
}

std::vector<std::string> to_strings(ctor::toolchain toolchain,
                                    const ctor::ld_flag& flag)
{
	if(flag.toolchain == ctor::toolchain::any ||
	   flag.toolchain == toolchain)
	{
		return ld_option(toolchain, flag.opt, flag.arg, flag.arg2);
	}

	return {};
}

std::vector<std::string> to_strings(ctor::toolchain toolchain,
                                    const ctor::ar_flag& flag)
{
	if(flag.toolchain == ctor::toolchain::any ||
	   flag.toolchain == toolchain)
	{
		return ar_option(toolchain, flag.opt, flag.arg, flag.arg2);
	}

	return {};
}

std::vector<std::string> to_strings(ctor::toolchain toolchain,
                                    const ctor::asm_flag& flag)
{
	if(flag.toolchain == ctor::toolchain::any ||
	   flag.toolchain == toolchain)
	{
		return asm_option(toolchain, flag.opt, flag.arg, flag.arg2);
	}

	return {};
}

namespace {
ctor::toolchain guess_toolchain(const std::string& opt)
{
	if(opt.empty())
	{
		return ctor::toolchain::any;
	}

	if(opt[0] == '-')
	{
		return ctor::toolchain::gcc;
	}

	//if(opt[0] == '/')
	//{
	//	return ctor::toolchain::msvc;
	//}
	return ctor::toolchain::any;
}
}

template<>
ctor::flag<ctor::c_opt>::flag(const char* str)
{
	*this = c_option(str, guess_toolchain(str));
}

template<>
ctor::flag<ctor::cxx_opt>::flag(const char* str)
{
	*this = cxx_option(str, guess_toolchain(str));
}

template<>
ctor::flag<ctor::ld_opt>::flag(const char* str)
{
	*this = ld_option(str, guess_toolchain(str));
}

template<>
ctor::flag<ctor::ar_opt>::flag(const char* str)
{
	*this = ar_option(str, guess_toolchain(str));
}

template<>
ctor::flag<ctor::asm_opt>::flag(const char* str)
{
	*this = asm_option(str, guess_toolchain(str));
}


ctor::target_type target_type_from_extension(ctor::toolchain toolchain,
                                             const std::filesystem::path& file)
{
	auto ext = to_lower(file.extension().string());
	// Loosely based on:
	// https://en.wikipedia.org/wiki/List_of_file_formats#Object_code,_executable_files,_shared_and_dynamically_linked_libraries
	if(toolchain == ctor::toolchain::any ||
	   toolchain == ctor::toolchain::gcc ||
	   toolchain == ctor::toolchain::clang)
	{
		if(ext == ".a")
		{
			return ctor::target_type::static_library;
		}

		if(ext == ".so" ||
		   ext == ".dylib")
		{
			return ctor::target_type::dynamic_library;
		}

		if(ext == ".o")
		{
			return ctor::target_type::object;
		}

		if(ext == "" ||
		   ext == ".bin" ||
		   ext == ".run" ||
		   ext == ".out")
		{
			return ctor::target_type::executable;
		}
	}

	if(toolchain == ctor::toolchain::any// ||
	   //toolchain == ctor::toolchain::msvc ||
	   //toolchain == ctor::toolchain::mingw ||
		)
	{
		if(ext == ".lib")
		{
			return ctor::target_type::static_library;
		}

		if(ext == ".dll")
		{
			return ctor::target_type::dynamic_library;
		}

		if(ext == ".obj")
		{
			return ctor::target_type::object;
		}

		if(ext == ".exe" ||
		   ext == ".com")
		{
			return ctor::target_type::executable;
		}
	}

	return ctor::target_type::unknown;
}


std::filesystem::path extension(ctor::toolchain toolchain,
                                ctor::target_type target_type,
                                ctor::output_system system,
                                const std::filesystem::path& file)
{
	auto type = target_type_from_extension(toolchain, file);
	if(type == target_type)
	{
		// File already has the correct extension
		return file;
	}

	const auto& c = ctor::get_configuration();
	ctor::arch arch{};
	switch(system)
	{
	case ctor::output_system::host:
		arch = c.host_arch;
		break;
	case ctor::output_system::build:
		arch = c.build_arch;
		break;
	}

	// This might be before configure - so detection is needed for boostrap
	if(arch == ctor::arch::unknown)
	{
		arch = get_arch(system, get_arch(system));
	}

	std::string ext{file.extension().string()};
	switch(target_type)
	{
	case ctor::target_type::automatic:
		break;
	case ctor::target_type::executable:
	case ctor::target_type::unit_test:
		switch(arch)
		{
		case ctor::arch::unix:
		case ctor::arch::apple:
			ext = "";
			break;
		case ctor::arch::windows:
			ext = ".exe";
			break;
		case ctor::arch::unknown:
			break;
		}
		break;
	case ctor::target_type::static_library:
	case ctor::target_type::unit_test_library:
		switch(arch)
		{
		case ctor::arch::unix:
		case ctor::arch::apple:
			ext = ".a";
			break;
		case ctor::arch::windows:
			ext = ".lib";
			break;
		case ctor::arch::unknown:
			break;
		}
		break;
	case ctor::target_type::dynamic_library:
		switch(arch)
		{
		case ctor::arch::unix:
			ext = ".so";
			break;
		case ctor::arch::apple:
			ext = ".dylib";
			break;
		case ctor::arch::windows:
			ext = ".dll";
			break;
		case ctor::arch::unknown:
			break;
		}
		break;
	case ctor::target_type::object:
		switch(arch)
		{
		case ctor::arch::unix:
		case ctor::arch::apple:
			ext = ".o";
			break;
		case ctor::arch::windows:
			ext = ".obj";
			break;
		case ctor::arch::unknown:
			break;
		}
		break;
	case ctor::target_type::function:
		break;
	case ctor::target_type::unknown:
		break;
	}

	auto output{file};
	output.replace_extension(ext);
	return output;
}