//network.cpp
//basic game hosting/joining and message sending code based on Network_Tool

#include <network_tool.h>
#include <stdio.h>
#include <string.h>
#include "gametime.h"
#include "config.h"
#include "network.h"
#include "error.h"
#include "gameframe.h"
#include "controls.h"
#include "gamesound.h"
#include "carphysics.h"
#include "interfaceutil.h"
#include "gamemem.h"
#include "Reggie.h"
#include "gamesystem.h"
#include "interfacemultiplayer.h"
#include "initexit.h"
#include "reg_tool_3.h"

extern ReggieRef gReggieRef;
extern int gReggieConnected;
int gAllowCompression=true;
#define kGameName "Redline"
#define kConnectionTimeOut 5.0
#define kCompressionSize 32
#define kCompressionFlag 0x80

NT_SessionRef gSessionRef=nil;

typedef struct{
	QStub qstub;
	tNetworkPacket packet;
}tQueuedPacket;

Queue gPacketQueue;
int gInternetAvailable=true;
int gTerminateSession=false;
float gLastPingReceived=0,gLastRTT=0;
char gHistoryBuffer[1024*500]="";
int gHistoryDumps=0;

void NetworkLockOutNewPlayers()
{
//	HandleError(NT_SetSessionLock(gSessionRef,true));
	int err=NT_SetSessionLock(gSessionRef,true);
	//PrintConsoleString("Locking session. returns %d",err);
}

void NetworkUnlockOutNewPlayers()
{
//	HandleError(NT_SetSessionLock(gSessionRef,false));
	int err=NT_SetSessionLock(gSessionRef,false);
	//PrintConsoleString("Unlocking session. returns %d",err);
}

int NetworkGetLocalNetID()
{
	NT_MemberInfo info;
	NT_GetSelfMemberInfo(gSessionRef,&info);
	return info.member;
}

void NetworkClearPacketQueue()
{
	int temp=gTerminateSession;
	gTerminateSession=false;
	tNetworkPacket packet;
	while(NetworkReceivePacket(&packet))
		if(packet.data)
			MemoryFreeBlock(packet.data);
	gTerminateSession=temp;
}

Result32 CompressPacket(void *buffer, long *length) { 
  	Result32	error = eCommonErrorNone;
   void ** indirect = NULL;

   qThrowIfNull(indirect = IndirectInitialize(buffer, *length),
      eCommonErrorOutOfMem, kCommonErrorOutOfMemStr);

   qThrowIfError(IndirectCompress(indirect, 
      kIndirectCompressorZLib, NULL), 0);

   MemoryCopy(buffer, *indirect, *length = IndirectGetSize(indirect));

CLEANUP:
   if (indirect) IndirectDeallocate(indirect);
   return(error);
}

Result32 DecompressPacket(void *buffer, long *length) { 
  	Result32	error = eCommonErrorNone;
   void ** indirect = NULL;

   qThrowIfNull(indirect = IndirectInitialize(buffer, *length),
      eCommonErrorOutOfMem, kCommonErrorOutOfMemStr);

   qThrowIfError(IndirectDecompress(indirect, 
      kIndirectCompressorZLib, NULL), 0);

   MemoryCopy(buffer, *indirect, *length = IndirectGetSize(indirect));

CLEANUP:
   if (indirect) IndirectDeallocate(indirect);
   return(error);
}

void NetworkGetStatusString(char *str)
{
	NT_ProblemInfo prob;
	if(NT_GetProblemInfo(gSessionRef,0,&prob))
	{
		if(prob.timeout<10)
			str[0]='.';
		else if(prob.timeout<50)
			str[0]='t';
		else
			str[0]='T';
		if(prob.latency<10)
			str[1]='.';
		else if(prob.latency<50)
			str[1]='l';
		else
			str[1]='L';
		if(prob.stalled<10)
			str[2]='.';
		else if(prob.stalled<50)
			str[2]='s';
		else
			str[2]='S';
		if(prob.backlog<10)
			str[3]='.';
		else if(prob.backlog<50)
			str[3]='b';
		else
			str[3]='B';
		if(prob.failure<10)
			str[4]='.';
		else if(prob.failure<50)
			str[4]='f';
		else
			str[4]='F';
		if(prob.errors<10)
			str[5]='.';
		else if(prob.errors<50)
			str[5]='e';
		else
			str[5]='E';
		str[6]='\0';
	}
}

