/*
 *  Original work: Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  RakNet License.txt file in the licenses directory of this source tree. An additional grant 
 *  of patent rights can be found in the RakNet Patents.txt file in the same directory.
 *
 *
 *  Modified work: Copyright (c) 2016-2020, SLikeSoft UG (haftungsbeschränkt)
 *
 *  This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
 *  license found in the license.txt file in the root directory of this source tree.
 */

// How to patch
// 1. Create a new server instance using the most recent image (optional)
// 2. Connect to the new (or any existing) instance through remote desktop. Immediately set max concurrent users to 0. Wait for users to finish patching, if any started.
// 3. Update the patch
// 4. Image the server
// 5. Wait for the image to complete (press l to check, or use Rackspace control panel. Takes like half an hour).
// 6. When the image completes, set the image spawn from the 'l' option to the image that was created in step 4
// 6. Send terminate command. Servers running the old patch will shutdown automatically.
// 9. Set max concurrent users back above 0.
// 10. Optional: Manually call SpawnServers() if load is immediately anticipated

// Common includes
#include <stdio.h>
#include <stdlib.h>
#include <limits> // used for std::numeric_limits
#include "slikenet/Kbhit.h"

#include "slikenet/GetTime.h"
#include "slikenet/peerinterface.h"
#include "slikenet/MessageIdentifiers.h"
#include "slikenet/BitStream.h"
#include "slikenet/StringCompressor.h"
#include "slikenet/FileListTransfer.h"
#include "slikenet/FileList.h" // FLP_Printf
#include "slikenet/PacketizedTCP.h"
#include "slikenet/Gets.h"
#include "CloudServerHelper.h"
#include "slikenet/FullyConnectedMesh2.h"
#include "slikenet/TwoWayAuthentication.h"
#include "slikenet/CloudClient.h"
#include "slikenet/DynDNS.h"
#include "slikenet/peerinterface.h"
#include "slikenet/sleep.h"
#include "slikenet/ConnectionGraph2.h"
#include "CloudServerHelper.h"
#include "slikenet/HTTPConnection2.h"
#include "Rackspace2.h"
#include "slikenet/GetTime.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"
// See http://www.digip.org/jansson/doc/2.4/
// This is used to make it easier to parse the JSON returned from the master server
#include "jansson.h"

// Server only includes
#include "AutopatcherServer.h"
// Replace this repository with your own implementation if you don't want to use PostgreSQL
#include "AutopatcherPostgreRepository.h"

#ifdef _WIN32
#include "slikenet/WindowsIncludes.h" // Sleep
#else
#include <unistd.h> // usleep
#endif

#define LISTEN_PORT_TCP_PATCHER 60000

using namespace SLNet;
static const SLNet::Time LOAD_CHECK_INTERVAL=1000*60*5;
static const SLNet::Time LOAD_CHECK_INTERVAL_AFTER_SPAWN=1000*60*60;
char databasePassword[128];
int workerThreadCount;
int sqlConnectionObjectCount;
int allowDownloadingUnmodifiedFiles;

unsigned short autopatcherLoad=0;

SLNet::RakPeerInterface *g_rakPeer;
SLNet::AutopatcherServer *autopatcherServer;

enum AppState
{
	AP_RUNNING,
	AP_TERMINATE_WHEN_ZERO_USERS_REACHED,
	AP_TERMINATE_IF_NOT_DNS_HOST,
	AP_TERMINATING,
	AP_TERMINATED,
} appState;

SLNet::Time timeSinceZeroUsers;


struct CloudServerHelper_RackspaceCloudDNS : public CloudServerHelper, public Rackspace2EventCallback
{
public:

	enum CSHState
	{
		AUTHENTICATING,
		AUTHENTICATED,
		CANNOT_FIND_PATCHER_DOMAIN,
		CANNOT_FIND_PATCHER_RECORD,
		GETTING_SERVERS,
		GOT_MY_SERVER,
		CANNOT_FIND_MY_SERVER,
		GETTING_DOMAINS,
		GOT_DOMAIN,
		GETTING_RECORDS,
		GOT_RECORDS,

	} cshState;

