/****************************/
/*    NETWORKING.C			*/
/* (c)2004 Pangea Software  */
/* By Brian Greenstone      */
/****************************/


#include <sys/socket.h>
#include <netinet/in.h>  
#include <netdb.h>      
#include <sys/fcntl.h>
#include <sys/errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

/**********************/
/*     EXTERNS        */
/**********************/

extern	WindowRef			gDialogWindow;
extern	IBNibRef 			gNibs;
extern	OGLPoint3D			gPlayerCoords[];
extern	float				gOneOverFramesPerSecond;
extern	Boolean				gQuitApplication;

/**********************/
/*     PROTOTYPES     */
/**********************/

static void AdvertiseOurGame(void);
static pascal OSStatus DoNetModeDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData);
static void AdvertisingCallback(CFNetServiceRef theService, CFStreamError *error, void *info);
static void StopAdvertisingGame(void);
static void StartBrowsingForHosts(void);
static void HostBrowserCallback(CFNetServiceBrowserRef browser, CFOptionFlags flags,
								CFTypeRef domainOrService, CFStreamError* error, void* info);
static void FoundAHost(CFNetServiceRef service);
static void LostAHost(CFNetServiceRef service);

static void DoNetModeDialog(void);
static int GetIPAddressOfHost(CFNetServiceRef service, char *outString);
static void DoJoinGameDialog(void);
static void DoHostGameDialog(void);
static pascal OSStatus DoJoinGameDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData);
static pascal OSStatus DoHostGameDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData);
static void BuildHostListMenu(void);
static void	SendConnectionRequestToHost(char *serverIPAddr);
static int CheckForConnectionRequestFromClient(void);
static pascal void HostDialogTimerCallback(EventLoopTimerRef  theTimer, void* userData);
static void ConvertIPAddressToString(struct sockaddr *ipAddr, char *outString);
static void CreateHostsListenerSocket(void);
static void UpdateClientList(void);
static void WaitForStartGameSignalFromHost(void);
static void SendStartGameSignalsToClients(Boolean hostCancelled);
static void ReadPlayerCoordinatesListFromHost(void);
static void ReadPlayerCoordinateFromClient(int socket);
static Boolean	IsSocketReadyForWrite(int socket);
static void StopBrowsingForHosts(void);


/**********************/
/*     CONSTANTS      */
/**********************/



#define kServiceType				CFSTR("_mygamename._tcp.")		// this string is what identifies our game on the network

#define	MY_PORT_NUM					45690		// arbitrary port ID # to use to connect on. 

#define	MAX_NAME_SIZE				100			// max # characters in the size of a network service name


#define	MAX_HOSTS_TO_FIND			20			// max # of host services to keep in our list when looking for a host

#define	DATA_RATE					20.0f		// # of times per second to send data to host/client during game play

#define	PADDING_SIZE				3000		// how much extra data to put at the end of each message to insure it gets flushed immediately


typedef struct
{
	char	name[MAX_NAME_SIZE];
	int		portNum;	
	char	ipAddress[32];
}HostInfoType;


typedef struct
{
	char	name[MAX_NAME_SIZE];
	int		connectionSocket;
}ClientInfoType;


typedef struct
{	
	Byte	numPlayers;											// total # players in game
	Byte	youArePlayerNum;									// tells recipient what player # they are
	char	names[MAX_PLAYERS][MAX_NAME_SIZE];		// names of all players in game
}StartGameDataType;


		/* MESSAGE TYPES */
		
enum
{
	kNetMessageType_ClientPlayerCoord,				// data is a player # followed by a single x,y,z coordinate
	kNetMessageType_AllPlayersCoords,				// data is a list of x,y,z coords, one for each player
	kNetMessageType_PlayerReady,					// game is up and running, so player can start reading data from the client or host
	kNetMessageType_PlayerExit,						// player is leaving the game
};


/**********************/
/*     VARIABLES      */
/**********************/

Boolean		gCancelNetDialog = false;
Boolean		gNetGameInProgress = false;
Boolean		gHostGame = false;						// true if this machine is hosting a net game, false if we're a client

static	CFNetServiceRef 		gMyRVService = nil;
static	CFNetServiceBrowserRef 	gServiceBrowserRef = nil;


static	short					gNumHostServices = 0;
static	HostInfoType			gHostList[MAX_HOSTS_TO_FIND];		// list of all the host services we found
static	HostInfoType			gChosenHost;

static	short					gNumClients = 0;						// # clients in our Net game (gNumPlayers -1 since Host is a player)
static	ClientInfoType			gClientList[MAX_PLAYERS];

static	int						gHostListenerSocket = -1;

static	int						gConnectionToHost = -1;				// the socket used to clients to read/write data from/to the host

static	char					gMyName[MAX_NAME_SIZE];

short							gNumPlayers;			// # players in the game
short							gMyPlayerNum;			// my player # (host is always #0)

static	float					gSendDataTimer = 0;

static	short					gReadyCount;

static	Byte					gPadding[PADDING_SIZE];


/***************** SETUP NET GAME *****************/

void SetupNetGame(void)
{

	gReadyCount = 0;							// nobody is ready to go
	
	Enter2D();
			
try_again:


		/* ASK THE USER IF THEY WANT TO HOST A NEW GAME OR JOIN ONE */
			
	DoNetModeDialog();
	
	
			/**************************/
			/* WE WANT TO HOST A GAME */
			/**************************/
			
	if (gHostGame)
	{	
				/* BRING UP DIALOG SHOWING CLIENTS JOINING */
				
		DoHostGameDialog();
		
		if (gCancelNetDialog)						// see if Host hit Cancel button
		{
			SendStartGameSignalsToClients(true);	// let clients know we've cancelled			
			CloseAllClientSocketConnections();		// close all client connections		
			goto try_again;
		}
		
		if (gNumClients == 0)						// did anyone join?
			goto try_again;
			
				/* SEND THE "START GAME" SIGNAL TO ALL CLIENTS */
				
		SendStartGameSignalsToClients(false);	
		gNetGameInProgress = true;
	}


			/**************************/
			/* WE WANT TO JOIN A GAME */
			/**************************/
	else
	{
				/* LET USER CHOOSE A HOST */
				
		DoJoinGameDialog();
		if (gCancelNetDialog)			// see if user hit Cancel button
			goto try_again;
	
									
				/* SEND CONNECTION REQUEST TO THE HOST */
				
		SendConnectionRequestToHost(gChosenHost.ipAddress);
		
		
				/* WAIT FOR "START GAME" SIGNAL FROM HOST */
		
		WaitForStartGameSignalFromHost();
		if (gNumPlayers == 0)							// if 0 players then the Host cancelled
		{
			CloseConnectionToHost();					// close connection to host
			goto try_again;
		}
		gNetGameInProgress = true;
	}
	
	Exit2D();
}


/************************ DO NET MODE DIALOG ***************************/
//
// Bring up dialog asking if you want to Host a game or Join a game.
//

