/* Copyright 2020, Michele Santullo
 * This file is part of orotool.
 *
 * Orotool is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Orotool is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Orotool.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "private/api_restccpp.hpp"
#include "datatypes.hpp"
#include "private/dateconv.hpp"
#include "private/v1_endpoints.hpp"
#include <restc-cpp/restc-cpp.h>
#include <restc-cpp/RequestBuilder.h>
#include <boost/fusion/adapted.hpp>
#if !defined(NDEBUG)
#	include <iostream>
#endif

namespace rc = restc_cpp;

BOOST_FUSION_ADAPT_STRUCT(
	oro::Ping,
	(oro::Timestamp, generation_timestamp)
	(std::string, message)
	(int, version)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::WhoAmI,
	(oro::Timestamp, generation_timestamp)
	(int, version)
	(unsigned int, master_id)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Item,
	(unsigned int, item_id)
	(std::string, unique_name)
	(std::string, name)
	(oro::ItemTypeWrapper, type)
	(std::optional<oro::ItemSubtypeWrapper>, subtype)
	(unsigned int, npc_price)
	(std::optional<unsigned int>, slots)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Items,
	(oro::Timestamp, generation_timestamp)
	(int, version)
	(std::vector<oro::Item>, items)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Icon,
	(unsigned int, item_id)
	(std::string, icon)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Icons,
	(oro::Timestamp, generation_timestamp)
	(int, version)
	(std::vector<oro::Icon>, icons)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Location,
	(std::string, map)
	(int, x)
	(int, y)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::ShopItem,
	(unsigned int, item_id)
	(unsigned int, amount)
	(unsigned int, price)
	(unsigned int, refine)
	(std::vector<unsigned int>, cards)
	(unsigned int, star_crumbs)
	(std::optional<std::string>, element)
	(std::optional<unsigned int>, creator)
	(std::optional<bool>, beloved)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Shop,
	(std::string, title)
	(std::string, owner)
	(oro::Location, location)
	(oro::Timestamp, creation_date)
	(oro::ShopTypeWrapper, type)
	(std::vector<oro::ShopItem>, items)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Shops,
	(oro::Timestamp, generation_timestamp)
	(int, version)
	(std::vector<oro::Shop>, shops)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Creator,
	(unsigned int, char_id)
	(std::string, name)
	(unsigned int, points)
)

BOOST_FUSION_ADAPT_STRUCT(
	oro::Creators,
	(oro::Timestamp, generation_timestamp)
	(int, version)
	(std::vector<oro::Creator>, brewers)
	(std::vector<oro::Creator>, forgers)
)

namespace oro {
namespace {
	constexpr const std::int64_t g_max_json_mem = 1024 * 1024 * 20;

	template <typename T>
	std::pair<Header, T> call_rest_api (
		rc::RestClient& rest_client,
		const std::string& url,
		const std::string& api_key,
		const std::string& client_name,
		const std::string& client_purpose
	) {
#if !defined(NDEBUG)
		std::cout << "call_rest_api(\"" << url << "\", \"" << api_key << "\", \""
			<< client_name << "\", \"" << client_purpose << "\")\n";
#endif

		return rest_client.template ProcessWithPromiseT< std::pair<Header, T> >([&](rc::Context& ctx) {
			std::unique_ptr<rc::Reply> reply = rc::RequestBuilder(ctx)
				.Get(url)
				.Header("X-Client", client_name)
				.Header("X-Client-Purpose", client_purpose)
				.Header("x-api-key", api_key)
				.Execute();

			Header h;
			if (auto val = reply->GetHeader("Date")) {
				h.date = from_header_timestamp(*val);
			}
			if (auto val = reply->GetHeader("X-RateLimit-Limit")) {
				h.rate_limit = std::stoi(*val);
			}
			if (auto val = reply->GetHeader("X-RateLimit-Remaining")) {
				h.rate_limit_remaining = std::stoi(*val);
			}
			if (auto val = reply->GetHeader("X-RateLimit-Reset")) {
				h.rate_limit_reset = std::stoul(*val);
			}
			if (auto val = reply->GetHeader("Retry-After")) {
				h.retry_after = std::stoul(*val);
			}
			if (auto val = reply->GetHeader("Server")) {
				h.server = std::move(*val);
			}

			T retval;
			rc::SerializeFromJson(retval, std::move(reply), nullptr, g_max_json_mem);

			return std::make_pair(std::move(h), std::move(retval));
		}).get();
	}
} //unnamed namespace

ApiRestcCpp::ApiRestcCpp (
	std::string&& root_address,
	std::string&& api_key,
	std::string&& client_name,
	std::string&& client_purpose
) :
	Api(std::move(root_address), std::move(api_key), std::move(client_name), std::move(client_purpose)),
	m_client(rc::RestClient::Create())
{
}

ApiRestcCpp::~ApiRestcCpp() noexcept = default;

std::pair<Header, Ping> ApiRestcCpp::ping() {
	return call_rest_api<oro::Ping>(*m_client, m_prefix + g_endpoint_ping, m_api_key, m_client_name, m_client_purpose);
}

std::pair<Header, WhoAmI> ApiRestcCpp::who_am_i() {
	return call_rest_api<oro::WhoAmI>(*m_client, m_prefix + g_endpoint_whoami, m_api_key, m_client_name, m_client_purpose);
}

std::pair<Header, Items> ApiRestcCpp::items_list() {
	return call_rest_api<oro::Items>(*m_client, m_prefix + g_endpoint_items_list, m_api_key, m_client_name, m_client_purpose);
}

std::pair<Header, Icons> ApiRestcCpp::items_icons() {
	return call_rest_api<oro::Icons>(*m_client, m_prefix + g_endpoint_items_icons, m_api_key, m_client_name, m_client_purpose);
}

std::pair<Header, Shops> ApiRestcCpp::market_list() {
	return call_rest_api<oro::Shops>(*m_client, m_prefix + g_endpoint_market_list, m_api_key, m_client_name, m_client_purpose);
}

std::pair<Header, Creators> ApiRestcCpp::fame_list() {
	return call_rest_api<oro::Creators>(*m_client, m_prefix + g_endpoint_fame_list, m_api_key, m_client_name, m_client_purpose);
}

} //namespace oro
