#include "LWeb.h"
#ifndef CC_DISABLE_LAUNCHER
#include "String.h"
#include "Launcher.h"
#include "Platform.h"
#include "Stream.h"
#include "Logger.h"
#include "Window.h"
#include "Options.h"
#include "PackedCol.h"
#include "Errors.h"
#include "Utils.h"
#include "Http.h"
#include "LBackend.h"

/*########################################################################################################################*
*----------------------------------------------------------JSON-----------------------------------------------------------*
*#########################################################################################################################*/
#define TOKEN_NONE  0
#define TOKEN_NUM   1
#define TOKEN_TRUE  2
#define TOKEN_FALSE 3
#define TOKEN_NULL  4
/* Consumes n characters from the JSON stream */
#define JsonContext_Consume(ctx, n) ctx->cur += n; ctx->left -= n;

static const cc_string strTrue  = String_FromConst("true");
static const cc_string strFalse = String_FromConst("false");
static const cc_string strNull  = String_FromConst("null");

static cc_bool Json_IsWhitespace(char c) {
	return c == '\r' || c == '\n' || c == '\t' || c == ' ';
}

static cc_bool Json_IsNumber(char c) {
	return c == '-' || c == '.' || (c >= '0' && c <= '9');
}

static cc_bool Json_ConsumeConstant(struct JsonContext* ctx, const cc_string* value) {
	int i;
	if (value->length > ctx->left) return false;

	for (i = 0; i < value->length; i++) {
		if (ctx->cur[i] != value->buffer[i]) return false;
	}

	JsonContext_Consume(ctx, value->length);
	return true;
}

static int Json_ConsumeToken(struct JsonContext* ctx) {
	char c;
	for (; ctx->left && Json_IsWhitespace(*ctx->cur); ) { JsonContext_Consume(ctx, 1); }
	if (!ctx->left) return TOKEN_NONE;

	c = *ctx->cur;
	if (c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == '"' || c == ':') {
		JsonContext_Consume(ctx, 1); return c;
	}

	/* number token forms part of value, don't consume it */
	if (Json_IsNumber(c)) return TOKEN_NUM;

	if (Json_ConsumeConstant(ctx, &strTrue))  return TOKEN_TRUE;
	if (Json_ConsumeConstant(ctx, &strFalse)) return TOKEN_FALSE;
	if (Json_ConsumeConstant(ctx, &strNull))  return TOKEN_NULL;

	/* invalid token */
	JsonContext_Consume(ctx, 1);
	ctx->failed = true;
	return TOKEN_NONE;
}

static cc_string Json_ConsumeNumber(struct JsonContext* ctx) {
	int len = 0;
	for (; ctx->left && Json_IsNumber(*ctx->cur); len++) { JsonContext_Consume(ctx, 1); }
	return String_Init(ctx->cur - len, len, len);
}

static void Json_ConsumeString(struct JsonContext* ctx, cc_string* str) {
	int codepoint, h[4];
	char c;
	str->length = 0;

	for (; ctx->left;) {
		c = *ctx->cur; JsonContext_Consume(ctx, 1);
		if (c == '"') return;
		if (c != '\\') { String_Append(str, c); continue; }

		/* form of \X */
		if (!ctx->left) break;
		c = *ctx->cur; JsonContext_Consume(ctx, 1);
		if (c == '/' || c == '\\' || c == '"') { String_Append(str, c); continue; }
		if (c == 'n') { String_Append(str, '\n'); continue; }

		/* form of \uYYYY */
		if (c != 'u' || ctx->left < 4) break;
		if (!PackedCol_Unhex(ctx->cur, h, 4)) break;

		codepoint = (h[0] << 12) | (h[1] << 8) | (h[2] << 4) | h[3];
		/* don't want control characters in names/software */
		/* TODO: Convert to CP437.. */
		if (codepoint >= 32) String_Append(str, codepoint);
		JsonContext_Consume(ctx, 4);
	}

	ctx->failed = true; str->length = 0;
}
static cc_string Json_ConsumeValue(int token, struct JsonContext* ctx);

