#include <vector>
#include <string>
#include <ostream>

std::ostream& operator<<(std::ostream& stream, const std::vector<std::string>& vs);

#include <uunit.h>

#include <initializer_list>
#include <cassert>

#include <tools.h>

using namespace std::string_literals;

std::ostream& operator<<(std::ostream& stream, const ToolChain& tool_chain)
{
	switch(tool_chain)
	{
	case ToolChain::gcc:
		stream << "ToolChain::gcc";
		break;
	case ToolChain::clang:
		stream << "ToolChain::clang";
		break;
	}
	return stream;
}

std::ostream& operator<<(std::ostream& stream, const std::vector<std::string>& vs)
{
	bool first{true};
	stream << "{ ";
	for(const auto& v : vs)
	{
		if(!first)
		{
			stream << ", ";
		}
		stream << "'" << v << "'";
		first = false;
	}
	stream << " }";

	return stream;
}

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

	return stream;
}

std::ostream& operator<<(std::ostream& stream, const std::pair<opt, std::string>& vs)
{
	stream << "{ " << vs.first << ", " << vs.second << " }";

	return stream;
}

// Controllable getConfiguration stub:
namespace
{
std::string conf_host_cxx{};
std::string conf_build_cxx{};
}
const std::string& getConfiguration(const std::string& key,
                                    const std::string& defval)
{
	if(key == cfg::host_cxx)
	{
		return conf_host_cxx;
	}

	if(key == cfg::build_cxx)
	{
		return conf_build_cxx;
	}

	assert(false); // bad key

	static std::string res{};
	return res;
}

class ToolsTest
	: public uUnit
{
public:
	ToolsTest()
	{
		uTEST(ToolsTest::getToolChain_test);
		uTEST(ToolsTest::getOption_toolchain_test);
		uTEST(ToolsTest::getOption_str_test);
	}

	void getToolChain_test()
	{
		// host
		conf_host_cxx = "/usr/bin/g++";
		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host));

		conf_host_cxx = "/usr/bin/g++-10";
		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host));

		conf_host_cxx = "/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0";
		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host));

		conf_host_cxx = "/usr/bin/clang++";
		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host));

		conf_host_cxx = "/usr/bin/clang++-10";
		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host));

		conf_host_cxx = "/usr/lib/llvm/12/bin/i686-pc-linux-gnu-clang++-12";
		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host));

		// build
		conf_build_cxx = "/usr/bin/g++";
		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build));

		conf_build_cxx = "/usr/bin/g++-10";
		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build));

		conf_build_cxx = "/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0";
		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build));

		conf_build_cxx = "/usr/bin/clang++";
		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build));

		conf_build_cxx = "/usr/bin/clang++-10";
		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build));

		conf_build_cxx = "/usr/lib/llvm/12/bin/i686-pc-linux-gnu-clang++-12";
		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build));
	}

	void getOption_toolchain_test()
	{
		using sv = std::vector<std::string>;

		uUnit::assert_equal(sv{ "-o", "foo" }, getOption(ToolChain::clang, opt::output, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-g" },        getOption(ToolChain::clang, opt::debug), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-s" },        getOption(ToolChain::clang, opt::strip), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Wall" },     getOption(ToolChain::clang, opt::warn_all), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Werror" },   getOption(ToolChain::clang, opt::warnings_as_errors), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-MMD" },      getOption(ToolChain::clang, opt::generate_dep_tree), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-c" },        getOption(ToolChain::clang, opt::no_link), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Ifoo" },     getOption(ToolChain::clang, opt::include_path, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Lfoo" },     getOption(ToolChain::clang, opt::library_path, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-lfoo" },     getOption(ToolChain::clang, opt::link, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-std=foo" },  getOption(ToolChain::clang, opt::cpp_std, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-shared" },   getOption(ToolChain::clang, opt::build_shared), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-pthread" },  getOption(ToolChain::clang, opt::threads), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Ofoo" },     getOption(ToolChain::clang, opt::optimization, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-fPIC" },     getOption(ToolChain::clang, opt::position_independent_code), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-fPIE" },     getOption(ToolChain::clang, opt::position_independent_executable), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-foo" },      getOption(ToolChain::clang, opt::custom, "-foo"), __FILE__, __LINE__);

		uUnit::assert_equal(sv{ "-o", "foo" }, getOption(ToolChain::gcc, opt::output, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-g" },        getOption(ToolChain::gcc, opt::debug), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-s" },        getOption(ToolChain::gcc, opt::strip), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Wall" },     getOption(ToolChain::gcc, opt::warn_all), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Werror" },   getOption(ToolChain::gcc, opt::warnings_as_errors), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-MMD" },      getOption(ToolChain::gcc, opt::generate_dep_tree), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-c" },        getOption(ToolChain::gcc, opt::no_link), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Ifoo" },     getOption(ToolChain::gcc, opt::include_path, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Lfoo" },     getOption(ToolChain::gcc, opt::library_path, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-lfoo" },     getOption(ToolChain::gcc, opt::link, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-std=foo" },  getOption(ToolChain::gcc, opt::cpp_std, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-shared" },   getOption(ToolChain::gcc, opt::build_shared), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-pthread" },  getOption(ToolChain::gcc, opt::threads), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-Ofoo" },     getOption(ToolChain::gcc, opt::optimization, "foo"), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-fPIC" },     getOption(ToolChain::gcc, opt::position_independent_code), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-fPIE" },     getOption(ToolChain::gcc, opt::position_independent_executable), __FILE__, __LINE__);
		uUnit::assert_equal(sv{ "-foo" },      getOption(ToolChain::gcc, opt::custom, "-foo"), __FILE__, __LINE__);
	}

	void getOption_str_test()
	{
		using p = std::pair<opt, std::string>;
		uUnit::assert_equal(p{ opt::include_path, "foo" }, getOption("-Ifoo", ToolChain::gcc), __FILE__, __LINE__);
		uUnit::assert_equal(p{ opt::library_path, "foo" }, getOption("-Lfoo", ToolChain::gcc), __FILE__, __LINE__);

		uUnit::assert_equal(p{ opt::include_path, "foo" }, getOption("-Ifoo", ToolChain::clang), __FILE__, __LINE__);
		uUnit::assert_equal(p{ opt::library_path, "foo" }, getOption("-Lfoo", ToolChain::clang), __FILE__, __LINE__);
	}



};

// Registers the fixture into the 'registry'
static ToolsTest test;