void NetworkGetBandwidth(float *rcv,float *snd)
{
	NT_LatencyInfo lat;
	NT_GetTotalLatencyInfo(gSessionRef,&lat);
	*rcv=lat.recvPackets10Sec*0.1;
	*snd=lat.sendPackets10Sec*0.1;
}

void NetworkQueuePacket(tNetworkPacket *insert)
{
	tQueuedPacket *q=(tQueuedPacket*)MemoryAllocateZeroedBlock(sizeof(tQueuedPacket));
	q->packet=*insert;
	QueueInsert(&gPacketQueue,(QStubPtr)q);
}

//#define PACKET_DUMP

#ifdef PACKET_DUMP
void DumpPacket(tNetworkPacket *packet)
{
	char st[256];
	sprintf(st,"what: %d",packet->what);
	PrintConsoleString(st);
	sprintf(st,"from: %d",packet->from);
	PrintConsoleString(st);
	sprintf(st,"size: 0x%x",packet->size);
	PrintConsoleString(st);
	
	int line=0;
	
	while(line<=packet->size/16)
	{
		char dataStr[256];
								
		sprintf(dataStr,"%04x  ",line*16);

		for(int pos=line*16;pos<line*16+16;pos++)
			if(pos<packet->size)
				sprintf(dataStr,"%s%02x",dataStr,*((UInt8*)packet->data+pos));
			else
				sprintf(dataStr,"%s  ",dataStr);

		sprintf(dataStr,"%s  ",dataStr);
				
		for(int pos=line*16;pos<line*16+16;pos++)
			if(pos<packet->size)
				if(*((UInt8*)packet->data+pos)>=32)
					sprintf(dataStr,"%s%c",dataStr,*((UInt8*)packet->data+pos));
				else
					sprintf(dataStr,"%s.",dataStr);
			else
				sprintf(dataStr,"%s ",dataStr);

		PrintConsoleString(dataStr);
		line++;
	}
	
	PrintConsoleString("");
}
#endif

