text:
// Boost Software License - Version 1.0 - August 17th, 2003
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include "stackcpp/client.h"

#include "stackcpp/misc/exceptions.h"
#include "stackcpp/misc/types.hpp"
#include "stackcpp/objects/answer.hpp"
#include "stackcpp/objects/user.hpp"
#include "stackcpp/parser.h"

#include <algorithm>
#include <boost/algorithm/string/join.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <cmath>
#include <iterator>
#include <limits>
#include <rapidjson/document.h>
#include <sstream>
#include <utility>

namespace stackcpp {

namespace {

// Workaround for CWG defect #2148.
struct enum_hash final {
    std::size_t operator()(sites site) const noexcept {
        return static_cast<std::size_t>(site);
    }
};

const std::unordered_map<sites, const std::string, enum_hash> site_identifiers = {
#include "stackcpp/sites/map.txt"
};

// Replace HTML reserved characters with their respective percentage encoding.
std::string encode_html(std::string html) noexcept {
    const auto find_replace = [](std::string& string, const std::string& find, const std::string& replace) {
        std::size_t index = 0;
        while ((index = string.find(find)) != std::string::npos)
            string.replace(index, find.length(), replace);
    };

    find_replace(html, "!", "%21");
    find_replace(html, "#", "%23");
    find_replace(html, "$", "%24");
    find_replace(html, "&", "%26");
    find_replace(html, "'", "%27");
    find_replace(html, "(", "%28");
    find_replace(html, ")", "%29");
    find_replace(html, "*", "%2A");
    find_replace(html, "+", "%2B");
    find_replace(html, ",", "%2C");
    find_replace(html, "/", "%2F");
    find_replace(html, ":", "%3A");
    find_replace(html, ";", "%3B");
    find_replace(html, "=", "%3D");
    find_replace(html, "?", "%3F");
    find_replace(html, "@", "%40");
    find_replace(html, "[", "%5B");
    find_replace(html, "]", "%5D");
    return html;
}

// Helper function to append key/value pairs to the map.
template<typename T> T append(T&& map) noexcept {
    return std::forward<T>(map);
}

template<typename T, typename U, typename V, typename... Ts>
T append(T&& map, U&& key, V&& value, Ts&&... args) noexcept {
    map.emplace(std::make_pair(std::forward<U>(key), std::forward<V>(value)));
    return append(std::forward<T>(map), std::forward<Ts>(args)...);
}

// Wrapper for boost::algorithm::join, because it doesn't return std::string :/.
template<typename T>
std::string join(T&& container) noexcept {
    return boost::algorithm::join(std::forward<T>(container), delimiter);
}

// Helper function for vector of unique ids.
std::string join(const std::vector<unique_id>& container) noexcept {
    return join(container | boost::adaptors::transformed([](const unique_id& id) { return std::to_string(id); }));
}
}

constexpr unsigned int client::default_amount;

client::client(std::string api_key, stackcpp::sites site) noexcept : client{std::move(api_key), "", std::move(site)} {}

client::client(std::string api_key, std::string access_token, stackcpp::sites site) noexcept
        : api_key_{std::move(api_key)}, access_token_{std::move(access_token)}, site_{std::move(site)}, max_quota_{}, remaining_quota_{} {}

sites client::site() const noexcept {
    return site_;
}

void client::site(stackcpp::sites site) noexcept {
    site_ = std::move(site);
}

integer_t client::max_quota() const noexcept {
    return max_quota_;
}

integer_t client::remaining_quota() const noexcept {
    return remaining_quota_;
}

template<typename T>
std::vector<T> client::execute_requests(
        const std::string& method, const map_t& properties, T (*parser)(const rapidjson::Value&), const std::size_t total_items, const bool network, const bool write) const {
    if (total_items == 0)
        return {};
    if (total_items > std::numeric_limits<integer_t>::max())
        throw std::out_of_range{"The container size exceeds the valid range of what the API allows."};

    // Used to know on which page we are on.
    static unsigned int page = 0;

    // Find out if splitting the request is necessary.
    // The amount is bounded by 100, except for the 'site' method.
    const auto amount = method == "sites" ? total_items : std::min(total_items, std::size_t{100});

    // Append each property to the parameters.
    std::string params;
    if (!api_key_.empty())
        params += "api_key=" + encode_html(api_key_) + "&";
    if (!network)
        params += "site=" + site_identifiers.at(site_) + "&";
    params += "pagesize=" + std::to_string(amount) + "&";
    if (page > 0)
        params += "page=" + std::to_string(page) + "&";

    for (const auto& pair : properties) {
        if (!pair.second.empty())
            params += pair.first + "=" + encode_html(pair.second) + "&";
    }
    params.pop_back();

    // Create socket to send the HTTP request to the API.
    boost::asio::io_service service;
    boost::asio::ssl::context context{service, boost::asio::ssl::context::method::sslv23_client};
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket{service, context};

    // Resolve the IP address, and choose the first one that responds.
    boost::asio::ip::tcp::resolver resolver{service};
    boost::asio::connect(socket.lowest_layer(), resolver.resolve({"api.stackexchange.com", "https"}));
    socket.handshake(boost::asio::ssl::stream_base::handshake_type::client);

    // Send the HTTP request.
    std::string request = (write ? "POST" : "GET") + (" /2.2/" + encode_html(method));

    if (!write)
        request += "?" + params;

    request += " HTTP/1.1\r\n";
    request += "Host: api.stackexchange.com\r\n";
    request += "Accept-Encoding: gzip\r\n";
    request += "Accept: */*\r\n";
    request += "Connection: close\r\n";

    if (write) {
        request += "Content-Type: application/x-www-form-urlencoded\r\n";
        request += "Content-Length: " + std::to_string(params.length()) + "\r\n\r\n";
        request += params;
    }

    request += "\r\n";
    boost::asio::write(socket, boost::asio::buffer(request));

    // Read the HTTP response completely.
    boost::asio::streambuf response;
    boost::system::error_code error_code;
    boost::asio::read(socket, response, error_code);
    if (error_code != boost::asio::error::eof)
        throw fatal_exception{"boost::asio::read failed: " + error_code.message()};

    std::string raw_response{boost::asio::buffers_begin(response.data()), boost::asio::buffers_end(response.data())};

    // HTTP version is pretty much not needed.
    raw_response.erase(0, raw_response.find(' ') + 1);

    try {
        // The HTTP status code is before the next space, so get that.
        const auto http_code = std::stoul(raw_response.substr(0, raw_response.find(' ')));

        // The HTTP status code needs to be either OK for successful requests, or BAD_REQUEST for failed requests.
        if (http_code != 200 && http_code != 400)
            throw fatal_exception{"Invalid HTTP return code " + std::to_string(http_code) + " executing query: " + params + "."};
    }
    catch (const fatal_exception&) {
        // Rethrow if the exception is not from the Standard Library.
        throw;
    }
    catch (const std::exception&) {
        throw fatal_exception{"Could not convert HTTP status code to int: " + raw_response.substr(0, raw_response.find(' '))};
    }

    // Ignore the rest of the HTTP header.
    raw_response.erase(0, raw_response.find("\r\n\r\n") + 4);

    // The result is compressed using gzip by default, so we need to decompress it before analyzing it.
    boost::iostreams::filtering_streambuf<boost::iostreams::input> decompressor;
    std::stringstream response_stream{raw_response};

    decompressor.push(boost::iostreams::gzip_decompressor());
    decompressor.push(response_stream);

    std::stringstream json_stream;
    boost::iostreams::copy(decompressor, json_stream);
    const std::string json_string = json_stream.str();

    rapidjson::Document json;
    if (json.Parse(json_string.c_str()).HasParseError())
        throw fatal_exception{"JSON response from query " + params + " is empty/null. Maybe it's not even a JSON response?"};
    if (!json.IsObject())
        throw invalid_json_value{"JSON Object"};

    // Check if response is an error.
    if (json.HasMember("error_id")) {
        if (!json["error_id"].IsNumber())
            throw invalid_json_value{"integer"};
        if (!json["error_message"].IsString())
            throw invalid_json_value{"string"};

        throw_code(json["error_id"].GetUint(), json["error_message"].GetString());
    }

    // Check if you need to back off.
    if (json.HasMember("backoff")) {
        if (!json["backoff"].IsNumber())
            throw invalid_json_value{"integer"};
        throw back_off{json["backoff"].GetUint()};
    }

    // Get properties to cache for later use.
    if (json.HasMember("quota_max")) {
        if (!json["quota_max"].IsNumber())
            throw invalid_json_value{"integer"};
        max_quota_ = json["quota_max"].GetInt();
    }

    if (json.HasMember("quota_remaining")) {
        if (!json["quota_remaining"].IsNumber())
            throw invalid_json_value{"integer"};
        remaining_quota_ = json["quota_remaining"].GetInt();
    }

    // Parse the actual response array
    if (!json.HasMember("items"))
        throw fatal_exception{"Could not find the 'items' tag."};
    if (!json["items"].IsArray())
        throw invalid_json_value{"JSON array"};

    std::vector<T> items;
    const auto items_size = json["items"].Size();
    for (rapidjson::SizeType i = 0; i < json["items"].Size(); ++i) {
        if (!json["items"][i].IsObject())
            throw invalid_json_value{"JSON object"};

        if (!json["items"][i].ObjectEmpty())
            items.push_back(parser(json["items"][i]));
    }

    // Check if the API response is not finished yet.
    if (json.HasMember("has_more") && json["has_more"].GetBool() && items_size < total_items) {
        ++page;
        auto&& more_items = execute_requests(method, properties, parser, total_items - items_size, network, write);
        items.insert(items.cend(), std::make_move_iterator(more_items.cbegin()), std::make_move_iterator(more_items.cend()));
        page = 0;
    }

    return items;
}

template<typename T>
T client::execute_request(const std::string& method, const map_t& properties, T (*parser)(const rapidjson::Value&), const bool network, const bool write) const {
    const auto results = execute_requests(method, properties, parser, 1, network, write);
    return !results.empty() ? results.front() : parser(rapidjson::Document{}.Parse("{}"));
}

template<typename T, typename U>
std::vector<T> client::execute_requests_loop(const std::vector<U>& ids, const std::string& base, const std::string& method, const map_t& properties,
        T (*parser)(const rapidjson::Value&), const std::size_t items_per_id, const bool network, const bool write) const {
    std::vector<T> results;
    for (const auto& id : ids) {
        auto&& vector = execute_requests(base + parse_value(id) + method, properties, parser, items_per_id, network, write);
        results.insert(results.cend(), std::make_move_iterator(vector.cbegin()), std::make_move_iterator(vector.cend()));
    }
    return results;
}

std::future<std::vector<answer>> client::answers(const uinteger_t amount, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<answer>, *this, "answers", restriction.as_map(), &parse_answer, amount, false, false);
}