static void DoNetModeDialog(void)
{
	EventHandlerUPP winEvtHandler;		
	OSErr			err;
	EventHandlerRef	ref;
	EventTypeSpec	list[] = { { kEventClassCommand,  kEventProcessCommand } };


			/* CREATE DIALOG WINDOW FROM THE NIB */
				
    err = CreateWindowFromNib(gNibs, CFSTR("NetMode"), &gDialogWindow);
	if (err)
		DoFatalAlert("\pCreateWindowFromNib failed!");

			/* CREATE NEW WINDOW EVENT HANDLER */
				
    winEvtHandler = NewEventHandlerUPP(DoNetModeDialog_EventHandler);
    InstallWindowEventHandler(gDialogWindow, winEvtHandler, GetEventTypeCount(list), list, 0, &ref);


			/* PROCESS THE DIALOG */
			
    ShowWindow(gDialogWindow);
	RunAppModalLoopForWindow(gDialogWindow);

				/* CLEANUP */
				
	DisposeEventHandlerUPP (winEvtHandler);
	DisposeWindow (gDialogWindow);	

}


/****************** DO NET MODE DIALOG EVENT HANDLER *************************/
//
// Event handler called by the OS to process events for this dialog.
//

static pascal OSStatus DoNetModeDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
{
#pragma unused (myHandler, userData)
HICommand 		command;

	switch(GetEventKind(event))
	{				
				/*******************/
				/* PROCESS COMMAND */
				/*******************/
				//
				// We only care if the user pressed the OK or Quit buttons.
				// Let the OS handle all other events.
				//
				
		case	kEventProcessCommand:
				GetEventParameter (event, kEventParamDirectObject, kEventParamHICommand,
								NULL, sizeof(command), NULL, &command);
				switch(command.commandID)
				{
							/* "HOST" BUTTON */
							
					case	'host':
							gHostGame = true;
		                    QuitAppModalLoopForWindow(gDialogWindow);
							break;

							/* "JOIN" BUTTON */
							
					case	'join':
							gHostGame = false;
		                    QuitAppModalLoopForWindow(gDialogWindow);
							break;

							/* "QUIT" BUTTON */
							
					case	kHICommandQuit:
		                    CleanQuit();
				}
				break;
    }

    return (eventNotHandledErr);
}

#pragma mark ____ JOIN GAME DIALOG ____

/********************** DO JOIN GAME DIALOG ***********************/

static void DoJoinGameDialog(void)
{
	Size			size;
	ControlID 		idControl;
	ControlRef 		control;
  	short			i;
	EventHandlerUPP winEvtHandler;		
	OSErr			err;
	EventHandlerRef	ref;
	EventTypeSpec	list[] = { { kEventClassCommand,  kEventProcessCommand } };



			/* CREATE DIALOG WINDOW FROM THE NIB */
				
    err = CreateWindowFromNib(gNibs, CFSTR("JoinGame"), &gDialogWindow);
	if (err)
		DoFatalAlert("\pCreateWindowFromNib failed!");


			/* CREATE NEW WINDOW EVENT HANDLER */
				
    winEvtHandler = NewEventHandlerUPP(DoJoinGameDialog_EventHandler);
    InstallWindowEventHandler(gDialogWindow, winEvtHandler, GetEventTypeCount(list), list, 0, &ref);


			/* START BROWSING FOR A HOST */
				
	StartBrowsingForHosts();


			/**********************/
			/* PROCESS THE DIALOG */
			/**********************/
			
	gCancelNetDialog = false;
    ShowWindow(gDialogWindow);
	RunAppModalLoopForWindow(gDialogWindow);



			/**********************/
			/* GET DIALOG RESULTS */
			/**********************/

			/* GET HOST MENU VALUE */
			
    idControl.signature = 'SvrM';
    idControl.id 		= 0; 
    GetControlByID(gDialogWindow, &idControl, &control);
	i = GetControlValue(control);
	
	gChosenHost = gHostList[i-1];				// remember who we chose
	
	
		/* GET USER'S NAME FROM TEXT EDIT FIELD */			
						
    idControl.signature = 'name';
    idControl.id 		= 0; 
	GetControlByID(gDialogWindow, &idControl, &control);
	
	err = GetControlData(control,
						kControlEditTextPart,
						kControlEditTextTextTag, 
						MAX_NAME_SIZE,
						gMyName,
						&size);
	
	if (err || (size == 0))						// if nothing then use default name
		strcpy(gMyName, "Default Name");
	

				/* CLEANUP */
				
	StopBrowsingForHosts();
				
	DisposeEventHandlerUPP (winEvtHandler);
	DisposeWindow (gDialogWindow);	
	
}


/****************** DO JOIN GAME DIALOG EVENT HANDLER *************************/
//
// Event handler called by the OS to process events for this dialog.
//

static pascal OSStatus DoJoinGameDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
{
#pragma unused (myHandler, userData)
HICommand 		command;

	switch(GetEventKind(event))
	{				
				/*******************/
				/* PROCESS COMMAND */
				/*******************/
				//
				// We only care if the user pressed the OK or Quit buttons.
				// Let the OS handle all other events.
				//
				
		case	kEventProcessCommand:
				GetEventParameter (event, kEventParamDirectObject, kEventParamHICommand,
								NULL, sizeof(command), NULL, &command);
				switch(command.commandID)
				{
							/* "JOIN" BUTTON */
							
					case	kHICommandOK:
		                    QuitAppModalLoopForWindow(gDialogWindow);
							break;

							/* "CANCEL" BUTTON */
							
					case	kHICommandCancel:
		                    QuitAppModalLoopForWindow(gDialogWindow);
							gCancelNetDialog = true;
							break;

							/************************/
							/* SELECTED A MENU ITEM */
							/************************/
							
					default:		
							if (command.attributes & kHICommandFromMenu)			// see if this command is from a menu
							{
//								HostListMenuWasSelected(command.menu.menuItemIndex - 2);
							}
								
				}
				break;
				

					
				
    }

    return (eventNotHandledErr);
}




/****************** BUILD HOST LIST MENU *************************/

static void BuildHostListMenu(void)
{
int			i;
char		menuStrings[MAX_HOSTS_TO_FIND][MAX_NAME_SIZE+32];
MenuHandle 	hMenu;
Size 		tempSize;
ControlID 	idControl;
ControlRef 	control;
short		menuIndex;
	
	menuIndex = GetControlValue(control);			// get current menu index
	
	
			/******************************************************/
			/* BUILD MENU ITEM STRINGS FROM THE HOST SERVICE LIST */
			/******************************************************/
				
	for (i = 0; i < gNumHostServices; i++)
	{
					/* FIRST ADD IN THE NAME */
	
		strcpy(menuStrings[i], gHostList[i].name);
						
				
					/* NEXT LET'S INCLUDE THE IP ADDRESS IN THERE */
									
		strcat(menuStrings[i], "    [");
		strcat(menuStrings[i], gHostList[i].ipAddress);		// append IP addr to host name
		strcat(menuStrings[i], "]");
		
		c2pstr(menuStrings[i]);								// convert to Pascal string for AppendMenu() below
	}
			
			
			/*******************************/
    		/* GET MENU HANDLE FROM DIALOG */
			/*******************************/
    		
    idControl.signature = 'SvrM';
    idControl.id 		= 0; 
    GetControlByID(gDialogWindow, &idControl, &control);
    GetControlData(control, kControlMenuPart, kControlPopupButtonMenuHandleTag,
					sizeof(MenuHandle), &hMenu, &tempSize);
        		


			/*****************************/
			/* ADD NEW ITEMS TO THE MENU */
			/*****************************/

    DeleteMenuItems(hMenu, 1, CountMenuItems (hMenu));	// start with empty menu
			
    for (i = 0; i < gNumHostServices; i++)
        AppendMenu(hMenu, menuStrings[i]);


			/* FORCE UPDATE OF MENU EXTENTS */
			
    SetControlMaximum(control, gNumHostServices);
	
	if (menuIndex < gNumHostServices)
		SetControlValue(control, gNumHostServices+1);			// set to current selected rez
	
}