void NTEventHandler(NT_SessionRef sessionRef,void *refcon,NT_SessionEventType event,NT_MemberIDType member)
{
	gAllowCompression=false;
	switch(event)
	{
		case eSessionEventSessionStart:{
				//PrintConsoleString("eSessionEventSessionStart");				
				tNetworkPacket packet;
				packet.what=kMessageTypePlayerJoined;
				packet.from=0;
				packet.size=0;
				packet.data=NULL;	
				NetworkQueuePacket(&packet);
			}
			break;
		case eSessionEventMemberJoin:{
				//PrintConsoleString("eSessionEventMemberJoin, member=%d",member);				
				tNetworkPacket packet;
				packet.what=kMessageTypePlayerJoined;
				packet.from=member;
				packet.size=0;
				packet.data=NULL;	
				NetworkQueuePacket(&packet);
				/*NT_ToleranceInfo tolerance;
				NT_GetToleranceInfo(gSessionRef,&tolerance);
				PrintConsoleString("Tolerance info:");
				PrintConsoleString("avgPackets %d.",tolerance.avgPackets);
				PrintConsoleString("avgRequests %d.",tolerance.avgRequests);
				PrintConsoleString("maxRequests %d.",tolerance.maxRequests);
				PrintConsoleString("minHeartbeat %d.",tolerance.minHeartbeat);
				PrintConsoleString("avgHeartbeat %d.",tolerance.avgHeartbeat);
				PrintConsoleString("maxHeartbeat %d.",tolerance.maxHeartbeat);
				PrintConsoleString("maxTimeout %d.",tolerance.maxTimeout);
				PrintConsoleString("maxFailures %d.",tolerance.maxFailures);
				PrintConsoleString("maxErrors %d.",tolerance.maxErrors);
				PrintConsoleString("minLinger %d.",tolerance.minLinger);*/
			}
			break;
		case eSessionEventMemberLeave:{
				//PrintConsoleString("eSessionEventMemberLeave, member=%d",member);
				tNetworkPacket packet;
				if(member==0)
				{
					if(*gDisconnectString=='\0')
						sprintf(gDisconnectString,"Network Failure (Failure on Host side).");
					packet.what=kMessageTypeGameTerminated;
				}
				else
					packet.what=kMessageTypePlayerLeft;	
				packet.from=member;
				packet.size=0;
				packet.data=NULL;					
				NetworkQueuePacket(&packet);
				NT_LatencyInfo latency;
				if(NT_GetLatencyInfo(gSessionRef,member,&latency))
				{
					PrintConsoleString("Latency info for dropped member:");
					PrintConsoleString("minLinkRTT %d.",latency.minLinkRTT);
					PrintConsoleString("avgLinkRTT %d.",latency.avgLinkRTT);
					PrintConsoleString("p80LinkRTT %d.",latency.p80LinkRTT);
					PrintConsoleString("devLinkRTT %d.",latency.devLinkRTT);
					PrintConsoleString("wAvgLinkRTT %d.",latency.wAvgLinkRTT);
					PrintConsoleString("wDevLinkRTT %d.",latency.wDevLinkRTT);
					PrintConsoleString("minTranRTT %d.",latency.minTranRTT);
					PrintConsoleString("avgTranRTT %d.",latency.avgTranRTT);
					PrintConsoleString("p80TranRTT %d.",latency.p80TranRTT);
					PrintConsoleString("maxTranRTT %d.",latency.maxTranRTT);
					PrintConsoleString("devTranRTT %d.",latency.devTranRTT);
					PrintConsoleString("wAvgTranRTT %d.",latency.wAvgTranRTT);
					PrintConsoleString("wDevTranRTT %d.",latency.wDevTranRTT);
				}
				NT_ProblemInfo prob;
				if(NT_GetProblemInfo(gSessionRef,member,&prob))
				{
					PrintConsoleString("Problem info for dropped member:");
					PrintConsoleString("timeout %d.",prob.timeout);
					PrintConsoleString("latency %d.",prob.latency);
					PrintConsoleString("stalled %d.",prob.stalled);
					PrintConsoleString("backlog %d.",prob.backlog);
					PrintConsoleString("failure %d.",prob.failure);
					PrintConsoleString("errors %d.",prob.errors);
				}
				
				NT_PacketHistory(gSessionRef,gHistoryBuffer,1024*500);
			}
			break;
		case eSessionEventSessionEnd:{		
				//PrintConsoleString("eSessionEventSessionEnd");
			/*	tNetworkPacket packet;
				packet.what=kMessageTypeGameTerminated;
				packet.from=member;
				packet.size=0;
				packet.data=NULL;					
				NetworkQueuePacket(&packet);*/
			}
			break;
		case eSessionEventRecvPacket:{
				UInt8 data[kNetworkToolPacketSize];
				UInt16 len;
				if(NT_RecvPacket(gSessionRef,&member,(char*)data,kNetworkToolPacketSize,&len))
				{
					tNetworkPacket packet;
					packet.data=MemoryAllocateBlock(kNetworkToolPacketSize);
					MemoryMove(packet.data,data+1,len-1);
					packet.size=len-1;
					packet.from=member;
					packet.what=*data;
					if(packet.what==kMessageTypeGameTerminated)
						gTerminateSession=true;
					/*#ifdef PACKET_DUMP
					PrintConsoleString("Packet Received:");
					DumpPacket(&packet);
					#endif
				*/
					//got a Ping request?
					if(packet.what==kMessageTypePing)
						//send out a reply packet with the original ping packets time stamp.
						NetworkSendPacket(kMessageTypePong,packet.data,sizeof(float),kMessagePriorityLow,packet.from);
					else if(packet.what==kMessageTypePong)
					{
						gLastPingReceived=TimeGetSeconds();
						gLastRTT=gLastPingReceived-*(float*)packet.data;
					}
					else
						NetworkQueuePacket(&packet);
				}
			}
			break;
		case eSessionEventRecvRequest:{
				UInt8 data[kNetworkToolPacketSize];
				UInt16 len;
				if(NT_RecvRequest(gSessionRef,&member,(char*)data,kNetworkToolPacketSize,&len))
				{
					NT_SendResponse(gSessionRef,NULL,0);
					tNetworkPacket packet;
					packet.data=MemoryAllocateBlock(kNetworkToolPacketSize);
					MemoryMove(packet.data,data+1,len-1);
					packet.size=len-1;
					packet.from=member;
					packet.what=*data;
					if(packet.what==kMessageTypeGameTerminated)
						gTerminateSession=true;
					
					#ifdef PACKET_DUMP
					PrintConsoleString("Request Packet Received:");
					DumpPacket(&packet);
					#endif

					NetworkQueuePacket(&packet);
				}
			}
			break;
		case eSessionEventRecvResponse:{
				UInt8 data[kNetworkToolPacketSize];
				UInt16 len;
				NT_RecvResponse(gSessionRef,&member,(char*)data,kNetworkToolPacketSize,&len);
			}			
			break;
	}
	gAllowCompression=true;
}

