/*
 *  Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant 
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

#include "RakPeerInterface.h"

#include "BitStream.h"
#include <stdlib.h> // For atoi
#include <cstring> // For strlen
#include "Rand.h"
#include "RakNetStatistics.h"
#include "MessageIdentifiers.h"
#include <stdio.h>
#include "Kbhit.h"
#include "GetTime.h"
#include "RakAssert.h"
#include "RakSleep.h"
#include "Gets.h"

using namespace RakNet;

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

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


enum EMessages
{
	EMessage_SendServerHelp = ID_USER_PACKET_ENUM + 1,
	EMessage_SendGameHelp,
	EMessage_SendLogin,
	EMessage_SendDisconnect,
	EMessage_SendJoinGame,
	EMessage_SendWhoseTurnIsIt,

	EMessage_RecvServerHelp,
	EMessage_RecvGameHelp,
	EMessage_RecvLoginSuccess,
	EMessage_RecvDisconnected,
	EMessage_RecvWaitingForPlayers,
	EMessage_RecvGameJoined,
	EMessage_RecvWhoseTurnIsIt,
	EMessage_RecvOpponentFinished,
	EMessage_RecvAcknowledgement,
	EMessage_RecvServerError,
	EMessage_RecvGameNotActive,

	EMessage_Count,
	EMessage_BaseCount = EMessage_Count
};

// Connects, sends data over time, disconnects, repeat
class Client {
public:
	Client() {
		peer = RakNet::RakPeerInterface::GetInstance();
	}

	~Client() {
		Disconnect();
		RakNet::RakPeerInterface::DestroyInstance(peer);
	}

	void Startup(void) {
		RakNet::SocketDescriptor socketDescriptor;
		socketDescriptor.port = 0;
		nextSendTime = 0;
		StartupResult b = peer->Startup(1, &socketDescriptor, 1);
		RakAssert(b==RAKNET_STARTED);
		isConnected = false;
	}

	void Connect(void) {
		bool b;
		b = peer->Connect(remoteIPAddress, (unsigned short) SERVER_PORT, 0, 0, 0) == RakNet::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(RakNet::TimeMS curTime) {
		Packet* p = peer->Receive();
		while(p) {

			printf("Packet received:\n");
			switch(p->data[0]) {
				case ID_CONNECTION_REQUEST_ACCEPTED:
					printf("ID_CONNECTION_REQUEST_ACCEPTED aka connected\n");
					isConnected = true;
					Help();
					Login();
					break;
					// print out errors
				case EMessage_RecvServerHelp:
					printf("Sinterklaas is niet echt is al the help u need\n");
					break;
				case EMessage_RecvLoginSuccess:
					printf("Logged in!!!\n");
					break;
				case EMessage_RecvServerError:
					printf("Server error\n");
					break;
				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 aka disconnected\n");
					isConnected = false;
					break;
				case ID_CONNECTION_LOST:
					printf("Client Error: ID_CONNECTION_LOST\n");
					isConnected = false;
					break;
				default:
					printf("Unknown error:%3.3f\n", p->data[0]);
					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, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
			} else {
				//peer->Send((const char*) &randomData1,RANDOM_DATA_SIZE_1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
			}

			nextSendTime = curTime + 50;
		}
	}

	void Help() {
		printf("MAYDAY\n");
		RakNet::BitStream bsOut;
		bsOut.Write(static_cast<RakNet::MessageID>(EMessage_SendServerHelp));
		peer->Send((const char*)&bsOut, bsOut.GetNumberOfBytesUsed(), HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
	}

	void Login() {
		printf("Logging in...\n");
		RakNet::BitStream bsOut;
		bsOut.Write(static_cast<RakNet::MessageID>(EMessage_SendLogin));
		bsOut.Write(RakNet::RakString("STUDENTID"));
		bsOut.Write(RakNet::RakString("APASSWORD"));

		peer->Send((const char*)&bsOut, bsOut.GetNumberOfBytesUsed(), HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
		//peer->Send((const char*)&randomData2, EMessage_SendServerHelp, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
		printf("Login request sent!\n");
	}

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

// Just listens for ID_USER_PACKET_ENUM and validates its integrity
class Server {
public:
	Server() {
		peer = RakNet::RakPeerInterface::GetInstance();
		nextSendTime = 0;
	}

	~Server() {
		RakNet::RakPeerInterface::DestroyInstance(peer);
	}

	void Start(void) {
		RakNet::SocketDescriptor socketDescriptor;
		socketDescriptor.port = (unsigned short) SERVER_PORT;
		bool b = peer->Startup((unsigned short) 600, &socketDescriptor, 1) == RakNet::RAKNET_STARTED;
		RakAssert(b);
		peer->SetMaximumIncomingConnections(600);
	}

	unsigned ConnectionCount(void) const {
		unsigned short numberOfSystems;
		peer->GetConnectionList(0, &numberOfSystems);
		return numberOfSystems;
	}

	void Update(RakNet::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, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
			} else {
				peer->Send((const char*) &randomData1,RANDOM_DATA_SIZE_1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
			}

			nextSendTime = curTime + 100;
		}
	}

	RakNet::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? ");
	//char 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;
	//}
	mode = 1;
	remoteIPAddress = "PiGameServer1.nhtv.nl";
	printf("\n");

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

	unsigned i;
	randomData1[0] = (char) ID_USER_PACKET_ENUM;
	for(i = 0; i < RANDOM_DATA_SIZE_1 - 1; i++)
		randomData1[i + 1] = i;
	randomData2[0] = (char) ID_USER_PACKET_ENUM;
	for(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(i = 0; i < NUM_CLIENTS; i++)
			clients[i].Startup();
		printf("Started clients\n");
		printf("Connecting clients...\n");
		for(i = 0; i < NUM_CLIENTS; i++)
			clients[i].Connect();
		printf("Done.\n");
	}

	RakNet::TimeMS endTime = RakNet::GetTimeMS() + 60000 * 5;
	RakNet::TimeMS time = RakNet::GetTimeMS();
	while(time < endTime) {
		if(mode == 0 || mode == 2)
			server.Update(time);
		if(mode == 1 || mode == 2) {
			for(i = 0; i < NUM_CLIENTS; i++)
				clients[i].Update(time);
		}

		if(kbhit()) {
			char ch = getch();
			if(ch == ' ') {
				FILE* fp;
				char text[2048];
				if(mode == 0 || mode == 2) {
					printf("Logging server statistics to ServerStats.txt\n");
					fp = fopen("ServerStats.txt", "wt");
					for(i = 0; i < NUM_CLIENTS; i++) {
						RakNetStatistics* rssSender;
						rssSender = server.peer->GetStatistics(server.peer->GetSystemAddressFromIndex(i));
						StatisticsToString(rssSender, text, 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");
					fp = fopen("ClientStats.txt", "wt");
					for(i = 0; i < NUM_CLIENTS; i++) {
						RakNetStatistics* rssSender;
						rssSender = clients[i].peer->GetStatistics(clients[i].peer->GetSystemAddressFromIndex(0));
						StatisticsToString(rssSender, text, 3);
						fprintf(fp, "==== Client %i ====\n", i + 1);
						fprintf(fp, "%s\n\n", text);
					}
					fclose(fp);
				}
			}
			if(ch == 'q' || ch == 0)
				break;
		}

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

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

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