#include <psemek/ui/tagged_text.hpp>
#include <psemek/util/to_string.hpp>

namespace psemek::ui
{

	tagged_text::parse_error::parse_error(std::string error, std::size_t position, std::string text)
		: std::runtime_error(std::move(error) + util::to_string(" at symbol ", position, ", full text: \"", text, "\""))
		, position_(position)
	{}

	tagged_text tagged_text::parse(std::string_view text)
	{
		tagged_text result;

		auto current = text.begin();

		auto error = [&](std::string message)
		{
			throw parse_error(message, current - text.begin(), std::string(text));
		};

		bool in_tag = false;

		while (current < text.end())
		{
			if (*current == '[')
			{
				if (in_tag)
					error("cannot open a tag inside another tag");
				if (current + 1 == text.end())
					error("unexpected end");
				if (current[1] == '/')
				{
					++current;
					result.tokens.push_back(closing_tag{});
				}
				else
					result.tokens.push_back(opening_tag{});
				in_tag = true;
			}
			else if (*current == ']')
			{
				if (!in_tag)
					error("closing a tag without opening one");
				in_tag = false;
			}
			else if (in_tag && *current == ':')
			{
				if (std::get_if<closing_tag>(&result.tokens.back()))
					error("closing tags cannot have attributes");
				auto & attr = std::get<opening_tag>(result.tokens.back()).attribute;
				if (attr)
					error("a tag can have at most one attribute");
				attr = std::string_view{};
			}
			else
			{
				auto append = [&](std::string & target)
				{
					target.push_back(*current);
				};

				if (in_tag)
				{
					if (auto token = std::get_if<opening_tag>(&result.tokens.back()))
					{
						if (token->attribute)
							append(*(token->attribute));
						else
							append(token->type);
					}
					else
						append(std::get<closing_tag>(result.tokens.back()).type);
				}
				else
				{
					if (*current == '\\')
					{
						++current;
						if (current < text.end())
						{
							result.tokens.push_back(std::string{});
							append(std::get<std::string>(result.tokens.back()));
						}
						else
							--current;
					}
					else
					{
						if (result.tokens.empty() || !std::get_if<std::string>(&result.tokens.back()))
							result.tokens.push_back(std::string{});
						append(std::get<std::string>(result.tokens.back()));
					}
				}
			}

			++current;
		}

		if (in_tag)
			error("unexpected end");

		return result;
	}

}
