/*
 *  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 "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_NatTypeDetectionServer==1

#include "NatTypeDetectionServer.h"
#include "SocketLayer.h"
#include "RakNetSmartPtr.h"
#include "SocketIncludes.h"
#include "RakPeerInterface.h"
#include "MessageIdentifiers.h"
#include "GetTime.h"
#include "BitStream.h"
#include "SocketDefines.h"

// #define NTDS_VERBOSE

using namespace RakNet;

STATIC_FACTORY_DEFINITIONS(NatTypeDetectionServer,NatTypeDetectionServer);

NatTypeDetectionServer::NatTypeDetectionServer()
{
	s1p2=s2p3=s3p4=s4p5=0;
}
NatTypeDetectionServer::~NatTypeDetectionServer()
{
	Shutdown();
}
void NatTypeDetectionServer::Startup(
									 const char *nonRakNetIP2,
									 const char *nonRakNetIP3,
									 const char *nonRakNetIP4
#ifdef __native_client__
									 ,_PP_Instance_ chromeInstance
#endif
									 )
{
	DataStructures::List<RakNetSocket2* > sockets;
	rakPeerInterface->GetSockets(sockets);
	char str[64];
	sockets[0]->GetBoundAddress().ToString(false,str);
	s1p2=
		CreateNonblockingBoundSocket(str,
#ifdef __native_client__
		chromeInstance, 
#endif
		this);

	s2p3=
		CreateNonblockingBoundSocket(nonRakNetIP2,
#ifdef __native_client__
		chromeInstance, 
#endif
		this);


	s3p4=
		CreateNonblockingBoundSocket(nonRakNetIP3,
#ifdef __native_client__
		chromeInstance, 
#endif
		this);

	s4p5=
		CreateNonblockingBoundSocket(nonRakNetIP4,
#ifdef __native_client__
		chromeInstance, 
#endif
		this);

	strcpy(s3p4Address, nonRakNetIP3);


	#if !defined(__native_client__) && !defined(WINDOWS_STORE_RT)
	if (s3p4->IsBerkleySocket())
		((RNS2_Berkley*) s3p4)->CreateRecvPollingThread(0);
	#endif
}
void NatTypeDetectionServer::Shutdown()
{
	if (s1p2!=0)
	{
		RakNet::OP_DELETE(s1p2,_FILE_AND_LINE_);
		s1p2=0;
	}
	if (s2p3!=0)
	{
		RakNet::OP_DELETE(s2p3,_FILE_AND_LINE_);
		s2p3=0;
	}
	if (s3p4!=0)
	{
#if !defined(__native_client__) && !defined(WINDOWS_STORE_RT)
		if (s3p4->IsBerkleySocket())
			((RNS2_Berkley *)s3p4)->BlockOnStopRecvPollingThread();
#endif

		RakNet::OP_DELETE(s3p4,_FILE_AND_LINE_);
		s3p4=0;
	}
	if (s4p5!=0)
	{
		RakNet::OP_DELETE(s4p5,_FILE_AND_LINE_);
		s4p5=0;
	}
	bufferedPacketsMutex.Lock();
	while (bufferedPackets.Size())
		RakNet::OP_DELETE(bufferedPackets.Pop(), _FILE_AND_LINE_);
	bufferedPacketsMutex.Unlock();
}
void NatTypeDetectionServer::Update(void)
{
	int i=0;
	RakNet::TimeMS time = RakNet::GetTimeMS();
	RakNet::BitStream bs;
	SystemAddress boundAddress;

	RNS2RecvStruct *recvStruct;
	bufferedPacketsMutex.Lock();
	if (bufferedPackets.Size()>0)
		recvStruct=bufferedPackets.Pop();
	else
		recvStruct=0;
	bufferedPacketsMutex.Unlock();
	while (recvStruct)
	{
		SystemAddress senderAddr = recvStruct->systemAddress;
		char *data = recvStruct->data;
		if (data[0]==NAT_TYPE_PORT_RESTRICTED && recvStruct->socket==s3p4)
		{
			RakNet::BitStream bsIn((unsigned char*) data,recvStruct->bytesRead,false);
			RakNetGUID senderGuid;
			bsIn.IgnoreBytes(sizeof(MessageID));
			bool readSuccess = bsIn.Read(senderGuid);
			RakAssert(readSuccess);
			if (readSuccess)
			{
				unsigned int i = GetDetectionAttemptIndex(senderGuid);
				if (i!=(unsigned int)-1)
				{
					bs.Reset();
					bs.Write((unsigned char) ID_NAT_TYPE_DETECTION_RESULT);
					// If different, then symmetric
					if (senderAddr!=natDetectionAttempts[i].systemAddress)
					{

#ifdef NTDS_VERBOSE
						printf("Determined client is symmetric\n");
#endif
						bs.Write((unsigned char) NAT_TYPE_SYMMETRIC);
					}
					else
					{
						// else port restricted
#ifdef NTDS_VERBOSE

						printf("Determined client is port restricted\n");
#endif
						bs.Write((unsigned char) NAT_TYPE_PORT_RESTRICTED);
					}

					rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,natDetectionAttempts[i].systemAddress,false);

					// Done
					natDetectionAttempts.RemoveAtIndexFast(i);
				}
				else
				{
					//		RakAssert("i==0 in Update when looking up GUID in NatTypeDetectionServer.cpp. Either a bug or a late resend" && 0);
				}
			}
			else
			{
				//	RakAssert("Didn't read GUID in Update in NatTypeDetectionServer.cpp. Message format error" && 0);
			}
		}

		DeallocRNS2RecvStruct(recvStruct, _FILE_AND_LINE_);
		bufferedPacketsMutex.Lock();
		if (bufferedPackets.Size()>0)
			recvStruct=bufferedPackets.Pop();
		else
			recvStruct=0;
		bufferedPacketsMutex.Unlock();
	}

	/*

	// Only socket that receives messages is s3p4, to see if the external address is different than that of the connection to rakPeerInterface
	char data[ MAXIMUM_MTU_SIZE ];
	int len;
	SystemAddress senderAddr;
	len=NatTypeRecvFrom(data, s3p4, senderAddr);
	// Client is asking us if this is port restricted. Only client requests of this type come in on s3p4
	while (len>0 && data[0]==NAT_TYPE_PORT_RESTRICTED)
	{
		RakNet::BitStream bsIn((unsigned char*) data,len,false);
		RakNetGUID senderGuid;
		bsIn.IgnoreBytes(sizeof(MessageID));
		bool readSuccess = bsIn.Read(senderGuid);
		RakAssert(readSuccess);
		if (readSuccess)
		{
			unsigned int i = GetDetectionAttemptIndex(senderGuid);
			if (i!=(unsigned int)-1)
			{
				bs.Reset();
				bs.Write((unsigned char) ID_NAT_TYPE_DETECTION_RESULT);
				// If different, then symmetric
				if (senderAddr!=natDetectionAttempts[i].systemAddress)
				{

				#ifdef NTDS_VERBOSE
					printf("Determined client is symmetric\n");
				#endif
					bs.Write((unsigned char) NAT_TYPE_SYMMETRIC);
				}
				else
				{
					// else port restricted

					#ifdef NTDS_VERBOSE
					printf("Determined client is port restricted\n");
					#endif
					bs.Write((unsigned char) NAT_TYPE_PORT_RESTRICTED);
				}

				rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,natDetectionAttempts[i].systemAddress,false);

				// Done
				natDetectionAttempts.RemoveAtIndexFast(i);
			}
			else
			{
		//		RakAssert("i==0 in Update when looking up GUID in NatTypeDetectionServer.cpp. Either a bug or a late resend" && 0);
			}
		}
		else
		{
		//	RakAssert("Didn't read GUID in Update in NatTypeDetectionServer.cpp. Message format error" && 0);
		}

		len=NatTypeRecvFrom(data, s3p4, senderAddr);
	}
	*/


	while (i < (int) natDetectionAttempts.Size())
	{
		if (time > natDetectionAttempts[i].nextStateTime)
		{
			RNS2_SendParameters bsp;
			natDetectionAttempts[i].detectionState=(NATDetectionState)((int)natDetectionAttempts[i].detectionState+1);
			natDetectionAttempts[i].nextStateTime=time+natDetectionAttempts[i].timeBetweenAttempts;
			SystemAddress saOut;
			unsigned char c;
			bs.Reset();
			switch (natDetectionAttempts[i].detectionState)
			{
			case STATE_TESTING_NONE_1:
			case STATE_TESTING_NONE_2:
				c = NAT_TYPE_NONE;

#ifdef NTDS_VERBOSE
				printf("Testing NAT_TYPE_NONE\n");
#endif
				// S4P5 sends to C2. If arrived, no NAT. Done. (Else S4P5 potentially banned, do not use again).
				saOut=natDetectionAttempts[i].systemAddress;
				saOut.SetPortHostOrder(natDetectionAttempts[i].c2Port);
				// SocketLayer::SendTo_PC( s4p5, (const char*) &c, 1, saOut, __FILE__, __LINE__  );
				bsp.data = (char*) &c;
				bsp.length = 1;
				bsp.systemAddress = saOut;
				s4p5->Send(&bsp, _FILE_AND_LINE_);
				break;
			case STATE_TESTING_FULL_CONE_1:
			case STATE_TESTING_FULL_CONE_2:

#ifdef NTDS_VERBOSE
				printf("Testing NAT_TYPE_FULL_CONE\n");
#endif
				rakPeerInterface->WriteOutOfBandHeader(&bs);
				bs.Write((unsigned char) ID_NAT_TYPE_DETECT);
				bs.Write((unsigned char) NAT_TYPE_FULL_CONE);
				// S2P3 sends to C1 (Different address, different port, to previously used port on client). If received, Full-cone nat. Done.  (Else S2P3 potentially banned, do not use again).
				saOut=natDetectionAttempts[i].systemAddress;
				saOut.SetPortHostOrder(natDetectionAttempts[i].systemAddress.GetPort());
				// SocketLayer::SendTo_PC( s2p3, (const char*) bs.GetData(), bs.GetNumberOfBytesUsed(), saOut, __FILE__, __LINE__  );
				bsp.data = (char*) bs.GetData();
				bsp.length = bs.GetNumberOfBytesUsed();
				bsp.systemAddress = saOut;
				s2p3->Send(&bsp, _FILE_AND_LINE_);
				break;
			case STATE_TESTING_ADDRESS_RESTRICTED_1:
			case STATE_TESTING_ADDRESS_RESTRICTED_2:

#ifdef NTDS_VERBOSE
				printf("Testing NAT_TYPE_ADDRESS_RESTRICTED\n");
#endif
				rakPeerInterface->WriteOutOfBandHeader(&bs);
				bs.Write((unsigned char) ID_NAT_TYPE_DETECT);
				bs.Write((unsigned char) NAT_TYPE_ADDRESS_RESTRICTED);
				// S1P2 sends to C1 (Same address, different port, to previously used port on client). If received, address-restricted cone nat. Done.
				saOut=natDetectionAttempts[i].systemAddress;
				saOut.SetPortHostOrder(natDetectionAttempts[i].systemAddress.GetPort());
				//SocketLayer::SendTo_PC( s1p2, (const char*) bs.GetData(), bs.GetNumberOfBytesUsed(), saOut, __FILE__, __LINE__  );
				bsp.data = (char*) bs.GetData();
				bsp.length = bs.GetNumberOfBytesUsed();
				bsp.systemAddress = saOut;
				s1p2->Send(&bsp, _FILE_AND_LINE_);
				break;
			case STATE_TESTING_PORT_RESTRICTED_1:
			case STATE_TESTING_PORT_RESTRICTED_2:
				// C1 sends to S3P4. If address of C1 as seen by S3P4 is the same as the address of C1 as seen by S1P1, then port-restricted cone nat. Done

#ifdef NTDS_VERBOSE
				printf("Testing NAT_TYPE_PORT_RESTRICTED\n");
#endif
				bs.Write((unsigned char) ID_NAT_TYPE_DETECTION_REQUEST);
				bs.Write(RakString::NonVariadic(s3p4Address));
				bs.Write(s3p4->GetBoundAddress().GetPort());
				rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,natDetectionAttempts[i].systemAddress,false);
				break;
			default:

#ifdef NTDS_VERBOSE
				printf("Warning, exceeded final check STATE_TESTING_PORT_RESTRICTED_2.\nExpected that client would have sent NAT_TYPE_PORT_RESTRICTED on s3p4.\nDefaulting to Symmetric\n");
#endif
				bs.Write((unsigned char) ID_NAT_TYPE_DETECTION_RESULT);
				bs.Write((unsigned char) NAT_TYPE_SYMMETRIC);
				rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,natDetectionAttempts[i].systemAddress,false);
				natDetectionAttempts.RemoveAtIndexFast(i);
				i--;
				break;
			}

		}
		i++;
	}
}
PluginReceiveResult NatTypeDetectionServer::OnReceive(Packet *packet)
{
	switch (packet->data[0])
	{
	case ID_NAT_TYPE_DETECTION_REQUEST:
		OnDetectionRequest(packet);
		return RR_STOP_PROCESSING_AND_DEALLOCATE;
	}
	return RR_CONTINUE_PROCESSING;
}
void NatTypeDetectionServer::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
{
	(void) lostConnectionReason;
	(void) rakNetGUID;

	unsigned int i = GetDetectionAttemptIndex(systemAddress);
	if (i==(unsigned int)-1)
		return;
	natDetectionAttempts.RemoveAtIndexFast(i);
}
void NatTypeDetectionServer::OnDetectionRequest(Packet *packet)
{
	unsigned int i = GetDetectionAttemptIndex(packet->systemAddress);

	RakNet::BitStream bsIn(packet->data, packet->length, false);
	bsIn.IgnoreBytes(1);
	bool isRequest=false;
	bsIn.Read(isRequest);
	if (isRequest)
	{
		if (i!=(unsigned int)-1)
			return; // Already in progress

		NATDetectionAttempt nda;
		nda.detectionState=STATE_NONE;
		nda.systemAddress=packet->systemAddress;
		nda.guid=packet->guid;
		bsIn.Read(nda.c2Port);
		nda.nextStateTime=0;
		nda.timeBetweenAttempts=rakPeerInterface->GetLastPing(nda.systemAddress)*3+50;
		natDetectionAttempts.Push(nda, _FILE_AND_LINE_);
	}
	else
	{
		if (i==(unsigned int)-1)
			return; // Unknown
		// They are done
		natDetectionAttempts.RemoveAtIndexFast(i);
	}

}
unsigned int NatTypeDetectionServer::GetDetectionAttemptIndex(const SystemAddress &sa)
{
	for (unsigned int i=0; i < natDetectionAttempts.Size(); i++)
	{
		if (natDetectionAttempts[i].systemAddress==sa)
			return i;
	}
	return (unsigned int) -1;
}
unsigned int NatTypeDetectionServer::GetDetectionAttemptIndex(RakNetGUID guid)
{
	for (unsigned int i=0; i < natDetectionAttempts.Size(); i++)
	{
		if (natDetectionAttempts[i].guid==guid)
			return i;
	}
	return (unsigned int) -1;
}

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void NatTypeDetectionServer::DeallocRNS2RecvStruct(RNS2RecvStruct *s, const char *file, unsigned int line)
{
	RakNet::OP_DELETE(s, file, line);
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
RNS2RecvStruct *NatTypeDetectionServer::AllocRNS2RecvStruct(const char *file, unsigned int line)
{
	return RakNet::OP_NEW<RNS2RecvStruct>(file,line);
}

void NatTypeDetectionServer::OnRNS2Recv(RNS2RecvStruct *recvStruct)
{
	bufferedPacketsMutex.Lock();
	bufferedPackets.Push(recvStruct,_FILE_AND_LINE_);
	bufferedPacketsMutex.Unlock();
}

#endif // _RAKNET_SUPPORT_*
