/*
 *  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-2017, 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/BitStream.h"
#include <stdlib.h> // For atoi
#include <cstring> // For strlen
#include "slikenet/Rand.h"
#include "slikenet/statistics.h"
#include "slikenet/MessageIdentifiers.h"
#include <stdio.h>
#include "slikenet/Kbhit.h"
#include "slikenet/GetTime.h"
#include "slikenet/assert.h"
#include "slikenet/sleep.h"
#include "slikenet/Gets.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"

using namespace SLNet;

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

static const int NUM_CLIENTS=100;
#define SERVER_PORT 1234
#define RANDOM_DATA_SIZE_1 50
unsigned char randomData1[RANDOM_DATA_SIZE_1];
#define RANDOM_DATA_SIZE_2 100
unsigned char randomData2[RANDOM_DATA_SIZE_2];
char *remoteIPAddress=0;

// Connects, sends data over time, disconnects, repeat
class Client
{
	public:
		Client()
		{
			peer = SLNet::RakPeerInterface::GetInstance();
		}
		~Client()
		{
			SLNet::RakPeerInterface::DestroyInstance(peer);
		}
		void Startup(void)
		{
			SLNet::SocketDescriptor socketDescriptor;
			socketDescriptor.port=0;
			nextSendTime=0;
			SLNET_VERIFY(peer->Startup(1, &socketDescriptor, 1) == RAKNET_STARTED);
			isConnected=false;
		}
		void Connect(void)
		{
			bool b;
			b=peer->Connect(remoteIPAddress, (unsigned short) SERVER_PORT, 0, 0, 0)== SLNet::CONNECTION_ATTEMPT_STARTED;
			if (b==false)
			{
				printf("Client connect call failed!\n");
			}
		}
		void Disconnect(void)
		{
			peer->CloseConnection(peer->GetSystemAddressFromIndex(0),true,0);
			isConnected=false;
		}
		void Update(SLNet::TimeMS curTime)
		{
			Packet *p = peer->Receive();
			while (p)
			{
				switch (p->data[0])
				{
				case ID_CONNECTION_REQUEST_ACCEPTED:
					printf("ID_CONNECTION_REQUEST_ACCEPTED\n");
					isConnected=true;
					break;
					// print out errors
				case ID_CONNECTION_ATTEMPT_FAILED:
					printf("Client Error: ID_CONNECTION_ATTEMPT_FAILED\n");
					isConnected=false;
					break;
				case ID_ALREADY_CONNECTED:
					printf("Client Error: ID_ALREADY_CONNECTED\n");
					break;
				case ID_CONNECTION_BANNED:
					printf("Client Error: ID_CONNECTION_BANNED\n");
					break;
				case ID_INVALID_PASSWORD:
					printf("Client Error: ID_INVALID_PASSWORD\n");
					break;
				case ID_INCOMPATIBLE_PROTOCOL_VERSION:
					printf("Client Error: ID_INCOMPATIBLE_PROTOCOL_VERSION\n");
					break;
				case ID_NO_FREE_INCOMING_CONNECTIONS:
					printf("Client Error: ID_NO_FREE_INCOMING_CONNECTIONS\n");
					isConnected=false;
					break;
				case ID_DISCONNECTION_NOTIFICATION:
					//printf("ID_DISCONNECTION_NOTIFICATION\n");
					isConnected=false;
					break;
				case ID_CONNECTION_LOST:
					printf("Client Error: ID_CONNECTION_LOST\n");
					isConnected=false;
					break;
				}
				peer->DeallocatePacket(p);
				p = peer->Receive();
			
			}

			if (curTime>nextSendTime && isConnected)
			{
				if (randomMT()%10==0)
				{
					peer->Send((const char*)&randomData2,RANDOM_DATA_SIZE_2,HIGH_PRIORITY,RELIABLE_ORDERED,0, SLNet::UNASSIGNED_SYSTEM_ADDRESS,true);
				}
				else
				{
					peer->Send((const char*)&randomData1,RANDOM_DATA_SIZE_1,HIGH_PRIORITY,RELIABLE_ORDERED,0, SLNet::UNASSIGNED_SYSTEM_ADDRESS,true);
				}

				nextSendTime=curTime+50;
			}
		}

		bool isConnected;
		RakPeerInterface *peer;
		SLNet::TimeMS nextSendTime;
};

// Just listens for ID_USER_PACKET_ENUM and validates its integrity
class Server
{
	public:
		Server()
		{
			peer = SLNet::RakPeerInterface::GetInstance();
			nextSendTime=0;
		}
		~Server()
		{
			SLNet::RakPeerInterface::DestroyInstance(peer);
		}
		void Start(void)
		{
			SLNet::SocketDescriptor socketDescriptor;
			socketDescriptor.port=(unsigned short) SERVER_PORT;
			SLNET_VERIFY(peer->Startup((unsigned short)600, &socketDescriptor, 1) == SLNet::RAKNET_STARTED);
			peer->SetMaximumIncomingConnections(600);
		}
		unsigned ConnectionCount(void) const
		{
			unsigned short numberOfSystems;
			peer->GetConnectionList(0,&numberOfSystems);
			return numberOfSystems;
		}
		void Update(SLNet::TimeMS curTime)
		{
			Packet *p = peer->Receive();
			while (p)
			{
				switch (p->data[0])
				{
				case ID_CONNECTION_LOST:
				case ID_DISCONNECTION_NOTIFICATION:
				case ID_NEW_INCOMING_CONNECTION:
					printf("Connections = %i\n", ConnectionCount());
					break;
// 				case ID_USER_PACKET_ENUM:
// 					{
// 						peer->Send((const char*) p->data, p->length, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->guid, true);
// 						break;
// 					}
				}
				peer->DeallocatePacket(p);
				p = peer->Receive();
			}

			if (curTime>nextSendTime)
			{
				if (randomMT()%10==0)
				{
					peer->Send((const char*)&randomData2,RANDOM_DATA_SIZE_2,HIGH_PRIORITY,RELIABLE_ORDERED,0, SLNet::UNASSIGNED_SYSTEM_ADDRESS,true);
				}
				else
				{
					peer->Send((const char*)&randomData1,RANDOM_DATA_SIZE_1,HIGH_PRIORITY,RELIABLE_ORDERED,0, SLNet::UNASSIGNED_SYSTEM_ADDRESS,true);
				}

				nextSendTime=curTime+100;
			}
		}
		

		SLNet::TimeMS nextSendTime;
		RakPeerInterface *peer;
};

int main(void)
{
	Client clients[NUM_CLIENTS];
	Server server;
//	int clientIndex;
	int mode;

	printf("Connects many clients to a single server.\n");
	printf("Difficulty: Intermediate\n\n");
	printf("Run as (S)erver or (C)lient or (B)oth? ");
	int ch = _getche();
	static char *defaultRemoteIP="127.0.0.1";
	char remoteIP[128];
	static char *localIP="127.0.0.1";
	if (ch=='s' || ch=='S')
	{
		mode=0;
	}
	else if (ch=='c' || ch=='c')
	{
		mode=1;
		remoteIPAddress=remoteIP;
	}
	else
	{
		mode=2;
		remoteIPAddress=localIP;
	}
	printf("\n");

	if (mode==1 || mode==2)
	{
		printf("Enter remote IP: ");
		Gets(remoteIP, sizeof(remoteIP));
		if (remoteIP[0]==0)
		{
			strcpy_s(remoteIP, defaultRemoteIP);
			printf("Using %s\n", defaultRemoteIP);
		}
	}

	randomData1[0]=(unsigned char)ID_USER_PACKET_ENUM;
	for (unsigned char i=0; i < RANDOM_DATA_SIZE_1-1; i++)
		randomData1[i+1]=i;
	randomData2[0]=(unsigned char)ID_USER_PACKET_ENUM;
	for (unsigned char i=0; i < RANDOM_DATA_SIZE_2-1; i++)
		randomData2[i+1]=i;

	if (mode==0 || mode==2)
	{
		server.Start();
		printf("Started server\n");
	}
	if (mode==1 || mode==2)
	{
		printf("Starting clients...\n");
		for (unsigned int i=0; i < NUM_CLIENTS; i++)
			clients[i].Startup();
		printf("Started clients\n");
		printf("Connecting clients...\n");
		for (unsigned int i=0; i < NUM_CLIENTS; i++)
			clients[i].Connect();
		printf("Done.\n");
	}
	
	SLNet::TimeMS endTime = SLNet::GetTimeMS()+60000*5;
	SLNet::TimeMS time = SLNet::GetTimeMS();
	while (time < endTime)
	{
		if (mode==0 || mode==2)
			server.Update(time);
		if (mode==1 || mode==2)
		{
			for (unsigned int i=0; i < NUM_CLIENTS; i++)
				clients[i].Update(time);
		}

		if (_kbhit())
		{
			ch = _getch();
			if (ch==' ')
			{
				FILE *fp;
				char text[2048];
				if (mode==0 || mode==2)
				{
					printf("Logging server statistics to ServerStats.txt\n");
					fopen_s(&fp,"ServerStats.txt","wt");
					for (unsigned int i=0; i < NUM_CLIENTS; i++)
					{
						RakNetStatistics *rssSender;
						rssSender=server.peer->GetStatistics(server.peer->GetSystemAddressFromIndex(i));
						StatisticsToString(rssSender, text, 2048, 3);
						fprintf(fp,"==== System %i ====\n", i+1);
						fprintf(fp,"%s\n\n", text);
					}
					fclose(fp);
				}
				if (mode==1 || mode==2)
				{
					printf("Logging client statistics to ClientStats.txt\n");
					fopen_s(&fp,"ClientStats.txt","wt");
					for (unsigned int i=0; i < NUM_CLIENTS; i++)
					{
						RakNetStatistics *rssSender;
						rssSender=clients[i].peer->GetStatistics(clients[i].peer->GetSystemAddressFromIndex(0));
						StatisticsToString(rssSender, text, 2048, 3);
						fprintf(fp,"==== Client %i ====\n", i+1);
						fprintf(fp,"%s\n\n", text);
					}
					fclose(fp);
				}
			}	
			if (ch=='q' || ch==0)
				break;
		}

		time = SLNet::GetTimeMS();
		RakSleep(30);
	}

	if (mode==0 || mode==2)
		server.peer->Shutdown(0);
	if (mode==1 || mode==2)
		for (unsigned int i=0; i < NUM_CLIENTS; i++)
			clients[i].peer->Shutdown(0);

	printf("Test completed");
	return 0;
}