/******************* CONVERT IP ADDRESS TO STRING ********************/
//
// Converts an sockaddr IP address into a C string
//

static void ConvertIPAddressToString(struct sockaddr *ipAddr, char *outString)
{
	int	j,i;

	j = 0;
	for (i = 2; i <= 5; i++)				// the IP addr is in bytes 2,3,4,5
	{
		Str32	s;
		UInt8	num = ipAddr->sa_data[i];	// get this part of the IP address
		
		NumToString(num, s);				// convert # to Pascal string
		p2cstrcpy(&outString[j], s);		// convert & copy to C string
		j += s[0];
		if (i == 5)
			outString[j] = 0x00;			// add 0x00 to end of C string
		else
			outString[j++] = '.';			// insert a "."	
	}
}


/*************** CLOSE CONNECTION TO HOST *******************/

void CloseConnectionToHost(void)
{
	if (gConnectionToHost != -1)
	{
		close(gConnectionToHost);					// close connection to host
		gConnectionToHost = -1;
	}
	
	gNetGameInProgress = false;
}		


#pragma mark ____ HOST GAME DIALOG ____

/********************** DO HOST GAME DIALOG ***********************/

static void DoHostGameDialog(void)
{
EventHandlerUPP		winEvtHandler;		
EventLoopTimerUPP	timerUPP;
EventLoopTimerRef	timerRef;
OSErr				err;
EventHandlerRef		ref;
EventTypeSpec		list[] = { { kEventClassCommand,  kEventProcessCommand } };
ControlID			idControl;
ControlRef			control;
Size				size;

	gNumClients = 0;

			/* CREATE DIALOG WINDOW FROM THE NIB */
				
    err = CreateWindowFromNib(gNibs, CFSTR("HostGame"), &gDialogWindow);
	if (err)
		DoFatalAlert("\pCreateWindowFromNib failed!");

			/* CREATE NEW WINDOW EVENT HANDLER */
				
    winEvtHandler = NewEventHandlerUPP(DoHostGameDialog_EventHandler);
    InstallWindowEventHandler(gDialogWindow, winEvtHandler, GetEventTypeCount(list), list, 0, &ref);


			/* INSTALL AN EVENT LOOP TIMER */
			//
			// This acts as our idle event so that we can test for
			// new clients in the background while the dialog runs.
			//
			
    timerUPP = NewEventLoopTimerUPP(HostDialogTimerCallback);
	InstallEventLoopTimer(GetMainEventLoop(),
						0,
						kEventDurationMillisecond, 
						timerUPP,
						nil,
						&timerRef);


			/* REGISTER WITH RENDEZVOUS SO CLIENTS CAN FIND US */
			
	AdvertiseOurGame();	
	CreateHostsListenerSocket();


			/**********************/
			/* PROCESS THE DIALOG */
			/**********************/
			
	gCancelNetDialog = false;
    ShowWindow(gDialogWindow);
	RunAppModalLoopForWindow(gDialogWindow);



		/* GET USER'S NAME FROM TEXT EDIT FIELD */			
						
    idControl.signature = 'name';
    idControl.id 		= 0; 
	GetControlByID(gDialogWindow, &idControl, &control);
	
	err = GetControlData(control,
						kControlEditTextPart,
						kControlEditTextTextTag, 
						MAX_NAME_SIZE,
						gMyName,
						&size);
	
	if (err || (size == 0))			// if nothing then use default name
		strcpy(gMyName, "Host with no Name");



				/* CLEANUP */
				
	RemoveEventLoopTimer(timerRef);
	DisposeEventLoopTimerUPP(timerUPP);
	DisposeEventHandlerUPP (winEvtHandler);
	DisposeWindow (gDialogWindow);	
	
	
			/* STOP ANNOUNCING OUR GAME TO THE WORLD */
			//
			// Now that we have all our clients and they have our IP address
			// we don't need to announce ourselves to the world anymore.
			// If our game let's user join at any time, then we want to leave
			// this active, but since we're not doing that here, let nix it.
			//
			
	StopAdvertisingGame();					// we're done with Rendezvous
	
	if (gHostListenerSocket != -1)			// close the listener socket
	{
		close(gHostListenerSocket);
		gHostListenerSocket = -1;
	}	
}


/****************** DO HOST GAME DIALOG EVENT HANDLER *************************/
//
// Event handler called by the OS to process events for this dialog.
//

static pascal OSStatus DoHostGameDialog_EventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
{
#pragma unused (myHandler, userData)
HICommand 		command;

	switch(GetEventKind(event))
	{				
				/*******************/
				/* PROCESS COMMAND */
				/*******************/
				//
				// We only care if the user pressed the OK or Quit buttons.
				// Let the OS handle all other events.
				//
				
		case	kEventProcessCommand:
				GetEventParameter (event, kEventParamDirectObject, kEventParamHICommand,
								NULL, sizeof(command), NULL, &command);
				switch(command.commandID)
				{
							/* "START" BUTTON */
							
					case	kHICommandOK:
		                    QuitAppModalLoopForWindow(gDialogWindow);
							break;

							/* "CANCEL" BUTTON */
							
					case	kHICommandCancel:
							gCancelNetDialog = true;
		                    QuitAppModalLoopForWindow(gDialogWindow);
							break;
				}
				break;
				
    }

    return (eventNotHandledErr);
}


/**************** HOST DIALOG TIMER CALLBACK *******************/
//
// This gets called by our Timer event that we installed above.
// Here we check if any new clients are asking to join our game.
//

static pascal void HostDialogTimerCallback(EventLoopTimerRef  theTimer, void* userData)
{
#pragma unused (theTimer, userData)
		
	int	socket;
		
	if (gNumClients >= (MAX_PLAYERS-1))					// see if alredy full
		return;
		
	
				/* SEE IF THERE ARE ANY CONNECTION REQUESTS */

	socket = CheckForConnectionRequestFromClient();		// see if got anything
	if (socket < 0)										//  -1 if no requests pending
		return;

	gClientList[gNumClients].connectionSocket = socket;


				/* READ CLIENT'S NAME */
				//
				// After issueing a connect() request, the client also
				// sends it's user name to use, so let's get it.
				// Calling ReadNetData() will block until all the data is available.
				//

	if (ReadNetData(socket, gClientList[gNumClients].name, MAX_NAME_SIZE) <= 0)
		DoFatalAlert("\pReadNetData() failed while reading client name");

	printf(gClientList[gNumClients].name);
	printf("\n");
	
	gNumClients++;	
	
	
	
			/* UPDATE THE DIALOG'S LIST OF CLIENTS */
			
	UpdateClientList();
}


/********************** UPDATE CLIENT LIST ***************************/

