/*
 *  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-2019, 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/GetTime.h"
#include "slikenet/MessageIdentifiers.h"
#include "slikenet/BitStream.h"
#include <cstdio>
#include <memory.h>
#include <cstring>
#include <stdlib.h>
#include "slikenet/Rand.h"
#include "slikenet/statistics.h"
#include "slikenet/sleep.h"
#include "slikenet/memoryoverride.h"
#include <stdio.h>
#include <limits> // used for std::numeric_limits
#include "slikenet/Gets.h"
#include "slikenet/Kbhit.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"

using namespace SLNet;

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

FILE *fp;
int memoryUsage=0;
void *LoggedMalloc(size_t size, const char *file, unsigned int line)
{
	memoryUsage+=size;
	if (fp)
		fprintf(fp,"Alloc %s:%i %i bytes %i total\n", file,line,size,memoryUsage);
	char *p = (char*) malloc(size+sizeof(size));
	memcpy(p,&size,sizeof(size));
	return p+sizeof(size);
}
void LoggedFree(void *p, const char *file, unsigned int line)
{
	char *realP=(char*)p-sizeof(size_t);
	size_t allocatedSize;
	memcpy(&allocatedSize,realP,sizeof(size_t));
	memoryUsage-=allocatedSize;
	if (fp)
		fprintf(fp,"Free %s:%i %i bytes %i total\n", file,line,allocatedSize,memoryUsage);
	free(realP);
}
void* LoggedRealloc(void *p, size_t size, const char *file, unsigned int line)
{
	char *realP=(char*)p-sizeof(size_t);
	size_t allocatedSize;
	memcpy(&allocatedSize,realP,sizeof(size_t));
	memoryUsage-=allocatedSize;
	memoryUsage+=size;
	p = realloc(realP,size+sizeof(size));
	memcpy(p,&size,sizeof(size));
	if (fp)
		fprintf(fp,"Realloc %s:%i %i to %i bytes %i total\n", file,line,allocatedSize,size,memoryUsage);
	return (char*)p+sizeof(size);
}

int main(int argc, char **argv)
{
	RakPeerInterface *sender, *receiver;
	unsigned int packetNumber[32], receivedPacketNumber;
	SLNet::Time receivedTime;
	char str[256];
	char ip[32];
	// initialize to silence false-positive warning C4701 with VS2015+
	SLNet::Time sendInterval = 0;
	SLNet::Time nextSend, currentTime, quitTime;
	unsigned short remotePort, localPort;
	unsigned char streamNumber;
	SLNet::BitStream bitStream;
	SLNet::Packet *packet;
	bool doSend=false;

	for (int i=0; i < 32; i++)
		packetNumber[i]=0;

	printf("This project tests RakNet's reliable ordered sending system.\n");
	printf("Difficulty: Beginner\n\n");

	printf("Act as (s)ender or (r)eceiver?\n");
	Gets(str, sizeof(str));
	if (str[0]==0)
		return 1;

	if (argc==2)
	{
		fopen_s(&fp,argv[1],"wt");
		SetMalloc_Ex(LoggedMalloc);
		SetRealloc_Ex(LoggedRealloc);
		SetFree_Ex(LoggedFree);
	}
	else
		fp=0;

	if (str[0]=='s' || str[0]=='S')
	{
		sender = SLNet::RakPeerInterface::GetInstance();

		receiver = 0;

		printf("Enter number of ms to pass between sends: ");
		Gets(str, sizeof(str));
		if (str[0]==0)
			sendInterval=33;
		else
			sendInterval=atoi(str);

		printf("Enter remote IP: ");
		Gets(ip, sizeof(ip));
		if (ip[0]==0)
			strcpy_s(ip, "127.0.0.1");
			// strcpy_s(ip, "natpunch.slikesoft.com");
		
		printf("Enter remote port: ");
		Gets(str, sizeof(str));
		if (str[0]==0)
			strcpy_s(str, "60000");
		const int intRemotePort = atoi(str);
		if ((intRemotePort < 0) || (intRemotePort > std::numeric_limits<unsigned short>::max())) {
			printf("Specified remote port %d is outside valid bounds [0, %u]", intRemotePort, std::numeric_limits<unsigned short>::max());
			return 2;
		}
		remotePort = static_cast<unsigned short>(intRemotePort);

		printf("Enter local port: ");
		Gets(str, sizeof(str));
		if (str[0]==0)
			strcpy_s(str, "0");
		const int intLocalPort = atoi(str);
		if ((intLocalPort < 0) || (intLocalPort > std::numeric_limits<unsigned short>::max())) {
			printf("Specified local port %d is outside valid bounds [0, %u]", intLocalPort, std::numeric_limits<unsigned short>::max());
			return 3;
		}
		localPort = static_cast<unsigned short>(intLocalPort);


		printf("Connecting...\n");
		SLNet::SocketDescriptor socketDescriptor(localPort,0);
		sender->Startup(8, &socketDescriptor, 1);
		// sender->ApplyNetworkSimulator(.2, 0, 0);
		sender->Connect(ip, remotePort, 0, 0);
	}
	else
	{
		receiver = SLNet::RakPeerInterface::GetInstance();
		// receiver->ApplyNetworkSimulator(.2, 0, 0);
		sender=0;

		printf("Enter local port: ");
		Gets(str, sizeof(str));
		if (str[0]==0)
			strcpy_s(str, "60000");
		const int intLocalPort = atoi(str);
		if ((intLocalPort < 0) || (intLocalPort > std::numeric_limits<unsigned short>::max())) {
			printf("Specified local port %d is outside valid bounds [0, %u]", intLocalPort, std::numeric_limits<unsigned short>::max());
			return 3;
		}
		localPort = static_cast<unsigned short>(intLocalPort);

		printf("Waiting for connections...\n");
		SLNet::SocketDescriptor socketDescriptor(localPort,0);
		receiver->Startup(8, &socketDescriptor, 1);
		receiver->SetMaximumIncomingConnections(8);
	}

	seedMT(1);
	
	printf("How long to run this test for, in seconds?\n");
	Gets(str, sizeof(str));
	if (str[0]==0)
		strcpy_s(str, "12000");
	
	currentTime = SLNet::GetTimeMS();
	quitTime = atoi(str) * 1000 + currentTime;

	nextSend=currentTime;

	printf("Test running.\n");

	//while (currentTime < quitTime)
	for(;;)
	{
#ifdef _WIN32
		if (_kbhit())
		{
			int ch=_getch();
			if (ch=='q')
				break;
			else if (ch==' ')
			{
				RakNetStatistics *rss;
				char message[2048];
				if (sender)
					rss=sender->GetStatistics(sender->GetSystemAddressFromIndex(0));
				else
					rss=receiver->GetStatistics(receiver->GetSystemAddressFromIndex(0));
				StatisticsToString(rss, message, 2048, 2);
				printf("%s", message);
			}
		}
#endif
		if (sender)
		{
			uint32_t msgNumber;
			packet = sender->Receive();
			while (packet)
			{
				// PARSE TYPES
				switch(packet->data[0])
				{
				case ID_CONNECTION_REQUEST_ACCEPTED:
					printf("ID_CONNECTION_REQUEST_ACCEPTED\n");
					doSend=true;
					nextSend=currentTime;
					break;
				case ID_NO_FREE_INCOMING_CONNECTIONS:
					printf("ID_NO_FREE_INCOMING_CONNECTIONS\n");
					break;
				case ID_DISCONNECTION_NOTIFICATION:
					printf("ID_DISCONNECTION_NOTIFICATION\n");
					break;
				case ID_CONNECTION_LOST:
					printf("ID_CONNECTION_LOST\n");
					break;
				case ID_CONNECTION_ATTEMPT_FAILED:
					printf("Connection attempt failed\n");
					break;
				case ID_SND_RECEIPT_ACKED:
					memcpy(&msgNumber, packet->data+1, 4);
					printf("Msg #%i was delivered.\n", msgNumber);
					break;
				case ID_SND_RECEIPT_LOSS:
					memcpy(&msgNumber, packet->data+1, 4);
					printf("Msg #%i was probably not delivered.\n", msgNumber);
					break;
				}

				sender->DeallocatePacket(packet);
				packet = sender->Receive();
			}

			char *type="UNDEFINED";
			while (doSend && currentTime > nextSend)
			{
				streamNumber=0;
				streamNumber = randomMT() % 4;
				// Do the send

                for (int i=0; i < 2; i++)
                {
					bitStream.Reset();
					bitStream.Write((unsigned char) (ID_TIMESTAMP));
					bitStream.Write(SLNet::GetTime());
					bitStream.Write((unsigned char) (ID_USER_PACKET_ENUM+1));
					bitStream.Write(packetNumber[streamNumber]);
					packetNumber[streamNumber]++;
					bitStream.Write(streamNumber);

                    PacketReliability reliability;
					// #med - review --- was commented out in RakNet via if(0)
                    /*if (0 && (randomMT()%2)==0)
                    {
                        type="UNRELIABLE_SEQUENCED";
                        reliability=UNRELIABLE_SEQUENCED;
                    }
                    else
                    {*/
                        type="RELIABLE_ORDERED";
                        reliability=RELIABLE_ORDERED;
                    //}

                    int padLength;
					padLength = (randomMT() % 25000) + 1;
                    bitStream.Write(reliability);
                    bitStream.PadWithZeroToByteLength(padLength);

				    if (sender->Send(&bitStream, HIGH_PRIORITY, reliability ,streamNumber, SLNet::UNASSIGNED_SYSTEM_ADDRESS, true)==0)
				    {
					    packetNumber[streamNumber]--;
				    }
                }
				