void NTErrorHandler(NT_SessionRef sessionRef,void *refcon,NT_SessionProblemType problem,NT_SessionSeverityType severity,NT_MemberIDType member,Bool8 *disconnectSelf,Bool8 *disconnectMember)
{
	gAllowCompression=false;
	if(severity>=100)
	{
		PrintConsoleString("NT Error %d",problem);
		
		NT_MemberInfo info;
		NT_GetSelfMemberInfo(gSessionRef,&info);
		if(info.member==0)
		{
			if(member!=0)
			{
				*disconnectMember=true;
				
				tChatMessage m;
				m.flags=kChatFlagSystem;
				switch(problem)
				{
					case eSessionProblemTimeout:
						sprintf(m.str,"### Player %d is disconnecting (Connection timed out).",member+1);
						break;
					case eSessionProblemLatency:
						sprintf(m.str,"### Player %d is disconnecting (Latency too high).",member+1);
						break;
					case eSessionProblemStalled:
						sprintf(m.str,"### Player %d is disconnecting (Stalled).",member+1);
						break;
					case eSessionProblemBacklog:
						sprintf(m.str,"### Player %d is disconnecting (Backlog).",member+1);
						break;
					case eSessionProblemFailure:
						sprintf(m.str,"### Player %d is disconnecting (Failure).",member+1);
						break;
					case eSessionProblemErrors:
						sprintf(m.str,"### Player %d is disconnecting (Too many errors).",member+1);
						break;
					default:
						sprintf(m.str,"### Player %d is disconnecting (Unknown error).",member+1);
						break;					
				}
				printf("%s\n",m.str);
				//NetworkSendPacket(kMessageTypeChat,&m,sizeof(m),kMessagePriorityHigh,kMessageSendToAll);					
			}
		}
		else if(member==0)
		{
			*disconnectMember=false;
			*disconnectSelf=true;
			tNetworkPacket packet;
			packet.data=NULL;
			packet.size=0;
			packet.from=member;
			packet.what=kMessageTypeGameTerminated;
			NetworkQueuePacket(&packet);
			switch(problem)
			{
				case eSessionProblemTimeout:
					sprintf(gDisconnectString,"Network Failure (Connection timed out).");
					break;
				case eSessionProblemLatency:
					sprintf(gDisconnectString,"Network Failure (Latency too high).");
					break;
				case eSessionProblemStalled:
					sprintf(gDisconnectString,"Network Failure (Stalled).");
					break;
				case eSessionProblemBacklog:
					sprintf(gDisconnectString,"Network Failure (Backlog).");
					break;
				case eSessionProblemFailure:
					sprintf(gDisconnectString,"Network Failure (Failure).");
					break;
				case eSessionProblemErrors:
					sprintf(gDisconnectString,"Network Failure (Too many errors).");
					break;
				default:
					sprintf(gDisconnectString,"Network Failure (unknown error).");
					break;
			}
		}
	}
	gAllowCompression=true;
}
	