static void UpdateClientList(void)
{
	int			c,i;
	HIViewRef	textView;	
	ControlID	id = { 'List', 0 };
	char		text[MAX_NAME_SIZE * MAX_PLAYERS];		// large buffer to hold a big string
	
			/* BUILD THE STRING */
			
	i = 0;										// init null string
	text[0] = 0x00;
	
	for (c = 0; c < gNumClients; c++)
	{
		strcat(&text[i], gClientList[c].name);	// add the client name to string
		i += strlen(gClientList[c].name);		// inc index
		strcat(&text[i], "\n");					// insert Return
		i++;
	}
	
	text[i++] = 0x00;							// put null at end of string
	
				/* GET THE TEXT VIEW CONTROL */
				
	HIViewFindByID(HIViewGetRoot(gDialogWindow), id, &textView);
  
  
			/* SET THE TEXT IN THE TEXT VIEW CONTROL */

	TXNSetData(HITextViewGetTXNObject(textView),
				kTXNTextData,
				text,
				i,
				kTXNStartOffset,
				kTXNEndOffset);

}


/******************** CLOSE ALL CLIENT SOCKET CONNECTIONS ***********************/
//
// The Host calls this when it's done and needs to disconnect from the Clients
//

void CloseAllClientSocketConnections(void)
{
short	i;

	if (gHostGame)							// only do this if we were the host
	{
		for (i = 0; i < gNumClients; i++)
		{
			close(gClientList[i].connectionSocket);
			gClientList[i].connectionSocket = -1;
		}
		
		gNumClients = 0;
	}
}


#pragma mark ___ ADVERTISING W/ RENDEZVOUS___

/**************** ADVERTISE OUR GAME *******************/
//
// Use Rendezvous to announce ourselves on the network so that clients can find us.
//

static void AdvertiseOurGame(void)
{    
    CFNetServiceClientContext	context = {0, nil, nil, nil, nil};
    CFStreamError				error;
	Boolean						registered;

				/* CREATE A NET SERVICE */
				//
				// Pass pass "" as the name so that OS X will use the
				// computer's name, and if there is a name collision
				// it will automatically handle it.
				//
					
    gMyRVService = CFNetServiceCreate(kCFAllocatorDefault,
								CFSTR(""),						// use default domain
								kServiceType,					// service type
								CFSTR(""),						// use computer's name
								MY_PORT_NUM);					// port #
											
	if (gMyRVService == nil)
		DoFatalAlert("\pCFNetServiceCreate() failed!");


				/* REGISTER/PUBLISH THE SERVICE ASYNCHONOUSLY */
				
    CFNetServiceSetClient(gMyRVService, AdvertisingCallback, &context);
    CFNetServiceScheduleWithRunLoop(gMyRVService,
									CFRunLoopGetCurrent(),
									kCFRunLoopCommonModes);
									
    registered = CFNetServiceRegister(gMyRVService, &error);
	
				/* HANDLE ERRORS */
				
    if (!registered)
    {
        CFNetServiceUnscheduleFromRunLoop(gMyRVService,
										CFRunLoopGetCurrent(),
										kCFRunLoopCommonModes);
        CFNetServiceSetClient(gMyRVService, nil, nil);
        CFRelease(gMyRVService);
		
		DoFatalAlert("\pCFNetServiceRegister() failed!");
    }

}


/************************ ADVERTISING CALLBACK *********************************/
//
// This callback will get called if errors occur during our Rendezvous registration process.
// The most common error is the kCFNetServicesErrorCollision error which will occur
// if you're trying to register a service with the same name as a service that already
// exists on the network.  If we lets the OS choose the name by passing "" to CFNetServiceCreate()
// then this won't ever happen.
//

static void AdvertisingCallback(CFNetServiceRef theService, CFStreamError *error, void *info)
{
#pragma unused (info, theService)

				/* SEE IF AN ERROR OCCURRED */
				
    if (error->domain == kCFStreamErrorDomainNetServices)
    {
				/* REPORT ERROR TYPE */
				
     	switch(error->error)
        {
			case	kCFNetServicesErrorCollision:							// name collision occured
					DoAlert("\pA kCFNetServicesErrorCollision has occurred");
					break;
				
			default:														// some other error
					DoAlert("\pA kCFNetServicesError has occurred");
        }
		
				/* CLEANUP THE NET SERVICE */
				
        StopAdvertisingGame();
    }
}


/********************* STOP ADVERTISING GAME ****************************/
//
// Releases the Host's Registration Service object that we used to announce ourselves
// to the world.  Once this is stopped no new players will be able to find us.
//

static void StopAdvertisingGame(void)
{
	if (gMyRVService == nil)
		return;
    
    CFNetServiceUnscheduleFromRunLoop(gMyRVService,
									CFRunLoopGetCurrent(),
									kCFRunLoopCommonModes);
									
    CFNetServiceSetClient(gMyRVService, nil, nil);
    CFNetServiceCancel(gMyRVService);
    CFRelease(gMyRVService);

    gMyRVService = nil;
}


#pragma mark ___BROWSE W/ RENDEZVOUS___

/********************* START BROWSING FOR HOSTS **************************/
//
// This creates a network browser object which works asynchronously searching for
// our matching kServiceType on the network.  In other words it's looking
// for game hosts.
// 
// When a new Host is found or an existing Host goes away, our callback function
// HostBrowserCallback() will get called to handle it.
//

static void StartBrowsingForHosts(void)
{
    CFNetServiceClientContext clientContext = { 0, nil, nil, nil, nil };
    CFStreamError	error;
    Boolean			result;
    OSStatus		err = noErr;


	gNumHostServices = 0;								// clear the list of hosts


				/* CREATE A NETWORK BROWSER OBJECT */
				
    gServiceBrowserRef = CFNetServiceBrowserCreate(kCFAllocatorDefault,
												HostBrowserCallback,
												&clientContext);
    if (gServiceBrowserRef == nil)
		DoFatalAlert("\pCFNetServiceBrowserCreate() failed!");


				/* INSTALL THE BROWSER */
						
	CFNetServiceBrowserScheduleWithRunLoop(gServiceBrowserRef,
											CFRunLoopGetCurrent(),
											kCFRunLoopCommonModes);
	
	
			/* START SEARCHING THE DOMAIN FOR SERVICES */
				
	result = CFNetServiceBrowserSearchForServices(gServiceBrowserRef,
											CFSTR(""),					// in default domain
											kServiceType,				// look for our service type
											&error);
	
				/* DID SOMETHING GO WRONG? */
				
	if (!result)
	{
		CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef,
												CFRunLoopGetCurrent(),
												kCFRunLoopCommonModes);
		CFRelease(gServiceBrowserRef);
		gServiceBrowserRef = nil;
		DoAlert("\pCFNetServiceBrowserUnscheduleFromRunLoop() failed!");
		fprintf(stderr, "CFNetServiceBrowserSearchForServices returned (domain = %d, error = %ld)\n", error.domain, error.error);
		err = error.error;
	}
}

/********************* HOST BROWSER CALLBACK **************************/
//
// You should do as little work as possible while in the Browser Callback because the
// mDNSResponder will timeoutif you don't extract items out of the message queue fast enough.  
//

static void HostBrowserCallback(CFNetServiceBrowserRef browser, CFOptionFlags flags,
								CFTypeRef domainOrService, CFStreamError* error, void* info)
{        
#pragma unused (browser, error, info)

    if (flags & kCFNetServiceFlagIsDomain)
        return;

    if (flags & kCFNetServiceFlagRemove)
    {
		printf("host went away...\n");
        LostAHost((CFNetServiceRef)domainOrService);
    }
    else
    {
		printf("new host...\n");
        FoundAHost((CFNetServiceRef)domainOrService);
    }
}


/************************ FOUND A HOST ********************************/
//
// Our Browser Callback has found a matching service (game host), so let's
// add it to our list and then update the Join Game dialog's host list.
//
// Note:  we may get multiple hits on the same service if there
// are multiple connections to it (ie.  Airport + Ethernet).
//

