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


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

static void InitApplication(void);
static void GetGameNib(void);
static MOGroupObject *CreateAnObject(short bg3dGroup, short bg3dModel, float scale, OGLPoint3D *coord);
static void CreatePlayerObjects(void);
static void UpdatePlayerMatrices(void);
static void MoveMyPlayer(void);


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

extern	CGDirectDisplayID			gCGDisplayID;
extern	int							gFrameCounter, gMouseDeltaX;
extern	GLuint						gVertexArrayRangeObjects[];
extern	float						gFramesPerSecond, gOneOverFramesPerSecond;
extern	float						gGammaBrightness;
extern	long						gMouseWheelDelta;
extern	PrefsType					gGamePrefs;
extern	InputNeedType				gControlNeeds[];
extern	MetaObjectPtr				gBG3DGroupList[NUM_MODEL_GROUPS][MAX_OBJECTS_IN_GROUP];
extern	short						gNumPlayers, gMyPlayerNum;
extern	Boolean						gHostGame;


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


#define	PLAYER_SCALE	.5



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

Boolean				gQuitApplication = false;

CFBundleRef 		gBundle = nil;
IBNibRef 			gNibs = nil;

Boolean				gPantherOrBetter = false;

short				gPrefsFolderVRefNum;
long				gPrefsFolderDirID;


MOGroupObject		*gPlayerObjects[MAX_PLAYERS];
OGLPoint3D			gPlayerCoords[MAX_PLAYERS];


/******************** INIT APPLICATION ************************/
//
// Called from main() to setup all the initial application stuff.
// 

static void InitApplication(void)
{
	NumVersion	vers;
	Boolean		oldOS = false;
	OSErr		iErr;
	long		createdDirID;

			/* DO SOME RESOURCE INITS */
			
	SetDefaultDirectory();						// set app's default directory
	GetGameNib();								// get ref to our .nib file
	GetMyApplicationResourcesFolder();			// get FSSpec and FSRef to our Resources folder
	
	
	
			/* FIND USER'S PREFERENCES FOLDER */
			
	iErr = FindFolder(kOnSystemDisk,kPreferencesFolderType,kDontCreateFolder,		// locate the folder
					&gPrefsFolderVRefNum,&gPrefsFolderDirID);
	if (iErr != noErr)
		DoAlert("\pCannot locate the Preferences folder.");

	DirCreate(gPrefsFolderVRefNum,gPrefsFolderDirID,"\pMyGame",&createdDirID);		// make "MyGame" folder in there

	
		/* DETERMINE IF RUNNING ON AT LEAST OS 10.3 (Panther) */
		//
		// We need to know this to work around bugs in the HID Manager
		//

	Gestalt(gestaltSystemVersion,(long *)&vers);
				
	if (vers.stage >= 0x10)													// see if at least OS 10
	{
		if ((vers.stage == 0x10) && (vers.nonRelRev < 0x20))				// must be at least OS 10.2 !!!
			oldOS = true; 
		else
		{
			if ((vers.stage == 0x10) && (vers.nonRelRev >= 0x30))			// see if 10.3 or higher
				gPantherOrBetter = true;
			else
				gPantherOrBetter = false;
		}
	}
	else
		oldOS = true; 
	
	if (oldOS)
		DoFatalAlert("\pThis game requires at least MacOS 10.2.6."); 
	
	
	
			/* GET THE LIST OF HID DEVICES & ELEMENTS */
			
	InitMyHIDManagerStuff();					// must do before loading prefs!

			/* INIT & LOAD PREFERENCES */
	
	InitDefaultPrefs();							// init to defaults
	LoadOurPrefs();								// then try to load the saved settings
	
	DoInputConfigDialog();						// bring up the config dialog

	
	
			/*******************/
			/* DO VIDEO CONFIG */
			/*******************/
			
	DoScreenModeDialog();						// ask user for mode
				
	if (gGamePrefs.playInWindow)				// play in window or full screen?
		CreateGameWindow();		
	else	
		SwitchDisplayMode(gGamePrefs.screenWidth, gGamePrefs.screenHeight, gGamePrefs.depth);	
	
	CalcDisplayVRAM();							// calc amount of VRAM on this display
	
	InitGammaValues();							// init gamma values
	
	
			/*****************************/
			/* DO NETWORK CONFIG DIALOGS */
			/*****************************/
			//
			// When we exit SetupNetGame() we're either
			// hosting a net game or we've joined one.
			//
				
	SetupNetGame();
	
	

			/********************/
			/* GET OPENGL GOING */
			/********************/
				
	OGL_CreateDrawContext();					// create the draw context
	OGL_InitFont();								// init an AGL font to use
	InitBG3DManager();							// init the BG3D containers
	
	
	
		/* INSTALL OUR EVENT HANDLER CALLBACK */
			
	InitMyEventHandler();

	
			/* INIT AUDIO SYSTEM */
			
	InitAudio();
	
	

}


