#pragma once

#include <psemek/parser/parser.hpp>
#include <psemek/parser/combinators.hpp>

namespace psemek::parser
{

	struct end_token{};

	const auto end = make_parser([](auto & buffer)
		-> result<end_token>
	{
		if (buffer.it == buffer.end)
			return end_token{};
		return error{buffer.offset(), "unexpected trailing data"};
	});

	template <typename T>
	auto pure(T && t)
	{
		return make_parser([t = std::forward<T>(t)](auto &)
			-> result<std::remove_cvref_t<T>>
		{
			return t;
		});
	}

	const auto peek = make_parser([](auto & buffer)
		-> result<typename std::remove_cvref_t<decltype(buffer)>::char_type>
	{
		if (buffer.it == buffer.end)
			return error{buffer.offset(), "unexpected end"};
		return *buffer.it++;
	});

	inline auto ch(char x)
	{
		return guard(peek, [x](auto const & y){ return x == y; }, std::string("expected \"") + std::string(1, x) + std::string("\""));
	}

	inline auto str(std::string s)
	{
		auto msg = std::string("expected \"") + s + std::string("\"");
		return make_parser([s = std::move(s), msg = std::move(msg)](auto & buffer)
			-> result<std::string_view>
		{
			auto it = buffer.it;
			std::size_t i = 0;

			while (it != buffer.end && i < s.size())
			{
				if (*it != s[i])
					return error{buffer.offset(), msg.data()};

				++it;
				++i;
			}

			if (i < s.size())
				return error{buffer.offset(), "unexpected end"};

			buffer.it = it;
			return s;
		});
	}

	struct ws_token{};

	inline auto ws = map(many(one_of(ch(' '), ch('\t'))), [](auto const &){ return ws_token{}; });

	struct newline_token{};

	inline auto newline = map(ch('\n'), [](auto const &){ return newline_token{}; });

	inline auto alpha = guard(peek, [](auto c){ return std::isalpha(c); });

	inline auto digit = map(guard(peek, [](auto c){ return '0' <= c && c <= '9'; }), [](char c){ return c - '0'; });

	// TODO: overflow check for integers

	template <typename T>
	inline auto integer = map(
		concat(
			maybe(ch('-')),
			fold(digit, [](int s, int d){ return 10 * s + d; })
		),
		[](auto const & t){ return std::get<1>(t) * (std::get<0>(t) ? -1 : 1); }
	);

	template <typename T>
	inline auto real = map(
		concat(
			maybe(ch('-')),
			fold(digit, [](T s, int d){ return 10 * s + d; }),
			maybe(
				concat(
					ch('.'),
					fold(digit, [](auto p, int d){
						return std::make_pair(p.first + d * p.second / 10, p.second / 10);
					}, std::make_pair(T{0}, T{1}))
				)
			)
		),
		[](auto const & t){
			T sign = (std::get<0>(t) ? -1 : 1);
			T i = std::get<1>(t);
			T f = (std::get<2>(t) ? std::get<1>(*std::get<2>(t)).first : 0);
			return sign * (i + f);
		}
	);

}