void NetworkPreSession()
{
	if(gSessionRef==NULL)
		HandleError(NT_SessionStartParam(&gSessionRef,NTEventHandler,NTErrorHandler,NULL,NULL,6,kGameName,""));	
	else
		HandleError(NT_SessionRestart(gSessionRef,6,kGameName,""));		
}

float NetworkGetPlayerPing(int netID)
{
	NT_MemberInfo info;
	NT_GetSelfMemberInfo(gSessionRef,&info);
	if(netID==info.member||netID==kMessageNoRecipients)
		return 0;
		
	NT_LatencyInfo latency;
	if(NT_GetLatencyInfo(gSessionRef,netID,&latency));
	{
	/*	PrintConsoleString("Player %d latency.",netID);
		PrintConsoleString("minLinkRTT %d.",latency.minLinkRTT);
		PrintConsoleString("avgLinkRTT %d.",latency.avgLinkRTT);
		PrintConsoleString("p80LinkRTT %d.",latency.p80LinkRTT);
		PrintConsoleString("devLinkRTT %d.",latency.devLinkRTT);
		PrintConsoleString("wAvgLinkRTT %d.",latency.wAvgLinkRTT);
		PrintConsoleString("wDevLinkRTT %d.",latency.wDevLinkRTT);
		PrintConsoleString("minTranRTT %d.",latency.minTranRTT);
		PrintConsoleString("avgTranRTT %d.",latency.avgTranRTT);
		PrintConsoleString("p80TranRTT %d.",latency.p80TranRTT);
		PrintConsoleString("maxTranRTT %d.",latency.maxTranRTT);
		PrintConsoleString("devTranRTT %d.",latency.devTranRTT);
		PrintConsoleString("wAvgTranRTT %d.",latency.wAvgTranRTT);
		PrintConsoleString("wDevTranRTT %d.",latency.wDevTranRTT);*/
		return latency.avgLinkRTT*0.001;	
	}
	return 0;
}
	
//Host a new Network Game
void HostNetGame(int maxPlayers,char *pass)
{
	HandleError(NT_SessionRestart(gSessionRef,maxPlayers,kGameName,pass));
	gTerminateSession=false;
}

void NetworkDisconnectPlayer(int netID,UInt8 type)
{
	NT_MemberInfo info;
	if(NT_GetMemberInfo(gSessionRef,netID,&info))
	{
		if(type!=kNetworkDisconnectLicenseCopies)
			HandleError(NT_SessionBan(gSessionRef,&info.address,false,NULL));
		NetworkSendPacket(kMessageTypeKicked,&type,sizeof(UInt8),kMessagePriorityHigh,netID);
	}
}

void NetworkChangePassword(char *pass)
{
	HandleError(NT_SetSessionPassword(gSessionRef,pass));
}

