//       Copyright Maarten L. Hekkelman, 2019-2026
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include "glob.hpp"

#include "zeep/unicode-support.hpp"

#include <algorithm>
#include <cctype>
#include <regex>
#include <utility>
#include <vector>

// --------------------------------------------------------------------

namespace zeep::http
{
namespace
{

	bool Match(const char *pattern, const char *name)
	{
		for (;;)
		{
			char op = *pattern;

			switch (op)
			{
				case 0:
					return *name == 0;
				case '*':
				{
					// separate shortcut
					if (pattern[1] == '*' and pattern[2] == '/' and Match(pattern + 3, name))
						return true;

					// ends with **
					if (pattern[1] == '*' and pattern[2] == 0)
						return true;

					// ends with *
					if (pattern[1] == 0)
					{
						while (*name)
						{
							if (*name == '/' or *name == '\\')
								return false;
							++name;
						}
						return true;
					}

					// contains **
					if (pattern[1] == '*')
					{
						while (*name)
						{
							if (Match(pattern + 2, name))
								return true;
							++name;
						}
						return false;
					}

					// contains just *
					while (*name)
					{
						if (Match(pattern + 1, name))
							return true;
						if (*name == '/')
							return false;
						++name;
					}

					return false;
				}
				case '?':
					if (*name)
						return Match(pattern + 1, name + 1);
					else
						return false;
				default:
					if ((*name == '/' and op == '\\') or
						(*name == '\\' and op == '/') or
						tolower(*name) == tolower(op))
					{
						++name;
						++pattern;
					}
					else
						return false;
					break;
			}
		}
	}

	void expand_group(std::string pattern, std::vector<std::string> &expanded)
	{
		static std::regex rx(R"(\{([^{},]*,[^{}]*)\})");

		std::smatch m;
		if (std::regex_search(pattern, m, rx))
		{
			std::vector<std::string> options;

			std::string group = m[1].str();
			split(options, group, ",", false);

			for (std::string &option : options)
				expand_group(m.prefix().str() + option + m.suffix().str(), expanded);
		}
		else
			expanded.push_back(std::move(pattern));
	}

} // namespace

// --------------------------------------------------------------------

bool glob_match(const std::filesystem::path &path, std::string glob_pattern)
{
	bool result = path.empty() and glob_pattern.empty();

	if (not glob_pattern.empty() and glob_pattern.back() == '/')
		glob_pattern += "**";

	std::vector<std::string> patterns;
	split(patterns, glob_pattern, ";");

	std::vector<std::string> expandedpatterns;
	std::ranges::for_each(patterns, [&expandedpatterns](std::string &pattern)
		{ expand_group(pattern, expandedpatterns); });

	for (std::string &pat : expandedpatterns)
	{
		if ((path.empty() and pat.empty()) or
			Match(pat.c_str(), path.generic_string().c_str()))
		{
			result = true;
			break;
		}
	}

	return result;
}

// --------------------------------------------------------------------

bool glob_match(const uri &path, std::string glob_pattern)
{
	bool result = path.empty() and glob_pattern.empty();

	if (not glob_pattern.empty() and glob_pattern.back() == '/')
		glob_pattern += "**";

	std::vector<std::string> patterns;
	split(patterns, glob_pattern, ";");

	std::vector<std::string> expandedpatterns;
	std::ranges::for_each(patterns, [&expandedpatterns](std::string &pattern)
		{ expand_group(pattern, expandedpatterns); });

	for (std::string &pat : expandedpatterns)
	{
		if ((path.empty() and pat.empty()) or
			Match(pat.c_str(), path.get_path().string().c_str()))
		{
			result = true;
			break;
		}
	}

	return result;
}

} // namespace zeep::http
