/*
 * Copyright (C) 2018 Yamagi
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * CURL backend for Quake II.
 *
 * =======================================================================
 */

#ifdef USE_CURL

// --------

#include <curl/curl.h>

#include "header/qcurl.h"
#include "../../common/header/common.h"

// --------

// True if cURL is initialized.
qboolean qcurlInitialized;

// Pointer to the dynamic library.
static void *curlhandle;

// CVars
static cvar_t *cl_libcurl;

// Function pointers to cURL.
void (*qcurl_easy_cleanup)(CURL *curl);
CURL *(*qcurl_easy_init)(void);
CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
const char *(*qcurl_easy_strerror)(CURLcode);

void (*qcurl_global_cleanup)(void);
CURLcode (*qcurl_global_init)(long flags);

CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle);
CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue);
CURLM *(*qcurl_multi_init)(void);
CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles);
CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle);
const char *(*qcurl_multi_strerror)(CURLMcode);

// --------

/*
 * Load libcurl, connect the function pointer
 * and call cURLs global init function.
 */
qboolean qcurlInit(void)
{
	Com_Printf("------- curl initialization -------\n");

	assert(!qcurlInitialized && "cURL already initialized?!");

	// Most systems have only one distinct name for the
	// libcurl, loading that one will be successfull in
	// at least 99% of all cases. But Linux, the mother
	// of chaos, is more complicated. Debian / Ubuntu
	// alone has 6 possible names... So:
	//  - Let's try whatever the user has written into
	//    the cl_libcurl cvar first.
	//  - If that fails try all possible names until we
	//    find a working one.
	//  - If we found one put it into cl_libcurl, so we
	//    don't need to iterate trought the whole list
	//    the next time the game is started.
	//  - If we found none error out.
#ifdef __APPLE__
	const char *libcurl[] = { "libcurl.dylib", NULL };
#elif __linux__
	const char *libcurl[] = { "libcurl.so.3", "libcurl.so.4", "libcurl-gnutls.so.3",
		"libcurl-gnutls.so.4", "libcurl-nss.so.3", "libcurl-nss.so.4", "libcurl.so",
		NULL };
#elif _WIN32
	const char *libcurl[] = { "curl.dll", "libcurl.dll", NULL };
#else
	const char *libcurl[] = { "libcurl.so", NULL };
#endif

	// Mkay, let's try to find a working libcurl.
	cl_libcurl = Cvar_Get("cl_libcurl", (char *)libcurl[0], CVAR_ARCHIVE);
	if (strstr(cl_libcurl->string, "..") || strstr(cl_libcurl->string, ":") || strstr(cl_libcurl->string,         "/") || strstr(cl_libcurl->string, "\\"))
	{
		Com_Printf("cl_libcurl must not contain '..', ':', '/' or '\': %s\n", cl_libcurl->string);
		goto error;
	}

	Com_Printf("Loading library: %s\n", cl_libcurl->string);
	Sys_LoadLibrary(cl_libcurl->string, NULL, &curlhandle);

	if (!curlhandle)
	{
		Com_Printf("Loading %s failed!\n", cl_libcurl->string);

		for (int i = 0; libcurl[i] != NULL; i++)
		{
			// Tried that one already.
			if (!strcmp(cl_libcurl->string, libcurl[i]))
			{
				continue;
			}

			Com_Printf("Loading library: %s\n", libcurl[i]);
			Sys_LoadLibrary(libcurl[i], NULL, &curlhandle);

			if (!curlhandle)
			{
				// No luck with this one.
				Com_Printf("Loading %s failed!\n", libcurl[i]);
				continue;
			}
			else
			{
				// Got one!
				Cvar_Set("cl_libcurl", (char *)libcurl[i]);
				break;
			}
		}

		if (!curlhandle)
		{
			goto error;
		}
	}

	// libcurl loaded sucessfully, connect the pointers.
	#define CONCURL(var, sym) do { \
		var = Sys_GetProcAddress(curlhandle, sym); \
		if (!var) goto error; \
	} while(0)
	
	CONCURL(qcurl_easy_cleanup, "curl_easy_cleanup");
	CONCURL(qcurl_easy_init, "curl_easy_init");
	CONCURL(qcurl_easy_getinfo, "curl_easy_getinfo");
	CONCURL(qcurl_easy_setopt, "curl_easy_setopt");
	CONCURL(qcurl_easy_strerror, "curl_easy_strerror");

	CONCURL(qcurl_global_cleanup, "curl_global_cleanup");
	CONCURL(qcurl_global_init, "curl_global_init");

	CONCURL(qcurl_multi_add_handle, "curl_multi_add_handle");
	CONCURL(qcurl_multi_cleanup, "curl_multi_cleanup");
	CONCURL(qcurl_multi_info_read, "curl_multi_info_read");
	CONCURL(qcurl_multi_init, "curl_multi_init");
	CONCURL(qcurl_multi_perform, "curl_multi_perform");
	CONCURL(qcurl_multi_remove_handle, "curl_multi_remove_handle");
	CONCURL(qcurl_multi_strerror, "curl_multi_strerror");

	#undef CONCURL

	// And finally the global cURL initialization.
	qcurl_global_init(CURL_GLOBAL_NOTHING);
	qcurlInitialized = true;

	Com_Printf("------------------------------------\n\n");

	return true;

error:
	qcurlShutdown();

	Com_Printf("------------------------------------\n\n");

	return false;
}

/*
 * Calls cURLs global shutdown funtion,
 * unloads libcurl and set the function
 * pointers to NULL.
 */
void qcurlShutdown(void)
{
	if (qcurlInitialized)
	{
		Com_Printf("Shutting down curl.\n");

		qcurl_global_cleanup();
		qcurlInitialized = false;
	}

	qcurl_easy_cleanup = NULL;
	qcurl_easy_init = NULL;
	qcurl_easy_getinfo = NULL;
	qcurl_easy_setopt = NULL;
	qcurl_easy_strerror = NULL;

	qcurl_global_cleanup = NULL;
	qcurl_global_init = NULL;

	qcurl_multi_add_handle = NULL;
	qcurl_multi_cleanup = NULL;
	qcurl_multi_info_read = NULL;
	qcurl_multi_init = NULL;
	qcurl_multi_perform = NULL;
	qcurl_multi_remove_handle = NULL;

	if (curlhandle)
	{
		Sys_FreeLibrary(curlhandle);
		curlhandle = NULL;
	}
}

// --------

#endif // USE_CURL