static void Json_ConsumeObject(struct JsonContext* ctx) {
	char keyBuffer[STRING_SIZE];
	cc_string value, oldKey = ctx->curKey;
	int token;
	ctx->depth++;
	ctx->OnNewObject(ctx);

	while (true) {
		token = Json_ConsumeToken(ctx);
		if (token == ',') continue;
		if (token == '}') break;

		if (token != '"') { ctx->failed = true; break; }
		String_InitArray(ctx->curKey, keyBuffer);
		Json_ConsumeString(ctx, &ctx->curKey);

		token = Json_ConsumeToken(ctx);
		if (token != ':') { ctx->failed = true; break; }

		token = Json_ConsumeToken(ctx);
		if (token == TOKEN_NONE) { ctx->failed = true; break; }

		value = Json_ConsumeValue(token, ctx);
		ctx->OnValue(ctx, &value);
		ctx->curKey = oldKey;
	}
	ctx->depth--;
}

static void Json_ConsumeArray(struct JsonContext* ctx) {
	cc_string value;
	int token;
	ctx->depth++;
	ctx->OnNewArray(ctx);

	while (true) {
		token = Json_ConsumeToken(ctx);
		if (token == ',') continue;
		if (token == ']') break;

		if (token == TOKEN_NONE) { ctx->failed = true; break; }
		value = Json_ConsumeValue(token, ctx);
		ctx->OnValue(ctx, &value);
	}
	ctx->depth--;
}

static cc_string Json_ConsumeValue(int token, struct JsonContext* ctx) {
	switch (token) {
	case '{': Json_ConsumeObject(ctx); break;
	case '[': Json_ConsumeArray(ctx);  break;
	case '"': Json_ConsumeString(ctx, &ctx->_tmp); return ctx->_tmp;

	case TOKEN_NUM:   return Json_ConsumeNumber(ctx);
	case TOKEN_TRUE:  return strTrue;
	case TOKEN_FALSE: return strFalse;
	case TOKEN_NULL:  break;
	}
	return String_Empty;
}

static void Json_NullOnNew(struct JsonContext* ctx) { }
static void Json_NullOnValue(struct JsonContext* ctx, const cc_string* v) { }
void Json_Init(struct JsonContext* ctx, STRING_REF char* str, int len) {
	ctx->cur    = str;
	ctx->left   = len;
	ctx->failed = false;
	ctx->curKey = String_Empty;
	ctx->depth  = 0;

	ctx->OnNewArray  = Json_NullOnNew;
	ctx->OnNewObject = Json_NullOnNew;
	ctx->OnValue     = Json_NullOnValue;
	String_InitArray(ctx->_tmp, ctx->_tmpBuffer);
}

cc_bool Json_Parse(struct JsonContext* ctx) {
	int token;
	do {
		token = Json_ConsumeToken(ctx);
		Json_ConsumeValue(token, ctx);
	} while (token != TOKEN_NONE);

	return !ctx->failed;
}

static cc_bool Json_Handle(cc_uint8* data, cc_uint32 len, 
						JsonOnValue onVal, JsonOnNew newArr, JsonOnNew newObj) {
	struct JsonContext ctx;
	/* NOTE: classicube.net uses \u JSON for non ASCII, no need to UTF8 convert characters here */
	Json_Init(&ctx, (char*)data, len);
	
	if (onVal)  ctx.OnValue     = onVal;
	if (newArr) ctx.OnNewArray  = newArr;
	if (newObj) ctx.OnNewObject = newObj;
	return Json_Parse(&ctx);
}


/*########################################################################################################################*
*--------------------------------------------------------Web task---------------------------------------------------------*
*#########################################################################################################################*/
static char servicesBuffer[FILENAME_SIZE];
static cc_string servicesServer = String_FromArray(servicesBuffer);
static struct StringsBuffer CC_BIG_VAR ccCookies;

static void LWebTask_Reset(struct LWebTask* task) {
	task->completed = false;
	task->working   = true;
	task->success   = false;
}