static void FoundAHost(CFNetServiceRef service)
{
Boolean		status;

			/* SEE IF WE'VE GOT TOO MANY HOSTS */
			
	if (gNumHostServices >= MAX_HOSTS_TO_FIND)
		return;
		

				/* GET THE SERVICE'S NAME */
				
	CFStringRef name = CFNetServiceGetName(service);

	status = CFStringGetCString(name,
								gHostList[gNumHostServices].name,
								MAX_NAME_SIZE,
								kCFStringEncodingMacRoman);

	if (!status)
		DoFatalAlert("\pCFStringGetCString failed!");

		
			/* GET THE SERVICE'S PORT ID & IP ADDRESS */
					
	gHostList[gNumHostServices].portNum = GetIPAddressOfHost(service, gHostList[gNumHostServices].ipAddress);
		
	gNumHostServices++;
	
									
			/* UPDATE JOIN-GAME DIALOG'S HOST MENU */
			
	BuildHostListMenu();
}



/********************* LOST A HOST ********************************/
//
// If a service (game host) goes away, this removes it from our list,
// and updates the Join Game dialog's host list.
//

static void LostAHost(CFNetServiceRef service)
{
	int		i, size;
	char	name[MAX_NAME_SIZE];
	Boolean	status;

				/**************************/
				/* GET THE SERVICE'S NAME */
				/**************************/
				
	CFStringRef nameRef = CFNetServiceGetName(service);

	status = CFStringGetCString(nameRef,
								name,
								MAX_NAME_SIZE,
								kCFStringEncodingMacRoman);

	if (!status)
		DoFatalAlert("\pCFStringGetCString failed!");

	
			/***************************************/
			/* SEARCH FOR THIS SERVICE IN OUR LIST */
			/***************************************/
			
	for (i = 0; i < gNumHostServices; i++)
	{
		if (strcmp(name, gHostList[i].name) == 0)					// do names match?
		{
			printf("removing host from list...\n");
			
			/* REMOVE THIS ARRAY ELEMENT BY SHIFTING THE ARRAY DOWN */
					
			size = sizeof(HostInfoType) * (MAX_HOSTS_TO_FIND - 1 - i);			
					
			BlockMove(&gHostList[i+1],
					&gHostList[i],
					size);
		
			gNumHostServices--;
			
				/* REBUILD OUR MENU */
				
			BuildHostListMenu();
			break;
		}
	}
}



/******************* GET IP ADDRESS OF HOST ***********************/
//
// Given the input CFNetServiceRef, this resolves that service's address,
// and then extracts the IP address from it.
//
// INPUT:	service = the service of host who's IP addr we want.
//
// OUTPUT:	outString = c string containing the IP address in the form "x.xx.xx.xx"
//			portNum = the port ID # of the address
//

static int GetIPAddressOfHost(CFNetServiceRef service, char *outString)
{
	int				portNum;
	CFArrayRef		addrList;
	CFDataRef		addrDataRef;
	struct sockaddr *theIPAddr;
				
				/* RESOLVE THE ADDRESS */
						
	if (!CFNetServiceResolve(service, nil))
		DoFatalAlert("\pCFNetServiceResolve() failed!");
	
	
				/* GET THE ADDRESS DATA */
				//
				// This actually returns an array of addresses, but since
				// we know that we're only looking for a single IP address
				// we just check the first array entry.
				//
				
	addrList = CFNetServiceGetAddressing(service);
	if (addrList == nil)
		DoFatalAlert("\pCFNetServiceGetAddressing() failed!");
		
	if (CFArrayGetCount(addrList) < 1)							// make sure there's something in there
		DoFatalAlert("\pThere is nothing in the addrList!");

		
	addrDataRef = CFArrayGetValueAtIndex(addrList, 0);			// get data from 1st element in array
		
		
				/* GET A POINTER TO THE IP ADDRESS */
				//
				// The IP address is in the sockaddr structure.
				// The port ID # is in the first two bytes of the sa_data list,
				// and the remaining 4 bytes contain the IP address 2.3.4.5
				//
				
	theIPAddr = (void *)CFDataGetBytePtr(addrDataRef);					// get ptr to the sockaddr data in the CDDataRef object
		
		
				/* CONVERT THE IP NUMBER TO A C STRING */

	ConvertIPAddressToString(theIPAddr, outString);

		
				/* CALCULATE THE PORT ID # */
		
	portNum = (u_long)(theIPAddr->sa_data[0]) << 8;				// combine two bytes to form this 16-bit #
	portNum |= (u_long)(theIPAddr->sa_data[1]);
		
	
						
	return(portNum);
}



/********************* STOP BROWSING FOR HOSTS ****************************/
//
// Releases the Browser Service object that we used to look for Hosts.
//

static void StopBrowsingForHosts(void)
{
	if (gServiceBrowserRef == nil)
		return;
    
	CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef,
											CFRunLoopGetCurrent(),
											kCFRunLoopCommonModes);
									
    CFRelease(gServiceBrowserRef);

    gServiceBrowserRef = nil;
}



#pragma mark ___BSD SOCKET CONNECTIONS___



/**************** CREATE HOST'S LISTENER SOCKET ********************/
//
// This will establish a BSD listener socket which can accept "calls"
// from other clients wanting to join our net game.  Call this if we want
// to be the host/server of the net game.
//
// This uses the computer's name (from the System Prefs) to name the socket,
// so that's the string that other's will see when they try to join our net game.
//
// OUTPUT:	gHostListenerSocket = the listener socket which is waiting for
//						connection requests.
//

static void CreateHostsListenerSocket(void)
{
	struct sockaddr_in	socketAddr;
		
		
			/* CREATE SOCKET */
			
	gHostListenerSocket = socket(AF_INET, SOCK_STREAM, 0);	// create streaming socket
	if (gHostListenerSocket < 0)									
		DoFatalAlert("\psocket() failed!");

//	flags = fcntl(theSocket, F_GETFL, 0);			// get socket's flags
//	flags |= O_NONBLOCK;							// set the non-blocking flag
//	fcntl(theSocket, F_SETFL, flags);				// update flags


		/* SET SOCKET ADDR INFO */
			
	bzero( &socketAddr, sizeof(socketAddr) );			// clear addr
	
	socketAddr.sin_family		= AF_INET;				// internet address
	socketAddr.sin_port			= htons(MY_PORT_NUM);	// our arbitrary port ID #
	socketAddr.sin_addr.s_addr	= htonl(INADDR_ANY);	// allow connection on any type of network (firewire, ethernet, airport, etc)
				
				
			/* BIND THE SOCKET */
			//
			// If the port ID is already being used then bind will fail.
			//
			
	if (bind(gHostListenerSocket, (struct sockaddr *)&socketAddr,	sizeof(socketAddr)) < 0)
	{
		close(gHostListenerSocket);
		DoFatalAlert("\pbind() failed!  The port # is probably already in use.  Wait 20 seconds and this should clear up.");
	}

	
			/* MAKE SOCKET LISTEN-ONLY */
			
	listen(gHostListenerSocket, MAX_PLAYERS);		// max # of queued connects
	
}