/******************** GET GAME NIB *********************/
//
// Our dialog resources are all stored in the Game.nib file.
// This code creates a reference to that .nib file so that we can
// easily get to the resources whenever we need to.
//

static void GetGameNib(void)
{
OSStatus	iErr;
			
	gBundle = CFBundleGetMainBundle();
	if (gBundle == nil)
		DoFatalAlert("\pCannot locate Main Bundle!");
		
	iErr = CreateNibReferenceWithCFBundle(gBundle, CFSTR("Game"), &gNibs);			// note:  we don't need the ".nib" extension in the filename
	if (iErr != noErr)
		DoFatalAlert("\pCreateNibReferenceWithCFBundle failed!");
}

#pragma mark -

/****************** CREATE PLAYER OBJECTS **********************/

static void CreatePlayerObjects(void)
{
short		i;
OGLPoint3D	coord = {0,0,0};
	
	for (i = 0; i < gNumPlayers; i++)
	{
		gPlayerCoords[i] = coord;
	
		gPlayerObjects[i] = CreateAnObject(MODEL_GROUP_SPHERES,	
									i & 0x3,					// use player # as model #
									PLAYER_SCALE,
									&coord);

		coord.x += 10.0f;
	}
}


/******************* CREATE AN OBJECT *******************/
//
// We create an MetaObject which represents and object that we want to draw in our scene.
//
// First we create a Group object to hold everything.  Then we add a Matrix object to the group
// and then we add a reference to the BG3D data to the group.  The BG3D data is another
// group object which contains all of the vertex and triangle data for the meshes making up
// the object.
//
// So, we're building an object that looks something like this:
//
//		BASE GROUP OBJECT
//					|
//				MATRIX OBJECT
//					|
// 				 BG3D GROUP
//						|
//						Mesh Data
//						Mesh Data
//						Mesh Data
//

static MOGroupObject *CreateAnObject(short bg3dGroup, short bg3dModel, float scale, OGLPoint3D *coord)
{
MOGroupObject	*baseGroupObj;
MOMatrixObject	*matrixObj;
OGLMatrix4x4	matrix;

				/* CREATE THE BASE GROUP OBJECT */
				
	baseGroupObj = MO_CreateNewObjectOfType(MO_TYPE_GROUP, 0, nil);


			/* CREATE A MATRIX OBJECT & ADD TO GROUP */

	OGLMatrix4x4_SetScale(&matrix, scale, scale, scale);		// initialize a transform matrix
	matrix.value[M03] = coord->x;								// set translation in the matrix
	matrix.value[M13] = coord->y;
	matrix.value[M23] = coord->z;

	matrixObj = MO_CreateNewObjectOfType(MO_TYPE_MATRIX,		// create matrix object from the matrix
										0,
										&matrix);
										
	MO_AppendToGroup(baseGroupObj, matrixObj);					// add matrix object to group


			/* APPEND THE BG3D MODEL DATA TO THE GROUP */
			
	MO_AppendToGroup(baseGroupObj, gBG3DGroupList[bg3dGroup][bg3dModel]);

	return(baseGroupObj);
}


/********************* MOVE MY PLAYER  **********************/

static void MoveMyPlayer(void)
{
short			p = gMyPlayerNum;


			/* READ INPUT VALUES TO MOVE THE PLAYER */			

	if (gControlNeeds[kNeed_Forward_Button].value != 0)
	{
		gPlayerCoords[p].y += 20.0f * gOneOverFramesPerSecond;
	}
	else
	if (gControlNeeds[kNeed_Backward_Button].value != 0)
	{
		gPlayerCoords[p].y -= 20.0f * gOneOverFramesPerSecond;
	}

	if (gControlNeeds[kNeed_Left_Button].value != 0)
	{
		gPlayerCoords[p].x -= 20.0f * gOneOverFramesPerSecond;
	}
	else
	if (gControlNeeds[kNeed_Right_Button].value != 0)
	{
		gPlayerCoords[p].x += 20.0f * gOneOverFramesPerSecond;
	}
	

}


/************************ UPDATE PLAYER MATRICES *************************/

static void UpdatePlayerMatrices(void)
{
short			p;
OGLMatrix4x4	*m;
MOMatrixObject	*matrixObj;

	for (p = 0; p < gNumPlayers; p++)
	{
		matrixObj = (MOMatrixObject *)gPlayerObjects[p]->objectData.groupContents[0];		// matrix object is 1st object in the base group
		m = &matrixObj->matrix;																// point to the actual matrix
		
		OGLMatrix4x4_SetScale(m, PLAYER_SCALE, PLAYER_SCALE, PLAYER_SCALE);					// set scale matrix
		m->value[M03] = gPlayerCoords[p].x;													// set translation in the matrix
		m->value[M13] = gPlayerCoords[p].y;
		m->value[M23] = gPlayerCoords[p].z;
	}
}


#pragma mark -

/***************** DRAW SCENE GEOMETRY *******************/
//
// Called from DrawOpenGLScene() when it's time to submit our
// geometry data to OpenGL.
//

