/*
 *  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/sleep.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits> // used for std::numeric_limits
#include "slikenet/Kbhit.h"
#include "slikenet/MessageIdentifiers.h"
#include "slikenet/BitStream.h"
#include "slikenet/sleep.h"
#include "slikenet/NatPunchthroughClient.h"
#include "slikenet/NatTypeDetectionClient.h"
#include "slikenet/Getche.h"
#include "slikenet/GetTime.h"
#include "slikenet/Router2.h"
#include "slikenet/UDPProxyClient.h"
#include "slikenet/Gets.h"
#include "slikenet/Itoa.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"

// To include miniupnp, see Samples\NATCompleteClient\readme.txt
#include "miniupnpc.h"
#include "upnpcommands.h"
#include "upnperrors.h"

using namespace SLNet;

#define DEFAULT_RAKPEER_PORT 50000
#define RAKPEER_PORT_STR "0"
#define DEFAULT_SERVER_PORT "61111"
#define DEFAULT_SERVER_ADDRESS "natpunch.slikesoft.com"

enum SampleResult
{
	PENDING,
	FAILED,
	SUCCEEDED
};

#define SUPPORT_UPNP FAILED
#define SUPPORT_NAT_TYPE_DETECTION FAILED
#define SUPPORT_NAT_PUNCHTHROUGH PENDING
#define SUPPORT_ROUTER2 FAILED
#define SUPPORT_UDP_PROXY FAILED

struct SampleFramework
{
	virtual const char * QueryName(void)=0;
	virtual bool QueryRequiresServer(void)=0;
	virtual const char * QueryFunction(void)=0;
	virtual const char * QuerySuccess(void)=0;
	virtual bool QueryQuitOnSuccess(void)=0;
	virtual void Init(SLNet::RakPeerInterface *rakPeer)=0;
	virtual void ProcessPacket(Packet *packet)=0;
	virtual void Update(SLNet::RakPeerInterface *rakPeer)=0;
	virtual void Shutdown(SLNet::RakPeerInterface *rakPeer)=0;

	SampleResult sampleResult;
};

SystemAddress SelectAmongConnectedSystems(SLNet::RakPeerInterface *rakPeer, const char *hostName)
{
	DataStructures::List<SystemAddress> addresses;
	DataStructures::List<RakNetGUID> guids;
	rakPeer->GetSystemList(addresses, guids);
	if (addresses.Size()==0)
	{
		return SLNet::UNASSIGNED_SYSTEM_ADDRESS;
	}
	if (addresses.Size()>1)
	{
		printf("Select IP address for %s.\n", hostName);
		char buff[64];
		for (unsigned int i=0; i < addresses.Size(); i++)
		{
			addresses[i].ToString(true, buff, static_cast<size_t>(64));
			printf("%i. %s\n", i+1, buff);
		}
		Gets(buff,sizeof(buff));
		if (buff[0]==0)
		{
			return SLNet::UNASSIGNED_SYSTEM_ADDRESS;
		}
		unsigned int idx = atoi(buff);
		if (idx<=0 || idx > addresses.Size())
		{
			return SLNet::UNASSIGNED_SYSTEM_ADDRESS;
		}
		return addresses[idx-1];
	}
	else
		return addresses[0];
};
SystemAddress ConnectBlocking(SLNet::RakPeerInterface *rakPeer, const char *hostName, const char *defaultAddress, const char *defaultPort)
{
	SystemAddress returnvalue = SLNet::UNASSIGNED_SYSTEM_ADDRESS;
	char ipAddr[64];
	if (defaultAddress==0 || defaultAddress[0]==0)
		printf("Enter IP of system %s is running on: ", hostName);
	else
		printf("Enter IP of system %s, or press enter for default: ", hostName);
	Gets(ipAddr,sizeof(ipAddr));
	if (ipAddr[0]==0)
	{
		if (defaultAddress==0 || defaultAddress[0]==0)
		{
			printf("Failed. No address entered for %s.\n", hostName);
			return SLNet::UNASSIGNED_SYSTEM_ADDRESS;
		}
		else
		{
			strcpy_s(ipAddr, defaultAddress);
		}
	}
	char port[64];
	if (defaultAddress==0 || defaultAddress[0]==0)
		printf("Enter port of system %s is running on: ", hostName);
	else
		printf("Enter port of system %s, or press enter for default: ", hostName);
	Gets(port, sizeof(port));
	if (port[0]==0)
	{
		if (defaultPort==0 || defaultPort[0]==0)
		{
			printf("Failed. No port entered for %s.\n", hostName);
			return SLNet::UNASSIGNED_SYSTEM_ADDRESS;
		}
		else
		{
			strcpy_s(port, defaultPort);
		}
	}
	const int intPort = atoi(port);
	if ((intPort < 0) || (intPort > std::numeric_limits<unsigned short>::max())) {
		printf("Failed. Specified port %d is outside valid bounds [0, %u]", intPort, std::numeric_limits<unsigned short>::max());
		return SLNet::UNASSIGNED_SYSTEM_ADDRESS;
	}
	if (rakPeer->Connect(ipAddr, static_cast<unsigned short>(intPort), 0, 0)!= SLNet::CONNECTION_ATTEMPT_STARTED)
	{
		printf("Failed connect call for %s.\n", hostName);
		return SLNet::UNASSIGNED_SYSTEM_ADDRESS;
	}
	printf("Connecting...\n");
	SLNet::Packet *packet;
	// #med - review --- at least we'd add a sleep interval here - also review whether the behavior is correct to only check the very first received packet (old RakNet code was bogus in this regards)
	do {
		packet = rakPeer->Receive();
	} while (packet == nullptr);

	if (packet->data[0] == ID_CONNECTION_REQUEST_ACCEPTED)
		returnvalue = packet->systemAddress;
	else if (packet->data[0] == ID_NO_FREE_INCOMING_CONNECTIONS)
		printf("ID_NO_FREE_INCOMING_CONNECTIONS");

	rakPeer->DeallocatePacket(packet);

	return returnvalue;
}
struct UPNPFramework : public SampleFramework
{
	UPNPFramework() { sampleResult=SUPPORT_UPNP;} 
	virtual const char * QueryName(void) {return "UPNPFramework";}
	virtual bool QueryRequiresServer(void) {return false;}
	virtual const char * QueryFunction(void) {return "Use UPNP to open the router";}
	virtual const char * QuerySuccess(void) {return "Other systems can now connect to you on the opened port.";}
	virtual bool QueryQuitOnSuccess(void) {return true;}
	virtual void Init(SLNet::RakPeerInterface *rakPeer)
	{
		if (sampleResult==FAILED) return;

		struct UPNPDev * devlist = 0;
		devlist = upnpDiscover(2000, 0, 0, 0, 0, 0);
		if (devlist)
		{
			printf("List of UPNP devices found on the network :\n");
			struct UPNPDev * device;
			for(device = devlist; device; device = device->pNext)
			{
				printf(" desc: %s\n st: %s\n\n",
					device->descURL, device->st);
			}

			char lanaddr[64];	/* my ip address on the LAN */
			struct UPNPUrls urls;
			struct IGDdatas data;
			if (UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr))==1)
			{
				// 4/16/2012 Why was I doing this? Just to read my external port? That shouldn't be necessary
				/*
				SystemAddress serverAddress=SelectAmongConnectedSystems(rakPeer, "NatTypeDetectionServer");
				if (serverAddress==SLNet::UNASSIGNED_SYSTEM_ADDRESS)
				{
					serverAddress=ConnectBlocking(rakPeer, "NatTypeDetectionServer", DEFAULT_SERVER_ADDRESS, DEFAULT_SERVER_PORT);
					if (serverAddress==SLNet::UNASSIGNED_SYSTEM_ADDRESS)
					{
						printf("Failed to connect to a server.\n");
						sampleResult=FAILED;
						return;
					}
				}


				char iport[32];
				Itoa(sockets[0]->boundAddress.GetPort(),iport,10);
				char eport[32];
				Itoa(rakPeer->GetExternalID(serverAddress).GetPort(),eport,10);
				*/

				// Use same external and internal ports
				DataStructures::List<RakNetSocket2* > sockets;
				rakPeer->GetSockets(sockets);
				char iport[32];
				Itoa(sockets[0]->GetBoundAddress().GetPort(),iport,10);
				char eport[32];
				strcpy_s(eport, iport);


				// Version 1.5
// 				int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
// 					 eport, iport, lanaddr, 0, "UDP", 0);

				// Version miniupnpc-1.6.20120410
				int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
					 					    eport, iport, lanaddr, 0, "UDP", 0, "0");

				if(r!=UPNPCOMMAND_SUCCESS)
					printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n",
					eport, iport, lanaddr, r, strupnperror(r));

				char intPort[6];
				char intClient[16];

				// Version 1.5