//				RakNetStatistics *rssSender;
				//rssSender=sender->GetStatistics(sender->GetSystemAddressFromIndex(0));
					
				printf("Snd: %i, %s, time %" PRINTF_64_BIT_MODIFIER "u, length %i\n", packetNumber[streamNumber]-1, type, currentTime, bitStream.GetNumberOfBytesUsed());

				nextSend+=sendInterval;

				// Test halting
			//	if (rand()%20==0)
			//		nextSend+=1000;
			}
		}
		else
		{
			packet = receiver->Receive();
			while (packet)
			{
				switch(packet->data[0])
				{
				case ID_NEW_INCOMING_CONNECTION:
					printf("ID_NEW_INCOMING_CONNECTION\n");
					break;
				case ID_DISCONNECTION_NOTIFICATION:
					printf("ID_DISCONNECTION_NOTIFICATION\n");
					break;
				case ID_CONNECTION_LOST:
					printf("ID_CONNECTION_LOST\n");
					break;
				case ID_TIMESTAMP:
					bitStream.Reset();
					bitStream.Write((char*)packet->data, packet->length);
					bitStream.IgnoreBits(8); // Ignore ID_TIMESTAMP
					bitStream.Read(receivedTime);
					bitStream.IgnoreBits(8); // Ignore ID_USER_ENUM+1
					bitStream.Read(receivedPacketNumber);
					bitStream.Read(streamNumber);
					PacketReliability reliability;
					bitStream.Read(reliability);
					char *type="UNDEFINED";
					if (reliability==UNRELIABLE_SEQUENCED)
						type="UNRELIABLE_SEQUENCED";
					else if (reliability==RELIABLE_ORDERED)
						type="RELIABLE_ORDERED";

					if (receivedPacketNumber>packetNumber[streamNumber])
						printf("Skipped %i got %i %s (channel %i).\n",packetNumber[streamNumber], receivedPacketNumber, type, streamNumber);
					else if (receivedPacketNumber<packetNumber[streamNumber])
						printf("Out of order packet! Expecting %i got %i %s (channel %i).\n",packetNumber[streamNumber], receivedPacketNumber, type, streamNumber);
					else
						printf("Got %i.%s.CH:%i.Len:%i.\n", packetNumber[streamNumber], type, streamNumber, packet->length);

//					printf("Sent=%" PRINTF_64_BIT_MODIFIER "u Received=%" PRINTF_64_BIT_MODIFIER "u Diff=%i.\n", receivedTime, currentTime, (int)(currentTime - receivedTime));

					packetNumber[streamNumber]=receivedPacketNumber+1;
					break;
				}

				
				receiver->DeallocatePacket(packet);
				packet = receiver->Receive();
			}
		}

// DO NOT COMMENT OUT THIS SLEEP!
// This sleep keeps RakNet responsive
#ifdef _WIN32
		Sleep(0);
#else
		usleep(0);
#endif
		currentTime= SLNet::GetTimeMS();
	}

	if (sender)
		SLNet::RakPeerInterface::DestroyInstance(sender);
	if (receiver)
		SLNet::RakPeerInterface::DestroyInstance(receiver);

	if (fp)
		fclose(fp);

	return 1;
}
