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

#include <set>
#include <algorithm>

#include "pointerlist.h"

class PointerListTest
	: public uUnit
{
public:
	PointerListTest()
	{
		uTEST(PointerListTest::test_zom_pointerlist_push);
		uTEST(PointerListTest::test_zom_pointerlist_from_args);
		uTEST(PointerListTest::test_zom_envmap_insert);
		uTEST(PointerListTest::test_zom_envmap_from_env);
		uTEST(PointerListTest::test_exceptional_env);
	}

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

		{ // Zero
			PointerList args;
			uASSERT_EQUAL(0u, args.size());
			auto [argc, argv] = args.get();
			uASSERT_EQUAL(0, argc);
			uASSERT(nullptr != argv);
			uASSERT_EQUAL(nullptr, argv[0]);
		}

		{ // One
			PointerList args;
			args.push_back("hello");
			uASSERT_EQUAL(1u, args.size());
			auto [argc, argv] = args.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello"s, std::string(argv[0]));
		}

		{ // Many
			PointerList args;
			args.push_back("hello");
			args.push_back("dear");
			args.push_back("world");
			uASSERT_EQUAL(3u, args.size());
			auto [argc, argv] = args.get();
			uASSERT_EQUAL(3, argc);
			uASSERT_EQUAL("hello"s, std::string(argv[0]));
			uASSERT_EQUAL("dear"s, std::string(argv[1]));
			uASSERT_EQUAL("world"s, std::string(argv[2]));
		}
	}

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

		{ // Zero
			PointerList args(0, nullptr);
			uASSERT_EQUAL(0u, args.size());
			auto [argc, argv] = args.get();
			uASSERT_EQUAL(0, argc);
			uASSERT(nullptr != argv);
			uASSERT_EQUAL(nullptr, argv[0]);
		}

		{ // One
			int _argc{1};
			const char* _argv[] = { "hello" };
			PointerList args(_argc, _argv);
			uASSERT_EQUAL(1u, args.size());
			auto [argc, argv] = args.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello"s, std::string(argv[0]));
		}

		{ // Many
			int _argc{3};
			const char* _argv[] = { "hello", "dear", "world" };
			PointerList args(_argc, _argv);
			uASSERT_EQUAL(3u, args.size());
			auto [argc, argv] = args.get();
			uASSERT_EQUAL(3, argc);
			// order must be preserved
			uASSERT_EQUAL("hello"s, std::string(argv[0]));
			uASSERT_EQUAL("dear"s, std::string(argv[1]));
			uASSERT_EQUAL("world"s, std::string(argv[2]));
		}
	}

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

		{ // Zero
			EnvMap env;
			uASSERT_EQUAL(0u, env.size());

			auto [argc, argv] = env.get();
			uASSERT_EQUAL(0, argc);
			uASSERT(nullptr != argv);
			uASSERT_EQUAL(nullptr, argv[0]);

			auto str = env.stringify();
			uASSERT_EQUAL(1u, str.size());
			uASSERT_EQUAL('\0', str[0]);
		}

		{ // One (key only)
			EnvMap env;
			env.insert("hello");
			uASSERT_EQUAL(1u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello="s, std::string(argv[0]));
			uASSERT_EQUAL(""s, env["hello"]);
		}
		{ // One (with value)
			EnvMap env;
			env.insert("hello=world");
			uASSERT_EQUAL(1u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello=world"s, std::string(argv[0]));
			uASSERT_EQUAL("world"s, env["hello"]);

			uASSERT_EQUAL("hello=world\0\0"s, env.stringify());
		}

		{ // Overwrite one
			EnvMap env;
			env.insert("hello=world");
			uASSERT_EQUAL(1u, env.size());
			uASSERT_EQUAL("world"s, env["hello"s]);

			env.insert("hello=foo");
			uASSERT_EQUAL(1u, env.size());
			uASSERT_EQUAL("foo"s, env["hello"s]);
		}

		{ // Many
			EnvMap env;
			env.insert("hello=world");
			env.insert("world=leader");
			env.insert("dear=boar");
			uASSERT_EQUAL(3u, env.size());

			uASSERT_EQUAL("boar"s, env["dear"s]);
			uASSERT_EQUAL("world"s, env["hello"s]);
			uASSERT_EQUAL("leader"s, env["world"s]);

			auto [argc, argv] = env.get();
			uASSERT_EQUAL(3, argc);
			// store and sort to verify unordered
			std::vector<std::string> vals{argv[0], argv[1], argv[2]};
			std::ranges::sort(vals);
			uASSERT_EQUAL("dear=boar"s, vals[0]);
			uASSERT_EQUAL("hello=world"s, vals[1]);
			uASSERT_EQUAL("world=leader"s, vals[2]);

			// test all combinations since ordering is not a requirement
			// exactly one of the must be true (boolean XOR)
			auto str = env.stringify();
			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) !=
			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) !=
			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) !=
			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) !=
			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) !=
			        ("world=leader\0hello=world\0dear=boar\0\0"s == str));
		}
	}

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

		{ // Zero
			const char* penv = nullptr;
			EnvMap env(penv);
			uASSERT_EQUAL(0u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(0, argc);
			uASSERT(nullptr != argv);
			uASSERT_EQUAL(nullptr, argv[0]);
		}

		{ // Zero
			const char** ppenv = nullptr;
			EnvMap env(ppenv);
			uASSERT_EQUAL(0u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(0, argc);
			uASSERT(nullptr != argv);
			uASSERT_EQUAL(nullptr, argv[0]);
		}

		{ // One (key only)
			const char* ptr = "hello\0\0";
			EnvMap env(ptr);
			uASSERT_EQUAL(1u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello="s, std::string(argv[0]));
			uASSERT_EQUAL(""s, env["hello"]);
		}

		{ // One (key only)
			const char* ptr[] = {"hello", nullptr};
			EnvMap env(ptr);
			uASSERT_EQUAL(1u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello="s, std::string(argv[0]));
			uASSERT_EQUAL(""s, env["hello"]);
		}

		{ // One (with value)
			const char* ptr = "hello=world\0\0";
			EnvMap env(ptr);
			uASSERT_EQUAL(1u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello=world"s, std::string(argv[0]));
			uASSERT_EQUAL("world"s, env["hello"]);

			uASSERT_EQUAL("hello=world\0\0"s, env.stringify());
		}

		{ // One (with value)
			const char* ptr[] = {"hello=world\0", nullptr};
			EnvMap env(ptr);
			uASSERT_EQUAL(1u, env.size());
			auto [argc, argv] = env.get();
			uASSERT_EQUAL(1, argc);
			uASSERT_EQUAL("hello=world"s, std::string(argv[0]));
			uASSERT_EQUAL("world"s, env["hello"]);

			uASSERT_EQUAL("hello=world\0\0"s, env.stringify());
		}

		{ // Many
			const char* ptr = "hello=world\0world=leader\0dear=boar\0\0";
			EnvMap env(ptr);
			uASSERT_EQUAL(3u, env.size());

			uASSERT_EQUAL("boar"s, env["dear"s]);
			uASSERT_EQUAL("world"s, env["hello"s]);
			uASSERT_EQUAL("leader"s, env["world"s]);

			auto [argc, argv] = env.get();
			uASSERT_EQUAL(3, argc);
			// store and sort to verify unordered
			std::vector<std::string> vals{argv[0], argv[1], argv[2]};
			std::ranges::sort(vals);
			uASSERT_EQUAL("dear=boar"s, vals[0]);
			uASSERT_EQUAL("hello=world"s, vals[1]);
			uASSERT_EQUAL("world=leader"s, vals[2]);

			// test all combinations since ordering is not a requirement
			// exactly one of the must be true (boolean XOR)
			auto str = env.stringify();
			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) !=
			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) !=
			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) !=
			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) !=
			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) !=
			        ("world=leader\0hello=world\0dear=boar\0\0"s == str));
		}

		{ // Many
			const char* ptr[] =
				{"hello=world\0", "world=leader\0", "dear=boar\0", nullptr};
			EnvMap env(ptr);
			uASSERT_EQUAL(3u, env.size());

			uASSERT_EQUAL("boar"s, env["dear"s]);
			uASSERT_EQUAL("world"s, env["hello"s]);
			uASSERT_EQUAL("leader"s, env["world"s]);

			auto [argc, argv] = env.get();
			uASSERT_EQUAL(3, argc);
			// store and sort to verify unordered
			std::vector<std::string> vals{argv[0], argv[1], argv[2]};
			std::ranges::sort(vals);
			uASSERT_EQUAL("dear=boar"s, vals[0]);
			uASSERT_EQUAL("hello=world"s, vals[1]);
			uASSERT_EQUAL("world=leader"s, vals[2]);

			// test all combinations since ordering is not a requirement
			// exactly one of the must be true (boolean XOR)
			auto str = env.stringify();
			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) !=
			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) !=
			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) !=
			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) !=
			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) !=
			        ("world=leader\0hello=world\0dear=boar\0\0"s == str));
		}
	}

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

		{ // Zero
			EnvMap env;
			uASSERT_EQUAL(0u, env.size());
			uASSERT_EQUAL(""s, env["foo"]); // lookup of non-existing key
		}
	}
};

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