void LWebTask_Tick(struct LWebTask* task, LWebTask_ErrorCallback errorCallback) {
	struct HttpRequest item;

	if (task->completed) return;
	if (!Http_GetResult(task->reqID, &item)) return;

	task->working   = false;
	task->completed = true;
	task->success   = item.success;

	if (item.success) {
		task->Handle(item.data, item.size);
	} else if (errorCallback) {
		errorCallback(&item);
	}
	HttpRequest_Free(&item);
}

void LWebTasks_Init(void) {
	Options_Get(SOPT_SERVICES, &servicesServer, SERVICES_SERVER);
}


/*########################################################################################################################*
*-------------------------------------------------------GetTokenTask------------------------------------------------------*
*#########################################################################################################################*/
/*
< GET /api/login/

> {
>	"username": null,
>	"authenticated": false,
>	"token": "f033ab37c30201f73f142449d037028d",
>	"errors": []
>}
*/
struct GetTokenTaskData GetTokenTask;

static void GetTokenTask_OnValue(struct JsonContext* ctx, const cc_string* str) {
	if (String_CaselessEqualsConst(&ctx->curKey, "token")) {
		String_Copy(&GetTokenTask.token, str);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "username")) {
		String_Copy(&GetTokenTask.username, str);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "errors")) {
		if (str->length) GetTokenTask.error = true;
	}
}

static void GetTokenTask_Handle(cc_uint8* data, cc_uint32 len) {
	static cc_string err_msg = String_FromConst("Error parsing get login token response JSON");

	cc_bool success = Json_Handle(data, len, GetTokenTask_OnValue, NULL, NULL);
	if (!success) Logger_WarnFunc(&err_msg);
}

void GetTokenTask_Run(void) {
	cc_string url; char urlBuffer[URL_MAX_SIZE];
	static char tokenBuffer[STRING_SIZE];
	static char userBuffer[STRING_SIZE];
	if (GetTokenTask.Base.working) return;

	LWebTask_Reset(&GetTokenTask.Base);
	String_InitArray(url, urlBuffer);
	String_Format1(&url, "%s/login", &servicesServer);

	String_InitArray(GetTokenTask.token,    tokenBuffer);
	String_InitArray(GetTokenTask.username, userBuffer);
	GetTokenTask.error = false;

	GetTokenTask.Base.Handle = GetTokenTask_Handle;
	GetTokenTask.Base.reqID  = Http_AsyncGetDataEx(&url, 0, NULL, NULL, &ccCookies);
}


/*########################################################################################################################*
*--------------------------------------------------------SignInTask-------------------------------------------------------*
*#########################################################################################################################*/
/*
< POST /api/login/
< username=AndrewPH&password=examplePassW0rd&token=f033ab37c30201f73f142449d037028d

> {
> 	"username": "AndrewPH",
> 	"authenticated": true,
> 	"token": "33e75ff09dd601bbe69f351039152189",
> 	"errors": []
> }
*/
struct SignInTaskData SignInTask;

static void SignInTask_LogError(const cc_string* str) {
	static char errBuffer[128];
	cc_string err;

	if (String_CaselessEqualsConst(str, "username") || String_CaselessEqualsConst(str, "password")) {
		SignInTask.error   = "&cWrong username or password";
	} else if (String_CaselessEqualsConst(str, "verification")) {
		SignInTask.error   = "&cAccount verification required";
	} else if (String_CaselessEqualsConst(str, "login_code")) {
		SignInTask.error   = "&cLogin code required (Check your emails)";
		SignInTask.needMFA = true;
	} else if (str->length) {
		String_InitArray_NT(err, errBuffer);
		String_Format1(&err, "&c%s", str);

		errBuffer[err.length] = '\0';
		SignInTask.error = errBuffer;
	}
}

static void SignInTask_OnValue(struct JsonContext* ctx, const cc_string* str) {
	if (String_CaselessEqualsConst(&ctx->curKey, "username")) {
		String_Copy(&SignInTask.username, str);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "errors")) {
		SignInTask_LogError(str);
	}
}

