/*
 *  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) 2017-2018, 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.
 */

#include "slikenet/peerinterface.h"
#include "slikenet/Rand.h" // randomMT
#include "slikenet/MessageIdentifiers.h" // Enumerations
#include "slikenet/types.h" // SystemAddress
#include "slikenet/Kbhit.h"
#include <cstdio>
using namespace SLNet;

#ifdef _WIN32
#include "slikenet/WindowsIncludes.h" // Sleep
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#ifdef _DEBUG
static const short NUMBER_OF_CLIENTS=9;
#else
static const short NUMBER_OF_CLIENTS=100;
#endif

void ShowHelp(void)
{
	printf("System started.\n(D)isconnect a random client silently\n(C)onnect a random client\n(R)andom silent disconnects and connects for all clients.\n(N)otify server of random disconnections.\nSpace to verify connection list.\n(H)elp\n(Q)uit\n");
}

int main(void)
{
	RakPeerInterface *server;
	RakPeerInterface *clients[NUMBER_OF_CLIENTS];
	unsigned connectionCount;
	int ch;
	SystemAddress serverID;
	SLNet::Packet *p;
	unsigned short numberOfSystems;
	int sender;
	
	// Buffer for input (an ugly hack to keep *nix happy)
	#ifndef _WIN32
	char buff[256];
	#endif

	printf("This is a project I use internally to test if dropped connections are detected\n");
	printf("Difficulty: Intermediate\n\n");

	printf("Dropped Connection Test.\n");

	unsigned short serverPort = 20000;
	server= SLNet::RakPeerInterface::GetInstance();
//	server->InitializeSecurity(0,0,0,0);
	SLNet::SocketDescriptor socketDescriptor(serverPort,0);
	server->Startup(NUMBER_OF_CLIENTS, &socketDescriptor, 1);
	server->SetMaximumIncomingConnections(NUMBER_OF_CLIENTS);
	server->SetTimeoutTime(10000,UNASSIGNED_SYSTEM_ADDRESS);

	for (unsigned short index=0; index < NUMBER_OF_CLIENTS; index++)
	{
		clients[index]= SLNet::RakPeerInterface::GetInstance();
		SLNet::SocketDescriptor socketDescriptor2(serverPort+1+index,0);
		clients[index]->Startup(1, &socketDescriptor2, 1);
		clients[index]->Connect("127.0.0.1", serverPort, 0, 0);
		clients[index]->SetTimeoutTime(5000, SLNet::UNASSIGNED_SYSTEM_ADDRESS);

		#ifdef _WIN32
				Sleep(10);
		#else
				usleep(10 * 1000);
		#endif
		printf("%u. ", index);
	}

	ShowHelp();

	for(;;)
	{
		// User input
		if (_kbhit())
		{
#ifndef _WIN32
			Gets(buff,sizeof(buff));
			ch=buff[0];
#else
			ch=_getch();
#endif			

			if (ch=='d' || ch=='D')
			{
				unsigned short index = randomMT() % NUMBER_OF_CLIENTS;
				
				clients[index]->GetConnectionList(0, &numberOfSystems);
				clients[index]->CloseConnection(serverID, false,0);
				if (numberOfSystems==0)
					printf("Client %u silently closing inactive connection.\n",index);
				else
					printf("Client %u silently closing active connection.\n",index);
			}
			else if (ch=='c' || ch=='C')
			{
				unsigned short index = randomMT() % NUMBER_OF_CLIENTS;

				clients[index]->GetConnectionList(0, &numberOfSystems);
				clients[index]->Connect("127.0.0.1", serverPort, 0, 0);
				if (numberOfSystems==0)
					printf("Client %u connecting to same existing connection.\n",index);
				else
					printf("Client %u connecting to closed connection.\n",index);
			}
			else if (ch=='r' || ch=='R' || ch=='n' || ch=='N')
			{
				printf("Randomly connecting and disconnecting each client\n");
				for (unsigned short index=0; index < NUMBER_OF_CLIENTS; index++)
				{
					if (NUMBER_OF_CLIENTS==1 || (randomMT()%2)==0)
					{
						if (clients[index]->IsActive())
						{
							if (ch=='r' || ch=='R')
								clients[index]->CloseConnection(serverID, false, 0);
							else
								clients[index]->CloseConnection(serverID, true, 0);
						}
					}
					else
					{
						clients[index]->Connect("127.0.0.1", serverPort, 0, 0);
					}
				}
			}
			else if (ch==' ')
			{
				server->GetConnectionList(0, &numberOfSystems);
				printf("The server thinks %i clients are connected.\n", numberOfSystems);
				connectionCount=0;
				for (unsigned short index=0; index < NUMBER_OF_CLIENTS; index++)
				{
					clients[index]->GetConnectionList(0, &numberOfSystems);
					if (numberOfSystems>1)
						printf("Bug: Client %u has %i connections\n", index, numberOfSystems);
					if (numberOfSystems==1)
					{
						connectionCount++;
					}
				}
				printf("%u clients are actually connected.\n", connectionCount);
				printf("server->NumberOfConnections==%u.\n", server->NumberOfConnections());
			}
			else if (ch=='h' || ch=='H')
			{
				ShowHelp();
			}
			else if (ch=='q' || ch=='Q')
			{
				break;
			}
			ch=0;
		}

		// Parse messages
		
		for(;;)
		{
			p = server->Receive();
			sender=NUMBER_OF_CLIENTS;
			if (p==0)
			{
				for (unsigned short index=0; index < NUMBER_OF_CLIENTS; index++)
				{
					p = clients[index]->Receive();
					if (p!=0)
					{
						sender=index;
						break;						
					}
				}
			}

			if (p)
			{
				switch (p->data[0])
				{
				case ID_CONNECTION_REQUEST_ACCEPTED:
					printf("%i: ID_CONNECTION_REQUEST_ACCEPTED from %i.\n",sender, p->systemAddress.GetPort());
					serverID=p->systemAddress;
					break;
				case ID_DISCONNECTION_NOTIFICATION:
					// Connection lost normally
					printf("%i: ID_DISCONNECTION_NOTIFICATION from %i.\n",sender, p->systemAddress.GetPort());
					break;

				case ID_NEW_INCOMING_CONNECTION:
					// Somebody connected.  We have their IP now
					printf("%i: ID_NEW_INCOMING_CONNECTION from %i.\n",sender, p->systemAddress.GetPort());
					break;

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

				case ID_NO_FREE_INCOMING_CONNECTIONS:
					printf("%i: ID_NO_FREE_INCOMING_CONNECTIONS from %i.\n",sender, p->systemAddress.GetPort());
					break;

				default:
					// Ignore anything else
					break;
				}
			}
			else
				break;

			if (sender==NUMBER_OF_CLIENTS)
				server->DeallocatePacket(p);
			else
				clients[sender]->DeallocatePacket(p);
		}

		// 11/29/05 - No longer necessary since I added the keepalive
		/*
		// Have everyone send a reliable packet so dropped connections are noticed.
		ch=255;
		server->Send((char*)&ch, 1, HIGH_PRIORITY, RELIABLE, 0, SLNet::UNASSIGNED_SYSTEM_ADDRESS, true);

		for (unsigned short index=0; index < NUMBER_OF_CLIENTS; index++)
			clients[index]->Send((char*)&ch, 1, HIGH_PRIORITY, RELIABLE, 0, SLNet::UNASSIGNED_SYSTEM_ADDRESS, true);
			*/

		// Sleep so this loop doesn't take up all the CPU time

#ifdef _WIN32
			Sleep(30);
#else
			usleep(30 * 1000);
#endif

	}

	SLNet::RakPeerInterface::DestroyInstance(server);
	for (unsigned short index=0; index < NUMBER_OF_CLIENTS; index++)
		SLNet::RakPeerInterface::DestroyInstance(clients[index]);
	return 1;
}