	CloudServerHelper_RackspaceCloudDNS() {
		rackspace2= SLNet::OP_NEW<Rackspace2>(_FILE_AND_LINE_);
		memset(&thisServerDetail, 0, sizeof(ServerDetail));
	}
	~CloudServerHelper_RackspaceCloudDNS() {
		SLNet::OP_DELETE(rackspace2,_FILE_AND_LINE_);
	}
	virtual bool Update(void) {
		UpdateTCP();
		return true;
	}
	void CopyServerDetails(json_t *arrayElement, const char *publicIPV4)
	{
		strcpy_s(thisServerDetail.publicIPV4, publicIPV4);
		strcpy_s(thisServerDetail.id, json_string_value(json_object_get(arrayElement, "id")));
		json_t *privateAddressesArray = json_object_get(json_object_get(arrayElement, "addresses"),"private");
		size_t privateAddressesArraySize = json_array_size(privateAddressesArray);
		for (size_t l=0; l < privateAddressesArraySize; l++)
		{
			json_t *privateAddressElement = json_array_get(privateAddressesArray, l);
			if (json_integer_value(json_object_get(privateAddressElement, "version"))==4)
			{
				strcpy_s(thisServerDetail.privateIPV4,json_string_value(json_object_get(privateAddressElement, "addr")));
				break;
			}
		}

		strcpy_s(thisServerDetail.flavorId,json_string_value(json_object_get(json_object_get(arrayElement, "flavor"),"id")));
		strcpy_s(thisServerDetail.hostId,json_string_value(json_object_get(arrayElement, "hostId")));
		strcpy_s(thisServerDetail.id,json_string_value(json_object_get(arrayElement, "id")));
		strcpy_s(thisServerDetail.imageId,json_string_value(json_object_get(json_object_get(arrayElement, "image"),"id")));
		strcpy_s(thisServerDetail.name,json_string_value(json_object_get(arrayElement, "name")));
		strcpy_s(spawningImageId, thisServerDetail.imageId);
		strcpy_s(spawningFlavorId, thisServerDetail.flavorId);
		printf("Using image id %s for new servers\n", spawningImageId);
	}
	virtual void OnTCPFailure(void){ printf("--ERROR--: OnTCPFailure()\n"); }
	virtual void OnTransmissionFailed(HTTPConnection2 *httpConnection2, RakString postStr, RakString authURLDomain)
	{
		printf("--ERROR--: OnTransmissionFailed().\npostStr=%s\nauthURLDomain=%s\n", postStr.C_String(), authURLDomain.C_String());

		RakSleep(30000);
		printf("Retrying...\n");
		httpConnection2->TransmitRequest(postStr,authURLDomain, 443, true);
	}
	virtual void OnEmptyResponse(RakString stringTransmitted)
	{
		printf("--ERROR--: OnEmptyResponse(). stringTransmitted=%s", stringTransmitted.C_String());
	}
	virtual void OnMessage(const char *message, RakString responseReceived, RakString stringTransmitted, ptrdiff_t contentOffset)
	{
		// unused parameters
		(void)responseReceived;
		(void)contentOffset;

		printf("--WARNING--: OnMessage(). message=%s\nstringTransmitted=%s", message, stringTransmitted.C_String());
	}
	virtual void OnResponse(Rackspace2ResponseCode r2rc, RakString responseReceived, ptrdiff_t contentOffset)
	{
		if (r2rc==R2RC_AUTHENTICATED)
		{
			printf("Authenticated with Rackspace\nX-Auth-Token: %s\n", rackspace2->GetAuthToken());
			cshState=AUTHENTICATED;

			json_error_t error;
			json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
			json_t *accessJson = json_object_get(root, "access");
			json_t *userJson  = json_object_get(accessJson, "user");
			json_t *regionJson = json_object_get(userJson, "RAX-AUTH:defaultRegion");
			const char *userRegionStr = json_string_value(regionJson);
			json_t *serviceCatalogArray = json_object_get(accessJson, "serviceCatalog");
			size_t arraySize = json_array_size(serviceCatalogArray);
			size_t i;
			printf("Services:\n");
			for (i=0; i < arraySize; i++)
			{
				json_t *arrayElement = json_array_get(serviceCatalogArray, i);
				json_t *elementNameJson = json_object_get(arrayElement, "name");
				const char *elementNameStr = json_string_value(elementNameJson);
				json_t *elementTypeJson = json_object_get(arrayElement, "type");
				const char *elementTypeStr = json_string_value(elementTypeJson);

				printf("%i. name=%s type=%s ", i+1, elementNameStr, elementTypeStr);

				json_t *endpointArray = json_object_get(arrayElement, "endpoints");
				size_t endpointArraySize = json_array_size(endpointArray);
				size_t j;
				char *myPublicURLStr=0;
				for (j=0; j < endpointArraySize; j++)
				{
					json_t *endpointArrayElement = json_array_get(endpointArray, j);
					json_t *publicURLJson = json_object_get(endpointArrayElement, "publicURL");
					const char *publicURLStr = json_string_value(publicURLJson);
					regionJson = json_object_get(endpointArrayElement, "region");
					if (regionJson)
					{
						const char *regionStr = json_string_value(regionJson);
						if (strcmp(regionStr, userRegionStr)==0)
						{
							printf("My URL=%s", publicURLStr);
							myPublicURLStr=(char*) publicURLStr;
							break;
						}
					}
					else
					{
						printf("My URL=%s", publicURLStr);
						myPublicURLStr=(char*) publicURLStr;
						break;
					}
				}
				if (j==endpointArraySize)
					printf("My URL=<unknown>");
				if (myPublicURLStr)
				{
					if (strcmp(elementNameStr, "cloudDNS")==0)
						strcpy_s(rackspaceDNSURL, myPublicURLStr);
					else if (strcmp(elementNameStr, "cloudServersOpenStack")==0)
						strcpy_s(rackspaceServersURL, myPublicURLStr);
				}
				printf("\n");
			}

			if (appState==AP_TERMINATING || appState==AP_TERMINATED ||
				appState==AP_TERMINATE_WHEN_ZERO_USERS_REACHED || appState==AP_TERMINATE_IF_NOT_DNS_HOST)
				Terminate();


			json_decref(root);
		}
		else if (r2rc==R2RC_GOT_SERVERS)
		{
			json_error_t error;
			json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
			json_t *serversArray = json_object_get(root, "servers");
			size_t arraySize = json_array_size(serversArray);
			size_t i;

			for (i=0; i < arraySize && cshState==GETTING_SERVERS; i++)
			{
				json_t *arrayElement = json_array_get(serversArray, i);
				//json_t *publicAddressesArray = json_object_get(json_object_get(arrayElement, "addresses"),"public");
				//size_t publicAddressesArraySize = json_array_size(publicAddressesArray);
				//for (size_t j=0; j < publicAddressesArraySize; j++)
				//{
				for (unsigned k=0; k < g_rakPeer->GetNumberOfAddresses(); k++)
				{
					if (strcmp(g_rakPeer->GetLocalIP(k), json_string_value(json_object_get(arrayElement, "accessIPv4")))==0)
					{
						CopyServerDetails(arrayElement, g_rakPeer->GetLocalIP(k));

						cshState=GOT_MY_SERVER;

						break;
					}
				}
				//}



			}		

			if (cshState==GETTING_SERVERS)
			{
// 				printf("Could not find a server with this IP address\n");
// 				printf("Continue anyway (y/n)?\n");
// 				if (_getch()=='y')
// 				{
				
				for (unsigned k=0; k < g_rakPeer->GetNumberOfAddresses(); k++)
				{
					SystemAddress sa = g_rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,k);
					if (sa.IsLANAddress()==false)
					{
						json_t *arrayElement = json_array_get(serversArray, 0);
						CopyServerDetails(arrayElement, g_rakPeer->GetLocalIP(0));
						cshState=GOT_MY_SERVER;;
						break;
					}
				}

					
// 				}
// 				else
// 					cshState=CANNOT_FIND_MY_SERVER;
			}

			json_decref(root);
		}
		else if (r2rc==R2RC_GOT_IMAGES)
		{
			json_error_t error;
			json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
			json_t *domainsArray = json_object_get(root, "images");
			size_t arraySize = json_array_size(domainsArray);
			size_t i;
			printf("Got %i images:\n", arraySize);
			for (i=0; i < arraySize; i++)
			{
				json_t *arrayElement = json_array_get(domainsArray, i);
				printf("%i. name=%s ", i+1, json_string_value(json_object_get(arrayElement, "name")));
				printf("progress=%" JSON_INTEGER_FORMAT " ", json_integer_value(json_object_get(arrayElement, "progress")));
				printf("status=%s ", json_string_value(json_object_get(arrayElement, "status")));
				printf("created=%s ", json_string_value(json_object_get(arrayElement, "created")));
				printf("id=%s\n", json_string_value(json_object_get(arrayElement, "id")));
			}

			printf("\n");
			if (arraySize>0)
			{
				printf("Select image to spawn for new servers?: ");
				char str[32];
				Gets(str, sizeof(str));
				if (str[0])
				{
					int idx = atoi(str);
					if (idx>=1 && static_cast<unsigned>(idx) <= arraySize)
					{
						json_t *arrayElement = json_array_get(domainsArray, idx-1);
						strcpy_s(spawningImageId, json_string_value(json_object_get(arrayElement, "id")));
						// Use spawningFlavorId of whatever it was when the server was started, I don't care about flavor
						printf("Using image id %s for new servers\n", spawningImageId);
					}
				}
				else
					printf("Aborted.\n");
			}

			json_decref(root);
		}
		else if (r2rc==R2RC_GOT_RECORDS)
		{
			json_error_t error;
			json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
			json_t *domainsArray = json_object_get(root, "records");
			size_t arraySize = json_array_size(domainsArray);
			size_t i;
			for (i=0; i < arraySize; i++)
			{
				json_t *arrayElement = json_array_get(domainsArray, i);
				json_t *elementNameJson = json_object_get(arrayElement, "name");
				const char *elementNameStr = json_string_value(elementNameJson);
				if (strcmp(elementNameStr, patcherHostSubdomainURL)==0)
				{
					arrayElement = json_array_get(domainsArray, i);
					json_t *elementIPJson = json_object_get(arrayElement, "data");
					strcpy_s(dnsHostIP, json_string_value(elementIPJson));
					json_t *elementIDJson = json_object_get(arrayElement, "id");
					strcpy_s(patchHostRecordID,json_string_value(elementIDJson));


					timeOfLastDNSHostCheck= SLNet::GetTime();
					if (appState==AP_TERMINATE_IF_NOT_DNS_HOST)
					{
						if (strcmp(dnsHostIP, g_rakPeer->GetLocalIP(0))==0)
						{
							// We are dns host. Just keep running
							appState=AP_RUNNING;
						}
						else
						{
							if (timeSinceZeroUsers!=0)
								Terminate();
							else
								appState=AP_TERMINATE_WHEN_ZERO_USERS_REACHED;
						}
					}

					cshState=GOT_RECORDS;
					break;
				}
			}
			if (i==arraySize)
			{
				printf("Can't find %s in record list\n", patcherHostSubdomainURL);
				cshState=CANNOT_FIND_PATCHER_RECORD;
			}
			json_decref(root);
		}
		else if (r2rc==R2RC_GOT_DOMAINS)
		{
			json_error_t error;
			json_t *root = json_loads(strstr(responseReceived.C_String() + contentOffset, "{"), JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
			json_t *domainsArray = json_object_get(root, "domains");
			size_t arraySize = json_array_size(domainsArray);
			size_t i;
			for (i=0; i < arraySize; i++)
			{
				json_t *arrayElement = json_array_get(domainsArray, i);
				json_t *elementNameJson = json_object_get(arrayElement, "name");
				const char *elementNameStr = json_string_value(elementNameJson);
				if (strcmp(elementNameStr, "raknetpatcher.com")==0)
				{
					// Primary domain
					json_t *idNameJson = json_object_get(arrayElement, "id");
					raknetPatcherDomainId = (int) json_integer_value(idNameJson);
					RakString url("%s/domains/%i/records", rackspaceDNSURL, raknetPatcherDomainId );
					rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);

					cshState=GETTING_RECORDS;

					break;
				}
			}
			json_decref(root);
			if (i==arraySize)
			{
				printf("Can't find raknetpatcher.com in domain list\n");
				cshState=CANNOT_FIND_PATCHER_DOMAIN;
			}
		}
		else
		{
			if (r2rc==R2RC_UNAUTHORIZED)
			{
				printf("Unauthorized\n");

				// Try to reauthenticate
				rackspace2->Authenticate(rackspaceAuthenticationURL, rackspaceCloudUsername, apiAccessKey);
			}
			else
			{
				printf("General response failure: %s\n", responseReceived.C_String());
			}
		}

	}

	virtual bool UpdateTCP() {
		rackspace2->Update();
		return false;
	}

	void Terminate(void)
	{
		appState=AP_TERMINATING;

		// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/Delete_Server-d1e2883.html
		RakString url("%s/servers/%s", rackspaceServersURL, thisServerDetail.id);
		rackspace2->AddOperation(url,Rackspace2::OT_DELETE,0, true);
	}
	void GetCustomImages(void)
	{
		// curl -k https://ord.servers.api.rackspacecloud.com/v2/570016/images?type=SNAPSHOT --header "X-Auth-Token: 97d8335b-7b9e-4a74-96a1-2a3a043c1000" --header "Content-Type: application/json"

		// List images
		// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Images-d1e4435.html
		RakString url("%s/images/detail?type=SNAPSHOT", rackspaceServersURL);
		rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);
	}
	void ImageThisServer(void)
	{
		// Create image
		// date
		// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/Create_Image-d1e4655.html
		time_t aclock;
		time( &aclock );   // Get time in seconds
		tm newtime;
		localtime_s( &newtime, &aclock );   // Convert time to struct tm form 
		char text[1024];
		sprintf_s(text, "%s_%s", patcherHostSubdomainURL, asctime( &newtime ));

		RakString url("%s/servers/%s/action", rackspaceServersURL, thisServerDetail.id);
		json_t *jsonObjectRoot = json_object();
		json_t *jsonObjectCreateImage = json_object();
		json_t *jsonMetaData = json_object();
		json_object_set(jsonObjectCreateImage, "name", json_string(text));
		json_object_set(jsonObjectRoot, "createImage", jsonObjectCreateImage);
		json_object_set(jsonObjectCreateImage, "metadata", jsonMetaData);
		json_object_set(jsonMetaData, "ImageType",  json_string("snapshot"));

		printf("Imaging server with name %s\n", text);
		rackspace2->AddOperation(url,Rackspace2::OT_POST,jsonObjectRoot, true);
	}

	void SpawnServers(unsigned count)
	{
		if (count==0)
			return;
		RakAssert(count <= 4);

		// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/CreateServers.html

		RakString url("%s/servers", rackspaceServersURL);

		time_t aclock;
		time( &aclock );   // Get time in seconds
		tm newtime;
		localtime_s( &newtime, &aclock );   // Convert time to struct tm form 
		char text[1024];
		for (unsigned int i=0; i < count; i++)
		{
			json_t *jsonObjectRoot = json_object();
			json_t *jsonObjectServer = json_object();
			json_object_set(jsonObjectRoot, "server", jsonObjectServer);
			sprintf_s(text, "%s_%s_%u", patcherHostSubdomainURL, asctime( &newtime ), i);
			json_object_set(jsonObjectServer, "name", json_string(text));
			json_object_set(jsonObjectServer, "imageRef", json_string(spawningImageId));
			json_object_set(jsonObjectServer, "flavorRef", json_string(spawningFlavorId));
			rackspace2->AddOperation(url,Rackspace2::OT_POST,jsonObjectRoot, true);
		}
	}


	void GetThisServer(void)
	{
		// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Servers-d1e2078.html
		cshState=GETTING_SERVERS;

		RakString url("%s/servers/detail?status=ACTIVE", rackspaceServersURL);
		//RakString url("%s/servers/detail", rackspaceServersURL);
		rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);
	}

	void GetDomainRecords(void) {
		cshState=GETTING_DOMAINS;
		RakString url("%s/domains/?name=%s", rackspaceDNSURL, "raknetpatcher.com");
		rackspace2->AddOperation(url,Rackspace2::OT_GET,0, true);
	}

	bool AuthenticateWithRackspaceBlocking(void) {
		rackspace2->SetEventCallback(this);
		rackspace2->Authenticate(rackspaceAuthenticationURL, rackspaceCloudUsername, apiAccessKey);
		cshState=AUTHENTICATING;

		while (cshState==AUTHENTICATING)
		{
			UpdateTCP();
			RakSleep(30);
		}
		if (cshState!=AUTHENTICATED)
			return false;

		GetThisServer();
		while (cshState==GETTING_SERVERS)
		{
			UpdateTCP();
			RakSleep(30);
		}
		if (cshState!=GOT_MY_SERVER)
		{
			return false;
		}

		// http://docs.rackspace.com/cdns/api/v1.0/cdns-devguide/content/list_domains.html
		GetDomainRecords();
		while (cshState==GETTING_DOMAINS || cshState==GETTING_RECORDS)
		{
			UpdateTCP();
			RakSleep(30);
		}
		if (cshState!=GOT_RECORDS)
			return false;

		return true;
	}
	void UpdateHostIPAsynch(SystemAddress sa) {
		if (appState!=AP_RUNNING)
			return;

		// RakString url("%s/domains/domainId/records/recordId");
		// rackspace2->AddOperation()

		// http://docs.rackspace.com/cdns/api/v1.0/cdns-devguide/content/Modify_Records-d1e5033.html
		//rackspace2;

		RakString url("%s/domains/%i/records/%s", rackspaceDNSURL, raknetPatcherDomainId, patchHostRecordID);

		json_t *jsonObject = json_object();
		json_object_set(jsonObject, "data", json_string(sa.ToString(false)));
		printf("Setting DNS to %s\n", sa.ToString(false));
		// TESTING HACK
		// json_object_set(jsonObject, "data", json_string("198.61.202.20"));
		json_object_set(jsonObject, "name", json_string(patcherHostSubdomainURL));
		rackspace2->AddOperation(url,Rackspace2::OT_PUT,jsonObject, true);
	}
	
	virtual void PrintHelp(void)
	{
		printf("Distributed authenticated CloudServer using DNS based host migration.\n");
		printf("Parameter1: serverToServerPassword\n");
		printf("Parameter2: serverPort\n");
		printf("Parameter3: allowedIncomingConnections\n");
		printf("Parameter4: allowedOutgoingConnections\n");
		printf("Parameter5: patcherHostRecordURL\n");
		printf("Parameter6: patcherHostDomainURL\n");
		printf("Parameter7: rackspaceAuthenticationURL\n");
		printf("Parameter8: rackspaceCloudUsername\n");
		printf("Parameter9: apiAccessKey\n");
		printf("Parameter10: databasePassword\n");
		printf("Parameter11: workerThreadCount\n");
		printf("Parameter12: sqlConnectionObjectCount\n");
		printf("Parameter13: allowDownloadingUnmodifiedFiles\n");
		printf("Example:\n");
		printf("AutopatcherServer_SelfScaling s2sPassword 60000 1024 128 game1.mycompanypatcher.com mycompanypatcher.com https://identity.api.rackspacecloud.com/v2.0 rackspaceUsername rackspacePw dbPassword 3 6 1");
	}
	virtual bool ParseCommandLineParameters(int argc, char **argv) {

		if (argc!=14)
			PrintHelp();

		serverToServerPassword=argv[1];
		const int intServerPort = atoi(argv[2]);
		if ((intServerPort < 0) || (intServerPort > std::numeric_limits<unsigned short>::max())) {
			printf("Specified server port %d is outside valid bounds [0, %u]", intServerPort, std::numeric_limits<unsigned short>::max());
			return false;
		}
		rakPeerPort = static_cast<unsigned short>(intServerPort);
		int intConnections = atoi(argv[3]);
		if ((intConnections < 0) || (intConnections > std::numeric_limits<unsigned short>::max())) {
			printf("Specified allowed incoming connections %d is outside valid bounds [0, %u]", intConnections, std::numeric_limits<unsigned short>::max());
			return false;
		}
		allowedIncomingConnections = static_cast<unsigned short>(intConnections);
		intConnections = atoi(argv[4]);
		if ((intConnections < 0) || (intConnections > std::numeric_limits<unsigned short>::max())) {
			printf("Specified allowed outgoing connections %d is outside valid bounds [0, %u]", intConnections, std::numeric_limits<unsigned short>::max());
			return false;
		}
		allowedOutgoingConnections = static_cast<unsigned short>(intConnections);
		strcpy_s(patcherHostSubdomainURL, argv[5]);
		strcpy_s(patcherHostDomainURL, argv[6]);
		strcpy_s(rackspaceAuthenticationURL, argv[7]);
		strcpy_s(rackspaceCloudUsername, argv[8]);
		strcpy_s(apiAccessKey, argv[9]);
		strcpy_s(databasePassword, argv[10]);
		workerThreadCount=atoi(argv[11]);
		sqlConnectionObjectCount=atoi(argv[12]);
		allowDownloadingUnmodifiedFiles=atoi(argv[13]);




	/*
	2 worker threads, 4 mb read: 5 minutes, 55 seconds
	4 worker threads, 4 mb read: 5 minutes, 7 seconds
	8 worker threads, 4 mb read: 4 minutes, 48 seconds
	2 worker threads, 2 mb read: 8 minutes, 31 seconds
	8 worker threads, 2 mb read: 4 minutes, 30 seconds, but one failed to start
	4 worker threads, 8 mb read: 4 minutes, 55 seconds
	4 worker threads, 16 mb read: 4 minutes, 48 seconds
	3 worker threads, 16 mb read: 4 minutes, 19 seconds
	2 worker threads, 16 mb read: 5 minutes, 18 seconds
	3 worker threads, 32 mb read: 4 minutes, 18 seconds
	*/


		return true;
	}
	// Call when you get ID_FCM2_NEW_HOST
	virtual void OnFCMNewHost(Packet *packet, RakPeerInterface *rakPeer) {
		RakAssert(packet->data[0]==ID_FCM2_NEW_HOST);
		SLNet::BitStream bsIn(packet->data, packet->length, false);
		bsIn.IgnoreBytes(sizeof(MessageID));
		RakNetGUID oldHost;
		bsIn.Read(oldHost);
		RakNetGUID newHost = packet->guid;
		if (newHost==rakPeer->GetMyGUID() && oldHost!=newHost)
		{
			for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++)
			{
				SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
				if (sa.IsLANAddress()==false)
				{
					printf("Assuming host. Updating DNS\n");
					UpdateHostIPAsynch(sa);
					break;
				}
			}
		}
	}

	const char *GetCloudHostAddress(void) const {return dnsHostIP;}
	SLNet::Time GetTimeOfLastDNSHostCheck(void) {return timeOfLastDNSHostCheck;}
	void SetTimeOfLastDNSHostCheck(SLNet::Time t) {timeOfLastDNSHostCheck=t;}

	virtual int OnJoinCloudResult(
		Packet *packet,
		SLNet::RakPeerInterface *rakPeer,
		SLNet::CloudServer *cloudServer,
		SLNet::CloudClient *cloudClient,
		SLNet::FullyConnectedMesh2 *fullyConnectedMesh2,
		SLNet::TwoWayAuthentication *twoWayAuthentication,
		SLNet::ConnectionGraph2 *connectionGraph2,
		const char *rakPeerIpOrDomain,
		char myPublicIP[32]
	) {
		if (packet->data[0]==ID_CONNECTION_ATTEMPT_FAILED)
		{

			for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++)
			{
				SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
				if (sa.IsLANAddress()==false)
				{
					printf("Failed connection. Changing DNS to point to this system.\n");
					UpdateHostIPAsynch(sa);
					break;
				}
			}
		}
		else if (packet->data[0]==ID_ALREADY_CONNECTED && packet->systemAddress.IsLANAddress())
		{
			for (unsigned int i=0; i < rakPeer->GetNumberOfAddresses(); i++)
			{
				SystemAddress sa = rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
				if (sa.IsLANAddress()==false)
				{
					printf("Connected to self-LAN address. Changing DNS to point to this system.\n");

					UpdateHostIPAsynch(sa);
					return CloudServerHelper::OnJoinCloudResult(packet, rakPeer, cloudServer, cloudClient, fullyConnectedMesh2, twoWayAuthentication, connectionGraph2, sa.ToString(false), myPublicIP);
				}
			}
		}

		return CloudServerHelper::OnJoinCloudResult(packet, rakPeer, cloudServer, cloudClient, fullyConnectedMesh2, twoWayAuthentication, connectionGraph2, rakPeerIpOrDomain, myPublicIP);
	}

	virtual void OnConnectionCountChange(RakPeerInterface *rakPeer, CloudClient *cloudClient)
	{
		SLNet::BitStream bs;
		CloudKey cloudKey("CloudConnCount",0);
		bs.Write(autopatcherLoad);
		cloudClient->Post(&cloudKey, bs.GetData(), bs.GetNumberOfBytesUsed(), rakPeer->GetMyGUID());
	}

	char patcherHostSubdomainURL[128];
	char patcherHostDomainURL[128];
	char dnsHostIP[128];
	char rackspaceAuthenticationURL[128];
	char rackspaceDNSURL[128];
	char rackspaceServersURL[128];
	char rackspaceCloudUsername[128];
	char apiAccessKey[128];
	Rackspace2 *rackspace2;
	char patchHostRecordID[128];