//join a network game at the ip address given in the string address.
//returns an error code or 0. passes the player's id in id.
int JoinNetGame(char *address,char *errorString)
{
	if(!*address)
	{
		sprintf(errorString,"No Address Entered.");
		return false;
	}
	//PrintConsoleString(address);
	NetworkAddress addy;
	Result32 err=SimpleResolve(eNetworkStackTCPIP,address,&addy);
	if(err)
	{
		sprintf(errorString,"Can't resolve host. (%d)",err);
		return false;
	}

	gTerminateSession=false;
	NetworkClearPacketQueue();
	HandleError(NT_SessionRejoin(gSessionRef,&addy,kGameName,NULL,&err));
	
	if(err==eNetworkToolErrorBadPass)
	{
		err=0;
		char password[256];
		if(InterfaceGetUserInputString("Please enter Game Password",password,256,true,true))
		{
			NetworkClearPacketQueue();
			HandleError(NT_SessionRejoin(gSessionRef,&addy,kGameName,password,&err));
		}
		else
			return false;
	}
	if(err)
	{
		switch(err)
		{
			case eNetworkToolErrorNoServer:
				sprintf(errorString,"No Server Running on that Machine",err);
				break;
			case eNetworkToolErrorTimeout:
				sprintf(errorString,"Network_Tool Timeout",err);
				break;
			case eNetworkToolErrorLatency:
				sprintf(errorString,"Latency is too high.",err);
				break;
			case eNetworkToolErrorBanned:
				sprintf(errorString,"You are banned from this host.",err);
				break;
			case eNetworkToolErrorTooMany:
				sprintf(errorString,"The game is full.",err);
				break;
			case eNetworkToolErrorBadPass:
				sprintf(errorString,"Wrong Password",err);
				break;
			case eNetworkToolErrorLocked:
				sprintf(errorString,"Can't join in the middle of a game.",err);
				break;
			default:
				sprintf(errorString,"Connection Error. (%d)",err);
		}
		return false;
	}
		
	float startTime=TimeGetSeconds();
	
	while(TimeGetSeconds()<startTime+kConnectionTimeOut)
	{
		tNetworkPacket message;
		while(NetworkReceivePacket(&message))
		{			
			//wait for servers response
			if(message.what==kMessageTypeID)
			{
				NetworkQueuePacket(&message);
				return true;
			}
			if(message.what==kMessageTypeVersion)
			{
				UInt32 reply=kVersion;
				U32Swap(reply);
				NetworkSendPacket(kMessageTypeVersion,&reply,sizeof(UInt32),kMessagePriorityHigh,message.from);				
			}
			if(message.what==kMessageTypeJoinDenied)
			{	
				if(*((UInt8*)message.data)==kJoinDeniedInProgress)
					sprintf(errorString,"Can't join in the middle of a game.",err);
				else if(*((UInt8*)message.data)==kJoinDeniedVersion)
					sprintf(errorString,"Version Mismatch.",err);
				else
					sprintf(errorString,"Host rejected connection.",err);		
				//PrintConsoleString("Rejected. Exiting.");			
				return false;
			}
			if(message.what==kMessageTypeGameTerminated)
			{
				sprintf(errorString,"Game Terminated.",err);		
				return false;
			}
			NetworkDisposePacket(&message);
		}
	}
	sprintf(errorString,"Connection Timed out.",err);
	return false;
}

//exits a network game
void ExitNetGame()
{
	if(gSessionRef)
	{
	//	HandleError(NT_SessionLeave(gSessionRef));
	//	gSessionRef=nil;
	}
}

