// SPDX-License-Identifier: BSD-3-Clause
#ifndef SUBSTRATE_COMMAND_LINE_ARGUMENTS
#define SUBSTRATE_COMMAND_LINE_ARGUMENTS

#include <cstddef>
#include <optional>
#include <vector>
#include <set>
#include <variant>
#include <any>
#include <substrate/command_line/options>

namespace substrate::commandLine
{
	namespace internal
	{
		struct tokeniser_t;
	}

	struct choice_t;

	struct SUBSTRATE_CLS_API flag_t
	{
	private:
		std::string_view _name;
		std::any _value{};

	public:
		explicit flag_t(const std::string_view &name) noexcept : _name{name} { }
		explicit flag_t(const std::string_view &name, std::any &&value) noexcept :
			_name{name}, _value{std::move(value)} { }

		[[nodiscard]] auto &name() const noexcept { return _name; }
		[[nodiscard]] auto &value() const noexcept { return _value; }
		[[nodiscard]] bool operator ==(const flag_t &other) const noexcept;
		[[nodiscard]] bool operator !=(const flag_t &other) const noexcept;
	};

	using item_t = std::variant<flag_t, choice_t>;

	struct SUBSTRATE_CLS_API arguments_t
	{
	public:
		using optionsVisited_t = std::set<internal::optionsItem_t>;

	private:
		using storage_t = std::multiset<item_t, std::less<void>>;
		using iterator_t = typename storage_t::const_iterator;

		storage_t _arguments{};

		[[nodiscard]] std::optional<bool> parseArgument(internal::tokeniser_t &lexer, const options_t &options,
			const optionsVisited_t &globalOptions, optionsVisited_t &optionsVisited) noexcept;

	public:
		arguments_t() noexcept;
		arguments_t(const arguments_t &arguments);
		arguments_t(arguments_t &&arguments) noexcept;
		// NOTLINTNEXTLINE(performance-trivially-destructible)
		~arguments_t() noexcept;
		arguments_t &operator =(const arguments_t &arguments);
		arguments_t &operator =(arguments_t &&arguments) noexcept;

		[[nodiscard]] bool parseFrom(internal::tokeniser_t &lexer, const options_t &options,
			optionsVisited_t globalOptions);
		[[nodiscard]] bool add(item_t argument) noexcept;

		[[nodiscard]] size_t count() const noexcept;
		[[nodiscard]] size_t countMatching(const std::string_view &option) const noexcept;
		[[nodiscard]] iterator_t begin() const noexcept;
		[[nodiscard]] iterator_t end() const noexcept;
		[[nodiscard]] iterator_t find(const std::string_view &option) const noexcept;
		[[nodiscard]] std::vector<const item_t *> findAll(const std::string_view &option) const noexcept;
		[[nodiscard]] const item_t *operator [](const std::string_view &option) const noexcept;
	};

	struct SUBSTRATE_CLS_API choice_t
	{
	private:
		std::string_view _name;
		std::string_view _value;
		arguments_t _arguments;

	public:
		explicit choice_t(const std::string_view &name, const std::string_view &value, arguments_t &&arguments) noexcept :
			_name{name}, _value{value}, _arguments{std::move(arguments)} { }

		[[nodiscard]] auto &name() const noexcept { return _name; }
		[[nodiscard]] auto &value() const noexcept { return _value; }
		[[nodiscard]] auto &arguments() const noexcept { return _arguments; }
		[[nodiscard]] bool operator ==(const choice_t &other) const noexcept;
		[[nodiscard]] bool operator !=(const choice_t &other) const noexcept;
	};

	[[nodiscard]] SUBSTRATE_API std::optional<arguments_t>
		parseArguments(size_t argCount, const char *const *argList, const options_t &options) noexcept;

	[[nodiscard, deprecated("Please use the optionsRoot_t variant instead")]] inline std::optional<arguments_t>
		parseArguments(size_t argCount, const char *const *argList, const internal::optionsHolder_t &options) noexcept
	{
		const options_t programOptions{options};
		return parseArguments(argCount, argList, programOptions);
	}

	template<typename... options_t> [[nodiscard]] inline std::optional<arguments_t>
		parseArguments(size_t argCount, const char *const *argList,
			const optionsRoot_t<options_t...> &optionsRoot) noexcept
		{ return parseArguments(argCount, argList, optionsRoot.options()); }

	[[nodiscard]] SUBSTRATE_API bool operator ==(const item_t &lhs, const item_t &rhs) noexcept;
	[[nodiscard]] SUBSTRATE_API bool operator !=(const item_t &lhs, const item_t &rhs) noexcept;
} // namespace substrate::commandLine

#endif /* SUBSTRATE_COMMAND_LINE_ARGUMENTS */
/* vim: set ft=cpp ts=4 sw=4 noexpandtab: */
