//========================================================================
//  Mech4Test
//
// MechWarrior 4 Network Test
// Written:    08/10/2000
// By:         Michael J. Lyons (mlyons@microsoft.com)
//
// This test is designed to simulate the type of network activity that
// happens when running MechWarrior 4. There are three different para-
// meters that you can control with this test: packet size, packet
// frequency,  and packet bundles. Packet size is the number of bytes
// sent in each packet. Packet frequency is how often to sent a packet
// bundle, and packet bundle is the number of packets to send all at
// once every frequency.
//
// This test makes a clear distinction between client and server. The
// clients only communicate with the server, and the server communicates
// with all the clients. Each client can specify its own parameter for
// how it's messages are communicated to the server, and the server can
// specify its parameters about how messages are sent to all the clients.
//

//========================================================================
#include <GameOS\GameOS.hpp>
#include <GameOS\ToolOS.hpp>
#include <memory.h>
#include <string.h>


//========================================================================
// macros
//========================================================================
#define MAX_PLAYERS			32
#define MAX_SAMPLES			100
#define MAX_NAME			256
#define TYPE_NETMESSAGE		13

//========================================================================
// external variables
//========================================================================

//========================================================================
// external functions
//========================================================================
extern void __stdcall ExitTestNetworkShell();
extern void __stdcall DoMainMenuLogic();


//========================================================================
// types
//========================================================================
typedef struct tagMACHINE
{
	DWORD		dwDPID;
	double		al;			// local running average
	double		ar;			// last remote running average
	double		al_total;	// total local values
	double		LastLatency;

	int			c_samples;	// the number of samples that have been
							// averaged to compute c

	int			iNextMsgNumSend;	// the next message number to send
	int			iNextMsgNumRecv;	// the next message number expected
	int			iPacketsLost;
	int			iPacketsOutOfOrder;

	double		nextsend;			// time to send the next packet bundle
	char		szName[MAX_NAME];

	bool		bDelete;
	struct tagMACHINE *	pNext;

} MACHINE;

//========================================================================
//========================================================================
typedef struct tagPACKET
{
	double		a;			// running average
	double		TimeStamp;	// time stamp
	int			message_number;
	char		data[];
} PACKET;





//========================================================================
// static data
//========================================================================
static bool			s_bServer		= false;
static HGOSFONT3D	s_fontLarge		= NULL;
static HGOSFONT3D	s_fontSmall		= NULL;
static MACHINE *	s_Head			= NULL;
static double		s_SendPeriod	= 1.000;
static int			s_SendSize		= 100;
static int			s_SendBundle	= 1;
static int			s_MaxFrameRate	= 100;
static double		s_NextFrame		= 0.0;
static double		s_MinFrameTime	= 1.0/s_MaxFrameRate;
static PACKET *		s_packet		= NULL;
static char			s_szName[200]	= {0};
static const double	c_MinPeriod		= 0.010;




//========================================================================
//========================================================================
MACHINE *FindMachine(DWORD dpId)
{
	MACHINE *p=s_Head;

	while (p)
	{
		if (p->dwDPID == dpId)
			break;

		p=p->pNext;
	}

	return p;
}

//========================================================================
//========================================================================
static void __forceinline ROUND(double &d)
{
	d=((int)((d*1000.0)+0.5))/1000.0;
}
static void __forceinline CAPMIN(double &d, const double &min)
{
	if (d < min)
		d=min;
}


