#include "json.h"
#include "utils.h"

#include <windef.h>
#include <minwinbase.h>
#include <winhttp.h>
#include <winuser.h>

HINTERNET session = NULL;
HINTERNET connection = NULL;

/* preferably we would use some other API for this, but
 * meh
*/
static int init_winhttp(void) {
	session = WinHttpOpen(
		NULL,
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME,
		WINHTTP_NO_PROXY_BYPASS,
		0
	);

	return !!session;
}

static int init_connect(void) {
	if (!session)
		return 0;

	connection = WinHttpConnect(
		session,
		L"api.deezer.com",
		/* require HTTPS, we aren't in 2001 anymore */
		INTERNET_DEFAULT_HTTPS_PORT,
		0
	);

	return !!connection;
}

/* do this on exit */
void close_open_http_handles(void) {
	if (session) WinHttpCloseHandle(session);
	if (connection) WinHttpCloseHandle(connection);
}

/* return MUST be free'd */
static LPCWSTR deezer_get_thumbnail_build_query(LPCWSTR restrict artist, LPCWSTR restrict album) {
	static LPCWSTR begin = L"/search/track?strict=on&q=artist:\"";
	static LPCWSTR album_query = L" album:\"";

	size_t len = wcslen(begin) + wcslen(artist) + 1 /* quote */;
	if (album && album[0]) {
		len += 1 /* space */ + wcslen(album_query) + wcslen(album) + 1 /* quote */;
	}

	LPWSTR final = calloc(len + 1, sizeof(WCHAR));

	wcscpy(final, begin);
	wcscat(final, artist);
	wcscat(final, L"\"");
	if (album && album[0]) {
		wcscat(final, album_query);
		wcscat(final, album);
		wcscat(final, L"\"");
	}

	return final;
}

static int deezer_album_object_get_cover(cJSON* restrict album, const char* restrict name, char** restrict url) {
	if (!url) return 0;

	cJSON* cover = cJSON_GetObjectItemCaseSensitive(album, name);
	if (cover) {
		if (*url)
			free(*url);
		size_t len = strlen(cover->valuestring);
		*url = malloc((len + 1) * sizeof(char));
		(*url)[len] = '\0';
		strncpy(*url, cover->valuestring, len);
		return 1;
	} else return 0;
}


static int deezer_get_thumbnail_download_url(LPCWSTR restrict query, char** restrict data, size_t* restrict size) {
	if (!data || !size)
		return -1;

	HINTERNET request = WinHttpOpenRequest(
		connection,
		L"GET",
		query,
		NULL,
		WINHTTP_NO_REFERER,
		WINHTTP_DEFAULT_ACCEPT_TYPES,
		WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_PERCENT
	);
	if (!request)
		return -1;

	BOOL result = WinHttpSendRequest(
		request, 
		L"Content-Type: application/json; charset=utf-8",
		0,
		WINHTTP_NO_REQUEST_DATA,
		0,
		0,
		0
	);
	if (!result) {
		WinHttpCloseHandle(request);
		return -1;
	}

	result = WinHttpReceiveResponse(request, NULL);
	if (!result) {
		WinHttpCloseHandle(request);
		return -1;
	}

	DWORD data_available = 0;
	DWORD data_downloaded = 0;
	do {
		data_available = 0;
		if (!WinHttpQueryDataAvailable(request, &data_available)) {
			WinHttpCloseHandle(request);
			return -1;
		}

		if (!data_available)
			break;

		*data = realloc(*data, *size + data_available + 1);
		(*data)[*size + data_available] = '\0';
		if (!WinHttpReadData(request, *data + *size, data_available, &data_downloaded)) {
			WinHttpCloseHandle(request);
			return -1;
		}
		*size += data_downloaded;
	} while (data_available > 0);

	WinHttpCloseHandle(request);
	return 0;
}

enum deezer_thumbnail_state {
	ERROR = 0,
	ALBUM,
	ARTIST,
};

static enum deezer_thumbnail_state deezer_get_thumbnail_parse_search_result(cJSON* json, char** restrict cover_url, int get_artist) {
	cJSON* album = cJSON_GetObjectItemCaseSensitive(json, "album");

	if (cJSON_IsObject(album)) {
		if (deezer_album_object_get_cover(album, "cover_medium", cover_url)
			|| deezer_album_object_get_cover(album, "cover_large", cover_url)
			|| deezer_album_object_get_cover(album, "cover_small", cover_url)
			|| deezer_album_object_get_cover(album, "cover_xl", cover_url))
			return ALBUM;
	} else if (get_artist) {
		cJSON* artist = cJSON_GetObjectItemCaseSensitive(json, "artist");
		if (!cJSON_IsObject(artist))
			return ERROR;

		/* treat artist pictures as a fallback and only retrieve them once */
		if (deezer_album_object_get_cover(artist, "picture_medium", cover_url)
			|| deezer_album_object_get_cover(artist, "picture_large", cover_url)
			|| deezer_album_object_get_cover(artist, "picture_small", cover_url)
			|| deezer_album_object_get_cover(artist, "picture_xl", cover_url))
			return ARTIST;
	}

	return ERROR;
}

static int deezer_get_thumbnail_parse_json(char** restrict cover_url, const char* restrict data, size_t size) {
	cJSON* json = cJSON_ParseWithLength(data, size);
	if (!json) {
		const char* err_ptr = cJSON_GetErrorPtr();
		if (err_ptr)
			MessageBoxA(NULL, err_ptr, "wgsdk: Error parsing Deezer JSON!", MB_ICONERROR | MB_OK);
		cJSON_Delete(json);
		return -1;
	}

	cJSON* json_data = cJSON_GetObjectItemCaseSensitive(json, "data"); 
	size_t json_data_size;
	if (!cJSON_IsArray(json_data) || !(json_data_size = cJSON_GetArraySize(json_data)))
		return -1;

	int have_artist = 0;
	for (size_t i = 0; i < json_data_size; i++) {
		cJSON* result = cJSON_GetArrayItem(json_data, i);
		if (!cJSON_IsObject(result))
			return -1;

		enum deezer_thumbnail_state state = deezer_get_thumbnail_parse_search_result(result, cover_url, have_artist);
		switch (state) {
			case ERROR: return -1;
			case ARTIST: have_artist = 1; break;
			case ALBUM: return 0;
		}
	}

	cJSON_Delete(json);
	return !have_artist;
}

char* deezer_get_thumbnail(LPCWSTR restrict artist, LPCWSTR restrict album) {
	char* response_data = NULL;
	size_t response_size = 0;
	LPCWSTR query = NULL;
	char* cover_url = NULL;

	/* make sure everything is OK */
	if (!(session || init_winhttp()) || !(connection || init_connect()) || (!artist || !artist[0]))
		return NULL;

	query = deezer_get_thumbnail_build_query(artist, album);
	if (!query)
		return NULL;

	if (deezer_get_thumbnail_download_url(query, &response_data, &response_size))
		return NULL;

	if (deezer_get_thumbnail_parse_json(&cover_url, response_data, response_size))
		return NULL;

	return cover_url;
}
