#include "pch.h"
#include "capesdk.h"
#include "twitchsdk/core/httprequestutils.h"
#include "twitchsdk/core/httprequest.h"
#include "HttpRequest.h"

using namespace ttv;
using namespace TwitchInGames;

namespace {
	string_t const clientIdHeaderName = _T("Client-ID");
}

TwitchInGames::HttpParam::HttpParam(tstring const& name, tstring const& value)
	: paramName(name)
	, paramValue(value) {}

TwitchInGames::HttpParam::HttpParam(tstring const& name, int value)
	: paramName(name)
	, paramValue(to_tstring(value)) {}

bool TwitchInGames::IsTwitchEndpoint(string_t url) {
	return ttv::IsTwitchEndpoint(FromTstring(url));
}

std::vector<char> const TwitchInGames::HttpRequest::EmptyBody;
tstring const TwitchInGames::HttpRequest::EmptyString;

TwitchInGames::HttpRequest::HttpRequest(tstring const& clientId_/* = EmptyString*/, tstring const& token_/* = EmptyString*/) :
	clientId(clientId_),
	token(token_),
	timeout(DefaultTimeout) {}

TwitchInGames::HttpRequest::~HttpRequest() {}

HttpResponse TwitchInGames::HttpRequest::Get(tstring const& url) {
	int result;
	uintptr_t handle;
	std::tie(result, handle) = StartHttpRequest(url, HTTP_GET_REQUEST, EmptyBody);
	return HttpResponse(result, handle);
}

HttpResponse TwitchInGames::HttpRequest::Post(tstring const& url, std::vector<char> const& requestBody) {
	int result;
	uintptr_t handle;
	std::tie(result, handle) = StartHttpRequest(url, HTTP_POST_REQUEST, requestBody);
	return HttpResponse(result, handle);
}

HttpResponse TwitchInGames::HttpRequest::Put(tstring const& url, std::vector<char> const& requestBody) {
	int result;
	uintptr_t handle;
	std::tie(result, handle) = StartHttpRequest(url, HTTP_PUT_REQUEST, requestBody);
	return HttpResponse(result, handle);
}

std::tuple<int, uintptr_t> TwitchInGames::HttpRequest::StartHttpRequest(tstring const& url, HttpRequestType requestType, std::vector<char> const& requestBody) {
	// Validate the arguments.
	if(url.empty()) {
		return std::make_tuple(FromPlatformError(ERROR_BAD_ARGUMENTS), 0);
	}
	switch(requestType) {
	case HTTP_GET_REQUEST:
	case HTTP_PUT_REQUEST:
	case HTTP_POST_REQUEST:
	case HTTP_DELETE_REQUEST:
		break;
	default:
		return std::make_tuple(FromPlatformError(ERROR_BAD_ARGUMENTS), 0);
	}

	// Ensure the CAPE SDK is initialized.
	TTV_InitializeLibrary();
#ifdef __ORBIS__
	std::static_pointer_cast<ttv::OrbisHttpRequest>(ttv::GetHttpRequest())->Initialize();
#endif

	// Add the authorization token, if provided.
	std::vector<HttpParam> requestHeaders;
	if(!token.empty()) {
		requestHeaders.emplace_back(HttpParam(_T("Authorization"), _T("Bearer ") + token));
	}

	// Prefer to receive gzipped responses if no other specified.
	requestHeaders.emplace_back(HttpParam(_T("Accept-Encoding"), _T("gzip")));

	// Add the client ID, if appropriate.
	if(!clientId.empty() && IsTwitchEndpoint(url.c_str())) {
		requestHeaders.emplace_back(HttpParam(clientIdHeaderName, clientId));
	}

	std::vector<ttv::HttpParam> ttvRequestHeaders;
	std::transform(requestHeaders.cbegin(), requestHeaders.cend(), std::back_inserter(ttvRequestHeaders), [](auto p) {
		return ttv::HttpParam(FromTstring(p.paramName), FromTstring(p.paramValue));
	});
	auto* response = new HttpResponse(0, 0);
	std::promise<void> awaitResponse;
	auto headersCallback = [response, &awaitResponse](uint statusCode, const std::map<std::string, std::string>& headers, void* /*userData*/) {
		response->resultCode = statusCode;
		std::for_each(headers.cbegin(), headers.cend(), [&headers = response->headers](auto p) {
			headers.insert({ ToTstring(p.first), ToTstring(p.second) });
		});
		if(statusCode >= 200 && statusCode < 300) {
			return true;
		} else {
			awaitResponse.set_value();
			return false;
		}
	};
	auto responseCallback = [response, &awaitResponse](uint /*statusCode*/, const std::vector<char>& body, void* /*userData*/) {
		response->response = body;
		awaitResponse.set_value();
	};
	auto result = ttv::SendHttpRequest(FromTstring(url), ttvRequestHeaders,
		reinterpret_cast<uint8_t const*>(requestBody.data()), requestBody.size(),
		static_cast<ttv::HttpRequestType>(requestType), timeout, headersCallback, responseCallback, nullptr);
	if(TTV_SUCCEEDED(result)) {
		std::future<void> awaitFuture = awaitResponse.get_future();
		return std::make_tuple(result, reinterpret_cast<uintptr_t>(response));
	} else {
		delete response;
		return std::make_tuple(result, 0);
	}
}

HttpResponse::HttpResponse(int result_, uintptr_t handle_) : resultCode(0), result(result_), handle(handle_) {}

HttpResponse::~HttpResponse() {
	auto* response_ = reinterpret_cast<HttpResponse*>(handle);
	delete response_;
}

std::map<tstring, tstring> const& HttpResponse::GetHeaders() const {
	auto* response_ = reinterpret_cast<HttpResponse*>(handle);
	return response_->headers;
}

std::vector<char> const& HttpResponse::GetResponse() const {
	auto* response_ = reinterpret_cast<HttpResponse*>(handle);
	return response_->response;
}

int HttpResponse::GetResultCode() const {
	auto* response_ = reinterpret_cast<HttpResponse*>(handle);
	return response_->resultCode;
}