std::future<std::vector<answer>> client::answers(const answer_restrict& restriction) const {
    return answers(default_amount, restriction);
}

std::future<std::vector<answer>> client::answers(const std::vector<unique_id>& ids, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<answer>, *this, "answers/" + join(ids), restriction.as_map(), &parse_answer, ids.size(), false, false);
}

std::future<answer> client::answers(const unique_id& id, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id), restriction.as_map(), &parse_answer, false, false);
}

std::future<answer> client::answers_accept(const unique_id& id, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/accept", restriction.as_map(), &parse_answer, false, true);
}

std::future<answer> client::answers_undo_accept(const unique_id& id, const answer_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/accept/undo", restriction.as_map(), &parse_answer, false, true);
}

std::future<std::vector<comment>> client::answers_comments(const std::vector<unique_id>& ids, const uinteger_t comments_per_answer, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<comment, unique_id>, *this, ids, "answers/", "/comments", restriction.as_map(), &parse_comment,
            comments_per_answer, false, false);
}

std::future<std::vector<comment>> client::answers_comments(const std::vector<unique_id>& ids, const comment_restrict& restriction) const {
    return answers_comments(ids, default_amount, restriction);
}

std::future<std::vector<comment>> client::answers_comments(unique_id id, const uinteger_t comments_per_answer, const comment_restrict& restriction) const {
    return answers_comments(std::vector<unique_id>{std::move(id)}, comments_per_answer, restriction);
}

std::future<std::vector<comment>> client::answers_comments(unique_id id, const comment_restrict& restriction) const {
    return answers_comments(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<void> client::answers_delete(unique_id id, const bool write) const {
    return std::async(std::launch::async,
            [](client inst, unique_id id, const bool write) {
                inst.execute_request("answers/" + std::to_string(id) + "/delete", {{"preview", parse_value(!write)}}, &parse_answer, false, true);
            },
            *this, std::move(id), write);
}

std::future<answer> client::answers_downvote(const unique_id& id, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/downvote", restriction.as_map(), &parse_answer, false, true);
}

std::future<answer> client::answers_undo_downvote(const unique_id& id, const answer_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/downvote/undo", restriction.as_map(), &parse_answer, false, true);
}

std::future<answer> client::answers_edit(unique_id id, std::string body, std::string comment, answer_restrict restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/edit",
            append(restriction.as_map(), "body", std::move(body), "comment", std::move(comment)), &parse_answer, false, true);
}

std::future<std::vector<flag_option>> client::answers_flags_options(const unique_id& id) const {
    return std::async(std::launch::async, &client::execute_requests<flag_option>, *this, "answers/" + std::to_string(id) + "/flags/options", map_t{}, &parse_flag_option,
            std::numeric_limits<integer_t>::max(), false, false);
}

std::future<answer> client::answers_flags_add(unique_id id, unique_id flag_id, std::string comment, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/flags/add",
            append(restriction.as_map(), "option_id", std::to_string(flag_id), "comment", std::move(comment)), &parse_answer, false, true);
}

std::future<std::vector<question>> client::answers_questions(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests<question>, *this, "answers/" + join(ids) + "/questions", restriction.as_map(), &parse_question, ids.size(), false, false);
}

std::future<question> client::answers_questions(const unique_id& id, const question_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<question>, *this, "answers/" + std::to_string(id) + "/questions", restriction.as_map(), &parse_question, false, false);
}

std::future<answer> client::answers_upvote(const unique_id& id, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/upvote", restriction.as_map(), &parse_answer, false, true);
}

std::future<answer> client::answers_undo_upvote(const unique_id& id, const answer_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<answer>, *this, "answers/" + std::to_string(id) + "/upvote/undo", restriction.as_map(), &parse_answer, false, true);
}

std::future<std::vector<badge>> client::badges(const uinteger_t amount, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<badge>, *this, "badges", restriction.as_map(), &parse_badge, amount, false, false);
}

std::future<std::vector<badge>> client::badges(const badge_restrict& restriction) const {
    return badges(default_amount, restriction);
}

std::future<std::vector<badge>> client::badges(const std::vector<unique_id>& ids, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<badge>, *this, "badges/" + join(ids), restriction.as_map(), &parse_badge, ids.size(), false, false);
}

std::future<badge> client::badges(const unique_id& id, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<badge>, *this, "badges/" + std::to_string(id), restriction.as_map(), &parse_badge, false, false);
}

std::future<std::vector<badge>> client::badges_name(const uinteger_t amount, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<badge>, *this, "badges/name", restriction.as_map(), &parse_badge, amount, false, false);
}

std::future<std::vector<badge>> client::badges_recipients(const uinteger_t amount, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<badge>, *this, "badges/recipients", restriction.as_map(), &parse_badge, amount, false, false);
}

std::future<std::vector<badge>> client::badges_recipients(const std::vector<unique_id>& ids, const uinteger_t badges_per_badge, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<badge, unique_id>, *this, ids, "badges/", "/recipients", restriction.as_map(), &parse_badge,
            badges_per_badge, false, false);
}

std::future<std::vector<badge>> client::badges_recipients(const std::vector<unique_id>& ids, const badge_restrict& restriction) const {
    return badges_recipients(ids, default_amount, restriction);
}

std::future<std::vector<badge>> client::badges_recipients(unique_id id, const uinteger_t badges_per_badge, const badge_restrict& restriction) const {
    return badges_recipients(std::vector<unique_id>{std::move(id)}, badges_per_badge, restriction);
}

std::future<std::vector<badge>> client::badges_recipients(unique_id id, const badge_restrict& restriction) const {
    return badges_recipients(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<badge>> client::badges_tags(const uinteger_t amount, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<badge>, *this, "badges/tags", restriction.as_map(), &parse_badge, amount, false, false);
}

std::future<std::vector<comment>> client::comments(const uinteger_t amount, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<comment>, *this, "comments", restriction.as_map(), &parse_comment, amount, false, false);
}

std::future<std::vector<comment>> client::comments(const std::vector<unique_id>& ids, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<comment>, *this, "comments/" + join(ids), restriction.as_map(), &parse_comment, ids.size(), false, false);
}

std::future<comment> client::comments(const unique_id& id, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<comment>, *this, "comments/" + std::to_string(id), restriction.as_map(), &parse_comment, false, false);
}

std::future<void> client::comments_delete(unique_id id, const bool write) const {
    return std::async(std::launch::async,
            [](client inst, unique_id id, const bool write) {
                inst.execute_request("comments/" + std::to_string(id) + "/delete", {{"preview", parse_value(!write)}}, &parse_comment, false, true);
            },
            *this, std::move(id), write);
}

std::future<comment> client::comments_edit(unique_id id, std::string body, comment_restrict restriction) const {
    return std::async(std::launch::async, &client::execute_request<comment>, *this, "comments/" + std::to_string(id) + "/edit",
            append(restriction.as_map(), "body", std::move(body)), &parse_comment, false, true);
}