static void SignInTask_Handle(cc_uint8* data, cc_uint32 len) {
	static cc_string err_msg = String_FromConst("Error parsing sign in response JSON");

	cc_bool success = Json_Handle(data, len, SignInTask_OnValue, NULL, NULL);
	if (success) return;
	
	SignInTask.error = "&cError parsing response";
	Logger_WarnFunc(&err_msg);
}

static void SignInTask_Append(cc_string* dst, const char* key, const cc_string* value) {
	String_AppendConst(dst, key);
	Http_UrlEncodeUtf8(dst, value);
}

void SignInTask_Run(const cc_string* user, const cc_string* pass, const cc_string* mfaCode) {
	cc_string url; char urlBuffer[URL_MAX_SIZE];
	static char userBuffer[STRING_SIZE];
	cc_string args; char argsBuffer[1024];
	if (SignInTask.Base.working) return;

	LWebTask_Reset(&SignInTask.Base);
	String_InitArray(url, urlBuffer);
	String_Format1(&url, "%s/login", &servicesServer);

	String_InitArray(SignInTask.username, userBuffer);
	SignInTask.error   = NULL;
	SignInTask.needMFA = false;

	String_InitArray(args, argsBuffer);
	SignInTask_Append(&args, "username=",    user);
	SignInTask_Append(&args, "&password=",   pass);
	SignInTask_Append(&args, "&token=",      &GetTokenTask.token);
	SignInTask_Append(&args, "&login_code=", mfaCode);

	SignInTask.Base.Handle = SignInTask_Handle;
	SignInTask.Base.reqID  = Http_AsyncPostData(&url, 0, args.buffer, args.length, &ccCookies);
}


/*########################################################################################################################*
*-----------------------------------------------------FetchServerTask-----------------------------------------------------*
*#########################################################################################################################*/
/*
< GET /api/server/a709fabdf836a2a102c952442bf2dab1

> { "servers" : [
>	{"hash": "a709fabdf836a2a102c952442bf2dab1", "maxplayers": 70, "name": "Freebuild server", "players": 5, "software": "MCGalaxy", "uptime": 185447, "country_abbr": "CA"},
> ]}
*/
struct FetchServerData FetchServerTask;
static struct ServerInfo* curServer;

static void ServerInfo_Init(struct ServerInfo* info) {
	String_InitArray(info->hash, info->_hashBuffer);
	String_InitArray(info->name, info->_nameBuffer);
	String_InitArray(info->ip,   info->_ipBuffer);
	String_InitArray(info->mppass,   info->_mppassBuffer);
	String_InitArray(info->software, info->_softBuffer);

	info->players    = 0;
	info->maxPlayers = 0;
	info->uptime     = 0;
	info->featured   = false;
	info->country[0] = 't';
	info->country[1] = '1'; /* 'T1' for unrecognised country */
	info->_order     = -100000;
}

static void ServerInfo_Parse(struct JsonContext* ctx, const cc_string* val) {
	struct ServerInfo* info = curServer;
	if (String_CaselessEqualsConst(&ctx->curKey, "hash")) {
		String_Copy(&info->hash, val);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "name")) {
		String_Copy(&info->name, val);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "players")) {
		Convert_ParseInt(val, &info->players);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "maxplayers")) {
		Convert_ParseInt(val, &info->maxPlayers);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "uptime")) {
		Convert_ParseInt(val, &info->uptime);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "mppass")) {
		String_Copy(&info->mppass, val);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "ip")) {
		String_Copy(&info->ip, val);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "port")) {
		Convert_ParseInt(val, &info->port);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "software")) {
		String_Copy(&info->software, val);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "featured")) {
		Convert_ParseBool(val, &info->featured);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "country_abbr")) {
		/* Two letter country codes, see ISO 3166-1 alpha-2 */
		if (val->length < 2) return;

		/* classicube.net only works with lowercase flag urls */
		info->country[0] = val->buffer[0]; Char_MakeLower(info->country[0]);
		info->country[1] = val->buffer[1]; Char_MakeLower(info->country[1]);
	}
}

