int main(int argc, char **argv)
{
	if (argc<8)
	{
		printf("Arguments: serverIP, pathToGame, gameName, patchImmediately, localPort, serverPort, fullScan");
		return 0;
	}

	RakNet::SystemAddress TCPServerAddress=RakNet::UNASSIGNED_SYSTEM_ADDRESS;
	RakNet::AutopatcherClient autopatcherClient;
	RakNet::FileListTransfer fileListTransfer;
	RakNet::CloudClient cloudClient;
	autopatcherClient.SetFileListTransferPlugin(&fileListTransfer);
	bool didRebalance=false; // So we only reconnect to a lower load server once, for load balancing

	bool fullScan = argv[7][0]=='1';

	unsigned short localPort;
	localPort=atoi(argv[5]);

	unsigned short serverPort=atoi(argv[6]);

	RakNet::PacketizedTCP packetizedTCP;
	if (packetizedTCP.Start(localPort,1)==false)
	{
		printf("Failed to start TCP. Is the port already in use?");
		return 1;
	}
	packetizedTCP.AttachPlugin(&autopatcherClient);
	packetizedTCP.AttachPlugin(&fileListTransfer);

	RakNet::RakPeerInterface *rakPeer;
	rakPeer = RakNet::RakPeerInterface::GetInstance();
	RakNet::SocketDescriptor socketDescriptor(localPort,0);
	rakPeer->Startup(1,&socketDescriptor, 1);
	rakPeer->AttachPlugin(&cloudClient);
	DataStructures::List<RakNet::RakNetSocket2* > sockets;
	rakPeer->GetSockets(sockets);
	printf("Started on port %i\n", sockets[0]->GetBoundAddress().GetPort());


	char buff[512];
	strcpy(buff, argv[1]);

	rakPeer->Connect(buff, serverPort, 0, 0);

	printf("Connecting...\n");
	char appDir[512];
	strcpy(appDir, argv[2]);
	char appName[512];
	strcpy(appName, argv[3]);

	bool patchImmediately=argc>=5 && argv[4][0]=='1';

	RakNet::Packet *p;
	while (1)
	{
		RakNet::SystemAddress notificationAddress;
		notificationAddress=packetizedTCP.HasCompletedConnectionAttempt();
		if (notificationAddress!=RakNet::UNASSIGNED_SYSTEM_ADDRESS)
		{
			printf("ID_CONNECTION_REQUEST_ACCEPTED\n");
			TCPServerAddress=notificationAddress;
		}
		notificationAddress=packetizedTCP.HasNewIncomingConnection();
		if (notificationAddress!=RakNet::UNASSIGNED_SYSTEM_ADDRESS)
			printf("ID_NEW_INCOMING_CONNECTION\n");
		notificationAddress=packetizedTCP.HasLostConnection();
		if (notificationAddress!=RakNet::UNASSIGNED_SYSTEM_ADDRESS)
			printf("ID_CONNECTION_LOST\n");
		notificationAddress=packetizedTCP.HasFailedConnectionAttempt();
		if (notificationAddress!=RakNet::UNASSIGNED_SYSTEM_ADDRESS)
		{
			printf("ID_CONNECTION_ATTEMPT_FAILED TCP\n");
			autopatcherClient.SetFileListTransferPlugin(0);
			autopatcherClient.Clear();
			packetizedTCP.Stop();
			rakPeer->Shutdown(500,0);
			RakNet::RakPeerInterface::DestroyInstance(rakPeer);
			return 0;
		}


		p=packetizedTCP.Receive();
		while (p)
		{
			if (p->data[0]==ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR)
			{
				char buff[256];
				RakNet::BitStream temp(p->data, p->length, false);
				temp.IgnoreBits(8);
				RakNet::StringCompressor::Instance()->DecodeString(buff, 256, &temp);
				printf("ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR\n");
				printf("%s\n", buff);
				autopatcherClient.SetFileListTransferPlugin(0);
				autopatcherClient.Clear();
				packetizedTCP.Stop();
				rakPeer->Shutdown(500,0);
				RakNet::RakPeerInterface::DestroyInstance(rakPeer);
				return 0;
			}
			else if (p->data[0]==ID_AUTOPATCHER_CANNOT_DOWNLOAD_ORIGINAL_UNMODIFIED_FILES)
			{
				printf("ID_AUTOPATCHER_CANNOT_DOWNLOAD_ORIGINAL_UNMODIFIED_FILES\n");
				autopatcherClient.SetFileListTransferPlugin(0);
				autopatcherClient.Clear();
				packetizedTCP.Stop();
				rakPeer->Shutdown(500,0);
				RakNet::RakPeerInterface::DestroyInstance(rakPeer);
				return 0;
			}			
			else if (p->data[0]==ID_AUTOPATCHER_FINISHED)
			{
				printf("ID_AUTOPATCHER_FINISHED with server time %f\n", autopatcherClient.GetServerDate());
				double srvDate=autopatcherClient.GetServerDate();
				FILE *fp = fopen("srvDate", "wb");
				fwrite(&srvDate,sizeof(double),1,fp);
				fclose(fp);
				autopatcherClient.SetFileListTransferPlugin(0);
				autopatcherClient.Clear();
				packetizedTCP.Stop();
				rakPeer->Shutdown(500,0);
				RakNet::RakPeerInterface::DestroyInstance(rakPeer);
				return 0;
			}
			else if (p->data[0]==ID_AUTOPATCHER_RESTART_APPLICATION)
			{
				printf("ID_AUTOPATCHER_RESTART_APPLICATION");
				autopatcherClient.SetFileListTransferPlugin(0);
				autopatcherClient.Clear();
				packetizedTCP.Stop();
				rakPeer->Shutdown(500,0);
				RakNet::RakPeerInterface::DestroyInstance(rakPeer);
				return 0;
			}
			// Launch \"AutopatcherClientRestarter.exe autopatcherRestart.txt\"\nQuit this application immediately after to unlock files.\n");

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

		p=rakPeer->Receive();
		while (p)
		{
			if (p->data[0]==ID_CONNECTION_REQUEST_ACCEPTED)
			{
				// UploadInstanceToCloud(&cloudClient, p->guid);
				// GetClientSubscription(&cloudClient, p->guid);
				GetServers(&cloudClient, p->guid);
				break;
			}
			else if (p->data[0]==ID_CONNECTION_ATTEMPT_FAILED)
			{
				printf("ID_CONNECTION_ATTEMPT_FAILED UDP\n");
				autopatcherClient.SetFileListTransferPlugin(0);
				autopatcherClient.Clear();
				packetizedTCP.Stop();
				rakPeer->Shutdown(500,0);
				RakNet::RakPeerInterface::DestroyInstance(rakPeer);
				return 0;
			}
			else if (p->data[0]==ID_CLOUD_GET_RESPONSE)
			{
				RakNet::CloudQueryResult cloudQueryResult;
				cloudClient.OnGetReponse(&cloudQueryResult, p);
				unsigned int rowIndex;
				const bool wasCallToGetServers=cloudQueryResult.cloudQuery.keys[0].primaryKey=="CloudConnCount";
				printf("\n");
				if (wasCallToGetServers)
					printf("Downloaded server list. %i servers.\n", cloudQueryResult.rowsReturned.Size());

				unsigned short connectionsOnOurServer=65535;
				unsigned short lowestConnectionsServer=65535;
				RakNet::SystemAddress lowestConnectionAddress;

				for (rowIndex=0; rowIndex < cloudQueryResult.rowsReturned.Size(); rowIndex++)
				{
					RakNet::CloudQueryRow *row = cloudQueryResult.rowsReturned[rowIndex];
					if (wasCallToGetServers)
					{
						unsigned short connCount;
						RakNet::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);

						unsigned short connectionsExcludingOurselves;
						if (row->serverGUID==p->guid)
							connectionsExcludingOurselves=connCount-1;
						else
							connectionsExcludingOurselves=connCount;

						// Find the lowest load server (optional)
						if (p->guid==row->serverGUID)
						{
							connectionsOnOurServer=connectionsExcludingOurselves;
						}
						else if (connectionsExcludingOurselves < lowestConnectionsServer)
						{
							lowestConnectionsServer=connectionsExcludingOurselves;
							lowestConnectionAddress=row->serverSystemAddress;
						}
					}
				}


				// Do load balancing by reconnecting to lowest load server (optional)
				if (didRebalance==false && wasCallToGetServers)
				{
					if (cloudQueryResult.rowsReturned.Size()>0 && connectionsOnOurServer>lowestConnectionsServer)
					{
						printf("Reconnecting to lower load server %s\n", lowestConnectionAddress.ToString(false));

						rakPeer->CloseConnection(p->guid, true);
						// Wait for the thread to close, otherwise will immediately get back ID_CONNECTION_ATTEMPT_FAILED because no free outgoing connection slots
						// Alternatively, just call Startup() with 2 slots instead of 1
						RakSleep(500);

						rakPeer->Connect(lowestConnectionAddress.ToString(false), lowestConnectionAddress.GetPort(), 0, 0);

						// TCP Connect to new IP address
						packetizedTCP.Connect(lowestConnectionAddress.ToString(false),serverPort,false);
					}
					else
					{
						// TCP Connect to original IP address
						packetizedTCP.Connect(buff,serverPort,false);
					}

					didRebalance=true;
				}

				cloudClient.DeallocateWithDefaultAllocator(&cloudQueryResult);
			}

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

		if (TCPServerAddress!=RakNet::UNASSIGNED_SYSTEM_ADDRESS && patchImmediately==true)
		{
			patchImmediately=false;
			char restartFile[512];
			strcpy(restartFile, appDir);
			strcat(restartFile, "/autopatcherRestart.txt");

			double lastUpdateDate;

			if (fullScan==false)
			{
				FILE *fp = fopen("srvDate", "rb");
				if (fp)
				{
					fread(&lastUpdateDate, sizeof(lastUpdateDate), 1, fp);
					fclose(fp);
				}
				else
					lastUpdateDate=0;
			}
			else
				lastUpdateDate=0;

			if (autopatcherClient.PatchApplication(appName, appDir, lastUpdateDate, TCPServerAddress, &transferCallback, restartFile, argv[0]))
			{
				printf("Patching process starting.\n");
			}
			else
			{
				printf("Failed to start patching.\n");
				autopatcherClient.SetFileListTransferPlugin(0);
				autopatcherClient.Clear();
				packetizedTCP.Stop();
				rakPeer->Shutdown(500,0);
				RakNet::RakPeerInterface::DestroyInstance(rakPeer);
				return 0;
			}
		}
		RakSleep(30);
	}

	// Dereference so the destructor doesn't crash
	autopatcherClient.SetFileListTransferPlugin(0);

	autopatcherClient.Clear();
	packetizedTCP.Stop();
	rakPeer->Shutdown(500,0);
	RakNet::RakPeerInterface::DestroyInstance(rakPeer);
	return 1;
}
int main(int argc, char** argv) {
	RakNet::RakPeerInterface *server = RakNet::RakPeerInterface::GetInstance();
	RakNet::Packet* p;
	unsigned char packetIdentifier;
	RakNet::SystemAddress clientID = RakNet::UNASSIGNED_SYSTEM_ADDRESS;
	RakNet::RakNetStatistics *rss;
	RakNet::SocketDescriptor socketDescriptors[2];
	char portstring[30];

	socketDescriptors[0].port = SERVER_PORT;
	socketDescriptors[0].socketFamily = AF_INET; // Test out IPV4
	socketDescriptors[1].port = SERVER_PORT;
	socketDescriptors[1].socketFamily = AF_INET6; // Test out IPV6
	if (server->Startup(4, socketDescriptors, 2) != RakNet::RAKNET_STARTED) {
		std::cout << "IPV6 not supported, starting with IPV4-only interface..." << std::endl;
		if (server->Startup(4, socketDescriptors, 1) != RakNet::RAKNET_STARTED) {
			std::cerr << "Error creating server, exiting..." << std::endl;
			exit(-1);
		}
	}
	std::cout << "Server started successfully. Listening on port " << SERVER_PORT << std::endl;
	server->SetMaximumIncomingConnections(20);
	server->SetOccasionalPing(true);
	server->SetUnreliableTimeout(1000);

	DataStructures::List<RakNet::RakNetSocket2*> sockets;
	server->GetSockets(sockets);
	std::cout << "Socket addresses used by RakNet:" << std::endl;
	for (unsigned int i = 0; i < sockets.Size(); i++) {
		printf("%i. %s\n", i + 1, sockets[i]->GetBoundAddress().ToString(true));
	}
	char message[2048];

	// Loop for input
	while (1)
	{

		// This sleep keeps RakNet responsive
		RakSleep(30);

		if (kbhit())
		{
			// Notice what is not here: something to keep our network running.  It's
			// fine to block on gets or anything we want
			// Because the network engine was painstakingly written using threads.
			Gets(message, sizeof(message));

			if (strcmp(message, "quit") == 0)
			{
				puts("Quitting.");
				break;
			}

			if (strcmp(message, "stat") == 0)
			{
				rss = server->GetStatistics(server->GetSystemAddressFromIndex(0));
				StatisticsToString(rss, message, 2);
				printf("%s", message);
				printf("Ping %i\n", server->GetAveragePing(server->GetSystemAddressFromIndex(0)));

				continue;
			}

			if (strcmp(message, "ping") == 0)
			{
				server->Ping(clientID);

				continue;
			}

			if (strcmp(message, "pingip") == 0)
			{
				printf("Enter IP: ");
				Gets(message, sizeof(message));
				printf("Enter port: ");
				Gets(portstring, sizeof(portstring));
				if (portstring[0] == 0)
					strcpy(portstring, "1234");
				server->Ping(message, atoi(portstring), false);

				continue;
			}

			if (strcmp(message, "kick") == 0)
			{
				server->CloseConnection(clientID, true, 0);

				continue;
			}

			if (strcmp(message, "getconnectionlist") == 0)
			{
				printf("Connections:\n");
				RakNet::SystemAddress systems[10];
				unsigned short numConnections = 10;
				server->GetConnectionList((RakNet::SystemAddress*) &systems, &numConnections);
				for (int i = 0; i < numConnections; i++)
				{
					printf("%i. %s\n", i + 1, systems[i].ToString(true));
				}
				continue;
			}

			if (strcmp(message, "ban") == 0)
			{
				printf("Enter IP to ban.  You can use * as a wildcard\n");
				Gets(message, sizeof(message));
				server->AddToBanList(message);
				printf("IP %s added to ban list.\n", message);

				continue;
			}


			// Message now holds what we want to broadcast
			char message2[2048];
			// Append Server: to the message so clients know that it ORIGINATED from the server
			// All messages to all clients come from the server either directly or by being
			// relayed from other clients
			message2[0] = 0;
			const static char prefix[] = "Server: ";
			strncpy(message2, prefix, sizeof(message2));
			strncat(message2, message, sizeof(message2) - strlen(prefix) - 1);

			// message2 is the data to send
			// strlen(message2)+1 is to send the null terminator
			// HIGH_PRIORITY doesn't actually matter here because we don't use any other priority
			// RELIABLE_ORDERED means make sure the message arrives in the right order
			// We arbitrarily pick 0 for the ordering stream
			// RakNet::UNASSIGNED_SYSTEM_ADDRESS means don't exclude anyone from the broadcast
			// true means broadcast the message to everyone connected
			server->Send(message2, (const int)strlen(message2) + 1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
		}

		// Get a packet from either the server or the client

		for (p = server->Receive(); p; server->DeallocatePacket(p), p = server->Receive())
		{
			// We got a packet, get the identifier with our handy function
			packetIdentifier = GetPacketIdentifier(p);

			// Check if this is a network message packet
			switch (packetIdentifier)
			{
			case ID_DISCONNECTION_NOTIFICATION:
				// Connection lost normally
				printf("ID_DISCONNECTION_NOTIFICATION from %s\n", p->systemAddress.ToString(true));;
				break;


			case ID_NEW_INCOMING_CONNECTION:
				// Somebody connected.  We have their IP now
				printf("ID_NEW_INCOMING_CONNECTION from %s with GUID %s\n", p->systemAddress.ToString(true), p->guid.ToString());
				clientID = p->systemAddress; // Record the player ID of the client

				printf("Remote internal IDs:\n");
				for (int index = 0; index < MAXIMUM_NUMBER_OF_INTERNAL_IDS; index++)
				{
					RakNet::SystemAddress internalId = server->GetInternalID(p->systemAddress, index);
					if (internalId != RakNet::UNASSIGNED_SYSTEM_ADDRESS)
					{
						printf("%i. %s\n", index + 1, internalId.ToString(true));
					}
				}

				break;

			case ID_INCOMPATIBLE_PROTOCOL_VERSION:
				printf("ID_INCOMPATIBLE_PROTOCOL_VERSION\n");
				break;

			case ID_CONNECTED_PING:
			case ID_UNCONNECTED_PING:
				printf("Ping from %s\n", p->systemAddress.ToString(true));
				break;

			case ID_CONNECTION_LOST:
				// Couldn't deliver a reliable packet - i.e. the other system was abnormally
				// terminated
				printf("ID_CONNECTION_LOST from %s\n", p->systemAddress.ToString(true));;
				break;

			default:
				// The server knows the static data of all clients, so we can prefix the message
				// With the name data
				printf("%s\n", p->data);

				// Relay the message.  We prefix the name for other clients.  This demonstrates
				// That messages can be changed on the server before being broadcast
				// Sending is the same as before
				sprintf(message, "%s", p->data);
				server->Send(message, (const int)strlen(message) + 1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, true);

				break;
			}

		}
	}

	server->Shutdown(300);
	// We're done with the network
	RakNet::RakPeerInterface::DestroyInstance(server);


}