std::future<comment> client::comments_flags_add(unique_id id, unique_id flag_id, std::string comment, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<objects::comment>, *this, "comments/" + std::to_string(id) + "/flags/add",
            append(restriction.as_map(), "option_id", std::to_string(flag_id), "comment", std::move(comment)), &parse_comment, false, true);
}

std::future<std::vector<flag_option>> client::comments_flags_options(const unique_id& id) const {
    return std::async(std::launch::async, &client::execute_requests<flag_option>, *this, "comments/" + std::to_string(id) + "/flags/options", map_t{}, &parse_flag_option,
            std::numeric_limits<integer_t>::max(), false, false);
}

std::future<comment> client::comments_upvote(const unique_id& id, const comment_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<comment>, *this, "comments/" + std::to_string(id) + "/upvote", restriction.as_map(), &parse_comment, false, true);
}

std::future<comment> client::comments_undo_upvote(const unique_id& id, const comment_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<comment>, *this, "comments/" + std::to_string(id) + "/upvote/undo", restriction.as_map(), &parse_comment, false, true);
}

std::future<std::vector<event>> client::events(const uinteger_t amount, const date_t& since) const {
    return std::async(std::launch::async, &client::execute_requests<event>, *this, "events", map_t{{"since", parse_value(since)}}, &parse_event, amount, false, false);
}

std::future<std::vector<event>> client::events(const uinteger_t amount) const {
    return events(amount, std::chrono::system_clock::now() - std::chrono::minutes(5));
}

std::future<std::vector<event>> client::events(const date_t& since) const {
    return events(default_amount, since);
}

std::future<info> client::info() const {
    return std::async(std::launch::async, &client::execute_request<objects::info>, *this, "info", map_t{}, &parse_info, false, false);
}

std::future<std::vector<post>> client::posts(const uinteger_t amount, const post_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<post>, *this, "posts", restriction.as_map(), &parse_post, amount, false, false);
}

std::future<std::vector<post>> client::posts(const std::vector<unique_id>& ids, const post_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<post>, *this, "posts/" + join(ids), restriction.as_map(), &parse_post, ids.size(), false, false);
}

std::future<post> client::posts(const unique_id& id, const post_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<post>, *this, "posts/" + std::to_string(id), restriction.as_map(), &parse_post, false, false);
}

std::future<std::vector<comment>> client::posts_comments(const std::vector<unique_id>& ids, const uinteger_t comments_per_post, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<comment, unique_id>, *this, ids, "posts/", "/comments", restriction.as_map(), &parse_comment,
            comments_per_post, false, false);
}

std::future<std::vector<comment>> client::posts_comments(const std::vector<unique_id>& ids, const comment_restrict& restriction) const {
    return posts_comments(ids, default_amount, restriction);
}

std::future<std::vector<comment>> client::posts_comments(unique_id id, const uinteger_t comments_per_post, const comment_restrict& restriction) const {
    return posts_comments(std::vector<unique_id>{std::move(id)}, comments_per_post, restriction);
}

std::future<std::vector<comment>> client::posts_comments(unique_id id, const comment_restrict& restriction) const {
    return posts_comments(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<comment> client::posts_comments_add(unique_id id, std::string body, comment_restrict restriction) const {
    return std::async(std::launch::async, &client::execute_request<comment>, *this, "posts/" + std::to_string(id) + "/comments/add",
            append(restriction.as_map(), "body", std::move(body)), &parse_comment, false, true);
}

std::future<comment> client::posts_comments_render(unique_id id, std::string body, comment_restrict restriction) const {
    return std::async(std::launch::async, &client::execute_request<comment>, *this, "posts/" + std::to_string(id) + "/comments/render",
            append(restriction.as_map(), "body", std::move(body)), &parse_comment, false, false);
}

std::future<std::vector<revision>> client::posts_revisions(const std::vector<unique_id>& ids, const uinteger_t revisions_per_post, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<revision, unique_id>, *this, ids, "posts/", "/revisions", restriction.as_map(), &parse_revision,
            revisions_per_post, false, false);
}

std::future<std::vector<revision>> client::posts_revisions(const std::vector<unique_id>& ids, const data_t& restriction) const {
    return posts_revisions(ids, default_amount, restriction);
}

std::future<std::vector<revision>> client::posts_revisions(unique_id id, const uinteger_t revisions_per_post, const data_t& restriction) const {
    return posts_revisions(std::vector<unique_id>{std::move(id)}, revisions_per_post, restriction);
}

std::future<std::vector<revision>> client::posts_revisions(unique_id id, const data_t& restriction) const {
    return posts_revisions(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<suggested_edit>> client::posts_suggested_edits(const std::vector<unique_id>& ids, const edit_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<suggested_edit>, *this, "posts/" + join(ids) + "/suggested-edits", restriction.as_map(), &parse_suggested_edit,
            ids.size(), false, false);
}

std::future<suggested_edit> client::posts_suggested_edits(const unique_id& id, const edit_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<suggested_edit>, *this, "posts/" + std::to_string(id) + "/suggested-edits", restriction.as_map(),
            &parse_suggested_edit, false, false);
}

std::future<std::vector<privilege>> client::privileges(const uinteger_t amount) const {
    return std::async(std::launch::async, &client::execute_requests<privilege>, *this, "privileges", map_t{}, &parse_privilege, amount, false, false);
}

std::future<std::vector<question>> client::questions(const uinteger_t amount, std::vector<std::string> tags, question_restrict restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests<question>, *this, "questions", append(restriction.as_map(), "tagged", join(tags)), &parse_question, amount, false, false);
}

std::future<std::vector<question>> client::questions(const uinteger_t amount, std::string tag, question_restrict restriction) const {
    return questions(amount, std::vector<std::string>{std::move(tag)}, std::move(restriction));
}

std::future<std::vector<question>> client::questions(const uinteger_t amount, question_restrict restriction) const {
    return questions(amount, std::vector<std::string>{}, std::move(restriction));
}

std::future<std::vector<question>> client::questions(std::vector<std::string> tags, question_restrict restriction) const {
    return questions(default_amount, std::move(tags), std::move(restriction));
}

std::future<std::vector<question>> client::questions(std::string tag, question_restrict restriction) const {
    return questions(default_amount, std::vector<std::string>{std::move(tag)}, std::move(restriction));
}

std::future<std::vector<question>> client::questions(question_restrict restriction) const {
    return questions(default_amount, std::vector<std::string>{}, std::move(restriction));
}

std::future<std::vector<question>> client::questions(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "questions/" + join(ids), restriction.as_map(), &parse_question, ids.size(), false, false);
}

std::future<question> client::questions(const unique_id& id, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id), restriction.as_map(), &parse_question, false, false);
}

std::future<std::vector<answer>> client::questions_answers(const std::vector<unique_id>& ids, const uinteger_t answers_per_question, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<answer, unique_id>, *this, ids, "questions/", "/answers", restriction.as_map(), &parse_answer,
            answers_per_question, false, false);
}

std::future<std::vector<answer>> client::questions_answers(const std::vector<unique_id>& ids, const answer_restrict& restriction) const {
    return questions_answers(ids, default_amount, restriction);
}

std::future<std::vector<answer>> client::questions_answers(unique_id id, const uinteger_t answers_per_question, const answer_restrict& restriction) const {
    return questions_answers(std::vector<unique_id>{std::move(id)}, answers_per_question, restriction);
}

std::future<std::vector<answer>> client::questions_answers(unique_id id, const answer_restrict& restriction) const {
    return questions_answers(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<answer> client::questions_answers_add(unique_id id, std::string body, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "questions/" + std::to_string(id) + "/answers/add",
            append(restriction.as_map(), "body", std::move(body)), &parse_answer, false, true);
}

std::future<answer> client::questions_answers_render(unique_id id, std::string body, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<answer>, *this, "questions/" + std::to_string(id) + "/answers/render",
            append(restriction.as_map(), "body", std::move(body)), &parse_answer, false, false);
}

std::future<std::vector<flag_option>> client::questions_close_options(const unique_id& id, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<flag_option>, *this, "questions/" + std::to_string(id) + "/close/options", restriction.as_map(),
            &parse_flag_option, std::numeric_limits<integer_t>::max(), false, false);
}

std::future<std::vector<comment>> client::questions_comments(const std::vector<unique_id>& ids, const uinteger_t comments_per_question, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<comment, unique_id>, *this, ids, "questions/", "/comments", restriction.as_map(), &parse_comment,
            comments_per_question, false, false);
}

std::future<std::vector<comment>> client::questions_comments(const std::vector<unique_id>& ids, const comment_restrict& restriction) const {
    return questions_comments(ids, default_amount, restriction);
}