static void FetchServerTask_Handle(cc_uint8* data, cc_uint32 len) {
	curServer = &FetchServerTask.server;
	Json_Handle(data, len, ServerInfo_Parse, NULL, NULL);
}

void FetchServerTask_Run(const cc_string* hash) {
	cc_string url; char urlBuffer[URL_MAX_SIZE];
	if (FetchServerTask.Base.working) return;

	LWebTask_Reset(&FetchServerTask.Base);
	ServerInfo_Init(&FetchServerTask.server);
	String_InitArray(url, urlBuffer);
	String_Format2(&url, "%s/server/%s", &servicesServer, hash);

	FetchServerTask.Base.Handle = FetchServerTask_Handle;
	FetchServerTask.Base.reqID  = Http_AsyncGetDataEx(&url, 0, NULL, NULL, &ccCookies);
}


/*########################################################################################################################*
*-----------------------------------------------------FetchServersTask----------------------------------------------------*
*#########################################################################################################################*/
/*
< GET /api/servers/

> { "servers" : [
>	{"hash": "a709fabdf836a2a102c952442bf2dab1", "maxplayers": 70, "name": "Freebuild server", "players": 5, "software": "MCGalaxy", "uptime": 185447, "country_abbr": "CA"},
>	{"hash": "23860c5e192cbaa4698408338efd61cc", "maxplayers": 30, "name": "Other server", "players": 0, software: "", "uptime": 54661, "country_abbr": "T1"}
> ]}
*/
struct FetchServersData FetchServersTask;
static void FetchServersTask_Count(struct JsonContext* ctx) {
	/* JSON is expected in this format: */
	/*  { "servers" :      (depth = 1)  */
	/*    [                (depth = 2)  */
	/*	     { server1 },  (depth = 3)  */
	/*		 { server2 },  (depth = 3)  */
	/*          ...                     */
	if (ctx->depth != 3) return;
	FetchServersTask.numServers++;
}

static void FetchServersTask_Next(struct JsonContext* ctx) {
	if (ctx->depth != 3) return;
	curServer++;
	ServerInfo_Init(curServer);
}

static void FetchServersTask_Handle(cc_uint8* data, cc_uint32 len) {
	static cc_string err_msg = String_FromConst("Error parsing servers list response JSON");

	int count;
	cc_bool success;
	Mem_Free(FetchServersTask.servers);
	Mem_Free(FetchServersTask.orders);
	Session_Save();

	FetchServersTask.numServers = 0;
	FetchServersTask.servers    = NULL;
	FetchServersTask.orders     = NULL;

	FetchServersTask.numServers = 0;
	success = Json_Handle(data, len, NULL, NULL, FetchServersTask_Count);
	count   = FetchServersTask.numServers;

	if (!success) Logger_WarnFunc(&err_msg);
	if (count <= 0) return;
	FetchServersTask.servers = (struct ServerInfo*)Mem_Alloc(count, sizeof(struct ServerInfo), "servers list");
	FetchServersTask.orders  = (cc_uint16*)Mem_Alloc(count, 2, "servers order");

	curServer = FetchServersTask.servers - 1;
	Json_Handle(data, len, ServerInfo_Parse, NULL, FetchServersTask_Next);
}

void FetchServersTask_Run(void) {
	cc_string url; char urlBuffer[URL_MAX_SIZE];
	if (FetchServersTask.Base.working) return;

	LWebTask_Reset(&FetchServersTask.Base);
	String_InitArray(url, urlBuffer);
	String_Format1(&url, "%s/servers", &servicesServer);

	FetchServersTask.Base.Handle = FetchServersTask_Handle;
	FetchServersTask.Base.reqID  = Http_AsyncGetDataEx(&url, 0, NULL, NULL, &ccCookies);
}