//========================================================================
// DoGameLogic
//
// called every frame by GameOS
//========================================================================
static void __stdcall DoGameLogic(void)
{
	DWORD KeyPress=gos_GetKey()&255;

	if( KeyPress==27 )
	{
		gos_DeleteFont( s_fontLarge );
		gos_DeleteFont( s_fontSmall );
		gos_NetEndGame();
		Environment.TerminateGameEngine=ExitTestNetworkShell;
		Environment.DoGameLogic=DoMainMenuLogic;
		Environment.UpdateRenderers=0;
		Environment.Key_Exit = KEY_ESCAPE;
		return;
	}
	else if( KeyPress=='q' )
	{
		if (s_SendSize<1024)
		{
			s_SendSize += 1;
			if (s_packet)
			{
				gos_Free(s_packet);
				s_packet = NULL;
			}
		}
	}
	else if( KeyPress=='a' )
	{
		if (s_SendSize > sizeof(PACKET))
			s_SendSize -= 1;

		if (s_packet)
		{
			gos_Free(s_packet);
			s_packet = NULL;
		}
	}
	else if( KeyPress=='Q' )
	{
		if (s_SendSize<1024)
		{
			s_SendSize += 10;
			if (s_SendSize > 1024)
				s_SendSize = 1024;
			if (s_packet)
			{
				gos_Free(s_packet);
				s_packet = NULL;
			}
		}
	}
	else if( KeyPress=='A' )
	{
		if (s_SendSize > sizeof(PACKET)+10)
			s_SendSize -= 10;

		if (s_packet)
		{
			gos_Free(s_packet);
			s_packet = NULL;
		}
	}
	else if( KeyPress=='w' )
	{
		s_SendPeriod += 0.01;
		ROUND(s_SendPeriod);
	}
	else if( KeyPress=='s' )
	{
		if (s_SendPeriod > 0.02)
			s_SendPeriod -= 0.01;
		ROUND(s_SendPeriod);
		CAPMIN(s_SendPeriod, c_MinPeriod);
	}
	else if( KeyPress=='W' )
	{
		s_SendPeriod += 0.001;
		ROUND(s_SendPeriod);
	}
	else if( KeyPress=='S' )
	{
		if (s_SendPeriod > 0.02)
			s_SendPeriod -= 0.001;
		ROUND(s_SendPeriod);
		CAPMIN(s_SendPeriod, c_MinPeriod);
	}
	else if(( KeyPress=='E' ) || ( KeyPress=='e' ))
	{
		if (s_SendBundle < 5)
			s_SendBundle++;
	}
	else if(( KeyPress=='D' ) || ( KeyPress=='d' ))
	{
		if (s_SendBundle > 1)
			s_SendBundle--;
	}
	else if(( KeyPress=='R' ) || ( KeyPress=='r' ))
	{
		s_MaxFrameRate++;
		s_MinFrameTime	= 1.0/s_MaxFrameRate;
	}
	else if(( KeyPress=='F' ) || ( KeyPress=='f' ))
	{
		if (s_MaxFrameRate > 1)
			s_MaxFrameRate--;
		s_MinFrameTime	= 1.0/s_MaxFrameRate;
	}




	NetPacket* Message;
	Message=gos_NetGetMessage();
	while( Message )
	{
		//
		// find out who the message is from
		//
		MACHINE *m = FindMachine(Message->FromID);

		if (m)
		{
			switch (Message->Type)
			{
			case TYPE_NETMESSAGE:
				PACKET *p = (PACKET *)Message->pData;

				//
				// make sure it's the message number we expect
				//
				if (p->message_number >= m->iNextMsgNumRecv)
				{
					//
					// process the message
					//
					if (m->c_samples < MAX_SAMPLES)
					{
						m->al_total += Message->TimeStamp - p->TimeStamp;
						m->c_samples++;
					}
					else
					{
						m->al_total *= ((double)MAX_SAMPLES-1)/((double)MAX_SAMPLES);
						m->al_total += Message->TimeStamp - p->TimeStamp;
					}
					m->al = m->al_total / m->c_samples;
					
					m->ar = p->a;
					m->LastLatency = Message->TimeStamp - p->TimeStamp + (m->ar-m->al)/2.0;

					m->iNextMsgNumRecv = p->message_number + 1;

					if (p->message_number > m->iNextMsgNumRecv)
					{
						m->iPacketsLost += p->message_number-m->iNextMsgNumRecv;
						m->iPacketsOutOfOrder += p->message_number-m->iNextMsgNumRecv;
					}

				}
				else
				{
					m->iPacketsLost--;
				}
				break;
			}
		}
		else
		{
			// message from unknown person!!
			__asm nop;
		}

		Message=gos_NetGetMessage();
	}


	//
	// mark players as inactive
	//
	MACHINE *m = s_Head;

	while (m)
	{
		m->bDelete = true;
		m=m->pNext;
	}

	//
	// Find new players
	//
	for( DWORD t0=0; t0<(DWORD)gos_NetInformation(gos_NumberOfPlayers); t0++ )
	{
		//
		// skip ourselves
		//
		if (gos_NetInformation(gos_PlayerID,t0) == gos_NetInformation(gos_MyID))
			continue;

		MACHINE *m = FindMachine((DWORD)gos_NetInformation(gos_PlayerID,t0));
		if (m)
		{
			m->bDelete=false;
		}
		else
		{
			//
			// if we're the server, we're interested in new players. If we're not,
			// then we're only interested in the server
			//
			gos_NetInformation(gos_AmITheServer);
			gos_NetInformation(gos_PlayerID,t0);
			gos_NetInformation(gos_ServerID);
			if (gos_NetInformation(gos_AmITheServer) || (gos_NetInformation(gos_PlayerID,t0) == gos_NetInformation(gos_ServerID)) || (s_Head==NULL))
			{
				m = new MACHINE;
				if (m)
				{
					m->pNext = s_Head;
					s_Head = m;
				}

				strcpy(m->szName, (char *)gos_NetInformation(gos_PlayerName, t0));
				m->al					= 0;
				m->al_total				= 0;
				m->ar					= 0;
				m->c_samples			= 0;
				m->dwDPID				= (DWORD)gos_NetInformation(gos_PlayerID,t0);
				m->iNextMsgNumRecv		= 0;
				m->iNextMsgNumSend		= 0;
				m->LastLatency			= 0;
				m->iPacketsLost			= 0;
				m->iPacketsOutOfOrder	= 0;
				m->bDelete				= false;
				m->nextsend				= gos_GetElapsedTime(1) + s_SendPeriod;
			}
		}
	}	

	//
	// delete players that are gone
	//
	MACHINE **p=&s_Head;
	while (*p)
	{
		while ((*p) && ((*p)->bDelete))
		{
			MACHINE *pDelete = *p;
			*p = (*p)->pNext;
			delete pDelete;
		}

		if (*p)
			p = &((*p)->pNext);
	}


	//
	// go through and build packets to send if necessary
	//
	m = s_Head;
	while (m)
	{
		if (m->nextsend < gos_GetElapsedTime(1))
		{
			m->nextsend += s_SendPeriod;
			if (m->nextsend < gos_GetElapsedTime(1))
				m->nextsend = gos_GetElapsedTime(1) + s_SendPeriod;

			if (!s_packet)
				s_packet = (PACKET *)gos_Malloc(s_SendSize);

			for (int i=0 ; i<s_SendBundle ; i++)
			{
				s_packet->a					= m->al;
				s_packet->TimeStamp			= gos_GetElapsedTime(1);
				s_packet->message_number	= m->iNextMsgNumSend;

				m->iNextMsgNumSend++;

				NetPacket Packet;
				Packet.ToID					= m->dwDPID;
				Packet.Type					= TYPE_NETMESSAGE;
				Packet.pData				= s_packet;
				Packet.Length				= (WORD)s_SendSize;
				Packet.Flags				= 0;
				gos_NetSendMessage(&Packet);
			}
		}

		m=m->pNext;
	}
}