/**************** CHECK FOR CONNECTION REQUEST FROM CLIENT ***************/
//
// If a client connection reqeuest is waiting on our listener socket then
// accept the connection.
//
// INPUT:	gHostListenerSocket = the socket created in CreateHostsListenerSocket()
//							which is listening for connection requests.
//
// OUTPUT:	connectionSocket = the new socket created when we accept
//								the connection, or -1 if no connection waiting.
//

static int CheckForConnectionRequestFromClient(void)
{
	int	connectionSocket;

			/* SEE IF THERE'S A CONNECTION REQUEST WAITING FOR US */
				
	if (!IsDataWaitingToBeRead(gHostListenerSocket))
		return(-1);
		
		
			/* ACCEPT A CONNECTION (IF ANY) */
			
	connectionSocket = accept(gHostListenerSocket, nil, nil);
	
					
	return(connectionSocket);
}


/***************** SEND CONNECTION REQUEST TO HOST ***************/
//
// This creates a BSD socket which clients use to connect to the
// remote server / game host.  If the connection is successful then we can
// immediately start sending and receiving data from the server, provided
// that the server received our connection request and accepted it.
//
// INPUT: ipAddrString = string containing the IP address of the server we
//						want to connect to.  ie.  "128.0.0.1"
//

static void	SendConnectionRequestToHost(char *ipAddrString)
{
	struct sockaddr_in	hostAddr;


		/* CREATE A CONNECTION SOCKET */
		//
		// This creates a TCP/IP streaming socket.
		//
		
	gConnectionToHost = socket(AF_INET, SOCK_STREAM, nil);
	if (gConnectionToHost < 0)
		DoFatalAlert("\psocket() failed");


		/* SET ADDRESS INFO OF HOST THAT WE WANT TO CONNECT TO */
			
	bzero(&hostAddr, sizeof(hostAddr));				// clear address info
	
	hostAddr.sin_family = AF_INET;					// internet
	hostAddr.sin_port = htons(MY_PORT_NUM);			// port #

	inet_pton(AF_INET, ipAddrString,					// convert IP string to in_addr
				&hostAddr.sin_addr);

	
		/* ISSUE THE CONNECTION REQUEST */
		//
		// connect() only issues the connect request to the server, but
		// it's up to the host to accept() it.
		//
		
	if (connect(gConnectionToHost, (struct sockaddr *)&hostAddr, sizeof(hostAddr)) < 0)
		DoFatalAlert("\pconnect() failed!  The host vanished!");
		
				
		
			/* SEND A MESSAGE TO THE HOST TO TELL IT WHAT OUR NAME IS */
			//
			// gMyName is a c-string which we acquired in the Join Game dialog
			//
	
	if (WriteNetData(gConnectionToHost, gMyName, MAX_NAME_SIZE) <= 0)
		DoFatalAlert("\pWriteNetData() failed!");
		
}


#pragma mark ___READ/WRITE DATA CALLS___


/****************** IS DATA WAITING TO BE READ *******************/
//
// Returns true if there is data on the socket that we can read().
//

Boolean	IsDataWaitingToBeRead(int socket)
{
	struct fd_set		connectionSet;
	struct timeval		timeout;
	int					result;

	if (socket == -1)						// make sure socket is valid
		return(false);

			/* SET TIMEOUT & SET VALUES */
			
	timeout.tv_sec			= 0;
	timeout.tv_usec			= 0;			// don't block at all
			
	FD_ZERO(&connectionSet);				// use macro to clear the set
	FD_SET(socket, &connectionSet);			// use macro to set socket
		
		
		/* CALL SELECT TO SEE IF THERE'S DATA WAITING ON THE SOCKET */

			
	result = select(socket+1,				// this is the "highest file descriptor in all given sets plus one"
					&connectionSet,			// pass set containing our connection socket info
					nil,
					nil,
					&timeout );				// pass timeout info
	

			/* CHECK RESULTS */
	
	if (result < 0)
		return(false);

	if (result > 0)								// was there an event on the socket?
	{
		if (FD_ISSET(socket, &connectionSet))	// is there data waiting to be read?
			return(true);
		else
			return(false);
	}
	
	return(false);
}



/****************** IS SOCKET READY FOR WRITE *******************/
//
// Returns true if the socket is ready to accept a write() call
//

static Boolean	IsSocketReadyForWrite(int socket)
{
	struct fd_set		connectionSet;
	struct timeval		timeout;
	int					result;

	if (socket == -1)						// make sure socket is valid
		return(false);


			/* SET TIMEOUT & SET VALUES */
			
	timeout.tv_sec			= 0;
	timeout.tv_usec			= 0;			// don't block at all
			
	FD_ZERO(&connectionSet);				// use macro to clear the set
	FD_SET(socket, &connectionSet);			// use macro to set socket
		
		
		/* CALL SELECT TO SEE IF THERE'S DATA WAITING ON THE SOCKET */

			
	result = select(socket+1,				// this is the "highest file descriptor in all given sets plus one"
					nil,
					&connectionSet,			// pass set containing our connection socket info
					nil,
					&timeout );				// pass timeout info
	

			/* CHECK RESULTS */
	
	if (result < 0)
		return(false);

	if (result > 0)								// was there an event on the socket?
	{
		if (FD_ISSET(socket, &connectionSet))	// is socket ready?
			return(true);
		else
			return(false);
	}
	
	return(false);
}




/***************** READ NET DATA *********************/
//
// This function is used read() data on the given socket.
// A read() call is not guaranteed to actually read the # of bytes requested,
// so this function loops until all of the data is read. 
//
// INPUT:		socket = connection socket
//				buffer = ptr to buffer to hold data
//				numBytes = # of bytes to read
//

int ReadNetData(int socket, void *buffer, int numBytes)
{
	Ptr		bytes = buffer;								// Ptr to buffer
	int		count, n;

	n = count = 0;

	while (count < numBytes)							// loop until we've got all the bytes
	{
		n = recv(socket, bytes, numBytes - count, 0);		// read data
		
		if (n > 0)										// we got some bytes
		{
			count += n;									// inc byte count
			bytes += n;									// inc ptr to buffer
		}
		else
		if (n < 0)											// error
			return(-1);
	}
		
	return(count);
}


/***************** WRITE NET DATA *********************/
//
// This function is used write() data on the given socket.
// A write() call is not guaranteed to actually write the # of bytes requested,
// so this function loops until all of the data is written. 
//
// INPUT:		socket = connection socket
//				buffer = ptr to buffer to hold data
//				numBytes = # of bytes to write
//

int WriteNetData(int socket, void *buffer, int numBytes)
{
	Ptr		bytes = buffer;								// Ptr to buffer
	int		count, n;

	n = count = 0;

	while (count < numBytes)							// loop until we've sent all the bytes
	{
		n = send(socket, bytes, numBytes - count, 0);		// write data
		
		if (n > 0)										// we wrote some bytes
		{
			count += n;									// inc byte count
			bytes += n;									// inc ptr to buffer
		}
		else
		if (n < 0)											// error
			return(-1);
	}
		
	return(count);
}


#pragma mark -

/********************** SEND START-GAME SIGNALS TO CLIENTS ********************/
//
// Sends a block of data to each client to tell them that we're ready to start playing.
// The block of data contains a list of all of the players.  For each client that
// we send this data to, we also set a Player # value so that each client knows which player
// number they are.
//
// INPUT: hostCancelled = this is true if the host cancelled the game.  In this
//							case we still send data to the clients, but pass 0 players
//							so that they'll know to bail.
//