// 				r = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
// 					data.first.servicetype,
// 					eport, "UDP",
// 					intClient, intPort);

				// Version miniupnpc-1.6.20120410
				char desc[128];
				char enabled[128];
				char leaseDuration[128];
				r = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
					data.first.servicetype,
					eport, "UDP",
					intClient, intPort,
					desc, enabled, leaseDuration);

				if(r!=UPNPCOMMAND_SUCCESS)
				{
					printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n",
					r, strupnperror(r));
					sampleResult=FAILED;
				}
				else
					sampleResult=SUCCEEDED;
			}
			else
				sampleResult=FAILED;
		}
		else
			sampleResult=FAILED;
	}

	virtual void ProcessPacket(Packet *packet)
	{
		// unused parameters
		(void)packet;
	}
	virtual void Update(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		if (sampleResult==FAILED) return;
	}
	virtual void Shutdown(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;
	}

};
struct NatTypeDetectionFramework : public SampleFramework
{
	// Set to FAILED to skip this test
	NatTypeDetectionFramework() { sampleResult=SUPPORT_NAT_TYPE_DETECTION; ntdc=0;}
	virtual const char * QueryName(void) {return "NatTypeDetectionFramework";}
	virtual bool QueryRequiresServer(void) {return true;}
	virtual const char * QueryFunction(void) {return "Determines router type to avoid NAT punch attempts that cannot\nsucceed.";}
	virtual const char * QuerySuccess(void) {return "If our NAT type is Symmetric, we can skip NAT punch to other symmetric NATs.";}
	virtual bool QueryQuitOnSuccess(void) {return false;}
	virtual void Init(SLNet::RakPeerInterface *rakPeer)
	{
		if (sampleResult==FAILED) return;

		SystemAddress serverAddress=SelectAmongConnectedSystems(rakPeer, "NatTypeDetectionServer");
		if (serverAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
		{
			serverAddress=ConnectBlocking(rakPeer, "NatTypeDetectionServer", DEFAULT_SERVER_ADDRESS, DEFAULT_SERVER_PORT);
			if (serverAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
			{
				printf("Failed to connect to a server.\n");
				sampleResult=FAILED;
				return;
			}
		}
		ntdc = new NatTypeDetectionClient;
		rakPeer->AttachPlugin(ntdc);
		ntdc->DetectNATType(serverAddress);
		timeout= SLNet::GetTimeMS() + 5000;
	}

	virtual void ProcessPacket(Packet *packet)
	{
		if (packet->data[0]==ID_NAT_TYPE_DETECTION_RESULT)
		{
			SLNet::NATTypeDetectionResult r = (SLNet::NATTypeDetectionResult) packet->data[1];
			printf("NAT Type is %s (%s)\n", NATTypeDetectionResultToString(r), NATTypeDetectionResultToStringFriendly(r));
			printf("Using NATPunchthrough can connect to systems using:\n");
			for (int i=0; i < (int)SLNet::NAT_TYPE_COUNT; i++)
			{
				if (CanConnect(r,(SLNet::NATTypeDetectionResult)i))
				{
					if (i!=0)
						printf(", ");
					printf("%s", NATTypeDetectionResultToString((SLNet::NATTypeDetectionResult)i));
				}
			}
			printf("\n");
			if (r== SLNet::NAT_TYPE_PORT_RESTRICTED || r== SLNet::NAT_TYPE_SYMMETRIC)
			{
				// For UPNP, see Samples\UDPProxy
				printf("Note: Your router must support UPNP or have the user manually forward ports.\n");
				printf("Otherwise NATPunchthrough may not always succeed.\n");
			}

			sampleResult=SUCCEEDED;
		}
	}
	virtual void Update(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		if (sampleResult==FAILED) return;

		if (sampleResult==PENDING && SLNet::GetTimeMS()>timeout)
		{
			printf("No response from the server, probably not running NatTypeDetectionServer plugin.\n");
			sampleResult=FAILED;
		}
	}
	virtual void Shutdown(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		delete ntdc;
		ntdc=0;
	}

	NatTypeDetectionClient *ntdc;
	SLNet::TimeMS timeout;
};

struct NatPunchthoughClientFramework : public SampleFramework, public NatPunchthroughDebugInterface_Printf
{
	SystemAddress serverAddress;

	// Set to FAILED to skip this test
	NatPunchthoughClientFramework() { sampleResult=SUPPORT_NAT_PUNCHTHROUGH; npClient=0;}
	virtual const char * QueryName(void) {return "NatPunchthoughClientFramework";}
	virtual bool QueryRequiresServer(void) {return true;}
	virtual const char * QueryFunction(void) {return "Causes two systems to try to connect to each other at the same\ntime, to get through routers.";}
	virtual const char * QuerySuccess(void) {return "We can now communicate with the other system, including connecting.";}
	virtual bool QueryQuitOnSuccess(void) {return true;}
	virtual void Init(SLNet::RakPeerInterface *rakPeer)
	{
		if (sampleResult==FAILED) return;

		serverAddress=SelectAmongConnectedSystems(rakPeer, "NatPunchthroughServer");
		if (serverAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
		{
			serverAddress=ConnectBlocking(rakPeer, "NatPunchthroughServer", DEFAULT_SERVER_ADDRESS, DEFAULT_SERVER_PORT);
			if (serverAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
			{
				printf("Failed to connect to a server.\n");
				sampleResult=FAILED;
				return;
			}
		}

		npClient = new NatPunchthroughClient;
		npClient->SetDebugInterface(this);
		rakPeer->AttachPlugin(npClient);


		char guid[128];
		printf("Enter RakNetGuid of the remote system, which should have already connected\nto the server.\nOr press enter to just listen.\n");
		Gets(guid,sizeof(guid));
		if (guid[0])
		{
			RakNetGUID remoteSystemGuid;
			remoteSystemGuid.FromString(guid);
			npClient->OpenNAT(remoteSystemGuid, serverAddress);
			isListening=false;

			timeout= SLNet::GetTimeMS() + 10000;
		}
		else
		{
			printf("Listening\n");
			printf("My GUID is %s\n", rakPeer->GetMyGUID().ToString());
			isListening=true;

			// Find the stride of our router in advance
			npClient->FindRouterPortStride(serverAddress);

		}
	}

	virtual void ProcessPacket(Packet *packet)
	{
		if (
			packet->data[0]==ID_NAT_TARGET_NOT_CONNECTED ||
			packet->data[0]==ID_NAT_TARGET_UNRESPONSIVE ||
			packet->data[0]==ID_NAT_CONNECTION_TO_TARGET_LOST ||
			packet->data[0]==ID_NAT_PUNCHTHROUGH_FAILED
			)
		{
			RakNetGUID guid;
			if (packet->data[0]==ID_NAT_PUNCHTHROUGH_FAILED)
			{
				guid=packet->guid;
			}
			else
			{
				SLNet::BitStream bs(packet->data,packet->length,false);
				bs.IgnoreBytes(1);
				SLNET_VERIFY(bs.Read(guid));
			}

			switch (packet->data[0])
			{
			case ID_NAT_TARGET_NOT_CONNECTED:
				printf("Failed: ID_NAT_TARGET_NOT_CONNECTED\n");
				break;
			case ID_NAT_TARGET_UNRESPONSIVE:
				printf("Failed: ID_NAT_TARGET_UNRESPONSIVE\n");
				break;
			case ID_NAT_CONNECTION_TO_TARGET_LOST:
				printf("Failed: ID_NAT_CONNECTION_TO_TARGET_LOST\n");
				break;
			case ID_NAT_PUNCHTHROUGH_FAILED:
				printf("Failed: ID_NAT_PUNCHTHROUGH_FAILED\n");
				break;
			}

			sampleResult=FAILED;
			return;
		}
		else if (packet->data[0]==ID_NAT_PUNCHTHROUGH_SUCCEEDED)
		{
			unsigned char weAreTheSender = packet->data[1];
			if (weAreTheSender)
				printf("NAT punch success to remote system %s.\n", packet->systemAddress.ToString(true));
			else
				printf("NAT punch success from remote system %s.\n", packet->systemAddress.ToString(true));

			char guid[128];
			printf("Enter RakNetGuid of the remote system, which should have already connected.\nOr press enter to quit.\n");
			Gets(guid,sizeof(guid));
			if (guid[0])
			{
				RakNetGUID remoteSystemGuid;
				remoteSystemGuid.FromString(guid);
				npClient->OpenNAT(remoteSystemGuid, serverAddress);
			
				timeout= SLNet::GetTimeMS() + 10000;
			}
			else
			{
				sampleResult=SUCCEEDED;
			}
		}
	}
	virtual void Update(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		if (sampleResult==FAILED) return;

		if (sampleResult==PENDING && SLNet::GetTimeMS()>timeout && isListening==false)
		{
			printf("No response from the server, probably not running NatPunchthroughServer plugin.\n");
			sampleResult=FAILED;
		}
	}
	virtual void Shutdown(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		delete npClient;
		npClient=0;
	}

	NatPunchthroughClient *npClient;
	SLNet::TimeMS timeout;
	bool isListening;
};

struct Router2Framework : public SampleFramework
{
	// Set to FAILED to skip this test
	Router2Framework() { sampleResult=SUPPORT_ROUTER2; router2=0;}
	virtual const char * QueryName(void) {return "Router2Framework";}
	virtual bool QueryRequiresServer(void) {return false;}
	virtual const char * QueryFunction(void) {return "Connect to a peer we cannot directly connect to using the\nbandwidth of a shared peer.";}
	virtual const char * QuerySuccess(void) {return "Router2 assumes we will now connect to the other system.";}
	virtual bool QueryQuitOnSuccess(void) {return true;}
	virtual void Init(SLNet::RakPeerInterface *rakPeer)
	{
		if (sampleResult==FAILED) return;

		printf("Given your application's bandwidth, how much traffic can be forwarded through a single peer?\nIf you use more than half the available bandwidth, then this plugin won't work for you.\n");;
		char supportedStr[64];
		do 
		{
			printf("Enter a number greater than or equal to 0: ");
			Gets(supportedStr,sizeof(supportedStr));
		} while (supportedStr[0]==0);
		int supported=atoi(supportedStr);
		if (supported<=0)
		{
			printf("Aborting Router2\n");
			sampleResult=FAILED;
			return;
		}

		SystemAddress peerAddress = SelectAmongConnectedSystems(rakPeer, "shared peer");
		if (peerAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
		{
			peerAddress=ConnectBlocking(rakPeer, "shared peer", "", RAKPEER_PORT_STR);
			if (peerAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
			{
				printf("Failed to connect to a shared peer.\n");
				sampleResult=FAILED;
				return;
			}
		}

		char guid[64];
		printf("Destination system must be connected to the shared peer.\n");
		do 
		{
			printf("Enter RakNetGUID of destination system: ");
			Gets(guid,sizeof(guid));
		} while (guid[0]==0);
		RakNetGUID endpointGuid;
		endpointGuid.FromString(guid);
		router2 = new Router2;
		rakPeer->AttachPlugin(router2);
		router2->EstablishRouting(endpointGuid);

		timeout= SLNet::GetTimeMS() + 5000;
	}
	virtual void ProcessPacket(Packet *packet)
	{
		// unused parameters
		(void)packet;
	}
	virtual void Update(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		if (sampleResult==FAILED) return;

		if (sampleResult==PENDING && SLNet::GetTimeMS()>timeout)
		{
			printf("No response from any system, probably not running Router2 plugin.\n");
			sampleResult=FAILED;
		}
	}
	virtual void Shutdown(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		delete router2;
		router2=0;
	}
	Router2 *router2;
	SLNet::TimeMS timeout;
};
struct UDPProxyClientFramework : public SampleFramework, public UDPProxyClientResultHandler
{
	// Set to FAILED to skip this test
	UDPProxyClientFramework() { sampleResult=SUPPORT_UDP_PROXY; udpProxy=0;}
	virtual const char * QueryName(void) {return "UDPProxyClientFramework";}
	virtual bool QueryRequiresServer(void) {return true;}
	virtual const char * QueryFunction(void) {return "Connect to a peer using a shared server connection.";}
	virtual const char * QuerySuccess(void) {return "We can now communicate with the other system, including connecting, within 5 seconds.";}
	virtual bool QueryQuitOnSuccess(void) {return false;}
	virtual void Init(SLNet::RakPeerInterface *rakPeer)
	{
		if (sampleResult==FAILED) return;

		SystemAddress serverAddress=SelectAmongConnectedSystems(rakPeer, "UDPProxyCoordinator");
		if (serverAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
		{
			serverAddress=ConnectBlocking(rakPeer, "UDPProxyCoordinator", DEFAULT_SERVER_ADDRESS, DEFAULT_SERVER_PORT);
			if (serverAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
			{
				printf("Failed to connect to a server.\n");
				sampleResult=FAILED;
				return;
			}
		}
		udpProxy = new UDPProxyClient;
		rakPeer->AttachPlugin(udpProxy);
		udpProxy->SetResultHandler(this);

		char guid[128];
		printf("Enter RakNetGuid of the remote system, which should have already connected\nto the server.\nOr press enter to just listen.\n");
		Gets(guid,sizeof(guid));
		RakNetGUID targetGuid;
		targetGuid.FromString(guid);

		if (guid[0])
		{
			RakNetGUID remoteSystemGuid;
			remoteSystemGuid.FromString(guid);
			udpProxy->RequestForwarding(serverAddress, UNASSIGNED_SYSTEM_ADDRESS, targetGuid, UDP_FORWARDER_MAXIMUM_TIMEOUT, 0);
			isListening=false;
		}
		else
		{
			printf("Listening\n");
			printf("My GUID is %s\n", rakPeer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS).ToString());
			isListening=true;
		}

		timeout= SLNet::GetTimeMS() + 5000;
	}
	virtual void ProcessPacket(Packet *packet)
	{
		// unused parameters
		(void)packet;
	}
	virtual void Update(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		if (sampleResult==FAILED) return;

		if (sampleResult==PENDING && SLNet::GetTimeMS()>timeout && isListening==false)
		{
			printf("No response from the server, probably not running UDPProxyCoordinator plugin.\n");
			sampleResult=FAILED;
		}
	}
	virtual void Shutdown(SLNet::RakPeerInterface *rakPeer)
	{
		// unused parameters
		(void)rakPeer;

		delete udpProxy;
		udpProxy=0;
	}

	virtual void OnForwardingSuccess(const char *proxyIPAddress, unsigned short proxyPort,
		SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNetGUID targetGuid, SLNet::UDPProxyClient *proxyClientPlugin)
	{
		// unused parameters
		(void)proxyCoordinator;
		(void)sourceAddress;
		(void)targetGuid;

		printf("Datagrams forwarded by proxy %s:%i to target %s.\n", proxyIPAddress, proxyPort, targetAddress.ToString(false));
		printf("Connecting to proxy, which will be received by target.\n");
		SLNET_VERIFY(proxyClientPlugin->GetRakPeerInterface()->Connect(proxyIPAddress, proxyPort, 0, 0) == CONNECTION_ATTEMPT_STARTED);
		sampleResult=SUCCEEDED;
	}
	virtual void OnForwardingNotification(const char *proxyIPAddress, unsigned short proxyPort,
		SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNetGUID targetGuid, SLNet::UDPProxyClient *proxyClientPlugin)
	{
		// unused parameters
		(void)proxyCoordinator;
		(void)targetAddress;
		(void)targetGuid;
		(void)proxyClientPlugin;

		printf("Source %s has setup forwarding to us through proxy %s:%i.\n", sourceAddress.ToString(false), proxyIPAddress, proxyPort);

		sampleResult=SUCCEEDED;
	}
	virtual void OnNoServersOnline(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNetGUID targetGuid, SLNet::UDPProxyClient *proxyClientPlugin)
	{
		// unused parameters
		(void)proxyCoordinator;
		(void)sourceAddress;
		(void)targetAddress;
		(void)targetGuid;
		(void)proxyClientPlugin;

		printf("Failure: No servers logged into coordinator.\n");
		sampleResult=FAILED;
	}
	virtual void OnRecipientNotConnected(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNetGUID targetGuid, SLNet::UDPProxyClient *proxyClientPlugin)
	{
		// unused parameters
		(void)proxyCoordinator;
		(void)sourceAddress;
		(void)targetAddress;
		(void)targetGuid;
		(void)proxyClientPlugin;

		printf("Failure: Recipient not connected to coordinator.\n");
		sampleResult=FAILED;
	}
	virtual void OnAllServersBusy(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNetGUID targetGuid, SLNet::UDPProxyClient *proxyClientPlugin)
	{
		// unused parameters
		(void)proxyCoordinator;
		(void)sourceAddress;
		(void)targetAddress;
		(void)targetGuid;
		(void)proxyClientPlugin;

		printf("Failure: No servers have available forwarding ports.\n");
		sampleResult=FAILED;
	}
	virtual void OnForwardingInProgress(const char *proxyIPAddress, unsigned short proxyPort, SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNetGUID targetGuid, SLNet::UDPProxyClient *proxyClientPlugin)
	{
		// unused parameters
		(void)proxyIPAddress;
		(void)proxyPort;
		(void)proxyCoordinator;
		(void)sourceAddress;
		(void)targetAddress;
		(void)targetGuid;
		(void)proxyClientPlugin;

		printf("Notification: Forwarding already in progress.\n");
	}

	UDPProxyClient *udpProxy;
	SLNet::TimeMS timeout;
	bool isListening;
};
void PrintPacketMessages(Packet *packet, RakPeerInterface *rakPeer)
{

	switch (packet->data[0])
	{
	case ID_DISCONNECTION_NOTIFICATION:
		// Connection lost normally
		printf("ID_DISCONNECTION_NOTIFICATION\n");
		break;
	case ID_NEW_INCOMING_CONNECTION:
		printf("ID_NEW_INCOMING_CONNECTION\n");
		break;
	case ID_ALREADY_CONNECTED:
		// Connection lost normally
		printf("ID_ALREADY_CONNECTED\n");
		break;
	case ID_INCOMPATIBLE_PROTOCOL_VERSION:
		printf("ID_INCOMPATIBLE_PROTOCOL_VERSION\n");
		break;
	case ID_REMOTE_DISCONNECTION_NOTIFICATION: // Server telling the clients of another client disconnecting gracefully.  You can manually broadcast this in a peer to peer enviroment if you want.
		printf("ID_REMOTE_DISCONNECTION_NOTIFICATION\n"); 
		break;
	case ID_REMOTE_CONNECTION_LOST: // Server telling the clients of another client disconnecting forcefully.  You can manually broadcast this in a peer to peer enviroment if you want.
		printf("ID_REMOTE_CONNECTION_LOST\n");
		break;
	case ID_REMOTE_NEW_INCOMING_CONNECTION: // Server telling the clients of another client connecting.  You can manually broadcast this in a peer to peer enviroment if you want.
		printf("ID_REMOTE_NEW_INCOMING_CONNECTION\n");
		break;
	case ID_CONNECTION_BANNED: // Banned from this server
		printf("We are banned from this server.\n");
		break;			
	case ID_CONNECTION_ATTEMPT_FAILED:
		printf("Connection attempt failed\n");
		break;
	case ID_NO_FREE_INCOMING_CONNECTIONS:
		printf("ID_NO_FREE_INCOMING_CONNECTIONS\n");
		break;

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

	case ID_CONNECTION_LOST:
		printf("ID_CONNECTION_LOST from %s\n", packet->systemAddress.ToString(true));
		break;

	case ID_CONNECTION_REQUEST_ACCEPTED:
		// This tells the client they have connected
		printf("ID_CONNECTION_REQUEST_ACCEPTED to %s with GUID %s\n", packet->systemAddress.ToString(true), packet->guid.ToString());
		printf("My external address is %s\n", rakPeer->GetExternalID(packet->systemAddress).ToString(true));
		break;
	}
}

enum FeatureList
{
	_UPNPFramework,
	_NatTypeDetectionFramework,
	_NatPunchthoughFramework,
	_Router2Framework,
	_UDPProxyClientFramework,
	FEATURE_LIST_COUNT
};
int main(void)
{
	SLNet::RakPeerInterface *rakPeer= SLNet::RakPeerInterface::GetInstance();
	printf("Enter local port, or press enter for default: ");
	char buff[64];
	Gets(buff,sizeof(buff));
	unsigned short port = DEFAULT_RAKPEER_PORT;
	if (buff[0]!=0) {
		const int intLocalPort = atoi(buff);
		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 2;
		}
		port = static_cast<unsigned short>(intLocalPort);
	}
	SLNet::SocketDescriptor sd(port,0);
	if (rakPeer->Startup(32,&sd,1)!= SLNet::RAKNET_STARTED)
	{
		printf("Failed to start rakPeer! Quitting\n");
		SLNet::RakPeerInterface::DestroyInstance(rakPeer);
		_getch();
		return 1;
	}
	rakPeer->SetMaximumIncomingConnections(32);

	SampleFramework *samples[FEATURE_LIST_COUNT];
	unsigned int i=0;
	samples[i++] = new UPNPFramework;
	samples[i++] = new NatTypeDetectionFramework;
	samples[i++] = new NatPunchthoughClientFramework;
	samples[i++] = new Router2Framework;
	samples[i++] = new UDPProxyClientFramework;
	assert(i==FEATURE_LIST_COUNT);

	bool isFirstPrint=true;
	for (i=0; i < FEATURE_LIST_COUNT; i++)
	{
		if (isFirstPrint)
		{
			printf("NAT traversal client\nSupported operations:\n");
			isFirstPrint=false;
		}
		printf("\n%s\nRequires server: %s\nDescription: %s\n", samples[i]->QueryName(), samples[i]->QueryRequiresServer()==1 ? "Yes" : "No", samples[i]->QueryFunction());
	}

	printf("\nDo you have a server running the NATCompleteServer project? (y/n): ");

	int responseLetter=_getche();
	bool hasServer=responseLetter=='y' || responseLetter=='Y' || responseLetter==' ';
	printf("\n");
	if (hasServer==false)
		printf("Note: Only UPNP and Router2 are supported without a server\nYou may want to consider using the Lobby2/Steam project. They host the\nservers for you.\n\n");

	FeatureList currentStage=_UPNPFramework;

	if (hasServer==false)
	{
		while (samples[(int) currentStage]->QueryRequiresServer()==true)
		{
			printf("No server: Skipping %s\n", samples[(int) currentStage]->QueryName());
			int stageInt = (int) currentStage;
			stageInt++;
			currentStage=(FeatureList)stageInt;
			if (currentStage==FEATURE_LIST_COUNT)
			{
				printf("Connectivity not possible. Exiting\n");
				_getch();
				return 1;
			}
		}
	}

	bool running = true;
	while(running)
	{
		printf("Executing %s\n", samples[(int) currentStage]->QueryName());
		samples[(int) currentStage]->Init(rakPeer);

		bool thisSampleDone=false;
		for(;;)
		{
			samples[(int) currentStage]->Update(rakPeer);
			SLNet::Packet *packet;
			for (packet=rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet=rakPeer->Receive())
			{
				for (i=0; i < FEATURE_LIST_COUNT; i++)
				{
					samples[i]->ProcessPacket(packet);
				}

				PrintPacketMessages(packet, rakPeer);
			}

			if (samples[(int) currentStage]->sampleResult==FAILED ||
				samples[(int) currentStage]->sampleResult==SUCCEEDED)
			{
				printf("\n");
				thisSampleDone=true;
				if (samples[(int) currentStage]->sampleResult==FAILED)
				{
					printf("Failed %s\n", samples[(int) currentStage]->QueryName());

					int stageInt = (int) currentStage;
					stageInt++;
					currentStage=(FeatureList)stageInt;
					if (currentStage==FEATURE_LIST_COUNT)
					{
						printf("Connectivity not possible. Exiting\n");
						rakPeer->Shutdown(100);
						SLNet::RakPeerInterface::DestroyInstance(rakPeer);
						running = false;
						break;
					}
					else
					{
						printf("Proceeding to next stage.\n");
						break;
					}
				}
				else
				{
					printf("Passed %s\n", samples[(int) currentStage]->QueryName());
					if (samples[(int) currentStage]->QueryQuitOnSuccess())
					{

						printf("Press any key to quit.\n");
						while (!_kbhit())
						{
							for (packet=rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet=rakPeer->Receive())
							{
								for (i=0; i < FEATURE_LIST_COUNT; i++)
								{
									samples[i]->ProcessPacket(packet);
								}

								PrintPacketMessages(packet, rakPeer);
							}
							RakSleep(30);
						}

						rakPeer->Shutdown(100);
						SLNet::RakPeerInterface::DestroyInstance(rakPeer);
						printf("Press enter to quit.\n");
						char temp[32];
						Gets(temp,sizeof(temp));
						running = false;
						break;
					}

					printf("Proceeding to next stage.\n");
					int stageInt = (int) currentStage;
					stageInt++;
					if (stageInt<FEATURE_LIST_COUNT)
					{
						currentStage=(FeatureList)stageInt;
					}
					else
					{
						printf("Press any key to quit when done.\n");

						while (!_kbhit())
						{
							for (packet=rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet=rakPeer->Receive())
							{
								for (i=0; i < FEATURE_LIST_COUNT; i++)
								{
									samples[i]->ProcessPacket(packet);
								}

								PrintPacketMessages(packet, rakPeer);
							}
							RakSleep(30);
						}

						rakPeer->Shutdown(100);
						SLNet::RakPeerInterface::DestroyInstance(rakPeer);
						running = false;
						break;
					}
					break;
				}
			}

			RakSleep(30);
		}
	}

	_getch();
	return 1;
}