//sends the data in *message to other players on the network
void NetworkSendPacket(char messageType,void *message,long size,int priority,int to,int notTo)
{
	if(messageType==kMessageTypeGameTerminated)
		gTerminateSession=true;
		
	if(to==kMessageNoRecipients)
		return;
	
	if(size>kNetworkToolPacketSize-1)
		FailWithErrorString("Packet too large.");

	NT_MemberInfo info;
	NT_GetSelfMemberInfo(gSessionRef,&info);
	NT_MemberListType self=0x00000001<<info.member;
	
	UInt8 data[kNetworkToolPacketSize];
	MemoryMove(data+1,message,size);
	*data=messageType;
	if(size>kCompressionSize&&gAllowCompression)
	{
		int oldsize=size;
		if(CompressPacket(data+1,&size)==eCommonErrorNone)
			if(oldsize<size)
				*data|=kCompressionFlag;
			else
			{
				size=oldsize;
				MemoryMove(data+1,message,size);
			}
	}
//	U32Swap(*data);
	
	NT_MemberListType members;
	switch(to)
	{
		case kMessageSendToAll:			members=eMemberListAll;			break;
		case kMessageSendToAllButSelf:	members=eMemberListAll^self;	break;
		case kMessageSendToHostOnly:	members=0x00000001;				break;
		default: members=0x00000001<<to;
	}
	if(notTo)
		members&=~(0x00000001<<notTo);
		
	if(members&self)
	{
		tNetworkPacket packet;
		packet.data=MemoryAllocateBlock(size);
		MemoryMove(packet.data,message,size);
		packet.size=size;
		packet.from=info.member;
		packet.what=messageType;	

		NetworkQueuePacket(&packet);	
	}
	
	Result32 err=0;
	if(priority<=kMessagePriorityNormal)
		err=NT_SendPacket(gSessionRef,members,(char*)data,size+4);
	else
	{
		NT_SessionInfo sessInfo;
		NT_MemberInfo member;
		
		NT_GetSessionInfo(gSessionRef,&sessInfo);
		for(int i=0;i<32;i++)
			if((members&(0x00000001<<i))&&(i!=info.member))
				if(NT_GetMemberInfo(gSessionRef,i,&member))
					err=NT_SendRequest(gSessionRef,i,(char*)data,size+4);
	}
	if(err)
	{
		PrintConsoleString("Error Sending Packet (%d)",err);		
		gTerminateSession=true;
	}
}

void NetworkSendPacket(char messageType,void *message,long size,int priority,int to)
{
	NetworkSendPacket(messageType,message,size,priority,to,0);
}

int NetworkReceivePacket(tNetworkPacket* packet)
{
	if(*gHistoryBuffer)
	{
		time_t t;
		time(&t);
		char ct[26];
		ctime_r(&t,ct);
		PrintConsoleString("Dumping Packet History %d. (%s)",gHistoryDumps,ct);
		FILE *f=fopen("/tmp/packethistory.log","a");
		fprintf(f,"Dump %d (%s)\n\n",gHistoryDumps++,ct);
		char *ch=gHistoryBuffer;
		while(*ch)
			fwrite(ch++,1,1,f);
		strcpy(gHistoryBuffer,"");
		
		fprintf(f,"Debug Snapshot:");
		NT_MemberListType mt=0xffff;
		char buffer[64*1024];
		NT_DebugSnapshot(gSessionRef,mt,buffer,32*1024);
		ch=buffer;
		while(*ch)
			fwrite(ch++,1,1,f);

		fclose(f);
	}
	if(gTerminateSession)
	{
		packet->what=kMessageTypeGameTerminated;
		packet->data=NULL;
		return true;
	}
	else if(QStubPtr rcv=QueueRemove(&gPacketQueue))
	{
		*packet=((tQueuedPacket*)rcv)->packet;
		if(packet->what&kCompressionFlag)
		{
			DecompressPacket(packet->data,&packet->size);
			packet->what^=kCompressionFlag;
		}
		MemoryFreeBlock(rcv);
		return true;
	}
	if(gReggieConnected)
		ReggieMessageCheck(gReggieRef);
	return false;
}

void NetworkDisposePacket(tNetworkPacket* packet)
{
	if(packet->data)
		MemoryFreeBlock(packet->data);
}

void NetworkInit()
{
	HandleError(NT_Open());
	QueueCreate(&gPacketQueue);
}

void NetworkIdle()
{
	static float lastStatus=0;	
	NT_Idle();
	if(gSessionRef)
	{
	/*	if(TimeGetSeconds()-lastStatus<0.5)
			return;
		lastStatus=TimeGetSeconds();
		char str[16];
		NetworkGetStatusString(str);
	//	printf("%s\n",str);
		NT_MemberInfo info;
		NT_GetSelfMemberInfo(gSessionRef,&info);
		if(info.member)
		{
			if(gLastRTT<0)
				printf("No Ping Response since %f seconds.\n",lastStatus-gLastPingReceived);
			else
			{
				//printf("Last RTT: %f\n",gLastRTT);
				gLastRTT=-1;
			}
			NetworkSendPacket(kMessageTypePing,&lastStatus,sizeof(float),kMessagePriorityLow,kMessageSendToHostOnly);
		}*/
	}
}

