/*
 *  JoeyDev
 *  Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty.  In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *  3. This notice may not be removed or altered from any source distribution.
*/


#include "http.h"


static CURLM  *_curlMulti   = NULL;
static HTTPT **_activeHTTPs = NULL;


gboolean httpUpdate(gpointer userData);  // Not static

static size_t httpWrite(char *data, size_t num, size_t length, void *userp);


HTTPT *httpDownload(HTTPCallback callback, char *filename, char *url) {
	HTTPT *download = NULL;
	CURL  *eh       = NULL;

	download = NEW(HTTPT);
	if (download) {
		download->file = fopen(filename, "wb");
		if (download->file) {
			download->filename = strdup(filename);
			download->length   = 0;
			download->progress = 0;
			download->finished = FALSE;
			download->success  = FALSE;
			download->callback = callback;
			eh = curl_easy_init();
#ifdef DEBUG_MODE
			//curl_easy_setopt(eh, CURLOPT_VERBOSE, 1L);
			curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0L);
			curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
			curl_easy_setopt(eh, CURLOPT_BUFFERSIZE, CURL_MAX_READ_SIZE);
			curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1L);
			curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, httpWrite);
			curl_easy_setopt(eh, CURLOPT_WRITEDATA, download);
			curl_easy_setopt(eh, CURLOPT_URL, url);
			curl_multi_add_handle(_curlMulti, eh);
			arrput(_activeHTTPs, download);
		} else {
			DEL(download);
		}
	}

	return download;
}


void httpShutdown(void) {
	//***TODO*** Any active sessions?

	g_idle_remove_by_data(httpUpdate);

	curl_multi_cleanup(_curlMulti);
	curl_global_cleanup();
}


void httpStartup(void) {
	curl_global_init(CURL_GLOBAL_ALL);
	_curlMulti = curl_multi_init();
	g_idle_add(httpUpdate, httpUpdate);
}


gboolean httpUpdate(gpointer userData) {
	CURLcode  code;
	CURL     *eh         = NULL;
	CURLMsg  *msg        = NULL;
	HTTPT    *dl         = NULL;
	int       msgsLeft   = -1;
	int       stillAlive =  1;
	long      httpCode   =  0;
	int       i;

	(void)userData;

	for (i=0; i<arrlen(_activeHTTPs); i++) {
		dl = _activeHTTPs[i];
		curl_multi_perform(_curlMulti, &stillAlive);
		while ((msg = curl_multi_info_read(_curlMulti, &msgsLeft))) {
			code = msg->data.result;
			eh   = msg->easy_handle;
			// Try to get remote file size.
			if (dl->length == 0) {
				curl_easy_getinfo(eh, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &dl->length);
#ifdef DEBUG_MODE
				debug("CURL Length %" CURL_FORMAT_CURL_OFF_T "\n", dl->length);
#endif
			}
			if (msg->msg == CURLMSG_DONE) {
				// HTTP Error?
				curl_easy_getinfo (eh, CURLINFO_RESPONSE_CODE, &httpCode);
				// Finished.  Tell CURL.
				curl_multi_remove_handle(_curlMulti, eh);
				curl_easy_cleanup(eh);
				// Close local file.
				fclose(dl->file);
				// Transfer okay?
				dl->success  = (code == CURLE_OK && httpCode == 200);
				dl->finished = TRUE;
				// Nuke anything we may have written if transfer was bad.
				if (dl->success == FALSE) {
					unlink(dl->filename);
				}
				// Callback whoever issued this download.
				if (dl->callback) {
					dl->callback(dl);
				}
				// Remove from download list.
				arrdel(_activeHTTPs, i);
				// Free resources.
				DEL(dl->filename);
				DEL(dl);
				break;
#ifdef DEBUG_MODE
			} else {
				debug("CURL:  %d\n", code);
#endif
			}
		}
		if (stillAlive) {
			curl_multi_wait(_curlMulti, NULL, 0, 100, NULL);
		}
	}

	return G_SOURCE_CONTINUE;
}


static size_t httpWrite(char *data, size_t num, size_t length, void *userp) {
	HTTPT *download = (HTTPT *)userp;

	// Write it to disk.
	fwrite(data, num, length, download->file);

	// Update UI.
	download->progress += num * length;
#ifdef DEBUG_MODE
	debug("CURL Progress %" CURL_FORMAT_CURL_OFF_T "  Length %" CURL_FORMAT_CURL_OFF_T "\n", download->progress, download->length);
#endif

	// Did the user request to cancel the transfer?
	/*
	if (download->finished) {
		return CURL_READFUNC_ABORT;
	}
	*/

	return num * length;
}
