// SPDX-License-Identifier: 0BSD


#include "linkbag-abi.h"
#include <cstdint>
#include <cstring>
#include <errno.h>
#include <inttypes.h>
#include <optional>
#include <string_view>
#include <variant>
#include <vector>


namespace bag {
	namespace {
		namespace requests {
			struct ls {};
			struct get {
				std::basic_string_view<std::uint8_t> name;
			};
			struct del {
				std::basic_string_view<std::uint8_t> name;
				std::optional<std::basic_string_view<std::uint8_t>> if_blob;
			};
			struct put {
				std::basic_string_view<std::uint8_t> name, blob;
				bool replace;
			};

			template <class = void>
			bool need_fd(...) {
				return false;
			}
			template <class = void>
			bool need_fd(const struct put &) {
				return true;
			}
		};
		using request = std::variant<requests::ls, requests::get, requests::del, requests::put>;


		namespace detail {
			std::optional<std::basic_string_view<std::uint8_t>> val_name(std::basic_string_view<std::uint8_t> name) {
				if(name.size() < 1 || name.size() > 255)
					return errno = name.empty() ? EINVAL : ENAMETOOLONG, std::nullopt;
				else
					return name;
			}
			std::optional<std::pair<std::basic_string_view<std::uint8_t>, std::basic_string_view<std::uint8_t>>>
			val_name_blob(std::basic_string_view<std::uint8_t> envelope_tail) {
				if(envelope_tail.empty())
					return errno = EMSGSIZE, std::nullopt;
				auto blob_len = envelope_tail[0];
				envelope_tail.remove_prefix(1);

				if(blob_len > envelope_tail.size())
					return errno = E2BIG, std::nullopt;

				if(auto name = val_name(envelope_tail.substr(blob_len)))
					return std::pair{*name, envelope_tail.substr(0, blob_len)};
				else
					return {};
			}
		}
		template <class = void>
		std::optional<request> parse_request(std::basic_string_view<std::uint8_t> envelope) {
			if(envelope.size() < sizeof(std::uint64_t) + sizeof(std::uint8_t))
				return errno = EMSGSIZE, std::nullopt;
			envelope.remove_prefix(sizeof(std::uint64_t));

			auto type = envelope[0];
			envelope.remove_prefix(1);
			switch(type) {
				case LINKBAG_REQ_LS:
					if(envelope.empty())
						return requests::ls{};
					else
						return errno = EMSGSIZE, std::nullopt;

				case LINKBAG_REQ_GET:
#define THEN_NAME(type)                      \
	if(auto name = detail::val_name(envelope)) \
		return type{*name};                      \
	else                                       \
		return {}
					THEN_NAME(requests::get);

				case LINKBAG_REQ_DEL:
					THEN_NAME(requests::del);

				case LINKBAG_REQ_DELIF:
#define THEN_BLOBNAME(type, blob, ...)                                             \
	if(auto name_blob = detail::val_name_blob(envelope))                             \
		return type{.name = name_blob->first, .blob = name_blob->second, __VA_ARGS__}; \
	else                                                                             \
		return {}
					THEN_BLOBNAME(requests::del, if_blob, );

				case LINKBAG_REQ_PUT: {
					if(envelope.empty())
						return errno = EMSGSIZE, std::nullopt;
					auto flags = envelope[0];
					envelope.remove_prefix(1);

					if(flags & ~LINKBAG_REQ_PUT_ALL)
						return errno = EINVAL, std::nullopt;

					THEN_BLOBNAME(requests::put, blob, .replace = static_cast<bool>(flags & LINKBAG_REQ_PUT_REPLACE));
				}

				default:
					return errno = EBADR, std::nullopt;
			}
		}


		namespace responses {
			using error = int;
			struct got {
				std::basic_string_view<std::uint8_t> name, blob;
				int fd;
			};
			struct names {};

			template <class = void>
			bool need_fd(linkbag_res & res) {
				switch(res.type) {
					case LINKBAG_RES_GOT:
					case LINKBAG_RES_NAMES:
						return true;
					default:
						return false;
				}
			}
		};
		using response = std::variant<responses::error, responses::got, responses::names>;


		template <class = void>
		std::optional<response> parse_response(std::basic_string_view<std::uint8_t> envelope) {
			if(envelope.size() < sizeof(std::uint64_t) + sizeof(std::uint8_t))
				return errno = EMSGSIZE, std::nullopt;
			envelope.remove_prefix(sizeof(std::uint64_t));

			auto type = envelope[0];
			envelope.remove_prefix(1);
			switch(type) {
				case LINKBAG_RES_ERROR:
					if(envelope.size() == sizeof(int)) {
						responses::error ret;
						std::memcpy(&ret, envelope.data(), sizeof(int));
						return ret;
					} else
						return errno = EMSGSIZE, std::nullopt;

				case LINKBAG_RES_GOT:
					THEN_BLOBNAME(responses::got, blob, .fd = -1);

				case LINKBAG_RES_NAMES:
					if(envelope.empty())
						return responses::names{};
					else
						return errno = EMSGSIZE, std::nullopt;

				default:
					return errno = EBADR, std::nullopt;
			}
		}
	}
}