void FetchServersTask_ResetOrder(void) {
	int i;
	for (i = 0; i < FetchServersTask.numServers; i++) {
		FetchServersTask.orders[i] = i;
	}
}


/*########################################################################################################################*
*-----------------------------------------------------CheckUpdateTask-----------------------------------------------------*
*#########################################################################################################################*/
/*
< GET /builds.json

> {"latest_ts": 1718187640.9587102, "release_ts": 1693265172.020421, "release_version": "1.3.6"}
*/
struct CheckUpdateData CheckUpdateTask;
static char relVersionBuffer[16];

CC_NOINLINE static cc_uint64 CheckUpdateTask_ParseTime(const cc_string* str) {
	cc_string time, fractional;
	cc_uint64 secs;
	/* timestamp is in form of "seconds.fractional" */
	/* But only need to care about the seconds here */
	String_UNSAFE_Separate(str, '.', &time, &fractional);

	Convert_ParseUInt64(&time, &secs);
	return secs;
}

static void CheckUpdateTask_OnValue(struct JsonContext* ctx, const cc_string* str) {
	if (String_CaselessEqualsConst(&ctx->curKey, "release_version")) {
		String_Copy(&CheckUpdateTask.latestRelease, str);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "latest_ts")) {
		CheckUpdateTask.devTimestamp = CheckUpdateTask_ParseTime(str);
	} else if (String_CaselessEqualsConst(&ctx->curKey, "release_ts")) {
		CheckUpdateTask.relTimestamp = CheckUpdateTask_ParseTime(str);
	}
}

static void CheckUpdateTask_Handle(cc_uint8* data, cc_uint32 len) {
	static cc_string err_msg = String_FromConst("Error parsing update check response JSON");

	cc_bool success = Json_Handle(data, len, CheckUpdateTask_OnValue, NULL, NULL);
	if (!success) Logger_WarnFunc(&err_msg);
}

void CheckUpdateTask_Run(void) {
	static const cc_string url = String_FromConst(UPDATES_SERVER "/builds.json");
	if (CheckUpdateTask.Base.working) return;

	LWebTask_Reset(&CheckUpdateTask.Base);
	String_InitArray(CheckUpdateTask.latestRelease, relVersionBuffer);

	CheckUpdateTask.Base.Handle = CheckUpdateTask_Handle;
	CheckUpdateTask.Base.reqID  = Http_AsyncGetData(&url, 0);
}


/*########################################################################################################################*
*-----------------------------------------------------FetchUpdateTask-----------------------------------------------------*
*#########################################################################################################################*/
struct FetchUpdateData FetchUpdateTask;
static void FetchUpdateTask_Handle(cc_uint8* data, cc_uint32 len) {
	static const cc_string path = String_FromConst(UPDATE_FILE);
	cc_result res;

	res = Stream_WriteAllTo(&path, data, len);
	if (res) { Logger_SysWarn(res, "saving update"); return; }

	res = Updater_SetNewBuildTime(FetchUpdateTask.timestamp);
	if (res) Logger_SysWarn(res, "setting update time");

	res = Updater_MarkExecutable();
	if (res) Logger_SysWarn(res, "making update executable");

#ifdef CC_BUILD_WIN
	Options_SetBool("update-dirty", true);
#endif
}

void FetchUpdateTask_Run(cc_bool release, int buildIndex) {
	cc_string url; char urlBuffer[URL_MAX_SIZE];
	String_InitArray(url, urlBuffer);

	String_Format2(&url, UPDATES_SERVER "/%c/%c",
		release ? "release" : "latest",
		Updater_Info.builds[buildIndex].path);

	LWebTask_Reset(&FetchUpdateTask.Base);
	FetchUpdateTask.timestamp   = release ? CheckUpdateTask.relTimestamp : CheckUpdateTask.devTimestamp;
	FetchUpdateTask.Base.Handle = FetchUpdateTask_Handle;
	FetchUpdateTask.Base.reqID  = Http_AsyncGetData(&url, 0);
}