std::future<std::vector<comment>> client::questions_comments(unique_id id, const uinteger_t comments_per_question, const comment_restrict& restriction) const {
    return questions_comments(std::vector<unique_id>{std::move(id)}, comments_per_question, restriction);
}

std::future<std::vector<comment>> client::questions_comments(unique_id id, const comment_restrict& restriction) const {
    return questions_comments(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<void> client::questions_delete(unique_id id, const bool write) const {
    return std::async(std::launch::async,
            [](const client& inst, const unique_id& id, const bool write) {
                inst.execute_request("questions/" + std::to_string(id) + "/delete", {{"preview", parse_value(!write)}}, &parse_question, false, true);
            },
            *this, std::move(id), write);
}

std::future<question> client::questions_downvote(const unique_id& id, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/downvote", restriction.as_map(), &parse_question, false, true);
}

std::future<question> client::questions_undo_downvote(const unique_id& id, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/downvote/undo", restriction.as_map(), &parse_question,
            false, true);
}

std::future<question> client::questions_edit(unique_id id, std::string title, std::string body, std::vector<std::string> tags, std::string comment, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/edit",
            append(restriction.as_map(), "title", std::move(title), "body", std::move(body), "tags", join(tags), "comment", std::move(comment)), &parse_question, false, true);
}

std::future<question> client::questions_edit(unique_id id, std::string title, std::string body, std::string comment, data_t restriction) const {
    return questions_edit(std::move(id), std::move(title), std::move(body), {}, std::move(comment), std::move(restriction));
}

std::future<question> client::questions_edit(unique_id id, std::string title, std::string comment, data_t restriction) const {
    return questions_edit(std::move(id), std::move(title), {}, {}, std::move(comment), std::move(restriction));
}

std::future<question> client::questions_favorite(const unique_id& id, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/favorite", restriction.as_map(), &parse_question, false, true);
}

std::future<question> client::questions_undo_favorite(const unique_id& id, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/favorite/undo", restriction.as_map(), &parse_question,
            false, true);
}

std::future<question> client::questions_flags_add(unique_id id, unique_id flag_id, std::string comment, std::string target_site, unique_id question_id, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/flags/add",
            append(restriction.as_map(), "option_id", std::to_string(flag_id), "comment", std::move(comment), "target_site", std::move(target_site), "question_id",
                    std::to_string(question_id)),
            &parse_question, false, true);
}

std::future<question> client::questions_flags_add(unique_id id, unique_id flag_id, std::string comment, data_t restriction) const {
    return questions_flags_add(std::move(id), std::move(flag_id), std::move(comment), "", unique_id{}, std::move(restriction));
}

std::future<std::vector<flag_option>> client::questions_flags_options(const unique_id& id, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<flag_option>, *this, "questions/" + std::to_string(id) + "/flags/options", restriction.as_map(),
            &parse_flag_option, std::numeric_limits<integer_t>::max(), false, false);
}

std::future<std::vector<question>> client::questions_linked(
        const std::vector<unique_id>& ids, const uinteger_t questions_per_question, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "questions/", "/linked", restriction.as_map(), &parse_question,
            questions_per_question, false, false);
}

