/*
 * Kangaroo Punch MultiPlayer Game Server Mark II
 * Copyright (C) 2020-2021  Scott Duensing
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */


#include <stdarg.h>
#include <curl/curl.h>
#include <openssl/err.h>

#include "os.h"
#include "rest.h"
#include "array.h"
#include "console.h"


typedef struct RestResponseS {
	uint64_t  length;
	char     *data;
} RestResponseT;


static char            *_restURL         = NULL;
static char            *_restUser        = NULL;
static char            *_restPass        = NULL;
static pthread_mutex_t *_restMutexBuffer = NULL;


static void           restMutexLocker(int mode, int n, const char *file, int line);
static size_t         restResponseWrite(void *ptr, size_t size, size_t nmemb, RestResponseT *s);
static unsigned long  restThreadtIdGet(void);
static json_object   *restUrlPost(json_object *request);


#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"  // It's used, but the compiler isn't picking it up because it's via a callback.
static void restMutexLocker(int mode, int n, const char *file, int line) {
	(void)file;
	(void)line;

	if (mode & CRYPTO_LOCK) {
		pthread_mutex_lock(&_restMutexBuffer[n]);
	} else {
		pthread_mutex_unlock(&_restMutexBuffer[n]);
	}
}
#pragma GCC diagnostic pop


void restRelease(json_object *object) {
	json_object_put(object);
}


json_object *restRequest(char *command, char *format, ...) {
	va_list      args        = { 0 };
	json_object *request     = json_object_new_object();
	json_object *response    = NULL;
	char        *key         = NULL;
	char        *string      = NULL;
	uint8_t      result      = 0;

	json_object_object_add(request, "command", json_object_new_string(command));

	va_start(args, format);
	if (format) {
		while (*format != 0) {
			key = va_arg(args, char *);
			switch (*format) {
				case 's':
					json_object_object_add(request, key, json_object_new_string(va_arg(args, char *)));
					break;

				case 'i':
					json_object_object_add(request, key, json_object_new_int64(va_arg(args, int64_t)));
					break;

				default:
					utilDie("restRequest:  Unknown format option '%c'.\n", *format);
					break;
			}
			format++;
		}
	}
	va_end(args);

	//logWrite("Request: %s\n", json_object_to_json_string_ext(request, JSON_C_TO_STRING_PRETTY));
	response = restUrlPost(request);
	json_object_put(request);
	//logWrite("Response: %s\n", json_object_to_json_string_ext(response, JSON_C_TO_STRING_PRETTY));

	if (response) {
		string = (char *)json_object_get_string(json_object_object_get(response, "result"));
		result =  (string[0] == 't' || string[0] == 'T') ? 1 : 0;
		if (!result) {
			logWrite("restRequest:  %s\n", json_object_get_string(json_object_object_get(response, "reason")));
			json_object_put(response);
			response = NULL;
		}
	}

	return response;
}


static size_t restResponseWrite(void *ptr, size_t size, size_t nmemb, RestResponseT *s) {
	size_t newLength = s->length + size * nmemb;

	s->data = realloc(s->data, newLength + 1);
	if (s->data == NULL) {
		utilDie("restResponseWrite:  realloc() failed.\n");
	}
	memcpy(s->data + s->length, ptr, size*nmemb);
	s->data[newLength] = 0;
	s->length = newLength;

	return size * nmemb;
}


void restShutdown(void) {
	uint64_t i;

	DEL(_restPass);
	DEL(_restUser);
	DEL(_restURL);

	if (_restMutexBuffer) {
		CRYPTO_set_id_callback(NULL);
		CRYPTO_set_locking_callback(NULL);
		for (i=0; i<CRYPTO_num_locks(); i++) {
			pthread_mutex_destroy(&_restMutexBuffer[i]);
		}
		DEL(_restMutexBuffer);
	}

	curl_global_cleanup();
}


uint8_t restStartup(char *url, char *user, char *password) {
	uint64_t     i        = 0;
	json_object *response = NULL;
	uint8_t      result   = SUCCESS;

	curl_global_init(CURL_GLOBAL_ALL);

	_restMutexBuffer = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
	if (!_restMutexBuffer) {
		logWrite("restSetup:  Mutex buffer creation failed.\n");
		return FAIL;
	}

	for (i=0; i<CRYPTO_num_locks(); i++) {
		if (pthread_mutex_init(&_restMutexBuffer[i], NULL)) {
			logWrite("restSetup:  Mutex creation failed.\n");
			return FAIL;
		}
	}

	CRYPTO_set_id_callback(restThreadtIdGet);
	CRYPTO_set_locking_callback(restMutexLocker);

	_restURL  = strdup(url);
	_restUser = strdup(user);
	_restPass = strdup(password);

	// Is the remote server there?
	response = restRequest("API_AVAILABLE", NULL);
	if (!response) result = FAIL;
	restRelease(response);

	return result;
}


#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"  // It's used, but the compiler isn't picking it up because it's via a callback.
static unsigned long restThreadtIdGet(void) {
  return ((unsigned long)pthread_self());
}
#pragma GCC diagnostic pop


static json_object *restUrlPost(json_object *request) {
	CURL                    *curl     = NULL;
	CURLcode                 res      = 0;
	RestResponseT            data     = { 0 };
	json_object             *response = NULL;
	struct curl_slist       *headers  = NULL;
	enum json_tokener_error  error    = json_tokener_success;

	data.length = 0;
	data.data = malloc(data.length + 1);
	if (data.data == NULL) {
		logWrite("restUrlPost:  malloc() failed.\n");
		return NULL;
	}
	data.data[0] = 0;

	curl = curl_easy_init();
	curl_easy_setopt(curl, CURLOPT_URL,            _restURL);
	curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST,  "POST");
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,  restResponseWrite);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA,      &data);
	curl_easy_setopt(curl, CURLOPT_HTTPAUTH,       (long)CURLAUTH_BASIC);
	curl_easy_setopt(curl, CURLOPT_USERNAME,       _restUser);
	curl_easy_setopt(curl, CURLOPT_PASSWORD,       _restPass);
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS,     json_object_to_json_string(request));
	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);  // Make the URL work even if your CA bundle is missing.
	curl_easy_setopt(curl, CURLOPT_VERBOSE,        0L);

	headers = curl_slist_append(headers, "Content-Type: application/json");
	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

	res = curl_easy_perform(curl);
	if (res == CURLE_OK) {
		response = json_tokener_parse_verbose(data.data, &error);
		if (error) {
			logWrite("Error:  %s\n", json_tokener_error_desc(error));
		}
	}

	curl_slist_free_all(headers);
	DEL(data.data);
	curl_easy_cleanup(curl);

	return response;
}