void NetworkExit()
{
	if(gSessionRef)
		NetworkSendPacket(kMessageTypeBye,0,NULL,kMessagePriorityHigh,kMessageSendToAll);
	if(gReggieConnected)
		ReggieDispose(gReggieRef,true);
	NT_Close();
	while(QStubPtr rcv=QueueRemove(&gPacketQueue))
		MemoryFreeBlock(rcv);
	QueueEmpty(&gPacketQueue);
}

//wait for a response from all players, to synchronize the start of the game.
int NetworkSynch(int numPlayers)
{	
	//PrintConsoleString("Synching %d...",numPlayers);

	NetworkSendPacket(kMessageTypeSynch,nil,0,kMessagePriorityHigh,kMessageSendToAll);
	UInt32 lastSynch=0;
	UInt32 startClock;
	int start=false;
	tNetworkPacket leftMessage[kMaxPlayers];
	int numLeftMessages=0;

	int synchMessagesReceived=0;
	while(synchMessagesReceived<numPlayers&&!start)
	{
		tNetworkPacket message;
		if(NetworkReceivePacket(&message))
		{
			int cancel=false;
			if(message.what==kMessageTypeSynch)
			{
				synchMessagesReceived++;
				//PrintConsoleString("Received Synch packet from player %d",message.from);
			}
			else if(message.what==kMessageTypeEndGame||message.what==kMessageTypeGameTerminated)
			{
				cancel=true;
				PrintConsoleString("Received Termination Packet. Aborting");
			}
			else if(message.what==kMessageTypePlayerLeft)
			{
				//cancel=true;
				leftMessage[numLeftMessages++]=message;
				numPlayers--;
				PrintConsoleString("A Player Left.");
			}
			else if(message.what==kMessageTypeSynchStart)
			{
				//PrintConsoleString("Received Start Signal.");

				startClock=*(UInt32*)message.data;
				U32Swap(startClock);
				start=true;
			}
			else
				PrintConsoleString("Unexpected Packet");
			
			NetworkDisposePacket(&message);
			if(cancel)
				return false;
		}
		SystemPoll(true);
	}

	
	NT_MemberInfo info;
	NT_GetSelfMemberInfo(gSessionRef,&info);
	if(info.member==0)
	{	

		//PrintConsoleString("Sent Start Signal.");
	
		startClock=NT_GetSessionClock(gSessionRef)+500;
		U32Swap(startClock);
		NetworkSendPacket(kMessageTypeSynchStart,&startClock,sizeof(UInt32),kMessagePriorityHigh,kMessageSendToAll);
		U32Swap(startClock);
	}
	else
	{
		float startTime=TimeGetSeconds();
		tNetworkPacket message;
		
		while(TimeGetSeconds()<startTime+kConnectionTimeOut&&!start)
		{
			if(NetworkReceivePacket(&message))
			{
				if(message.what==kMessageTypeSynchStart)
				{
					//PrintConsoleString("Received Start Signal.");

					startClock=*(UInt32*)message.data;
					U32Swap(startClock);
					start=true;
				}
				else if(message.what==kMessageTypePlayerLeft)
				{
					leftMessage[numLeftMessages++]=message;
				}
				else
					PrintConsoleString("Unexpected Packet");
				NetworkDisposePacket(&message);
			}		
			SystemPoll(false);
		}
		if(!start)
			return false;
	}
	for(int i=0;i<numLeftMessages;i++)
		NetworkQueuePacket(leftMessage+i);
	while(NT_GetSessionClock(gSessionRef)<startClock);
	return true;
}