std::future<std::vector<question>> client::questions_linked(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return questions_linked(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::questions_linked(unique_id id, const uinteger_t questions_per_question, const question_restrict& restriction) const {
    return questions_linked(std::vector<unique_id>{std::move(id)}, questions_per_question, restriction);
}

std::future<std::vector<question>> client::questions_linked(unique_id id, const question_restrict& restriction) const {
    return questions_linked(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<question>> client::questions_related(
        const std::vector<unique_id>& ids, const uinteger_t questions_per_question, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "questions/", "/related", restriction.as_map(), &parse_question,
            questions_per_question, false, false);
}

std::future<std::vector<question>> client::questions_related(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return questions_related(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::questions_related(unique_id id, const uinteger_t questions_per_question, const question_restrict& restriction) const {
    return questions_related(std::vector<unique_id>{std::move(id)}, questions_per_question, restriction);
}

std::future<std::vector<question>> client::questions_related(unique_id id, const question_restrict& restriction) const {
    return questions_related(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<question_timeline>> client::questions_timeline(
        const std::vector<unique_id>& ids, const uinteger_t timelines_per_question, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question_timeline, unique_id>, *this, ids, "questions/", "/timeline", restriction.as_map(),
            &parse_question_timeline, timelines_per_question, false, false);
}

std::future<std::vector<question_timeline>> client::questions_timeline(const std::vector<unique_id>& ids, const data_t& restriction) const {
    return questions_timeline(ids, default_amount, restriction);
}

std::future<std::vector<question_timeline>> client::questions_timeline(unique_id id, const uinteger_t timelines_per_question, const data_t& restriction) const {
    return questions_timeline(std::vector<unique_id>{std::move(id)}, timelines_per_question, restriction);
}

std::future<std::vector<question_timeline>> client::questions_timeline(unique_id id, const data_t& restriction) const {
    return questions_timeline(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<question> client::questions_upvote(const unique_id& id, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/upvote", restriction.as_map(), &parse_question, false, true);
}

std::future<question> client::questions_undo_upvote(const unique_id& id, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<question>, *this, "questions/" + std::to_string(id) + "/upvote/undo", restriction.as_map(), &parse_question, false, true);
}

std::future<question> client::questions_add(std::string title, std::string body, std::vector<std::string> tags, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<question>, *this, "questions/add",
            append(restriction.as_map(), "title", std::move(title), "body", std::move(body), "tags", join(tags)), &parse_question, false, true);
}

std::future<std::vector<question>> client::questions_featured(std::vector<std::string> tags, const uinteger_t amount, question_restrict restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "questions/featured", append(restriction.as_map(), "tagged", join(tags)), &parse_question,
            amount, false, false);
}

std::future<std::vector<question>> client::questions_featured(std::string tag, const uinteger_t amount, question_restrict restriction) const {
    return questions_featured(std::vector<std::string>{std::move(tag)}, amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_featured(std::vector<std::string> tags, question_restrict restriction) const {
    return questions_featured(std::move(tags), default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_featured(std::string tag, question_restrict restriction) const {
    return questions_featured(std::vector<std::string>{std::move(tag)}, default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_featured(const uinteger_t amount, question_restrict restriction) const {
    return questions_featured(std::vector<std::string>{}, amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_featured(question_restrict restriction) const {
    return questions_featured(std::vector<std::string>{}, default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_no_answers(std::vector<std::string> tags, const uinteger_t amount, question_restrict restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "questions/no-answers", append(restriction.as_map(), "tagged", join(tags)), &parse_question,
            amount, false, false);
}

std::future<std::vector<question>> client::questions_no_answers(std::string tag, const uinteger_t amount, question_restrict restriction) const {
    return questions_no_answers(std::vector<std::string>{std::move(tag)}, amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_no_answers(std::vector<std::string> tags, question_restrict restriction) const {
    return questions_no_answers(std::move(tags), default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_no_answers(std::string tag, question_restrict restriction) const {
    return questions_no_answers(std::vector<std::string>{std::move(tag)}, default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_no_answers(const uinteger_t amount, question_restrict restriction) const {
    return questions_no_answers(std::vector<std::string>{}, amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_no_answers(question_restrict restriction) const {
    return questions_no_answers(std::vector<std::string>{}, default_amount, std::move(restriction));
}

std::future<question> client::questions_render(std::string title, std::string body, std::vector<std::string> tags, data_t restriction) const {
    return std::async(std::launch::async, &client::execute_request<question>, *this, "questions/render",
            append(restriction.as_map(), "title", std::move(title), "body", std::move(body), "tags", join(tags)), &parse_question, false, false);
}

std::future<question> client::questions_render(std::string title, std::string body, std::string tag, data_t restriction) const {
    return questions_render(std::move(title), std::move(body), std::vector<std::string>{std::move(tag)}, std::move(restriction));
}

std::future<std::vector<question>> client::questions_unanswered(std::vector<std::string> tags, const uinteger_t amount, question_restrict restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "questions/unanswered", append(restriction.as_map(), "tagged", join(tags)), &parse_question,
            amount, false, false);
}

std::future<std::vector<question>> client::questions_unanswered(std::string tag, const uinteger_t amount, question_restrict restriction) const {
    return questions_unanswered(std::vector<std::string>{std::move(tag)}, amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_unanswered(std::vector<std::string> tags, question_restrict restriction) const {
    return questions_unanswered(std::move(tags), default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_unanswered(std::string tag, question_restrict restriction) const {
    return questions_unanswered(std::vector<std::string>{std::move(tag)}, default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_unanswered(const uinteger_t amount, question_restrict restriction) const {
    return questions_unanswered(std::vector<std::string>{}, amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_unanswered(question_restrict restriction) const {
    return questions_unanswered(std::vector<std::string>{}, default_amount, std::move(restriction));
}

std::future<std::vector<question>> client::questions_unanswered_my_tags(const uinteger_t amount, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "questions/unanswered/my-tags", restriction.as_map(), &parse_question, amount, false, false);
}

std::future<std::vector<question>> client::questions_unanswered_my_tags(const question_restrict& restriction) const {
    return questions_unanswered_my_tags(default_amount, restriction);
}

std::future<std::vector<revision>> client::revisions(const std::vector<std::string>& guids, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests<revision>, *this, "revisions/" + join(guids), restriction.as_map(), &parse_revision, guids.size(), false, false);
}

std::future<revision> client::revisions(const std::string& guid, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_request<revision>, *this, "revisions/" + guid, restriction.as_map(), &parse_revision, false, false);
}

std::future<std::vector<question>> client::search(std::string title, const uinteger_t amount, const search_restrict& restriction) const {
    return search(std::move(title), std::vector<std::string>{}, std::vector<std::string>{}, amount, restriction);
}

std::future<std::vector<question>> client::search(std::string title, const search_restrict& restriction) const {
    return search(std::move(title), std::vector<std::string>{}, std::vector<std::string>{}, default_amount, restriction);
}

std::future<std::vector<question>> client::search(std::string title, const std::vector<std::string>& tagged, const uinteger_t amount, const search_restrict& restriction) const {
    return search(std::move(title), tagged, std::vector<std::string>{}, amount, restriction);
}

std::future<std::vector<question>> client::search(std::string title, const std::vector<std::string>& tagged, const search_restrict& restriction) const {
    return search(std::move(title), tagged, std::vector<std::string>{}, default_amount, restriction);
}

std::future<std::vector<question>> client::search(
        std::string title, const std::vector<std::string>& tagged, const std::vector<std::string>& nottagged, const uinteger_t amount, const search_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "search",
            append(restriction.as_map(), "intitle", std::move(title), "tagged", join(tagged), "nottagged", join(nottagged)), &parse_question, amount, false, false);
}

std::future<std::vector<question>> client::search(
        std::string title, const std::vector<std::string>& tagged, const std::vector<std::string>& nottagged, const search_restrict& restriction) const {
    return search(std::move(title), tagged, nottagged, default_amount, restriction);
}

std::future<std::vector<question>> client::search(const std::vector<std::string>& tagged, const uinteger_t amount, const search_restrict& restriction) const {
    return search("", tagged, std::vector<std::string>{}, amount, restriction);
}

std::future<std::vector<question>> client::search(const std::vector<std::string>& tagged, const search_restrict& restriction) const {
    return search("", tagged, std::vector<std::string>{}, default_amount, restriction);
}

std::future<std::vector<question>> client::search(
        const std::vector<std::string>& tagged, const std::vector<std::string>& nottagged, const uinteger_t amount, const search_restrict& restriction) const {
    return search("", tagged, nottagged, amount, restriction);
}

std::future<std::vector<question>> client::search(const std::vector<std::string>& tagged, const std::vector<std::string>& nottagged, const search_restrict& restriction) const {
    return search("", tagged, nottagged, default_amount, restriction);
}

std::future<std::vector<question>> client::search_advanced(const uinteger_t amount, const search_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "search/advanced", restriction.as_map(), &parse_question, amount, false, false);
}

std::future<std::vector<question>> client::search_advanced(const search_restrict& restriction) const {
    return search_advanced(default_amount, restriction);
}

std::future<std::vector<search_excerpt>> client::similar(const uinteger_t amount, const search_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<search_excerpt>, *this, "search/excerpts", restriction.as_map(), &parse_search_excerpt, amount, false, false);
}

std::future<std::vector<search_excerpt>> client::similar(const search_restrict& restriction) const {
    return similar(default_amount, restriction);
}

std::future<std::vector<suggested_edit>> client::suggested_edits(const uinteger_t amount, const edit_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<suggested_edit>, *this, "suggested-edits", restriction.as_map(), &parse_suggested_edit, amount, false, false);
}

std::future<std::vector<suggested_edit>> client::suggested_edits(const edit_restrict& restriction) const {
    return suggested_edits(default_amount, restriction);
}

std::future<std::vector<suggested_edit>> client::suggested_edits(const std::vector<unique_id>& ids, const edit_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<suggested_edit>, *this, "suggested-edits/" + join(ids), restriction.as_map(), &parse_suggested_edit, ids.size(),
            false, false);
}

std::future<suggested_edit> client::suggested_edits(const unique_id& id, const edit_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<suggested_edit>, *this, "suggested-edits/" + std::to_string(id), restriction.as_map(), &parse_suggested_edit,
            false, false);
}

std::future<std::vector<tag>> client::tags(std::string name, const uinteger_t amount, const tag_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<tag>, *this, "tags", append(restriction.as_map(), "inname", std::move(name)), &parse_tag, amount, false, false);
}

std::future<std::vector<tag>> client::tags(std::string name, const tag_restrict& restriction) const {
    return tags(std::move(name), default_amount, restriction);
}

std::future<std::vector<tag>> client::tags(const uinteger_t amount, const tag_restrict& restriction) const {
    return tags("", amount, restriction);
}

std::future<std::vector<tag>> client::tags(const tag_restrict& restriction) const {
    return tags("", default_amount, restriction);
}

std::future<std::vector<tag>> client::tags_info(const std::vector<std::string>& names, const tag_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<tag>, *this, "tags/" + join(names) + "/info", restriction.as_map(), &parse_tag, names.size(), false, false);
}

std::future<tag> client::tags_info(const std::string& name, const tag_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<tag>, *this, "tags/" + name + "/info", restriction.as_map(), &parse_tag, false, false);
}

std::future<std::vector<tag>> client::tags_moderator(std::string name, const uinteger_t amount, const tag_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<tag>, *this, "tags/moderator-only", append(restriction.as_map(), "inname", std::move(name)), &parse_tag, amount,
            false, false);
}

std::future<std::vector<tag>> client::tags_moderator(std::string name, const tag_restrict& restriction) const {
    return tags_moderator(std::move(name), default_amount, restriction);
}

std::future<std::vector<tag>> client::tags_moderator(const uinteger_t amount, const tag_restrict& restriction) const {
    return tags_moderator("", amount, restriction);
}

std::future<std::vector<tag>> client::tags_moderator(const tag_restrict& restriction) const {
    return tags_moderator("", default_amount, restriction);
}

std::future<std::vector<tag>> client::tags_required(std::string name, const uinteger_t amount, const tag_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests<tag>, *this, "tags/required", append(restriction.as_map(), "inname", std::move(name)), &parse_tag, amount, false, false);
}

std::future<std::vector<tag>> client::tags_required(std::string name, const tag_restrict& restriction) const {
    return tags_required(std::move(name), default_amount, restriction);
}

std::future<std::vector<tag>> client::tags_required(const uinteger_t amount, const tag_restrict& restriction) const {
    return tags_required("", amount, restriction);
}

std::future<std::vector<tag>> client::tags_required(const tag_restrict& restriction) const {
    return tags_required("", default_amount, restriction);
}

std::future<std::vector<tag_synonym>> client::tags_synonyms(const uinteger_t amount, const synonym_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<tag_synonym>, *this, "tags/synonyms", restriction.as_map(), &parse_tag_synonym, amount, false, false);
}

std::future<std::vector<tag_synonym>> client::tags_synonyms(const synonym_restrict& restriction) const {
    return tags_synonyms(default_amount, restriction);
}

std::future<std::vector<question>> client::tags_faq(const std::vector<std::string>& tags, const uinteger_t amount, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "tags/" + join(tags) + "/faq", restriction.as_map(), &parse_question, amount, false, false);
}

std::future<std::vector<question>> client::tags_faq(const std::vector<std::string>& tags, const data_t& restriction) const {
    return tags_faq(tags, default_amount, restriction);
}

std::future<std::vector<question>> client::tags_faq(std::string tag, const uinteger_t amount, const data_t& restriction) const {
    return tags_faq(std::vector<std::string>{std::move(tag)}, amount, restriction);
}

std::future<std::vector<question>> client::tags_faq(std::string tag, const data_t& restriction) const {
    return tags_faq(std::vector<std::string>{std::move(tag)}, default_amount, restriction);
}

std::future<std::vector<tag>> client::tags_related(const std::vector<std::string>& tags, const uinteger_t amount, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<tag>, *this, "tags/" + join(tags) + "/related", restriction.as_map(), &parse_tag, amount, false, false);
}

std::future<std::vector<tag>> client::tags_related(const std::vector<std::string>& tags, const data_t& restriction) const {
    return tags_related(tags, default_amount, restriction);
}

std::future<std::vector<tag>> client::tags_related(std::string tag, const uinteger_t amount, const data_t& restriction) const {
    return tags_related(std::vector<std::string>{std::move(tag)}, amount, restriction);
}

std::future<std::vector<tag>> client::tags_related(std::string tag, const data_t& restriction) const {
    return tags_related(std::vector<std::string>{std::move(tag)}, default_amount, restriction);
}

std::future<std::vector<tag_synonym>> client::tags_synonyms(const std::vector<std::string>& tags, const uinteger_t tags_per_tag, const synonym_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<tag_synonym, std::string>, *this, tags, "tags/", "/synonyms", restriction.as_map(), &parse_tag_synonym,
            tags_per_tag, false, false);
}

std::future<std::vector<tag_synonym>> client::tags_synonyms(const std::vector<std::string>& tags, const synonym_restrict& restriction) const {
    return tags_synonyms(tags, default_amount, restriction);
}

std::future<std::vector<tag_synonym>> client::tags_synonyms(std::string tag, const uinteger_t tags_per_tag, const synonym_restrict& restriction) const {
    return tags_synonyms(std::vector<std::string>{std::move(tag)}, tags_per_tag, restriction);
}

std::future<std::vector<tag_synonym>> client::tags_synonyms(std::string tag, const synonym_restrict& restriction) const {
    return tags_synonyms(std::vector<std::string>{std::move(tag)}, default_amount, restriction);
}

std::future<std::vector<tag_score>> client::tags_top_answerers(const std::string& tag, const stackcpp::period& period, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<tag_score>, *this, "tags/" + tag + "/top-answerers/" + parse_value(period), restriction.as_map(),
            &parse_tag_score, default_amount, false, false);
}

std::future<std::vector<tag_score>> client::tags_top_questioners(const std::string& tag, const stackcpp::period& period, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<tag_score>, *this, "tags/" + tag + "/top-askers/" + parse_value(period), restriction.as_map(), &parse_tag_score,
            default_amount, false, false);
}

std::future<std::vector<tag_wiki>> client::tags_wikis(const std::vector<std::string>& tags, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests<tag_wiki>, *this, "tags/" + join(tags) + "/wikis", restriction.as_map(), &parse_tag_wiki, tags.size(), false, false);
}

std::future<tag_wiki> client::tags_wikis(const std::string& tag, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_request<tag_wiki>, *this, "tags/" + tag + "/wikis", restriction.as_map(), &parse_tag_wiki, false, false);
}

std::future<std::vector<user>> client::users(std::string username, const uinteger_t amount, user_restrict restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests<user>, *this, "users", append(restriction.as_map(), "inname", std::move(username)), &parse_user, amount, false, false);
}

std::future<std::vector<user>> client::users(std::string username, user_restrict restriction) const {
    return users(std::move(username), default_amount, std::move(restriction));
}

std::future<std::vector<user>> client::users(const uinteger_t amount, user_restrict restriction) const {
    return users("", amount, std::move(restriction));
}

std::future<std::vector<user>> client::users(user_restrict restriction) const {
    return users("", default_amount, std::move(restriction));
}

std::future<std::vector<user>> client::users(const std::vector<unique_id>& ids, const user_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<user>, *this, "users/" + join(ids), restriction.as_map(), &parse_user, ids.size(), false, false);
}

std::future<user> client::users(const unique_id& id, const user_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_request<user>, *this, "users/" + std::to_string(id), restriction.as_map(), &parse_user, false, false);
}

std::future<std::vector<answer>> client::users_answers(const std::vector<unique_id>& ids, const uinteger_t answers_per_user, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<answer, unique_id>, *this, ids, "users/", "/answers", restriction.as_map(), &parse_answer,
            answers_per_user, false, false);
}

std::future<std::vector<answer>> client::users_answers(const std::vector<unique_id>& ids, const answer_restrict& restriction) const {
    return users_answers(ids, default_amount, restriction);
}

std::future<std::vector<answer>> client::users_answers(unique_id id, const uinteger_t answers_per_user, const answer_restrict& restriction) const {
    return users_answers(std::vector<unique_id>{std::move(id)}, answers_per_user, restriction);
}

std::future<std::vector<answer>> client::users_answers(unique_id id, const answer_restrict& restriction) const {
    return users_answers(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<badge>> client::users_badges(const std::vector<unique_id>& ids, const uinteger_t badges_per_user, const badge_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<badge, unique_id>, *this, ids, "users/", "/badges", restriction.as_map(), &parse_badge, badges_per_user,
            false, false);
}

std::future<std::vector<badge>> client::users_badges(const std::vector<unique_id>& ids, const badge_restrict& restriction) const {
    return users_badges(ids, default_amount, restriction);
}

std::future<std::vector<badge>> client::users_badges(unique_id id, const uinteger_t badges_per_user, const badge_restrict& restriction) const {
    return users_badges(std::vector<unique_id>{std::move(id)}, badges_per_user, restriction);
}

std::future<std::vector<badge>> client::users_badges(unique_id id, const badge_restrict& restriction) const {
    return users_badges(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<comment>> client::users_comments(const std::vector<unique_id>& ids, const uinteger_t comments_per_user, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<comment, unique_id>, *this, ids, "users/", "/comments", restriction.as_map(), &parse_comment,
            comments_per_user, false, false);
}

std::future<std::vector<comment>> client::users_comments(const std::vector<unique_id>& ids, const comment_restrict& restriction) const {
    return users_comments(ids, default_amount, restriction);
}

std::future<std::vector<comment>> client::users_comments(unique_id id, const uinteger_t comments_per_user, const comment_restrict& restriction) const {
    return users_comments(std::vector<unique_id>{std::move(id)}, comments_per_user, restriction);
}

std::future<std::vector<comment>> client::users_comments(unique_id id, const comment_restrict& restriction) const {
    return users_comments(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<comment>> client::users_comments(
        const std::vector<unique_id>& ids, const unique_id& to_id, const uinteger_t comments_per_user, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<comment, unique_id>, *this, ids, "users/", "/comments/" + std::to_string(to_id), restriction.as_map(),
            &parse_comment, comments_per_user, false, false);
}

std::future<std::vector<comment>> client::users_comments(const std::vector<unique_id>& ids, const unique_id& to_id, const comment_restrict& restriction) const {
    return users_comments(ids, to_id, default_amount, restriction);
}

std::future<std::vector<comment>> client::users_comments(unique_id id, const unique_id& to_id, const uinteger_t comments_per_user, const comment_restrict& restriction) const {
    return users_comments(std::vector<unique_id>{std::move(id)}, to_id, comments_per_user, restriction);
}

std::future<std::vector<comment>> client::users_comments(unique_id id, const unique_id& to_id, const comment_restrict& restriction) const {
    return users_comments(std::vector<unique_id>{std::move(id)}, to_id, default_amount, restriction);
}

std::future<std::vector<question>> client::users_favorites(const std::vector<unique_id>& ids, const uinteger_t questions_per_user, const favorite_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "users/", "/favorites", restriction.as_map(), &parse_question,
            questions_per_user, false, false);
}

std::future<std::vector<question>> client::users_favorites(const std::vector<unique_id>& ids, const favorite_restrict& restriction) const {
    return users_favorites(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::users_favorites(unique_id id, const uinteger_t questions_per_user, const favorite_restrict& restriction) const {
    return users_favorites(std::vector<unique_id>{std::move(id)}, questions_per_user, restriction);
}

std::future<std::vector<question>> client::users_favorites(unique_id id, const favorite_restrict& restriction) const {
    return users_favorites(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<comment>> client::users_mentioned(const std::vector<unique_id>& ids, const uinteger_t comments_per_user, const comment_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<comment, unique_id>, *this, ids, "users/", "/mentioned", restriction.as_map(), &parse_comment,
            comments_per_user, false, false);
}

std::future<std::vector<comment>> client::users_mentioned(const std::vector<unique_id>& ids, const comment_restrict& restriction) const {
    return users_mentioned(ids, default_amount, restriction);
}

std::future<std::vector<comment>> client::users_mentioned(unique_id id, const uinteger_t comments_per_user, const comment_restrict& restriction) const {
    return users_mentioned(std::vector<unique_id>{std::move(id)}, comments_per_user, restriction);
}

std::future<std::vector<comment>> client::users_mentioned(unique_id id, const comment_restrict& restriction) const {
    return users_mentioned(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<network_activity>> client::users_activity(const unique_id& id, const uinteger_t amount, const activity_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<network_activity>, *this, "users/" + std::to_string(id) + "/network-activity", restriction.as_map(),
            &parse_network_activity, amount, true, false);
}

std::future<std::vector<network_activity>> client::users_activity(const unique_id& id, const activity_restrict& restriction) const {
    return users_activity(id, default_amount, restriction);
}

std::future<std::vector<notification>> client::users_notifications(const unique_id& id, const uinteger_t amount) const {
    return std::async(std::launch::async, &client::execute_requests<notification>, *this, "users/" + std::to_string(id) + "/notifications", map_t{}, &parse_notification, amount,
            false, true);
}

std::future<std::vector<notification>> client::users_notifications_unread(const unique_id& id, const uinteger_t amount) const {
    return std::async(std::launch::async, &client::execute_requests<notification>, *this, "users/" + std::to_string(id) + "notifications/unread", map_t{}, &parse_notification,
            amount, false, true);
}

std::future<std::vector<post>> client::users_posts(const std::vector<unique_id>& ids, const uinteger_t posts_per_user, const post_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests_loop<post, unique_id>, *this, ids, "users/", "/posts", restriction.as_map(), &parse_post, posts_per_user, false, false);
}

std::future<std::vector<post>> client::users_posts(const std::vector<unique_id>& ids, const post_restrict& restriction) const {
    return users_posts(ids, default_amount, restriction);
}

std::future<std::vector<post>> client::users_posts(unique_id id, const uinteger_t posts_per_user, const post_restrict& restriction) const {
    return users_posts(std::vector<unique_id>{std::move(id)}, posts_per_user, restriction);
}

std::future<std::vector<post>> client::users_posts(unique_id id, const post_restrict& restriction) const {
    return users_posts(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<privilege>> client::users_privileges(const unique_id& id, const uinteger_t amount) const {
    return std::async(
            std::launch::async, &client::execute_requests<privilege>, *this, "users/" + std::to_string(id) + "/privileges", map_t{}, &parse_privilege, amount, false, false);
}

std::future<std::vector<question>> client::users_questions(const std::vector<unique_id>& ids, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "users/", "/questions", restriction.as_map(), &parse_question,
            questions_per_user, false, false);
}

std::future<std::vector<question>> client::users_questions(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return users_questions(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions(unique_id id, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return users_questions(std::vector<unique_id>{std::move(id)}, questions_per_user, restriction);
}

std::future<std::vector<question>> client::users_questions(unique_id id, const question_restrict& restriction) const {
    return users_questions(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_featured(
        const std::vector<unique_id>& ids, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "users/", "/questions/featured", restriction.as_map(), &parse_question,
            questions_per_user, false, false);
}

std::future<std::vector<question>> client::users_questions_featured(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return users_questions_featured(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_featured(unique_id id, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return users_questions_featured(std::vector<unique_id>{std::move(id)}, questions_per_user, restriction);
}

std::future<std::vector<question>> client::users_questions_featured(unique_id id, const question_restrict& restriction) const {
    return users_questions_featured(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_no_answers(
        const std::vector<unique_id>& ids, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "users/", "/questions/no-answers", restriction.as_map(), &parse_question,
            questions_per_user, false, false);
}

std::future<std::vector<question>> client::users_questions_no_answers(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return users_questions_no_answers(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_no_answers(unique_id id, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return users_questions_no_answers(std::vector<unique_id>{std::move(id)}, questions_per_user, restriction);
}

std::future<std::vector<question>> client::users_questions_no_answers(unique_id id, const question_restrict& restriction) const {
    return users_questions_no_answers(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_unaccepted(
        const std::vector<unique_id>& ids, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "users/", "/questions/unaccepted", restriction.as_map(), &parse_question,
            questions_per_user, false, false);
}

std::future<std::vector<question>> client::users_questions_unaccepted(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return users_questions_unaccepted(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_unaccepted(unique_id id, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return users_questions_unaccepted(std::vector<unique_id>{std::move(id)}, questions_per_user, restriction);
}

std::future<std::vector<question>> client::users_questions_unaccepted(unique_id id, const question_restrict& restriction) const {
    return users_questions_unaccepted(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_unanswered(
        const std::vector<unique_id>& ids, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<question, unique_id>, *this, ids, "users/", "/questions/unanswered", restriction.as_map(), &parse_question,
            questions_per_user, false, false);
}

std::future<std::vector<question>> client::users_questions_unanswered(const std::vector<unique_id>& ids, const question_restrict& restriction) const {
    return users_questions_unanswered(ids, default_amount, restriction);
}

std::future<std::vector<question>> client::users_questions_unanswered(unique_id id, const uinteger_t questions_per_user, const question_restrict& restriction) const {
    return users_questions_unanswered(std::vector<unique_id>{std::move(id)}, questions_per_user, restriction);
}

std::future<std::vector<question>> client::users_questions_unanswered(unique_id id, const question_restrict& restriction) const {
    return users_questions_unanswered(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<reputation>> client::users_reputation(const std::vector<unique_id>& ids, const uinteger_t reputations_per_user, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<reputation, unique_id>, *this, ids, "users/", "/reputation", restriction.as_map(), &parse_reputation,
            reputations_per_user, false, false);
}

std::future<std::vector<reputation>> client::users_reputation(const std::vector<unique_id>& ids, const data_t& restriction) const {
    return users_reputation(ids, default_amount, restriction);
}

std::future<std::vector<reputation>> client::users_reputation(unique_id id, const uinteger_t reputations_per_user, const data_t& restriction) const {
    return users_reputation(std::vector<unique_id>{std::move(id)}, reputations_per_user, restriction);
}

std::future<std::vector<reputation>> client::users_reputation(unique_id id, const data_t& restriction) const {
    return users_reputation(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<reputation_history>> client::users_reputation_history(const std::vector<unique_id>& ids, const uinteger_t history_per_user) const {
    return std::async(std::launch::async, &client::execute_requests_loop<reputation_history, unique_id>, *this, ids, "users/", "/reputation-history", map_t{},
            &parse_reputation_history, history_per_user, false, false);
}

std::future<std::vector<reputation_history>> client::users_reputation_history(unique_id id, const uinteger_t history_per_user) const {
    return users_reputation_history(std::vector<unique_id>{std::move(id)}, history_per_user);
}

std::future<std::vector<reputation_history>> client::users_reputation_history_full(const unique_id& id, const uinteger_t history_per_user) const {
    return std::async(std::launch::async, &client::execute_requests<reputation_history>, *this, "users/" + std::to_string(id) + "/reputation-history/full", map_t{},
            &parse_reputation_history, history_per_user, false, true);
}

std::future<std::vector<suggested_edit>> client::users_suggested_edits(const std::vector<unique_id>& ids, const uinteger_t edits_per_user, const edit_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<suggested_edit, unique_id>, *this, ids, "users/", "/suggested-edits", restriction.as_map(),
            &parse_suggested_edit, edits_per_user, false, false);
}

std::future<std::vector<suggested_edit>> client::users_suggested_edits(const std::vector<unique_id>& ids, const edit_restrict& restriction) const {
    return users_suggested_edits(ids, default_amount, restriction);
}

std::future<std::vector<suggested_edit>> client::users_suggested_edits(unique_id id, const uinteger_t edits_per_user, const edit_restrict& restriction) const {
    return users_suggested_edits(std::vector<unique_id>{std::move(id)}, edits_per_user, restriction);
}

std::future<std::vector<suggested_edit>> client::users_suggested_edits(unique_id id, const edit_restrict& restriction) const {
    return users_suggested_edits(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<tag>> client::users_tags(const std::vector<unique_id>& ids, const uinteger_t tags_per_user, const tag_restrict& restriction) const {
    return std::async(
            std::launch::async, &client::execute_requests_loop<tag, unique_id>, *this, ids, "users/", "/tags", restriction.as_map(), &parse_tag, tags_per_user, false, false);
}

std::future<std::vector<tag>> client::users_tags(const std::vector<unique_id>& ids, const tag_restrict& restriction) const {
    return users_tags(ids, default_amount, restriction);
}

std::future<std::vector<tag>> client::users_tags(unique_id id, const uinteger_t tags_per_user, const tag_restrict& restriction) const {
    return users_tags(std::vector<unique_id>{std::move(id)}, tags_per_user, restriction);
}

std::future<std::vector<tag>> client::users_tags(unique_id id, const tag_restrict& restriction) const {
    return users_tags(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<answer>> client::users_tags_top_answers(const unique_id& id, const std::vector<std::string>& tags, const answer_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<answer>, *this, "users/" + std::to_string(id) + "/tags/" + join(tags) + "/top-answers", restriction.as_map(),
            &parse_answer, default_amount, false, false);
}

std::future<std::vector<answer>> client::users_tags_top_answers(const unique_id& id, std::string tag, const answer_restrict& restriction) const {
    return users_tags_top_answers(id, std::vector<std::string>{std::move(tag)}, restriction);
}

std::future<std::vector<question>> client::users_tags_top_questions(const unique_id& id, const std::vector<std::string>& tags, const question_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<question>, *this, "users/" + std::to_string(id) + "/tags/" + join(tags) + "/top-questions",
            restriction.as_map(), &parse_question, default_amount, false, false);
}

std::future<std::vector<question>> client::users_tags_top_questions(const unique_id& id, std::string tag, const question_restrict& restriction) const {
    return users_tags_top_questions(id, std::vector<std::string>{std::move(tag)}, restriction);
}

std::future<std::vector<user_timeline>> client::users_timeline(const std::vector<unique_id>& ids, const uinteger_t timelines_per_user, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<user_timeline, unique_id>, *this, ids, "users/", "/timeline", restriction.as_map(), &parse_user_timeline,
            timelines_per_user, false, false);
}

std::future<std::vector<user_timeline>> client::users_timeline(const std::vector<unique_id>& ids, const data_t& restriction) const {
    return users_timeline(ids, default_amount, restriction);
}

std::future<std::vector<user_timeline>> client::users_timeline(unique_id id, const uinteger_t timelines_per_user, const data_t& restriction) const {
    return users_timeline(std::vector<unique_id>{std::move(id)}, timelines_per_user, restriction);
}

std::future<std::vector<user_timeline>> client::users_timeline(unique_id id, const data_t& restriction) const {
    return users_timeline(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}

std::future<std::vector<top_tag>> client::users_top_answer_tags(const unique_id& id, const uinteger_t amount) const {
    return std::async(
            std::launch::async, &client::execute_requests<top_tag>, *this, "users/" + std::to_string(id) + "/top-answer-tags", map_t{}, &parse_top_tag, amount, false, false);
}

std::future<std::vector<top_tag>> client::users_top_question_tags(const unique_id& id, const uinteger_t amount) const {
    return std::async(
            std::launch::async, &client::execute_requests<top_tag>, *this, "users/" + std::to_string(id) + "/top-question-tags", map_t{}, &parse_top_tag, amount, false, false);
}

std::future<std::vector<top_tag>> client::users_top_tags(const unique_id& id, const uinteger_t amount) const {
    return std::async(std::launch::async, &client::execute_requests<top_tag>, *this, "users/" + std::to_string(id) + "/top-tags", map_t{}, &parse_top_tag, amount, false, false);
}

std::future<std::vector<user>> client::users_moderators(const uinteger_t amount, const user_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<user>, *this, "users/moderators", restriction.as_map(), &parse_user, amount, false, false);
}

std::future<std::vector<user>> client::users_moderators(const user_restrict& restriction) const {
    return users_moderators(default_amount, restriction);
}

std::future<std::vector<user>> client::users_elected_moderators(const uinteger_t amount, const user_restrict& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<user>, *this, "users/moderators/elected", restriction.as_map(), &parse_user, amount, false, false);
}

std::future<std::vector<user>> client::users_elected_moderators(const user_restrict& restriction) const {
    return users_elected_moderators(default_amount, restriction);
}

std::future<std::vector<inbox_item>> client::users_inbox(const unique_id& id, const uinteger_t amount) const {
    return std::async(std::launch::async, &client::execute_requests<inbox_item>, *this, "users/" + std::to_string(id) + "/inbox", map_t{}, &parse_inbox_item, amount, false, false);
}

std::future<std::vector<inbox_item>> client::users_unread_inbox(const unique_id& id, const uinteger_t amount, const date_t& since) const {
    return std::async(std::launch::async, &client::execute_requests<inbox_item>, *this, "users/" + std::to_string(id) + "/inbox/unread", map_t{{"since", parse_value(since)}},
            &parse_inbox_item, amount, false, false);
}

std::future<std::vector<inbox_item>> client::users_unread_inbox(const unique_id& id, const uinteger_t amount) const {
    return users_unread_inbox(id, amount, std::chrono::system_clock::now() - std::chrono::minutes(5));
}

std::future<std::vector<inbox_item>> client::users_unread_inbox(const unique_id& id, const date_t& since) const {
    return users_unread_inbox(id, default_amount, since);
}

std::future<std::vector<access_token>> client::access_tokens_invalidate(const std::vector<std::string>& tokens, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<access_token>, *this, "access-tokens/" + join(tokens) + "/invalidate", restriction.as_map(),
            &parse_access_token, tokens.size(), true, false);
}

std::future<access_token> client::access_tokens_invalidate(const std::string& token, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<access_token>, *this, "access-tokens/" + token + "/invalidate", restriction.as_map(), &parse_access_token, true, false);
}

std::future<std::vector<access_token>> client::access_tokens(const std::vector<std::string>& tokens, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<access_token>, *this, "access-tokens/" + join(tokens), restriction.as_map(), &parse_access_token, tokens.size(),
            true, false);
}

std::future<access_token> client::access_tokens(const std::string& token, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_request<access_token>, *this, "access-tokens/" + token, restriction.as_map(), &parse_access_token, true, false);
}

std::future<std::vector<access_token>> client::apps_deauthorize(const std::vector<std::string>& tokens, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<access_token>, *this, "apps/" + join(tokens) + "/de-authenticate", restriction.as_map(), &parse_access_token,
            tokens.size(), true, false);
}

std::future<access_token> client::apps_deauthorize(const std::string& token, const data_t& restriction) const {
    return std::async(
            std::launch::async, &client::execute_request<access_token>, *this, "apps/" + token + "/de-authenticate", restriction.as_map(), &parse_access_token, true, false);
}

std::future<std::vector<inbox_item>> client::inbox(const uinteger_t amount, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<inbox_item>, *this, "inbox", restriction.as_map(), &parse_inbox_item, amount, true, false);
}

std::future<std::vector<inbox_item>> client::inbox_unread(const uinteger_t amount, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<inbox_item>, *this, "inbox/unread", restriction.as_map(), &parse_inbox_item, amount, true, false);
}

std::future<std::vector<notification>> client::notifications(const uinteger_t amount, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<notification>, *this, "notifications", restriction.as_map(), &parse_notification, amount, true, false);
}

std::future<std::vector<notification>> client::notifications_unread(const uinteger_t amount, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<notification>, *this, "notifications/unread", restriction.as_map(), &parse_notification, amount, true, false);
}

std::future<std::vector<site>> client::sites(const uinteger_t amount, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests<objects::site>, *this, "sites", restriction.as_map(), &parse_site, amount, true, false);
}

std::future<std::vector<network_user>> client::users_associated(
        const std::vector<unique_id>& ids, const site_type& type, const uinteger_t users_per_user, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<network_user, unique_id>, *this, ids, "users/", "/associated",
            append(restriction.as_map(), "types", parse_value(type)), &parse_network_user, users_per_user, true, false);
}

std::future<std::vector<network_user>> client::users_associated(const std::vector<unique_id>& ids, const site_type& type, const data_t& restriction) const {
    return users_associated(ids, type, default_amount, restriction);
}

std::future<std::vector<network_user>> client::users_associated(const std::vector<unique_id>& ids, const uinteger_t users_per_user, const data_t& restriction) const {
    return users_associated(ids, site_type{}, users_per_user, restriction);
}

std::future<std::vector<network_user>> client::users_associated(const std::vector<unique_id>& ids, const data_t& restriction) const {
    return users_associated(ids, site_type{}, default_amount, restriction);
}

std::future<std::vector<network_user>> client::users_associated(unique_id id, const site_type& type, const uinteger_t users_per_user, const data_t& restriction) const {
    return users_associated(std::vector<unique_id>{std::move(id)}, type, users_per_user, restriction);
}

std::future<std::vector<network_user>> client::users_associated(unique_id id, const site_type& type, const data_t& restriction) const {
    return users_associated(std::vector<unique_id>{std::move(id)}, type, default_amount, restriction);
}

std::future<std::vector<network_user>> client::users_associated(unique_id id, const uinteger_t users_per_user, const data_t& restriction) const {
    return users_associated(std::vector<unique_id>{std::move(id)}, site_type{}, users_per_user, restriction);
}

std::future<std::vector<network_user>> client::users_associated(unique_id id, const data_t& restriction) const {
    return users_associated(std::vector<unique_id>{std::move(id)}, site_type{}, default_amount, restriction);
}

std::future<std::vector<account_merge>> client::users_merges(const std::vector<unique_id>& ids, const uinteger_t merges_per_user, const data_t& restriction) const {
    return std::async(std::launch::async, &client::execute_requests_loop<account_merge, unique_id>, *this, ids, "users/", "/merges", restriction.as_map(), &parse_account_merge,
            merges_per_user, true, false);
}

std::future<std::vector<account_merge>> client::users_merges(const std::vector<unique_id>& ids, const data_t& restriction) const {
    return users_merges(ids, default_amount, restriction);
}

std::future<std::vector<account_merge>> client::users_merges(unique_id id, const uinteger_t merges_per_user, const data_t& restriction) const {
    return users_merges(std::vector<unique_id>{std::move(id)}, merges_per_user, restriction);
}

std::future<std::vector<account_merge>> client::users_merges(unique_id id, const data_t& restriction) const {
    return users_merges(std::vector<unique_id>{std::move(id)}, default_amount, restriction);
}
}