void DrawSceneGeometry(void)
{
short	i;

			/* DRAW INFO IN THE UPPER-LEFT CORNER */
			
	OGL_DrawString("\pfps:", 20, 40);
	OGL_DrawInt(gFramesPerSecond, 70, 40);
	OGL_DrawString("\p#frames:", 20, 60);
	OGL_DrawInt(gFrameCounter, 70, 60);
	
	OGL_DrawString("\p# players:", 20, 80);
	OGL_DrawInt(gNumPlayers, 70, 80);
	OGL_DrawString("\pI am #: ", 20, 100);
	OGL_DrawInt(gMyPlayerNum, 70, 100);
		

			/* DRAW MESSAGE IN OPPOSITE CORNER */
			
	OGL_DrawString("\pType Command-Q to Quit", 500, 440);
		
	

			/* SET STATE */
	
	OGL_SetColor4f(1,1,1,1);	
	OGL_DisableTexture2D();
	OGL_DisableBlend();
	
	
			/****************/
			/* DRAW OBJECTS */
			/****************/
			//
			// To draw an object all we do is pass the Group Object containing the matrix and model to MO_DrawObject().
			// However, we first need to make sure our Vertex Array Range is correctly set for maximum performance
			// so we call glBindVertexArrayAPPLE().  
			//

	glBindVertexArrayAPPLE(gVertexArrayRangeObjects[MODEL_GROUP_SPHERES]);		// bind to the correct vertex array range memory for the GLOBAL models
	
	
	for (i = 0; i < gNumPlayers; i++)
	{
		MO_DrawObject(gPlayerObjects[i]);
	}
}




#pragma mark -


/********************* MY MAIN LOOP **********************/

static pascal void MyMainLoop (EventLoopTimerRef  theTimer, void* userData)
{
	#pragma unused (theTimer, userData)

			/* EXIT EVENT LOOP IF QUIT FLAG SET */
			
	if (gQuitApplication)
		QuitApplicationEventLoop();

				/*************************/
				/* DO 1 PASS OF THE LOOP */
				/*************************/
				
	CalcFramesPerSecond();					// calc FPS

			/* MOVE & UPDATE STUFF */
			
	PollAllHIDInputNeeds();					// update input values
	GammaFadeInOneFrame(2.0);				// do 2-second fade-in
	MoveMyPlayer();							// move my player
	
			/* HOST READS ANY CLIENT DATA & THEN SENDS PLAYER DATA TO CLIENTS */
			//
			// Each client is sending the Host data on each frame.  Here we
			// check our connection socket to each client and read any
			// data that may be waiting for us. 
			//
			// Then the Host sends update information to each client.
			//
			
	if (gHostGame)
	{		
		ProcessNetDataFromClients();		// read any pending network data from the Clients
		SendPlayerDataToClients();			// send all player data to the clients
	}
			/* CLIENT READS DATA FROM HOST & SENDS HIS DATA TO HOST */
	else
	{
		ProcessNetDataFromHost();			// read any pending network data from the Host
		SendMyCoordinatesToHost();			// send my coordinate to Host
	}

			/* UPDATE ALL PLAYER'S WITH THEIR LATEST COORD INFO */
			
	UpdatePlayerMatrices();

	
			/* DRAW SCENE */
				
	OGL_DrawScene();
				
}	


/************************ MAIN **************************/

int main(void)
{
	OGLPoint3D	cameraCoord = {0,0,40};
	OGLPoint3D	lookAt = {0,0,0};
	OGLVector3D	up = {0,1,0};
	

			/* INITIALIZE THINGS */

	InitApplication();
			
			
			
				/* DIM DISPLAY */
		
	if (!gGamePrefs.playInWindow)							// only gamma fade if full-screen
	{
		gGammaBrightness = 0;
		SetGammaFade(0);
	}
	
	
			/* PLAY A SONG */
			
//	PlaySong("\pSong.m4a", true);
	
	
			/* LOAD MODELS FROM BG3D FILE */
			
	ImportBG3D("\pSpheres.bg3d", MODEL_GROUP_SPHERES);				// load egg models
			
	
			/* CREATE PLAYER OBJECTS */
			
	CreatePlayerObjects();
	OGL_SetCamera(&cameraCoord, &lookAt, &up);
	
	
			/* WE'RE READY TO START RECEIVING NET DATA, SO LET OTHERS KNOW */
			
	if (gHostGame)
		SendReadySignalSignalsToClients();
	else
		SendReadySignalSignalToHost();


			/********************/
			/* DO THE MAIN LOOP */
			/********************/

	SetMyMainLoopEventTimer(MyMainLoop);		// install the main loop timer
	
	RunApplicationEventLoop();					// process all the timer events

	RemoveMyMainLoopEventTimer();				// we're all done, so cleanup
	
			
	
		/* DO A 1.0 SECOND FADE OUT */
			
	if (!gGamePrefs.playInWindow)					// don't do gamma fades if playing in window
		GammaFadeOut(1.0);
	
	
	CleanQuit();
}