//========================================================================
// UpdateRenderers
//
// called every frame by GameOS
//========================================================================
#define COLWID	83
#define COL0	5
#define COL1	(COL0+COLWID*0)
#define COL2	(COL0+COLWID*1)
#define COL3	(COL0+COLWID*2)
#define COL4	(COL0+COLWID*3)
#define COL5	(COL0+COLWID*4)
#define COL6	(COL0+COLWID*5)
#define COL7	(COL0+COLWID*6)
#define COL8	(COL0+COLWID*7)
static void __stdcall UpdateRenderers(void)
{
	//
	// cap frame rate
	//
	while (gos_GetHiResTime() < s_NextFrame);
	s_NextFrame += s_MinFrameTime;
	if (gos_GetHiResTime() < s_NextFrame)
		s_NextFrame = gos_GetHiResTime() + s_MinFrameTime;




	gos_SetupViewport( 0,0,1,0x00000020, 1.0, 0.0, 0.0, 1.0 );
	gos_TextSetRegion( 0, 0, Environment.screenWidth, Environment.screenHeight );

	if (s_szName[0]==0)
	{
		strcpy(s_szName, (char *)gos_NetInformation(gos_PlayerName, (DWORD)gos_NetInformation(gos_PlayerNumber, (DWORD)gos_NetInformation(gos_MyID))));
	}

	if (gos_NetInformation(gos_AmITheServer))
	{
		gos_TextSetAttributes( s_fontLarge, 0xFFFF4040, -48, 0, 1, 1, 0 );
		gos_TextSetPosition( 10,5 );
		gos_TextDraw( "Server: %s", s_szName);
	}
	else
	{
		gos_TextSetAttributes( s_fontLarge, 0xFF0000FF, -48, 0, 1, 1, 0 );
		gos_TextSetPosition( 10,5 );
		gos_TextDraw( "Client: %s", s_szName );
	}

	int	y=100;
	gos_TextSetAttributes( s_fontSmall, 0xFFFFFFFF, -12, 0, 1, 1, 0 );
	gos_TextSetPosition( COL1, y );
	gos_TextDraw( "Name" );

	gos_TextSetPosition( COL2, y-20 );
	gos_TextDraw( "Last" );
	gos_TextSetPosition( COL2, y );
	gos_TextDraw( "Latency" );

	gos_TextSetPosition( COL3, y-20 );
	gos_TextDraw( "Avg" );
	gos_TextSetPosition( COL3, y );
	gos_TextDraw( "Latency" );

	gos_TextSetPosition( COL4, y-20 );
	gos_TextDraw( "Packets" );
	gos_TextSetPosition( COL4, y );
	gos_TextDraw( "Received" );

	gos_TextSetPosition( COL5, y-20 );
	gos_TextDraw( "Packets" );
	gos_TextSetPosition( COL5, y );
	gos_TextDraw( "Lost" );

	gos_TextSetPosition( COL6, y-20 );
	gos_TextDraw( "Packets" );
	gos_TextSetPosition( COL6, y );
	gos_TextDraw( "Out of order" );

	y += 50;
	gos_TextSetAttributes( s_fontSmall, 0xFF808080, -12, 0, 1, 1, 0 );

	MACHINE *m = s_Head;
	while (m)
	{
		gos_TextSetPosition( COL1, y );
		gos_TextDraw( "%s", m->szName );
		gos_TextSetPosition( COL2, y );
		gos_TextDraw( "%4d ms", (int)(m->LastLatency * 1000.0));
		gos_TextSetPosition( COL3, y );
		gos_TextDraw( "%4d ms", (int)((m->al+m->ar) * 500.0));
		gos_TextSetPosition( COL4, y );
		gos_TextDraw( "%4d", m->iNextMsgNumRecv);
		gos_TextSetPosition( COL5, y );
		gos_TextDraw( "%4d", m->iPacketsLost);
		gos_TextSetPosition( COL6, y );
		gos_TextDraw( "%4d", m->iPacketsOutOfOrder);

		y += 30;

		m=m->pNext;
	}

	gos_TextSetAttributes( s_fontSmall, 0xFF00FF00, -12, 0, 1, 1, 0 );
	gos_TextSetPosition( 10, 420 );
	gos_TextDraw( "Q/A: packet size (%4d bytes)   W/S: packet period (%4d ms)",
		s_SendSize,
		(int)(s_SendPeriod*1000.0f));
	gos_TextSetPosition( 10, 450 );
	gos_TextDraw( "E/D: packet bundle (%2d)   R/F: Max frame rate (%2d Hz)",
		s_SendBundle,
		s_MaxFrameRate);

}