static void SendStartGameSignalsToClients(Boolean hostCancelled)
{
	int					client;
	StartGameDataType	data;
	
	printf("Sending Start Game signals...\n");


			/**************************************************/
			/* BUILD THE BLOCK OF DATA TO SEND TO EACH CLIENT */
			/**************************************************/

	gMyPlayerNum = 0;											// host is always player #0
	
	if (hostCancelled)
		data.numPlayers = 0;
	else
		data.numPlayers = gNumPlayers = gNumClients+1;				// set # players in game


			/* PLAYER #0 IS ALWAYS THE HOST, SO PUT THAT DATA IN THERE */
			
	strcpy(data.names[0], gMyName);
	
				/* NOW ADD ALL CLIENT NAMES TO THE LIST */
				
	for (client = 0; client < gNumClients; client++)
	{
		strcpy(data.names[client+1], gClientList[client].name);
	}


				/**************************************/
				/* SEND THE DATA BLOCK TO EACH CLIENT */
				/**************************************/


	for (client = 0; client < gNumClients; client++)
	{
		int	clientSocket = gClientList[client].connectionSocket;	// get socket of this client
		
		data.youArePlayerNum = client + 1;							// set player # for this client
	
		if (WriteNetData(clientSocket, &data, sizeof(data)) <= 0)		// send the data
			DoFatalAlert("\pWriteNetData() failed!");
	}
}


/********************** WAIT FOR START-GAME SIGNAL FROM HOST ********************/
//
// Calls ReadNetData() which will block us until the start-game data is recieved from the Host.
// The Host will send us this data once the Start Game button is pressed in the Host dialog.
//
// It's possible that the Host will cancel while we're waiting for this data.  Even if
// this happens, the host will still send the signal, but the # players will be 0.
// We check that later to bail out.
//

static void WaitForStartGameSignalFromHost(void)
{
	StartGameDataType	data;
	int					i;
	
	printf("Waiting for Host to send us a Start Game signal...\n");

	
				/* READ THE START-GAME DATA */
				
	if (ReadNetData(gConnectionToHost, &data, sizeof(data)) <= 0)
		DoFatalAlert("\pReadNetData() failed reading start game signal!");
	
	
				/* DISSEMINATE THE DATA */
					
	gNumPlayers = data.numPlayers;						// get # players in net game
	gMyPlayerNum = data.youArePlayerNum;				// get my player #	
	
	
	printf("These are the names of all of the players the Host found:\n");
	
	for (i = 0; i < gNumPlayers; i++)					// print all the player names to the console
	{
		printf(data.names[i]);
		printf("\n");	
	}
}

#pragma mark -


/********************** SEND READY SIGNALS TO CLIENTS ********************/
//
// This just sends a kNetMessageType_PlayerReady to each client letting them
// know that the Host's game has started up and it's ok to start sending data over.
//

void SendReadySignalSignalsToClients(void)
{
int			client;
static Byte	messageType = kNetMessageType_PlayerReady;	// send this message type to each client
	
	for (client = 0; client < gNumClients; client++)
	{
		if (WriteNetData(gClientList[client].connectionSocket, &messageType, 1) <= 0)
				DoFatalAlert("\pWriteNetData() failed!");
	}
}


/********************** SEND READY SIGNAL TO HOST ********************/
//
// This just sends a kNetMessageType_PlayerReady to the Host letting it
// know that this Client's game has started up and it's ok to start sending data over.
//

void SendReadySignalSignalToHost(void)
{
	static Byte	messageType = kNetMessageType_PlayerReady;	// send this message type to each client
	
	if (WriteNetData(gConnectionToHost, &messageType, 1) <= 0)
		DoFatalAlert("\pWriteNetData() failed!");
}





#pragma mark -

/**************** SEND MY COORDINATES TO HOST *******************/
//
// If we're a Client then we call this once per frame to let the host
// know where we are.  The host will then in turn pass this coordinate info
// on to all of the clients.
//

void SendMyCoordinatesToHost(void)
{
Byte		messageType, playerNum;
	
	if (gConnectionToHost == -1)				// verify socket
		return;
	
	if (gReadyCount == 0)						// don't send data until host is ready
		return;
	
	
			/* CHECK THE TIMER TO SEE IF WE CAN SEND DATA NOW */
			//
			// We don't want to flood the network with data if the game is running
			// at a high frame rate, so here we limit how often we send the data.
			//
			
	gSendDataTimer -= gOneOverFramesPerSecond;
	if (gSendDataTimer > 0.0f)
		return;
		
	gSendDataTimer += 1.0/DATA_RATE;				// send up to n times per second
	
	
			/* SEE IF OK TO WRITE DATA TO SOCKET */
			
	if (!IsSocketReadyForWrite(gConnectionToHost))
	{
		gSendDataTimer = 0;							// nope, so try again next time
		return;
	}
	
	
		/* FIRST SEND OUR MESSAGE-TYPE BYTE */
		//
		// This byte lets the Host know what kind of data is to follow
		//
		
	messageType = kNetMessageType_ClientPlayerCoord;
		
	if (WriteNetData(gConnectionToHost, &messageType, 1) <= 0)
		DoFatalAlert("\pWriteNetData() failed!");


			/* FOLLOW THAT WITH PLAYER # */

	playerNum = gMyPlayerNum;
	if (WriteNetData(gConnectionToHost, &playerNum, 1) <= 0)
		DoFatalAlert("\pWriteNetData() failed!");
	
	
			/* AND FINALLY THE PLAYER COORD */

	if (WriteNetData(gConnectionToHost, &gPlayerCoords[playerNum], sizeof(OGLPoint3D)) <= 0)
		DoFatalAlert("\pWriteNetData() failed!");
		

			/* FLUSH THE DATA BY WRITING A LARGE BUFFER */
			
	WriteNetData(gConnectionToHost, &gPadding, PADDING_SIZE);
		
}


/***************** READ PLAYER COORDINATE FROM CLIENT ********************/
//
// The host calls this when there is a kNetMessageType_ClientPlayerCoord message
// waiting.  The data contains a player # followed by an x,y,z coordinate for
// that player.
//
// INPUT:	socket = the connection socket to the client 
//

static void ReadPlayerCoordinateFromClient(int socket)
{
	Byte	playerNum;
	
		
			/* READ THE PLAYER # */

	if (ReadNetData(socket, &playerNum, 1) <= 0)
		DoFatalAlert("\pReadNetData() failed reading playerNum!");
	
	
			/* THEN READ THE COORD */
			
	if (ReadNetData(socket, &gPlayerCoords[playerNum], sizeof(OGLPoint3D)) <= 0)
		DoFatalAlert("\pReadNetData() failed reading client coord!");


			/* READ THE PADDING GARBAGE THAT WE USED TO FLUSH THE BUFFER */

	ReadNetData(socket, &gPadding, PADDING_SIZE);

}



/******************** READ PLAYER COORDINATES LIST FROM HOST *********************/
//
// Clients end up calling this when a kNetMessageType_AllPlayersCoords is recieved.
// It reads in the coordinate data for all of the players sent by the Host in SendPlayerDataToClients().
//

