#include <argparser.h>

#include <iostream>
#include <string>

std::ostream& operator<<(std::ostream& ostr, const arg::error& err)
{
	switch(err)
	{
	case arg::error::invalid_arg:
		ostr << "arg::error::invalid_arg";
		break;
	case arg::error::invalid_opt:
		ostr << "arg::error::invalid_opt";
		break;
	case arg::error::missing_arg:
		ostr << "arg::error::missing_arg";
		break;
	}
	return ostr;
}

#include <uunit.h>

class ArgParserTest
	: public uUnit
{
public:
	ArgParserTest()
	{
		uTEST(ArgParserTest::test_zero);
		uTEST(ArgParserTest::test_one);
		uTEST(ArgParserTest::test_many);
		uTEST(ArgParserTest::test_exceptional);
		uTEST(ArgParserTest::test_err_callback);
		uTEST(ArgParserTest::test_pos_callback);
		uTEST(ArgParserTest::test_grouped);
		uTEST(ArgParserTest::test_nullprogram);
	}

	void test_zero()
	{
		const char* const argv[] = { "app-name" };
		int argc = sizeof(argv)/sizeof(*argv);

		arg::Parser<int> args(argc, argv);

		auto res = args.parse();
		uASSERT_EQUAL(0, res);
	}

	void test_one()
	{
		const char* argv[] = { "app-name", "-x", "42" };
		int argc = sizeof(argv)/sizeof(*argv);

		arg::Parser<int> args(argc, argv);

		int x{};
		args.add('x', "--long-x",
		         std::function([&](int i){ x = i; return 0;}), "Help x");

		auto res = args.parse();
		uASSERT_EQUAL(0, res);
		uASSERT_EQUAL(42, x);
	}

	void test_many()
	{
		const char* argv[] = { "app-name", "-x", "42", "-y17", "-z",
		                       "--long-X=12", "--long-Y", "18" };
		int argc = sizeof(argv)/sizeof(*argv);

		arg::Parser<int> args(argc, argv);

		int x{};
		int y{};
		bool z{false};
		int X{};
		int Y{};

		args.add('x', "--long-x",
		         std::function([&](int i){ x = i; return 0;}), "Help x");

		args.add('y', "--long-y",
		         std::function([&](int i){ y = i; return 0;}), "Help y");

		args.add('z', "--long-z",
		         std::function([&](){ z = true; return 0;}), "Help z");

		args.add('X', "--long-X",
		         std::function([&](int i){ X = i; return 0;}), "Help X");

		args.add('Y', "--long-Y",
		         std::function([&](int i){ Y = i; return 0;}), "Help Y");

		auto res = args.parse();
		uASSERT_EQUAL(0, res);
		uASSERT_EQUAL(42, x);
		uASSERT_EQUAL(17, y);
		uASSERT_EQUAL(true, z);
		uASSERT_EQUAL(12, X);
		uASSERT_EQUAL(18, Y);
	}

	void test_exceptional()
	{

		{ // Missing arg at trailing opt
			const char* argv[] = { "app-name", "-x" };
			int argc = sizeof(argv)/sizeof(*argv);
			arg::Parser<int> args(argc, argv);

			int x{};
			int y{};

			args.add('x', "--long-x",
			         std::function([&](int i){ x = i; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](int i){ y = i; return 0;}), "Help y");

			auto res = args.parse();
			uASSERT_EQUAL(1, res);
		}

		{ // Missing arg before other opt
			const char* argv[] = { "app-name", "-x", "-y" };
			int argc = sizeof(argv)/sizeof(*argv);
			arg::Parser<int> args(argc, argv);

			int x{};
			int y{};

			args.add('x', "--long-x",
			         std::function([&](int i){ x = i; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](int i){ y = i; return 0;}), "Help y");

			auto res = args.parse();
			uASSERT_EQUAL(1, res);
		}

		{ // Unknown arg
			const char* argv[] = { "app-name", "-T" };
			int argc = sizeof(argv)/sizeof(*argv);
			arg::Parser<int> args(argc, argv);

			int x{};
			int y{};

			args.add('x', "--long-x",
			         std::function([&](int i){ x = i; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](int i){ y = i; return 0;}), "Help y");

			auto res = args.parse();
			uASSERT_EQUAL(1, res);
		}
	}

	void test_err_callback()
	{
		using namespace std::string_literals;

		{ // Missing arg at trailing opt
			const char* argv[] = { "app-name", "-x" };
			int argc = sizeof(argv)/sizeof(*argv);
			arg::Parser<int> args(argc, argv);

			int x{};
			args.add('x', "--long-x",
			         std::function([&](int i){ x = i; return 0;}), "Help x");

			bool called{false};
			args.set_err_cb(
				[&](arg::error err, std::string_view opt)
				{
					called = true;
					uASSERT_EQUAL(arg::error::missing_arg, err);
					uASSERT_EQUAL("-x"s, opt);
				});
			auto res = args.parse();
			uASSERT_EQUAL(1, res);
			uASSERT(called);
		}

		{ // Invalid arg format
			const char* argv[] = { "app-name", "-x", "abc" };
			int argc = sizeof(argv)/sizeof(*argv);
			arg::Parser<int> args(argc, argv);

			int x{};
			args.add('x', "--long-x",
			         std::function([&](int i){ x = i; return 0;}), "Help x");

			bool called{false};
			args.set_err_cb(
				[&](arg::error err, std::string_view opt)
				{
					called = true;
					uASSERT_EQUAL(arg::error::invalid_arg, err);
					uASSERT_EQUAL("abc"s, opt);
				});
			auto res = args.parse();
			uASSERT_EQUAL(1, res);
			uASSERT(called);
		}

		{ // Invalid opt
			const char* argv[] = { "app-name", "-y" };
			int argc = sizeof(argv)/sizeof(*argv);
			arg::Parser<int> args(argc, argv);

			int x{};
			args.add('x', "--long-x",
			         std::function([&](int i){ x = i; return 0;}), "Help x");

			bool called{false};
			args.set_err_cb(
				[&](arg::error err, std::string_view opt)
				{
					called = true;
					uASSERT_EQUAL(arg::error::invalid_opt, err);
					uASSERT_EQUAL("-y"s, opt);
				});
			auto res = args.parse();
			uASSERT_EQUAL(1, res);
			uASSERT(called);
		}
	}

	void test_pos_callback()
	{
		using namespace std::string_literals;
		const char* argv[] =
			{ "app-name", "foo", "-x", "42", "bar", "-X43", "-Y", "42" };
		int argc = sizeof(argv)/sizeof(*argv);
		arg::Parser<int, std::optional<int>> args(argc, argv);

		int x{};
		int X{};
		int Y{};
		args.add('x', "--long-x",
		         std::function([&](int i){ x = i; return 0;}), "Help x");

		args.add('X', "--opt-x",
		         std::function([&](std::optional<int> i)
		         {
			         uASSERT(i.has_value());
			         X = *i;
			         return 0;
		         }),
		         "Help X");

		args.add('Y', "--opt-y",
		         std::function([&](std::optional<int> i)
		         {
			         uASSERT(!i.has_value());
			         Y = 1;
			         return 0;
		         }),
		         "Help X");

		std::vector<std::string> pos;
		args.set_pos_cb(
			[&](std::string_view sv)
			{
				pos.push_back(std::string(sv));
				return 0;
			});
		auto res = args.parse();
		uASSERT_EQUAL(0, res);
		uASSERT_EQUAL(3u, pos.size());
		uASSERT_EQUAL("foo"s, pos[0]);
		uASSERT_EQUAL("bar"s, pos[1]);
		uASSERT_EQUAL("42"s, pos[2]);
		uASSERT_EQUAL(42, x);
		uASSERT_EQUAL(43, X);
		uASSERT_EQUAL(1, Y);
	}

	void test_grouped()
	{
		{
			const char* argv[] = { "app-name", "-xyz", "42" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<int> args(argc, argv);

			bool x{false};
			bool y{false};
			int z{};
			args.add('x', "--long-x",
			         std::function([&](){ x = true; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](){ y = true; return 0;}), "Help y");

			args.add('z', "--long-z",
			         std::function([&](int i){ z = i; return 0;}), "Help z");

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT(x);
			uASSERT(y);
			uASSERT_EQUAL(42, z);
		}

		{
			const char* argv[] = { "app-name", "-xyz42" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<int> args(argc, argv);

			bool x{false};
			bool y{false};
			int z{};
			args.add('x', "--long-x",
			         std::function([&](){ x = true; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](){ y = true; return 0;}), "Help y");

			args.add('z', "--long-z",
			         std::function([&](int i){ z = i; return 0;}), "Help z");

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT(x);
			uASSERT(y);
			uASSERT_EQUAL(42, z);
		}


		{
			const char* argv[] = { "app-name", "-xyz42" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<int, std::optional<int>> args(argc, argv);

			bool x{false};
			bool y{false};
			int z{};
			args.add('x', "--long-x",
			         std::function([&](){ x = true; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](){ y = true; return 0;}), "Help y");

			args.add('z', "--long-z",
			         std::function([&](std::optional<int> i)
			         {
				         uASSERT(i.has_value());
				         z = *i; return 0;
			         }), "Help z");

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT(x);
			uASSERT(y);
			uASSERT_EQUAL(42, z);
		}

		{
			const char* argv[] = { "app-name", "-xyz" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<int, std::optional<int>> args(argc, argv);

			bool x{false};
			bool y{false};
			int z{};
			args.add('x', "--long-x",
			         std::function([&](){ x = true; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](){ y = true; return 0;}), "Help y");

			args.add('z', "--long-z",
			         std::function([&](std::optional<int> i)
			         {
				         uASSERT(!i.has_value());
				         z = 1; return 0;
			         }), "Help z");

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT(x);
			uASSERT(y);
			uASSERT_EQUAL(1, z);
		}

		{
			const char* argv[] = { "app-name", "-xyz", "42" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<int, std::optional<int>> args(argc, argv);

			bool x{false};
			bool y{false};
			int z{};
			args.add('x', "--long-x",
			         std::function([&](){ x = true; return 0;}), "Help x");

			args.add('y', "--long-y",
			         std::function([&](){ y = true; return 0;}), "Help y");

			args.add('z', "--long-z",
			         std::function([&](std::optional<int> i)
			         {
				         uASSERT(!i.has_value());
				         z = 1; return 0;
			         }), "Help z");

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT(x);
			uASSERT(y);
			uASSERT_EQUAL(1, z);
		}
	}

	void test_nullprogram()
	{
		using namespace std::string_literals;
		// Inspired by https://nullprogram.com/blog/2020/08/01/

		//
		// Short options
		//
		{
			const char* argv[] = { "program", "-a", "-b", "-c" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<char> r;
			args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
			args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(3u, r.size());
			uASSERT_EQUAL('a', r[0]);
			uASSERT_EQUAL('b', r[1]);
			uASSERT_EQUAL('c', r[2]);
		}

		{
			const char* argv[] = { "program", "-abc" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<char> r;
			args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
			args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(3u, r.size());
			uASSERT_EQUAL('a', r[0]);
			uASSERT_EQUAL('b', r[1]);
			uASSERT_EQUAL('c', r[2]);
		}

		{
			const char* argv[] = { "program", "-acb" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<char> r;
			args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
			args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(3u, r.size());
			uASSERT_EQUAL('a', r[0]);
			uASSERT_EQUAL('c', r[1]);
			uASSERT_EQUAL('b', r[2]);
		}

		{
			const char* argv[] = { "program", "-i", "input.txt", "-o", "output.txt" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::string> args(argc, argv);

			std::vector<std::string> r;
			args.add('i', {},
			         std::function([&](std::string input)
			         {
				         r.push_back(input);
				         return 0;
			         }), {});

			args.add('o', {},
			         std::function([&](std::string input)
			         {
				         r.push_back(input);
				         return 0;
			         }), {});


			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("input.txt"s, r[0]);
			uASSERT_EQUAL("output.txt"s, r[1]);
		}

		{
			const char* argv[] = { "program", "-iinput.txt", "-ooutput.txt" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::string> args(argc, argv);

			std::vector<std::string> r;
			args.add('i', {},
			         std::function([&](std::string input)
			         {
				         r.push_back(input);
				         return 0;
			         }), {});
			args.add('o', {},
			         std::function([&](std::string input)
			         {
				         r.push_back(input);
				         return 0;
			         }), {});


			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("input.txt"s, r[0]);
			uASSERT_EQUAL("output.txt"s, r[1]);
		}

		{
			const char* argv[] = { "program", "-abco", "output.txt" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::string> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {});
			args.add('o', {},
			         std::function([&](std::string input)
			         {
				         r.push_back(input);
				         return 0;
			         }), {});


			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(4u, r.size());
			uASSERT_EQUAL("a"s, r[0]);
			uASSERT_EQUAL("b"s, r[1]);
			uASSERT_EQUAL("c"s, r[2]);
			uASSERT_EQUAL("output.txt"s, r[3]);
		}

		{
			const char* argv[] = { "program", "-abcooutput.txt" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::string> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {});
			args.add('o', {},
			         std::function([&](std::string input)
			         {
				         r.push_back(input);
				         return 0;
			         }), {});


			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(4u, r.size());
			uASSERT_EQUAL("a"s, r[0]);
			uASSERT_EQUAL("b"s, r[1]);
			uASSERT_EQUAL("c"s, r[2]);
			uASSERT_EQUAL("output.txt"s, r[3]);
		}

		{
			// Optional omitted
			const char* argv[] = { "program", "-c" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::optional<std::string>> args(argc, argv);

			std::vector<std::string> r;
			args.add('c', {},
			         std::function([&](std::optional<std::string> c)
			         {
				         uASSERT(!c.has_value());
				         r.push_back("c");
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(1u, r.size());
			uASSERT_EQUAL("c"s, r[0]);
		}

		{
			// Optional provided
			const char* argv[] = { "program", "-cblue" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::optional<std::string>> args(argc, argv);

			std::vector<std::string> r;
			args.add('c', {},
			         std::function([&](std::optional<std::string> c)
			         {
				         uASSERT(c.has_value());
				         r.push_back(*c);
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(1u, r.size());
			uASSERT_EQUAL("blue"s, r[0]);
		}

		{
			// Optional omitted (blue is a new argument)
			const char* argv[] = { "program", "-c", "blue" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::optional<std::string>> args(argc, argv);

			std::vector<std::string> r;
			args.add('c', {},
			         std::function([&](std::optional<std::string> c)
			         {
				         uASSERT(!c.has_value());
				         r.push_back("c");
				         return 0;
			         }), {});
			args.set_pos_cb(std::function([&](std::string_view pos)
			         {
				         r.push_back(std::string(pos));
				         return 0;
			         }));

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("c"s, r[0]);
			uASSERT_EQUAL("blue"s, r[1]);
		}

		{
			// Two seperate flags
			const char* argv[] = { "program", "-c", "-x" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::optional<std::string>> args(argc, argv);

			std::vector<std::string> r;
			args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {});
			args.add('c', {},
			         std::function([&](std::optional<std::string> c)
			         {
				         uASSERT(!c.has_value());
				         r.push_back("c");
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("c"s, r[0]);
			uASSERT_EQUAL("x"s, r[1]);
		}

		{
			// -c with argument "-x"
			const char* argv[] = { "program", "-c-x" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::optional<std::string>> args(argc, argv);

			std::vector<std::string> r;
			args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {});
			args.add('c', {},
			         std::function([&](std::optional<std::string> c)
			         {
				         uASSERT(c.has_value());
				         r.push_back(*c);
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(1u, r.size());
			uASSERT_EQUAL("-x"s, r[0]);
		}

		{
			const char* argv[] = { "program", "-a", "-b", "foo", "bar" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.set_pos_cb(std::function([&](std::string_view pos)
			         {
				         r.push_back(std::string(pos));
				         return 0;
			         }));

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(4u, r.size());
			uASSERT_EQUAL("a"s, r[0]);
			uASSERT_EQUAL("b"s, r[1]);
			uASSERT_EQUAL("foo"s, r[2]);
			uASSERT_EQUAL("bar"s, r[3]);
		}

		{
			const char* argv[] = { "program", "-b", "-a", "foo", "bar" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.set_pos_cb(std::function([&](std::string_view pos)
			         {
				         r.push_back(std::string(pos));
				         return 0;
			         }));

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(4u, r.size());
			uASSERT_EQUAL("b"s, r[0]);
			uASSERT_EQUAL("a"s, r[1]);
			uASSERT_EQUAL("foo"s, r[2]);
			uASSERT_EQUAL("bar"s, r[3]);
		}

		{
			const char* argv[] = { "program", "-a", "foo", "-b", "bar" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.set_pos_cb(std::function([&](std::string_view pos)
			         {
				         r.push_back(std::string(pos));
				         return 0;
			         }));

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(4u, r.size());
			uASSERT_EQUAL("a"s, r[0]);
			uASSERT_EQUAL("foo"s, r[1]);
			uASSERT_EQUAL("b"s, r[2]);
			uASSERT_EQUAL("bar"s, r[3]);
		}

		{
			const char* argv[] = { "program", "foo", "-a", "-b", "bar" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.set_pos_cb(std::function([&](std::string_view pos)
			         {
				         r.push_back(std::string(pos));
				         return 0;
			         }));

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(4u, r.size());
			uASSERT_EQUAL("foo"s, r[0]);
			uASSERT_EQUAL("a"s, r[1]);
			uASSERT_EQUAL("b"s, r[2]);
			uASSERT_EQUAL("bar"s, r[3]);
		}

		{
			const char* argv[] = { "program", "foo", "bar", "-a", "-b" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.set_pos_cb(std::function([&](std::string_view pos)
			         {
				         r.push_back(std::string(pos));
				         return 0;
			         }));

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(4u, r.size());
			uASSERT_EQUAL("foo"s, r[0]);
			uASSERT_EQUAL("bar"s, r[1]);
			uASSERT_EQUAL("a"s, r[2]);
			uASSERT_EQUAL("b"s, r[3]);
		}

		{
			const char* argv[] = { "program", "-a", "-b", "--", "-x", "foo", "bar" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
			args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
			args.set_pos_cb(std::function([&](std::string_view pos)
			         {
				         r.push_back(std::string(pos));
				         return 0;
			         }));

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(5u, r.size());
			uASSERT_EQUAL("a"s, r[0]);
			uASSERT_EQUAL("b"s, r[1]);
			uASSERT_EQUAL("-x"s, r[2]);
			uASSERT_EQUAL("foo"s, r[3]);
			uASSERT_EQUAL("bar"s, r[4]);
		}

		//
		// Long options
		//
		{
			const char* argv[] = { "program", "--sort" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add({}, "--sort",
			         std::function([&](){ r.push_back("sort"); return 0;}), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(1u, r.size());
			uASSERT_EQUAL("sort"s, r[0]);
		}

		{
			const char* argv[] = { "program", "--no-sort" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<> args(argc, argv);

			std::vector<std::string> r;
			args.add({}, "--no-sort",
			         std::function([&](){ r.push_back("no-sort"); return 0;}), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(1u, r.size());
			uASSERT_EQUAL("no-sort"s, r[0]);
		}

		{
			const char* argv[] =
				{ "program", "--output", "output.txt", "--block-size", "1024" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::string, int> args(argc, argv);

			std::vector<std::string> r;
			args.add({}, "--output",
			         std::function([&](std::string output)
			         {
				         r.push_back(output);
				         return 0;
			         }), {});
			args.add({}, "--block-size",
			         std::function([&](int block_size)
			         {
				         r.push_back("["s + std::to_string(block_size) + "]"s);
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("output.txt"s, r[0]);
			uASSERT_EQUAL("[1024]"s, r[1]);
		}

		{
			const char* argv[] =
				{ "program", "--output=output.txt", "--block-size=1024" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::string, int> args(argc, argv);

			std::vector<std::string> r;
			args.add({}, "--output",
			         std::function([&](std::string output)
			         {
				         r.push_back(output);
				         return 0;
			         }), {});
			args.add({}, "--block-size",
			         std::function([&](int block_size)
			         {
				         r.push_back("["s + std::to_string(block_size) + "]"s);
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("output.txt"s, r[0]);
			uASSERT_EQUAL("[1024]"s, r[1]);
		}

		{
			const char* argv[] =
				{ "program", "--color", "--reverse" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::optional<std::string>> args(argc, argv);

			std::vector<std::string> r;
			args.add({}, "--color",
			         std::function([&](std::optional<std::string> color)
			         {
				         uASSERT(!color.has_value());
				         r.push_back("color");
				         return 0;
			         }), {});
			args.add({}, "--reverse",
			         std::function([&]()
			         {
				         r.push_back("reverse");
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("color"s, r[0]);
			uASSERT_EQUAL("reverse"s, r[1]);
		}

		{
			const char* argv[] =
				{ "program", "--color=never", "--reverse" };
			int argc = sizeof(argv)/sizeof(*argv);

			arg::Parser<std::optional<std::string>> args(argc, argv);

			std::vector<std::string> r;
			args.add({}, "--color",
			         std::function([&](std::optional<std::string> color)
			         {
				         uASSERT(color.has_value());
				         r.push_back(*color);
				         return 0;
			         }), {});
			args.add({}, "--reverse",
			         std::function([&]()
			         {
				         r.push_back("reverse");
				         return 0;
			         }), {});

			auto res = args.parse();
			uASSERT_EQUAL(0, res);
			uASSERT_EQUAL(2u, r.size());
			uASSERT_EQUAL("never"s, r[0]);
			uASSERT_EQUAL("reverse"s, r[1]);
		}

	}
};

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