/*########################################################################################################################*
*-----------------------------------------------------FetchFlagsTask------------------------------------------------------*
*#########################################################################################################################*/
struct FetchFlagsData FetchFlagsTask;
static int flagsCount, flagsCapacity;
static struct Flag* flags;

static void FetchFlagsTask_DownloadNext(void);
static void FetchFlagsTask_Handle(cc_uint8* data, cc_uint32 len) {
	struct Flag* flag = &flags[FetchFlagsTask.count];
	LBackend_DecodeFlag(flag, data, len);
	
	FetchFlagsTask.count++;
	FetchFlagsTask_DownloadNext();
}

static void FetchFlagsTask_DownloadNext(void) {
	cc_string url; char urlBuffer[URL_MAX_SIZE];
	String_InitArray(url, urlBuffer);

	if (FetchFlagsTask.Base.working)        return;
	if (FetchFlagsTask.count == flagsCount) return;

	LWebTask_Reset(&FetchFlagsTask.Base);
	String_Format2(&url, RESOURCE_SERVER "/img/flags/%r%r.png",
			&flags[FetchFlagsTask.count].country[0], &flags[FetchFlagsTask.count].country[1]);

	FetchFlagsTask.Base.Handle = FetchFlagsTask_Handle;
	FetchFlagsTask.Base.reqID  = Http_AsyncGetData(&url, 0);
}

static void FetchFlagsTask_Ensure(void) {
	if (flagsCount < flagsCapacity) return;
	flagsCapacity = flagsCount + 10;

	if (flags) {
		flags = (struct Flag*)Mem_Realloc(flags, flagsCapacity, sizeof(struct Flag), "flags");
	} else {
		flags = (struct Flag*)Mem_Alloc(flagsCapacity,          sizeof(struct Flag), "flags");
	}
}

void FetchFlagsTask_Add(const struct ServerInfo* server) {
	int i;
	for (i = 0; i < flagsCount; i++) 
	{
		if (flags[i].country[0] != server->country[0]) continue;
		if (flags[i].country[1] != server->country[1]) continue;
		/* flag is already or will be downloaded */
		return;
	}
	FetchFlagsTask_Ensure();

	Bitmap_Init(flags[flagsCount].bmp, 0, 0, NULL);
	flags[flagsCount].country[0] = server->country[0];
	flags[flagsCount].country[1] = server->country[1];
	flags[flagsCount].meta = NULL;

	flagsCount++;
	FetchFlagsTask_DownloadNext();
}

struct Flag* Flags_Get(const struct ServerInfo* server) {
	int i;
	for (i = 0; i < FetchFlagsTask.count; i++) 
	{
		if (flags[i].country[0] != server->country[0]) continue;
		if (flags[i].country[1] != server->country[1]) continue;
		return &flags[i];
	}
	return NULL;
}

void Flags_Free(void) {
	int i;
	for (i = 0; i < FetchFlagsTask.count; i++) {
		Mem_Free(flags[i].bmp.scan0);
	}

    flagsCount = 0;
    FetchFlagsTask.count = 0;
}


/*########################################################################################################################*
*------------------------------------------------------Session cache------------------------------------------------------*
*#########################################################################################################################*/
static cc_string sessionKey = String_FromConst("session");
static cc_bool loadedSession;

#ifdef CC_BUILD_NETWORKING
void Session_Load(void) {
	cc_string session; char buffer[3072];
	if (loadedSession) return;
	loadedSession = true;
	/* Increase from max 512 to 2048 per entry */
	StringsBuffer_SetLengthBits(&ccCookies, 11);

	String_InitArray(session, buffer);
	Options_GetSecure(LOPT_SESSION, &session);
	if (!session.length) return;
	EntryList_Set(&ccCookies, &sessionKey, &session, '=');
}

void Session_Save(void) {
	cc_string session = EntryList_UNSAFE_Get(&ccCookies, &sessionKey, '=');
	if (!session.length) return;
	Options_SetSecure(LOPT_SESSION, &session);
}
#else
void Session_Load(void) { }

void Session_Save(void) { }
#endif

#endif
