#include "libfilezilla/buffer.hpp"
#include "libfilezilla/encode.hpp"

namespace fz {

namespace {
template<typename DataContainer>
void base64_encode_impl(std::string & out, DataContainer const& in, base64_type type, bool pad)
{
	static_assert(sizeof(typename DataContainer::value_type) == 1, "Bad container type");

	std::string::value_type const* const base64_chars =
		 (type == base64_type::standard)
			? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
			: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

	size_t len = in.size();
	size_t pos{};

	size_t const newcap = out.size() + ((len + 2) / 3) * 4;
	if (out.capacity() < newcap) {
		out.reserve(newcap);
	}

	while (len >= 3) {
		len -= 3;
		auto const c1 = static_cast<unsigned char>(in[pos++]);
		auto const c2 = static_cast<unsigned char>(in[pos++]);
		auto const c3 = static_cast<unsigned char>(in[pos++]);

		out += base64_chars[(c1 >> 2) & 0x3fu];
		out += base64_chars[((c1 & 0x3u) << 4) | ((c2 >> 4) & 0xfu)];
		out += base64_chars[((c2 & 0xfu) << 2) | ((c3 >> 6) & 0x3u)];
		out += base64_chars[(c3 & 0x3fu)];
	}
	if (len) {
		auto const c1 = static_cast<unsigned char>(in[pos++]);
		out += base64_chars[(c1 >> 2) & 0x3fu];
		if (len == 2) {
			auto const c2 = static_cast<unsigned char>(in[pos++]);
			out += base64_chars[((c1 & 0x3u) << 4) | ((c2 >> 4) & 0xfu)];
			out += base64_chars[(c2 & 0xfu) << 2];
		}
		else {
			out += base64_chars[(c1 & 0x3u) << 4];
			if (pad) {
				out += '=';
			}
		}
		if (pad) {
			out += '=';
		}
	}
}
}

std::string base64_encode(std::string_view const& in, base64_type type, bool pad)
{
	std::string ret;
	base64_encode_impl(ret, in, type, pad);
	return ret;
}

std::string base64_encode(std::vector<uint8_t> const& in, base64_type type, bool pad)
{
	std::string ret;
	base64_encode_impl(ret, in, type, pad);
	return ret;
}

std::string base64_encode(fz::buffer const& in, base64_type type, bool pad)
{
	std::string ret;
	base64_encode_impl(ret, in.to_view(), type, pad);
	return ret;
}

void base64_encode_append(std::string& result, std::string_view const& in, base64_type type, bool pad)
{
	base64_encode_impl(result, in, type, pad);
}

namespace {
template<typename Ret, typename View>
Ret base64_decode_impl(View const& in)
{
	using Unsigned = std::make_unsigned_t<typename View::value_type>;

	unsigned char const chars[256] =
	{
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x80, 0xff, 0x80, 0x80, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0x3e, 0xff, 0x3f,
	    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0x40, 0xff, 0xff,
	    0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
	    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0x3f,
	    0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
	    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
	};

	Ret ret;
	ret.reserve((in.size() / 4) * 3);

	size_t pos{};
	size_t len = in.size();

	// Trim trailing whitespace
	while (len && static_cast<Unsigned>(in[len - 1]) <= 255 && chars[static_cast<unsigned char>(in[len - 1])] == 0x80) {
		--len;
	}

	auto next = [&]() {
		while (pos < len) {
			auto const u = static_cast<Unsigned>(in[pos++]);
			unsigned char c = (u <= 255) ? chars[u] : 0xffu;
			if (c != 0x80u) {
				return c;
			}
		}
		return static_cast<unsigned char>(0x40u);
	};

	while (pos < len) {
		auto c1 = next();
		auto c2 = next();
		auto c3 = next();
		auto c4 = next();

		if (c1 == 0xff || c1 == 0x40 ||
		    c2 == 0xff || c2 == 0x40 ||
		    c3 == 0xff || c4 == 0xff)
		{
			// Bad input
			return Ret();
		}

		if (c4 == 0x40) {
			// Pad
			if (pos < len) {
				// Not at end of string
				return Ret();
			}
			ret.push_back((c1 << 2) | ((c2 >> 4) & 0x3));

			if (c3 != 0x40) {
				ret.push_back(((c2 & 0xf) << 4) | ((c3 >> 2) & 0xf));
			}
		}
		else {
			if (c3 == 0x40) {
				// Bad input
				return Ret();
			}

			ret.push_back((c1 << 2) | ((c2 >> 4) & 0x3));
			ret.push_back(((c2 & 0xf) << 4) | ((c3 >> 2) & 0xf));
			ret.push_back(((c3 & 0x3) << 6) | c4);
		}
	}

	return ret;
}
}

std::vector<uint8_t> base64_decode(std::string_view const& in)
{
	return base64_decode_impl<std::vector<uint8_t>>(in);
}

std::vector<uint8_t> base64_decode(std::wstring_view const& in)
{
	return base64_decode_impl<std::vector<uint8_t>>(in);
}

std::vector<uint8_t> base64_decode(fz::buffer const& in)
{
	return base64_decode_impl<std::vector<uint8_t>>(in.to_view());
}

std::string base64_decode_s(std::string_view const& in)
{
	return base64_decode_impl<std::string>(in);
}

std::string base64_decode_s(std::wstring_view const& in)
{
	return base64_decode_impl<std::string>(in);
}

std::string base64_decode_s(fz::buffer const& in)
{
	return base64_decode_impl<std::string>(in.to_view());
}


namespace {
template<typename DataContainer>
std::string base32_encode_impl(DataContainer const& in, base32_type type, bool pad)
{
	static_assert(sizeof(typename DataContainer::value_type) == 1, "Bad container type");

	std::string::value_type const* const base32_chars =
		 (type == base32_type::standard)
			? "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
			: (type == base32_type::base32hex)
				 ? "0123456789ABCDEFGHIJKLMNOPQRSTUV"
				 : "123456789ABCDEFGHJKMNPQRSTUVWXYZ";

	std::string ret;

	size_t len = in.size();
	size_t pos{};

	ret.reserve(((len + 4) / 5) * 8);

	while (len >= 5) {
		len -= 5;
		auto const c1 = static_cast<unsigned char>(in[pos++]);
		auto const c2 = static_cast<unsigned char>(in[pos++]);
		auto const c3 = static_cast<unsigned char>(in[pos++]);
		auto const c4 = static_cast<unsigned char>(in[pos++]);
		auto const c5 = static_cast<unsigned char>(in[pos++]);

		ret += base32_chars[(c1 >> 3) & 0x1fu];
		ret += base32_chars[((c1 & 0x7u) << 2) | ((c2 >> 6) & 0x3u)];
		ret += base32_chars[(c2 >> 1) & 0x1fu];
		ret += base32_chars[((c2 & 0x1u) << 4) | ((c3 >> 4) & 0xfu)];
		ret += base32_chars[((c3 & 0xfu) << 1) | ((c4 >> 7) & 0x1u)];
		ret += base32_chars[(c4 >> 2) & 0x1fu];
		ret += base32_chars[((c4 & 0x3u) << 3) | ((c5 >> 5) & 0x7u)];
		ret += base32_chars[c5 & 0x1fu];
	}
	if (len) {
		auto const c1 = static_cast<unsigned char>(in[pos++]);
		ret += base32_chars[(c1 >> 3) & 0x1fu];
		if (len >= 2) {
			auto const c2 = static_cast<unsigned char>(in[pos++]);
			ret += base32_chars[((c1 & 0x7u) << 2) | ((c2 >> 6) & 0x3u)];
			ret += base32_chars[(c2 >> 1) & 0x1fu];
			if (len >= 3) {
				auto const c3 = static_cast<unsigned char>(in[pos++]);
				ret += base32_chars[((c2 & 0x1u) << 4) | ((c3 >> 4) & 0xfu)];
				if (len >= 4) {
					auto const c4 = static_cast<unsigned char>(in[pos++]);
					ret += base32_chars[((c3 & 0xfu) << 1) | ((c4 >> 7) & 0x1u)];
					ret += base32_chars[(c4 >> 2) & 0x1fu];
					ret += base32_chars[((c4 & 0x3u) << 3)];
					if (pad) {
						ret += '=';
					}
				}
				else {
					ret += base32_chars[((c3 & 0xfu) << 1)];
					if (pad) {
						ret += "===";
					}
				}
			}
			else {
				ret += base32_chars[((c2 & 0x1u) << 4)];
				if (pad) {
					ret += "====";
				}
			}
		}
		else {
			ret += base32_chars[((c1 & 0x7u) << 2)];
			if (pad) {
				ret += "======";
			}
		}
	}

	return ret;
}
}

std::string base32_encode(std::string_view const& in, base32_type type, bool pad)
{
	return base32_encode_impl(in, type, pad);
}

std::string base32_encode(std::vector<uint8_t> const& in, base32_type type, bool pad)
{
	return base32_encode_impl(in, type, pad);
}

std::string base32_encode(fz::buffer const& in, base32_type type, bool pad)
{
	return base32_encode_impl(in.to_view(), type, pad);
}

namespace {
template<typename Ret, typename View>
Ret base32_decode_impl(View const& in, base32_type type)
{
	using Unsigned = std::make_unsigned_t<typename View::value_type>;

	unsigned char const chars_s[256] =
	{
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x80, 0xff, 0x80, 0x80, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0x00, 0x00, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x40, 0xff, 0xff,
		0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
		0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
		0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
	};

	unsigned char const chars_h[256] =
	{
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x80, 0xff, 0x80, 0x80, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0x40, 0xff, 0xff,
		0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
		0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
		0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
	};

	unsigned char const chars_l[256] =
	{
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x80, 0xff, 0x80, 0x80, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0x40, 0xff, 0xff,
		0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0xff, 0x11, 0x12, 0xff, 0x13, 0x14, 0xff,
		0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0xff, 0x11, 0x12, 0xff, 0x13, 0x14, 0xff,
		0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
	};

	auto const chars = (type == base32_type::standard) ? chars_s : (type == base32_type::base32hex) ? chars_h : chars_l;

	Ret ret;
	ret.reserve((in.size() / 8) * 5);

	size_t pos{};
	size_t len = in.size();

	// Trim trailing whitespace
	while (len && static_cast<Unsigned>(in[len - 1]) <= 255 && chars[static_cast<unsigned char>(in[len - 1])] == 0x80) {
		--len;
	}

	unsigned char end{};
	auto next = [&]() -> unsigned char {
		while (pos < len) {
			auto const u = static_cast<Unsigned>(in[pos++]);
			unsigned char c = (u <= 255) ? chars[u] : 0xffu;
			if (c == 0x80u) {
				continue;
			}

			if (end) {
				if (c != end) {
					end = 0xffu;
				}
				return end;
			}
			else if (c == 0xffu || c == 0x40u) {
				end = c;
			}

			return c;
		}
		return end ? end : 0x40u;
	};

	while (pos < len) {
		auto const c1 = next();
		auto const c2 = next();
		auto const c3 = next();
		auto const c4 = next();
		auto const c5 = next();
		auto const c6 = next();
		auto const c7 = next();
		auto const c8 = next();

		if (c2 == 0x40 || c8 == 0xff) {
			// Bad input
			return Ret();
		}

		ret.push_back((c1 << 3) | ((c2 >> 2) & 0x7));

		if (c3 != 0x40) {
			if (c4 == 0x40) {
				// Bad input
				return Ret();
			}
			ret.push_back(((c2 & 0x3) << 6) | (c3 << 1) | ((c4 >> 4) & 0x1));

			if (c5 != 0x40) {
				ret.push_back(((c4 & 0xf) << 4) | (c5 >> 1));

				if (c6 != 0x40) {
					if (c7 == 0x40) {
						// Bad input
						return Ret();
					}
					ret.push_back(((c5 & 0x1) << 7) | (c6 << 2) | ((c7 >> 3) & 0x3));

					if (c8 != 0x40) {
						ret.push_back(((c7 & 0x7) << 5) | c8);
					}
				}
			}
		}
	}

	return ret;
}
}

std::vector<uint8_t> base32_decode(std::string_view const& in, base32_type type)
{
	return base32_decode_impl<std::vector<uint8_t>>(in, type);
}

std::vector<uint8_t> base32_decode(std::wstring_view const& in, base32_type type)
{
	return base32_decode_impl<std::vector<uint8_t>>(in, type);
}

std::vector<uint8_t> base32_decode(fz::buffer const& in, base32_type type)
{
	return base32_decode_impl<std::vector<uint8_t>>(in.to_view(), type);
}

std::string base32_decode_s(std::string_view const& in, base32_type type)
{
	return base32_decode_impl<std::string>(in, type);
}

std::string base32_decode_s(std::wstring_view const& in, base32_type type)
{
	return base32_decode_impl<std::string>(in, type);
}

std::string base32_decode_s(fz::buffer const& in, base32_type type)
{
	return base32_decode_impl<std::string>(in.to_view(), type);
}


std::string percent_encode(std::string_view const& s, bool keep_slashes)
{
	std::string ret;
	ret.reserve(s.size());

	for (auto const& c : s) {
		if (!c) {
			break;
		}
		else if ((c >= '0' && c <= '9') ||
		    (c >= 'a' && c <= 'z') ||
		    (c >= 'A' && c <= 'Z') ||
		    c == '-' || c == '.' || c == '_' || c == '~')
		{
			ret += c;
		}
		else if (c == '/' && keep_slashes) {
			ret += c;
		}
		else {
			ret += '%';
			ret += int_to_hex_char<char, false>(static_cast<unsigned char>(c) >> 4);
			ret += int_to_hex_char<char, false>(c & 0xf);
		}
	}

	return ret;
}

std::string percent_encode(std::wstring_view const& s, bool keep_slashes)
{
	return percent_encode(to_utf8(s), keep_slashes);
}

std::wstring percent_encode_w(std::wstring_view const& s, bool keep_slashes)
{
	return to_wstring(percent_encode(s, keep_slashes));
}

namespace {
template<typename Ret, typename View>
Ret percent_decode_impl(View const & s, bool allow_embedded_null, bool plus_is_space)
{
	Ret ret;
	ret.reserve(s.size());

	auto const* c = s.data();
	auto const* const end = c + s.size();
	while (c < end) {
		if (*c == '%') {
			if (++c == end) {
				return Ret();
			}
			int const high = hex_char_to_int(*c);
			if (high == -1) {
				return Ret();
			}
			if (++c == end) {
				return Ret();
			}
			int const low = hex_char_to_int(*c);
			if (low == -1) {
				return Ret();
			}

			if (!high && !low && !allow_embedded_null) {
				return Ret();
			}
			ret.push_back(static_cast<typename Ret::value_type>(static_cast<uint8_t>((high << 4) + low)));
		}
		else if (plus_is_space && *c == '+') {
			ret.push_back(static_cast<typename Ret::value_type>(' '));
		}
		else {
			if (!*c && !allow_embedded_null) {
				return Ret();
			}
			using Unsigned = std::make_unsigned_t<typename View::value_type>;
			auto const u = static_cast<Unsigned>(*c);
			if (u > 255) {
				return Ret();
			}
			ret.push_back(static_cast<typename Ret::value_type>(u));
		}
		++c;
	}

	return ret;
}
}

std::vector<uint8_t> percent_decode(std::string_view const& s, bool allow_embedded_null, bool plus_is_space)
{
	return percent_decode_impl<std::vector<uint8_t>>(s, allow_embedded_null, plus_is_space);
}

std::vector<uint8_t> percent_decode(std::wstring_view const& s, bool allow_embedded_null, bool plus_is_space)
{
	return percent_decode_impl<std::vector<uint8_t>>(s, allow_embedded_null, plus_is_space);
}

std::string percent_decode_s(std::string_view const& s, bool allow_embedded_null, bool plus_is_space)
{
	return percent_decode_impl<std::string>(s, allow_embedded_null, plus_is_space);
}

std::string percent_decode_s(std::wstring_view const& s, bool allow_embedded_null, bool plus_is_space)
{
	return percent_decode_impl<std::string>(s, allow_embedded_null, plus_is_space);
}

}