protected:

	int raknetPatcherDomainId;
	SLNet::Time timeOfLastDNSHostCheck;

	struct ServerDetail
	{
		char publicIPV4[32];
		char privateIPV4[32];
		char flavorId[128];
		char hostId[128];
		char id[128];
		char imageId[128];
		char name[128];
	} thisServerDetail;

	char spawningImageId[128];
	char spawningFlavorId[128];

} *cloudServerHelper;

class AutopatcherLoadNotifier : public SLNet::AutopatcherServerLoadNotifier
{
	void AutopatcherLoadNotifier::OnQueueUpdate(SystemAddress remoteSystem,
		AutopatcherServerLoadNotifier::RequestType requestType,
		AutopatcherServerLoadNotifier::QueueOperation queueOperation,
		AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
	{
		// unused parameters
		(void)remoteSystem;
		(void)requestType;
		(void)queueOperation;

		// #med - consider changing autopatcherLoad to unsigned int
		autopatcherLoad=static_cast<unsigned short>(autopatcherState->requestsQueued+autopatcherState->requestsWorking);

		//AutopatcherServerLoadNotifier_Printf::OnQueueUpdate(remoteSystem, requestType, queueOperation, autopatcherState);
	}

	void AutopatcherLoadNotifier::OnGetChangelistCompleted(
		SystemAddress remoteSystem,
		AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult,
		AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
	{
		// unused parameters
		(void)remoteSystem;
		(void)getChangelistResult;

		autopatcherLoad=static_cast<unsigned short>(autopatcherState->requestsQueued+autopatcherState->requestsWorking);

		//AutopatcherServerLoadNotifier_Printf::OnGetChangelistCompleted(remoteSystem, getChangelistResult, autopatcherState);
	}

	void AutopatcherLoadNotifier::OnGetPatchCompleted(SystemAddress remoteSystem,
		AutopatcherServerLoadNotifier::PatchResult patchResult,
		AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
	{
		// unused parameters
		(void)remoteSystem;
		(void)patchResult;

		autopatcherLoad=static_cast<unsigned short>(autopatcherState->requestsQueued+autopatcherState->requestsWorking);

		//AutopatcherServerLoadNotifier_Printf::OnGetPatchCompleted(remoteSystem, patchResult, autopatcherState);
	}
};

void SetMaxConcurrentUsers(int i)
{
	autopatcherServer->SetMaxConurrentUsers(i);
}


//char WORKING_DIRECTORY[MAX_PATH];
//char PATH_TO_XDELTA_EXE[MAX_PATH];

// The default AutopatcherPostgreRepository2 uses bsdiff which takes too much memory for large files.
// I override MakePatch to use XDelta in this case
class AutopatcherPostgreRepository2_WithXDelta : public SLNet::AutopatcherPostgreRepository2
{
	int MakePatch(const char *oldFile, const char *newFile, char **patch, unsigned int *patchLength, int *patchAlgorithm)
	{
		FILE *fpOld;
		fopen_s(&fpOld, oldFile, "rb");
		fseek(fpOld, 0, SEEK_END);
		int contentLengthOld = ftell(fpOld);
		FILE *fpNew;
		fopen_s(&fpNew, newFile, "rb");
		fseek(fpNew, 0, SEEK_END);
		int contentLengthNew = ftell(fpNew);

		char WORKING_DIRECTORY[MAX_PATH];
		GetTempPathA(MAX_PATH, WORKING_DIRECTORY);
		if (WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=='\\' || WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=='/')
			WORKING_DIRECTORY[strlen(WORKING_DIRECTORY)-1]=0;

		const char *PATH_TO_XDELTA_EXE = "c:/xdelta3-3.0.6-win32.exe";

		bool shortContent = contentLengthOld < 33554432 && contentLengthNew < 33554432;
		if (shortContent || PATH_TO_XDELTA_EXE[0]==0)
		{
			if (shortContent==false)
			{
				printf("Warning: PATH_TO_XDELTA_EXE needed but not set.\ncontentLengthOld=%i\ncontentLengthNew=%i\n", contentLengthOld, contentLengthNew );

			}
			// Use bsdiff, which does a good job but takes a lot of memory based on the size of the file
			*patchAlgorithm=0;
			bool b = MakePatchBSDiff(fpOld, contentLengthOld, fpNew, contentLengthNew, patch, patchLength);
			fclose(fpOld);
			fclose(fpNew);
			return b == false ? -1 : 0;
		}
		else
		{
			*patchAlgorithm=1;
			fclose(fpOld);
			fclose(fpNew);

			char buff[128];
			SLNet::TimeUS time = SLNet::GetTimeUS();
#if defined(_WIN32)
			sprintf_s(buff, "%I64u", time);
#else
			sprintf_s(buff, "%llu", (long long unsigned int) time);
#endif

			// Invoke xdelta
			// See https://code.google.com/p/xdelta/wiki/CommandLineSyntax
			char commandLine[512];
			_snprintf(commandLine, sizeof(commandLine)-1, "-f -s %s %s patchServer_%s.tmp", oldFile, newFile, buff);
			commandLine[511]=0;


			SHELLEXECUTEINFOA shellExecuteInfo;
			shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFOA);
			shellExecuteInfo.fMask = SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE;
			shellExecuteInfo.hwnd = nullptr;
			shellExecuteInfo.lpVerb = "open";
			shellExecuteInfo.lpFile = PATH_TO_XDELTA_EXE;
			shellExecuteInfo.lpParameters = commandLine;
			shellExecuteInfo.lpDirectory = WORKING_DIRECTORY;
			shellExecuteInfo.nShow = SW_SHOWNORMAL;
			shellExecuteInfo.hInstApp = nullptr;
			ShellExecuteExA(&shellExecuteInfo);

			// ShellExecute is blocking, but if it writes a file to disk that file is not always immediately accessible after it returns. And this only happens in release, and only when not running in the debugger
			//ShellExecute(nullptr, "open", PATH_TO_XDELTA_EXE, commandLine, WORKING_DIRECTORY, SW_SHOWNORMAL);

			char pathToPatch[MAX_PATH];
			sprintf_s(pathToPatch, "%s/patchServer_%s.tmp", WORKING_DIRECTORY, buff);
			FILE *fpPatch;
			errno_t error = fopen_s(&fpPatch, pathToPatch, "r+b");
			SLNet::TimeUS stopWaiting = time + 60000000 * 5;
			while (error!=0 && SLNet::GetTimeUS() < stopWaiting)
			{
				RakSleep(1000);
				error = fopen_s(&fpPatch, pathToPatch, "r+b");
			}
			if (error!=0)
			{
				char buff2[1024];
				strerror_s(buff2, errno);
				printf("\nERROR: Could not open %s.\nerr=%i (%s)\narguments=%s\n", pathToPatch, errno, buff2, commandLine);
				return 1;
			}
			fseek(fpPatch, 0, SEEK_END);
			*patchLength = ftell(fpPatch);
			fseek(fpPatch, 0, SEEK_SET);
			*patch = (char*) rakMalloc_Ex(*patchLength, _FILE_AND_LINE_);
			fread(*patch, 1, *patchLength, fpPatch);
			fclose(fpPatch);

			int unlinkRes = _unlink(pathToPatch);
			while (unlinkRes!=0 && SLNet::GetTimeUS() < stopWaiting)
			{
				RakSleep(1000);
				unlinkRes = _unlink(pathToPatch);
			}
			if (unlinkRes!=0) {
				char buff2[1024];
				strerror_s(buff2, errno);
				printf("\nWARNING: unlink %s failed.\nerr=%i (%s)\n", pathToPatch, errno, buff2);
			}

			return 0;
		}
	}
};

int main(int argc, char **argv)
{
#if OPEN_SSL_CLIENT_SUPPORT==0
#error RakNet must be compiled with OPEN_SSL_CLIENT_SUPPORT=1 to connect to Rackspace
	return 0;
#endif

	cloudServerHelper = SLNet::OP_NEW<CloudServerHelper_RackspaceCloudDNS>(_FILE_AND_LINE_);
	if (!cloudServerHelper->ParseCommandLineParameters(argc, argv))
	{
		return 1;
	}

	g_rakPeer = SLNet::RakPeerInterface::GetInstance();


	SLNet::Time timeForNextLoadCheck= SLNet::GetTime()+LOAD_CHECK_INTERVAL;

	if (!cloudServerHelper->AuthenticateWithRackspaceBlocking())
	{
		printf("Exiting due to failed startup with Rackspace\n");
		return 1;
	}

	// This does not work unless it comes after AuthenticateWithRackspaceBlocking
	SLNet::PacketizedTCP packetizedTCP;
	if (packetizedTCP.Start(LISTEN_PORT_TCP_PATCHER,cloudServerHelper->allowedIncomingConnections)==false)
	{
		printf("Failed to start TCP. Is the port already in use?");
		return 1;
	}

	appState=AP_RUNNING;
	timeSinceZeroUsers= SLNet::GetTime();
	printf("Server starting... ");
	autopatcherServer = SLNet::OP_NEW<AutopatcherServer>(_FILE_AND_LINE_);
	// SLNet::FLP_Printf progressIndicator;
	SLNet::FileListTransfer fileListTransfer;
	AutopatcherPostgreRepository2_WithXDelta *connectionObject;
	connectionObject = SLNet::OP_NEW_ARRAY<AutopatcherPostgreRepository2_WithXDelta>(sqlConnectionObjectCount, _FILE_AND_LINE_);
	// SLNet::AutopatcherRepositoryInterface **connectionObjectAddresses[sqlConnectionObjectCount];
	AutopatcherRepositoryInterface **connectionObjectAddresses = SLNet::OP_NEW_ARRAY<AutopatcherRepositoryInterface *>(sqlConnectionObjectCount, _FILE_AND_LINE_);

	for (int i=0; i < sqlConnectionObjectCount; i++)
		connectionObjectAddresses[i]=&connectionObject[i];
	autopatcherServer->SetFileListTransferPlugin(&fileListTransfer);
	// PostgreSQL is fast, so this may not be necessary, or could use fewer threads
	// This is used to read increments of large files concurrently, thereby serving users downloads as other users read from the DB
	fileListTransfer.StartIncrementalReadThreads(sqlConnectionObjectCount);
	autopatcherServer->SetMaxConurrentUsers(workerThreadCount); // More users than this get queued up
	AutopatcherLoadNotifier autopatcherLoadNotifier;
	autopatcherServer->SetLoadManagementCallback(&autopatcherLoadNotifier);
	packetizedTCP.AttachPlugin(autopatcherServer);
	packetizedTCP.AttachPlugin(&fileListTransfer);

	autopatcherServer->SetAllowDownloadOfOriginalUnmodifiedFiles(allowDownloadingUnmodifiedFiles!=0);

	// ---- PLUGINS -----
	// Used to load balance clients, allow for client to client discovery
	SLNet::CloudServer cloudServer;
	// Used to update the local cloudServer
	SLNet::CloudClient cloudClient;
	// Used to determine the host of the server fully connected mesh, as well as to connect servers automatically
	SLNet::FullyConnectedMesh2 fullyConnectedMesh2;
	// Used for servers to verify each other - otherwise any system could pose as a server
	// Could also be used to verify and restrict clients if paired with the MessageFilter plugin
	SLNet::TwoWayAuthentication twoWayAuthentication;
	// Used to tell servers about each other
	SLNet::ConnectionGraph2 connectionGraph2;

	g_rakPeer->AttachPlugin(&cloudServer);
	g_rakPeer->AttachPlugin(&cloudClient);
	g_rakPeer->AttachPlugin(&fullyConnectedMesh2);
	g_rakPeer->AttachPlugin(&twoWayAuthentication);
	g_rakPeer->AttachPlugin(&connectionGraph2);
	
	if (!cloudServerHelper->StartRakPeer(g_rakPeer))
		return 1;

	SLNet::CloudServerHelperFilter sampleFilter; // Keeps clients from updating stuff to the server they are not supposed to
	sampleFilter.serverGuid= g_rakPeer->GetMyGUID();
	cloudServerHelper->SetupPlugins(&cloudServer, &sampleFilter, &cloudClient, &fullyConnectedMesh2, &twoWayAuthentication,&connectionGraph2, cloudServerHelper->serverToServerPassword);

	int ret;
	do 
	{
		ret = cloudServerHelper->JoinCloud(g_rakPeer, &cloudServer, &cloudClient, &fullyConnectedMesh2, &twoWayAuthentication, &connectionGraph2, cloudServerHelper->GetCloudHostAddress());
	} while (ret==2);
	if (ret==1)
		return 1;

	printf("started.\n");

	printf("Enter database password:\n");
	char connectionString[256];
	char username[256];
	bool connectionSuccess=false;
	while (connectionSuccess==false)
	{
		strcpy_s(username, "postgres");
		strcpy_s(connectionString, "user=");
		strcat_s(connectionString, username);
		strcat_s(connectionString, " password=");
		strcat_s(connectionString, databasePassword);
		int conIdx;
		for (conIdx=0; conIdx < sqlConnectionObjectCount; conIdx++)
		{
			if (connectionObject[conIdx].Connect(connectionString)==false)
			{
				printf("Database connection failed.\n");
				printf("Try a different password? (y/n)\n");
				if (_getch()!='y')
				{
					return 1;
				}
				else
				{
					connectionSuccess=false;
					printf("Enter password: ");
					Gets(databasePassword, sizeof(databasePassword));
					break;
				}
			}
		}
		if (conIdx==sqlConnectionObjectCount)
			connectionSuccess=true;
	}

	printf("Database connection suceeded.\n");
	printf("Starting threads\n");
	// 4 Worker threads, which is CPU intensive
	// A greater number of SQL connections, which read files incrementally for large downloads
	autopatcherServer->StartThreads(workerThreadCount,sqlConnectionObjectCount, connectionObjectAddresses);
	autopatcherServer->CacheMostRecentPatch(0);
	printf("System ready for connections\n");

	printf("(D)rop database\n(C)reate database.\n(U)pdate revision.\n(S)pawn a clone of this server\n(M)ax concurrent users (this server only)\n(I)mage the current state of this server\n(L)ist images\n(T)erminate cloud\n(Q)uit\n");

	int ch;
	SLNet::Packet *p;
	while (appState!=AP_TERMINATED)
	{
		SLNet::SystemAddress notificationAddress;
		notificationAddress=packetizedTCP.HasCompletedConnectionAttempt();
// 		if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS)
// 			printf("ID_CONNECTION_REQUEST_ACCEPTED\n");
		notificationAddress=packetizedTCP.HasNewIncomingConnection();
// 		if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS)
// 			printf("ID_NEW_INCOMING_CONNECTION\n");
		notificationAddress=packetizedTCP.HasLostConnection();
// 		if (notificationAddress!=SLNet::UNASSIGNED_SYSTEM_ADDRESS)
// 			printf("ID_CONNECTION_LOST\n");

		p=packetizedTCP.Receive();
		while (p)
		{
			packetizedTCP.DeallocatePacket(p);
			p=packetizedTCP.Receive();
		}


		if (autopatcherLoad==0)
		{
			if (appState==AP_TERMINATE_WHEN_ZERO_USERS_REACHED)
			{
				cloudServerHelper->Terminate();
			}
			if (timeSinceZeroUsers==0)
				timeSinceZeroUsers= SLNet::GetTime();
		}
		else
		{
			timeSinceZeroUsers=0;
		}

		p= g_rakPeer->Receive();
		while (p)
		{
			/*
			if (p->data[0]==ID_NEW_INCOMING_CONNECTION)
				printf("ID_NEW_INCOMING_CONNECTION (TCP) from %s\n", p->systemAddress.ToString(true));
			else if (p->data[0]==ID_DISCONNECTION_NOTIFICATION)
				printf("ID_DISCONNECTION_NOTIFICATION (TCP) from %s\n", p->systemAddress.ToString(true));
			else if (p->data[0]==ID_CONNECTION_LOST)
				printf("ID_CONNECTION_LOST (TCP) from %s\n", p->systemAddress.ToString(true));
			else
			*/
			if (p->data[0]==ID_FCM2_NEW_HOST)
			{
				if (appState==AP_RUNNING)
				{
					SLNet::BitStream bs(p->data,p->length,false);
					bs.IgnoreBytes(1);
					RakNetGUID oldHost;
					bs.Read(oldHost);

					if (p->guid== g_rakPeer->GetMyGUID())
					{
						if (oldHost!=UNASSIGNED_RAKNET_GUID)
						{
							printf("ID_FCM2_NEW_HOST: Taking over as host from the old host.\n");
						}
						else
						{
							// Room not hosted if we become host the first time since this was done in CreateRoom() already
							printf("ID_FCM2_NEW_HOST: We have become host for the first time.\n");
						}
					}

					timeForNextLoadCheck= SLNet::GetTime()+LOAD_CHECK_INTERVAL;
				}
			}
			else if (p->data[0]==ID_USER_PACKET_ENUM)
			{
				SLNet::BitStream bsIn(p->data, p->length, false);
				bsIn.IgnoreBytes((sizeof(MessageID)));
				RakString incomingPw;
				bsIn.Read(incomingPw);
				if (incomingPw==cloudServerHelper->serverToServerPassword)
				{
					SetMaxConcurrentUsers(0);

					if (appState==AP_RUNNING)
					{
						if (timeSinceZeroUsers!=0)
							cloudServerHelper->Terminate();
						else
							appState=AP_TERMINATE_WHEN_ZERO_USERS_REACHED;
					}

					// Disconnect from the cloud, and do not accept new connections from users
					// The autopatcher will still run since it is on TCP
					g_rakPeer->Shutdown(1000);
				}

			}
			else if (p->data[0]==ID_CLOUD_GET_RESPONSE)
			{
				SLNet::CloudQueryResult cloudQueryResult;
				cloudClient.OnGetReponse(&cloudQueryResult, p);
				unsigned int rowIndex;
				const bool wasCallToGetServers=cloudQueryResult.cloudQuery.keys[0].primaryKey=="CloudConnCount";
				RakAssert(wasCallToGetServers);
				/*
				if (wasCallToGetServers)
					printf("Downloaded server list. %i servers.\n", cloudQueryResult.rowsReturned.Size());
					*/

				SLNet::SystemAddress lowestConnectionAddress;

				int totalConnections=0;
				for (rowIndex=0; rowIndex < cloudQueryResult.rowsReturned.Size(); rowIndex++)
				{
					SLNet::CloudQueryRow *row = cloudQueryResult.rowsReturned[rowIndex];

					unsigned short connCount;
					SLNet::BitStream bsIn(row->data, row->length, false);
					bsIn.Read(connCount);
					printf("%i. Server found at %s with %i connections\n", rowIndex+1, row->serverSystemAddress.ToString(true), connCount);

					totalConnections+=connCount;
				}

				const int MAX_SERVERS_EVER=32;
				int available = cloudQueryResult.rowsReturned.Size() * sqlConnectionObjectCount - totalConnections;
				if (available < 0 &&
					cloudQueryResult.rowsReturned.Size() < MAX_SERVERS_EVER // no more than MAX_SERVERS_EVER servers ever, to control costs
					)
				{
					int newServersNeeded = -available / sqlConnectionObjectCount;
					if (newServersNeeded > 4)
						newServersNeeded = 4; // Do not start more than 4 servers at a time
					if (cloudQueryResult.rowsReturned.Size() + newServersNeeded > MAX_SERVERS_EVER)
					{
						newServersNeeded = MAX_SERVERS_EVER - cloudQueryResult.rowsReturned.Size();
					}
					if (newServersNeeded>0)
						cloudServerHelper->SpawnServers(static_cast<unsigned>(newServersNeeded));
				}

				timeForNextLoadCheck = SLNet::GetTime() + LOAD_CHECK_INTERVAL_AFTER_SPAWN;

				cloudClient.DeallocateWithDefaultAllocator(&cloudQueryResult);
			}

			cloudServerHelper->OnPacket(p, g_rakPeer, &cloudClient, &cloudServer, &fullyConnectedMesh2, &twoWayAuthentication, &connectionGraph2);

			g_rakPeer->DeallocatePacket(p);
			p=g_rakPeer->Receive();
		}

		if (timeSinceZeroUsers!=0 && appState==AP_RUNNING && autopatcherServer->GetMaxConurrentUsers()>0)
		{
			// No users currently connected
			SLNet::Time curTime = SLNet::GetTime();
			SLNet::Time diff = curTime - timeSinceZeroUsers;
			if (diff > 0)
			{
				if (fullyConnectedMesh2.IsHostSystem()==false)
				{
					// 2. Shutdown automatically if no users for 12 hours, except if we are the host
					if (diff > 1000 * 60 * 60 * 12)
						cloudServerHelper->Terminate();
				}
				else
				{
					// We are host according to RakPeer. But no users for 24 hours
					// Verify we are the host according to Rackspace DNS records. If not, shutdown
					if (diff > 1000 * 60 * 60 * 24)
					{
						// No users for 24 hours, verify if we are the host according to Rackspace DNS records. If not, shutdown
						SLNet::Time diff2;
						diff2 = curTime - cloudServerHelper->GetTimeOfLastDNSHostCheck();
						if (diff2 > 1000 * 60 * 60 * 24)
						{
							appState=AP_TERMINATE_IF_NOT_DNS_HOST;
							cloudServerHelper->SetTimeOfLastDNSHostCheck(curTime);
							cloudServerHelper->GetDomainRecords();
						}
					}
				}
			}
		}

		cloudServerHelper->Update();

		
		// 4. If we are the host, every 1 hour check load among all servers (including self). It takes like 30 minutes to build a new server.
		if (fullyConnectedMesh2.IsHostSystem() && autopatcherServer->GetMaxConurrentUsers()>0)
		{
			SLNet::Time curTime = SLNet::GetTime();
			if (curTime > timeForNextLoadCheck)
			{
				timeForNextLoadCheck = curTime + LOAD_CHECK_INTERVAL;

				// If the load exceeds such that >=1 new fully loaded server is needed, add that many servers
				// See ID_CLOUD_GET_RESPONSE
				SLNet::CloudQuery cloudQuery;
				cloudQuery.keys.Push(SLNet::CloudKey("CloudConnCount",0),_FILE_AND_LINE_); // CloudConnCount is defined at the top of CloudServerHelper.cpp
				cloudQuery.subscribeToResults=false;
				cloudClient.Get(&cloudQuery, g_rakPeer->GetMyGUID());
			}
		}

		if (_kbhit())
		{
			ch=_getch();
			if (ch=='q')
				break;
			else if (ch=='c')
			{
				if (connectionObject[0].CreateAutopatcherTables()==false)
					printf("%s", connectionObject[0].GetLastError());
				else
					printf("Created tables.\n");

				if (connectionObject[0].AddApplication(cloudServerHelper->patcherHostSubdomainURL, username)==false)
					printf("%s", connectionObject[0].GetLastError());
				else
					printf("Added application\n");
			}
			else if (ch=='d')
			{
                if (connectionObject[0].DestroyAutopatcherTables()==false)
					printf("%s", connectionObject[0].GetLastError());
			}
			
			else if (ch=='i')
			{
				printf("Image this server?\nWill clone this server so that future servers made from this image will use its current state.\nPress 'y' to image.\n");
				if (_getch()=='y')
				{
					cloudServerHelper->ImageThisServer();
				}
			}
			else if (ch=='s')
			{
				printf("How many servers to spawn? (0-4) ");
				char str[32];
				Gets(str, sizeof(str));
				int num = atoi(str);
				if (num>0)
					cloudServerHelper->SpawnServers(static_cast<unsigned>(num));
			}
			else if (ch=='m')
			{
				char maxConcurrent[64];
				printf("Enter new allowed max concurrent users: ");
				Gets(maxConcurrent, sizeof(maxConcurrent));
				if (maxConcurrent[0])
					SetMaxConcurrentUsers(atoi(maxConcurrent));
			}
			else if (ch=='t')
			{
				printf("Setting max concurrent users to 0 on all servers\n");
				SetMaxConcurrentUsers(0);

				BitStream bsOut;
				bsOut.Write((MessageID)ID_USER_PACKET_ENUM);
				bsOut.Write(cloudServerHelper->serverToServerPassword);
				DataStructures::List<RakNetGUID> remoteServersOut;
				cloudServer.GetRemoteServers(remoteServersOut);
				for (unsigned int i=0; i < remoteServersOut.Size(); i++)
				{
					g_rakPeer->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, remoteServersOut[i], false);
					g_rakPeer->CloseConnection(remoteServersOut[i], true);
				}

				printf("Remote servers will shutdown when all users are done\n");
				printf("Disconnected from remote servers\n");
				

				for (unsigned int i=0; i < g_rakPeer->GetNumberOfAddresses(); i++)
				{
					SystemAddress sa = g_rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS,i);
					if (sa.IsLANAddress()==false)
					{
						printf("Updating host DNS entry to this server\n");
						cloudServerHelper->UpdateHostIPAsynch(sa);
						break;
					}
				}
			}
			else if (ch=='l')
			{
				cloudServerHelper->GetCustomImages();
			}
			else if (ch=='u')
			{
				// printf("Enter application name: ");
				char appName[512];
				strcpy_s(appName, cloudServerHelper->patcherHostSubdomainURL);
// 				Gets(appName,sizeof(appName));
// 				if (appName[0]==0)
// 					strcpy_s(appName, "TestApp");

				printf("Enter application directory: ");
				char appDir[512];
				Gets(appDir,sizeof(appDir));
				if (appDir[0]==0)
					strcpy_s(appDir, "D:/temp");

				if (connectionObject[0].UpdateApplicationFiles(appName, appDir, username, 0)==false)
				{
					printf("%s", connectionObject[0].GetLastError());
				}
				else
				{
					printf("Update success.\n");
					autopatcherServer->CacheMostRecentPatch(appName);
				}
			}
		}

		RakSleep(30);
	}



	SLNet::OP_DELETE_ARRAY(connectionObject, _FILE_AND_LINE_);
	SLNet::OP_DELETE_ARRAY(connectionObjectAddresses, _FILE_AND_LINE_);
	packetizedTCP.Stop();
	SLNet::RakPeerInterface::DestroyInstance(g_rakPeer);
	SLNet::OP_DELETE(cloudServerHelper, _FILE_AND_LINE_);


return 0;
}