static void ReadPlayerCoordinatesListFromHost(void)
{
OGLPoint3D	myCoord;


			/* KEEP A BACKUP OF MY COORDINATE */
			//
			// When we read the coord data from the host, the coord data we get
			// will overwrite our current coordinate, so let's back it up first
			//
			
	myCoord = gPlayerCoords[gMyPlayerNum];


			/* READ THE PLAYER COORD LIST */
			
	if (ReadNetData(gConnectionToHost, &gPlayerCoords[0], sizeof(OGLPoint3D) * gNumPlayers) <= 0)
		DoFatalAlert("\pReadNetData() failed sending gPlayerCoords[]!");


			/* READ THE PADDING GARBAGE THAT WE USED TO FLUSH THE BUFFER */

	ReadNetData(gConnectionToHost, &gPadding, PADDING_SIZE);


			/* RESTORE MY COORD */
			
	gPlayerCoords[gMyPlayerNum] = myCoord;
}




/**************** SEND PLAYER DATA TO CLIENTS *******************/
//
// If we're a Host then we call this once per frame to send
// each player's coordinate (x,y,z) data to each client, thus
// letting them know where each player is in the 3D world.
//

void SendPlayerDataToClients(void)
{
static Byte	messageType = kNetMessageType_AllPlayersCoords;	// send this message type to each client
short		i;
int			socket;		

	if (gReadyCount < gNumClients)				// don't send data until *all* clients are ready!
		return;



			/* CHECK THE TIMER TO SEE IF WE CAN SEND DATA NOW */
			//
			// We don't want to flood the network with data if the game is running
			// at a high frame rate, so here we limit how often we send the data.
			//
			
	gSendDataTimer -= gOneOverFramesPerSecond;
	if (gSendDataTimer > 0.0f)
		return;
		
	gSendDataTimer += 1.0f/DATA_RATE;				// send up to n times per second


			/****************************/
			/* SEND DATA TO EACH CLIENT */
			/****************************/
			
	for (i = 0; i < gNumClients; i++)
	{	
		socket = gClientList[i].connectionSocket;				// get connection socket to this client
		if (socket == -1)
			continue;
			
				/* SEE IF OK TO WRITE DATA TO SOCKET */
				
		if (!IsSocketReadyForWrite(socket))
			return;
	
		
			/* FIRST SEND OUR MESSAGE-TYPE BYTE */
			//
			// This byte lets the Client know what kind of data is to follow
			//
						
		if (WriteNetData(socket, &messageType, 1) <= 0)
			DoFatalAlert("\pWriteNetData() failed sending kNetMessageType_AllPlayersCoords!");

			
				/* THEN SEND THE LIST OF PLAYER COORDS */

		if (WriteNetData(socket, &gPlayerCoords[0], sizeof(OGLPoint3D) * gNumPlayers) <= 0)
			DoFatalAlert("\pWriteNetData() failed sending gPlayerCoords[]!");


			/* FLUSH THE DATA BY WRITING A LARGE BUFFER */

		WriteNetData(socket, &gPadding, PADDING_SIZE);
	}
	
	
}

#pragma mark -

/**************** SEND EXIT MESSAGE TO HOST *******************/
//
// The Client calls then when they're leaving the Host's net game
// to let the Host know we're gone.
//

void SendExitMessageToHost(void)
{
static Byte	messageType = kNetMessageType_PlayerExit;	// send this message type to each client
		
						
	if (WriteNetData(gConnectionToHost, &messageType, 1) <= 0)
		DoFatalAlert("\pWriteNetData() failed!");


			/* FLUSH THE DATA BY WRITING A LARGE BUFFER */
			
	WriteNetData(gConnectionToHost, &gPadding, PADDING_SIZE);
		
}


/**************** SEND EXIT MESSAGE TO CLIENTS *******************/
//
// If we're a Host then we call this to let all the Clients know that
// we've stopped the game.
//

void SendExitMessageToClients(void)
{
static Byte	messageType = kNetMessageType_PlayerExit;	// send this message type to each client
short		i;
int			socket;		


			/****************************/
			/* SEND DATA TO EACH CLIENT */
			/****************************/
			
	for (i = 0; i < gNumClients; i++)
	{	
		socket = gClientList[i].connectionSocket;				// get connection socket to this client
		if (socket == -1)										// make sure its valid (goes to -1 when client leaves the game)
			continue;
		
		
			/* SEND OUR MESSAGE-TYPE BYTE */
						
		if (WriteNetData(socket, &messageType, 1) <= 0)
			DoFatalAlert("\pWriteNetData() failed sending kNetMessageType_PlayerExit!");

			/* FLUSH THE DATA BY WRITING A LARGE BUFFER */

		WriteNetData(socket, &gPadding, PADDING_SIZE);
	}
		
}





#pragma mark ___ MAIN NET PROCESSING ___

/*************** PROCESS NET DATA FROM CLIENTS ***********************/
//
// This checks the connection socket for each client and if there is data waiting,
// then we read it and process it.
//

void ProcessNetDataFromClients(void)
{
short		i;
int			socket;
Byte		messageType;

	for (i = 0; i < gNumClients; i++)
	{	
		socket = gClientList[i].connectionSocket;				// get connection socket to this client
		if (socket == -1)										// make sure its valid (goes to -1 when client leaves the game)
			continue;
			
			
				/* SEE IF THERE'S DATA WAITING ON THIS SOCKET */
				
		while(IsDataWaitingToBeRead(socket))					// keep reading until nothing remains
		{
					
					/* YES, SO READ THE MESSAGE BYTE TO SEE WHAT IT IS */
											
			if (ReadNetData(socket, &messageType, 1) <= 0)
				DoFatalAlert("\pReadNetData() failed reading messageType!");


					/* HANDLE READING THE SPECIFIC MESSAGE DATA */
					
			switch(messageType)
			{
				case	kNetMessageType_PlayerReady:
						gReadyCount++;					// inc count of # of clients ready to receive data
						break;
			
				case	kNetMessageType_ClientPlayerCoord:
						ReadPlayerCoordinateFromClient(socket);
						break;
			
				case	kNetMessageType_PlayerExit:
						close(socket);							// close the connection to this Client
						DoAlert("\pA Client has left the game");
						gClientList[i].connectionSocket = -1;	
						goto next_client;
						break;
				default:
						DoFatalAlert("\pProcessNetDataFromClients: unknown messageType");		
			}
		}
next_client:;		
	}
}


/*************** PROCESS NET DATA FROM HOST ***********************/
//
// This checks the connection socket to the Host and if there is data waiting,
// then we read it and process it.
//

void ProcessNetDataFromHost(void)
{
Byte		messageType;

				/* SEE IF THERE'S DATA WAITING ON THIS SOCKET */
				
	while (IsDataWaitingToBeRead(gConnectionToHost))		// keep reading until nothing remains
	{							
				/* YES, SO READ THE MESSAGE BYTE TO SEE WHAT IT IS */
					
		if (ReadNetData(gConnectionToHost, &messageType, 1) <= 0)
			DoFatalAlert("\pReadNetData() failed reading messageType from Host!");


				/* HANDLE READING THE SPECIFIC MESSAGE DATA */
				
		switch(messageType)
		{
			case	kNetMessageType_PlayerReady:
					gReadyCount++;					// host is ready to receive data
					break;
					
			case	kNetMessageType_AllPlayersCoords:
					ReadPlayerCoordinatesListFromHost();
					break;
		
			case	kNetMessageType_PlayerExit:			
					close(gConnectionToHost);		// close the connection to the Host
					DoAlert("\pHost has terminated the game");
					CloseConnectionToHost();
					gQuitApplication = true;
					return;
		
			default:
					DoFatalAlert("\pProcessNetDataFromHost: unknown messageType");		
		}
	}
}







