#pragma once

#include <variant>
#include <string>
#include <string_view>
#include <stdexcept>
#include <functional>
#include <memory>

#include <psemek/util/to_string.hpp>
#include <psemek/util/exception.hpp>

namespace psemek::parser
{

	struct parse_error
		: util::exception
	{
		parse_error(std::string message, std::size_t line, std::size_t character, util::stacktrace stacktrace = {})
			: util::exception(util::to_string(message, " at ", line, "#", character), std::move(stacktrace))
			, message_{std::move(message)}
			, line_{line}
			, character_{character}
		{}

		std::string const & message() const { return message_; }
		std::size_t line() const { return line_; }
		std::size_t character() const { return character_; }

	private:
		std::string message_;
		std::size_t line_;
		std::size_t character_;
	};

	struct grammar_error
		: util::exception
	{
		using util::exception::exception;
	};

	namespace detail
	{

		template <typename P, typename B>
		auto result_type(P const & p, B & b) -> std::remove_cvref_t<decltype(std::get<0>(p.apply(b)))>;

		template <typename Iterator>
		struct buffer
		{
			using char_type = std::remove_cvref_t<decltype(*std::declval<Iterator>())>;

			buffer(Iterator begin, Iterator end)
				: begin{begin}
				, end{end}
				, it{begin}
			{}

			Iterator begin;
			Iterator end;
			Iterator it;

			std::ptrdiff_t offset() const { return it - begin; }
		};

		template <typename P, typename Tag = void>
		struct parser
		{
			using tag = Tag;

			P p;

			template <typename Buffer>
			auto apply(Buffer & buf) const
			{
				return p(buf);
			}

			auto parse(std::string_view text) const
			{
				buffer<char const *> buf{text.data(), text.data() + text.size()};
				auto res = apply(buf);
				if (res.index() == 1)
				{
					auto const & e = std::get<1>(res);
					std::size_t l = 0;
					std::size_t offset = e.offset;

					std::size_t last_newline = 0;
					for (std::size_t i = 0; i < e.offset; ++i)
					{
						if (buf.begin[i] == '\n')
						{
							++l;
							offset -= i - last_newline;
							last_newline = i;
						}
					}

					throw parse_error(e.message, l, offset);
				}
				return std::get<0>(res);
			}
		};

		template <typename P>
		struct parser_ref
		{
			P * p;

			template <typename Buffer>
			auto apply(Buffer & buf) const
			{
				return p->apply(buf);
			}

			auto parse(std::string_view text) const
			{
				return p->parse(text);
			}
		};

	}

	struct error
	{
		std::ptrdiff_t offset;
		char const * message;
	};

	template <typename T>
	using result = std::variant<T, error>;

	template <typename P>
	auto make_parser(P && p)
	{
		return detail::parser<std::remove_cvref_t<P>>{std::forward<P>(p)};
	}

	template <typename P, typename Tag>
	auto make_parser(P && p, Tag)
	{
		return detail::parser<std::remove_cvref_t<P>, Tag>{std::forward<P>(p)};
	}

}