//========================================================================
//========================================================================
static void __stdcall TerminateGameEngine()
{
	while (s_Head)
	{
		MACHINE *p=s_Head->pNext;
		delete s_Head;
		s_Head = p;
	}

	if (s_packet)
	{
		gos_Free(s_packet);
		s_packet=NULL;
	}
//
// Send 'game finished' packet to everyone
//
	if( !Environment.NetServerMigration && gos_NetInformation(gos_AmITheServer) )
	{
		NetPacket Packet;
		memset( &Packet, 0, sizeof(Packet) );
		Packet.ToID = (DWORD)gos_NetInformation(gos_AllID);
		Packet.Type = 100;
		Packet.pData = 0;
		Packet.Flags = 0;
		Packet.Length = 0;
		gos_NetSendMessage( &Packet );
	}
	ExitTestNetworkShell();
	gos_DeleteFont( s_fontLarge );
	gos_DeleteFont( s_fontSmall );
}


//========================================================================
//
// Decode game specific data (in Test Latency, it's just an ASCII string anyway)
//
//========================================================================
static char* DecodeMech4Data(void* Data )
{
	if( Data )
	{
		return (char*)Data;
	}
	else
	{
		return "";
	}
}




//========================================================================
//========================================================================
DWORD InitMech4Test()
{
	s_bServer		= false;
	s_fontLarge		= NULL;
	s_fontSmall		= NULL;
	s_Head			= NULL;
	s_SendPeriod	= 1.000;
	s_SendSize		= 100;
	s_SendBundle	= 1;
	s_MaxFrameRate	= 100;
	s_NextFrame		= 0.0;
	s_MinFrameTime	= 1.0 / s_MaxFrameRate;
	s_packet		= NULL;
	s_szName[0]		= 0;


	Environment.NetworkMaxPlayers	= MAX_PLAYERS;
	Environment.DecodeGameInfo		= DecodeMech4Data;
	Environment.NetServerMigration	= 1;
	strncpy( (char*)Environment.NetGameInfo, gos_FileTimeString( gos_GetTimeDate() ), 15 );

	if( 0==gos_NetInformation(gos_Networking) )
	{
		if( !gos_NetStartGame(0) )
		{
			return 1;
		}
	}

	Environment.DoGameLogic			= DoGameLogic;
	Environment.UpdateRenderers		= UpdateRenderers;
	Environment.TerminateGameEngine	= TerminateGameEngine;
	Environment.Key_Exit			= 0;

	s_fontLarge=gos_LoadFont("times.ttf");
	s_fontSmall=gos_LoadFont("arial.ttf");


	return 0;
}