#include "global.h"

LARGE_INTEGER lasttime;
LARGE_INTEGER timenow;
LARGE_INTEGER timerfreq;

GameInformation game;

extern float renderTime;

double minFrameTime = 0.050;
double maxFrameTime = 0.500;  // Disallow more than 1/2 sec updates to prevent jumping

/*
DWORD sysTimeNow;
DWORD sysTimeOld;
*/

double serverUpdateDelay = 0.050;
double nextServerUpdateTime;

void ConvertCompactToStandardInput(InputStatus_type& tgt, CompactInputStatus_type src)
{
	ZeroMemory(&tgt,sizeof(tgt));
	tgt.left		= 1==src.left;
	tgt.right	= 1==src.right;
	tgt.fire		= 1==src.fire;
	tgt.brake	= 1==src.brake;
	tgt.thrust	= 1==src.thrust;
	tgt.shield	= 1==src.shield;
	tgt.pause   = 1==src.pause;
	tgt.pause   = false;
}

extern int gDesiredTris;

void mainLoop(void)
{
	QueryPerformanceCounter(&timenow);
	/*
	sysTimeNow = timeGetTime();
	game.world.dtime = (sysTimeNow-sysTimeOld)/1000.0;
	*/
	game.world.dtime = (timenow.QuadPart-lasttime.QuadPart)/(double)timerfreq.QuadPart;

	// Limit update rate
	if (game.world.dtime < minFrameTime)		// Update once every 50ms
	{
		DWORD sleeptime = (minFrameTime-game.world.dtime)*1000;
		if (sleeptime > 0 && sleeptime < minFrameTime * 1000)
			Sleep(sleeptime);
	}
	else if (game.world.dtime > maxFrameTime)
		game.world.dtime = maxFrameTime;
		

	game.world.currentTime = timenow.QuadPart/(double)timerfreq.QuadPart;
	//game.world.currentTime = sysTimeNow / 1000.0;
	lasttime = timenow;
	//sysTimeOld = sysTimeNow;

	//-- Mandatory update functions:
	// Update input status
	checkInput();
	CheckSong();

	HTMSG_UPDATE_PLAYER tstatus;
	if (game.state == GS_PLAYING)
	{
		int i;
		InputStatus_type tinput;
		bool pauseReq = false;

		static bool chatKeyDown = false;
		if (input.chat && !game.hud.IsEditingMessage())
			chatKeyDown = true;
		if (chatKeyDown && !input.chat)
		{
			game.hud.StartEditingMessage();
			chatKeyDown = false;
		}

		for (i=0; i < MAX_PLAYERS; i++)
			if (game.netclient.m_PlayerSlotValid[i])
			{
				tstatus = game.netclient.getPlayerStatus(i);
				// Check to see if new information has arrived
				if (game.netclient.m_PlayerSlotUpdated[i])
				{
					/*
					const double InterpTime = DefaultStatusUpdateDelay*0.5;
					vec3f tgtpos = tstatus.pos + tstatus.vel * InterpTime + game.world.tank[i].model.acc * 0.5 * InterpTime * InterpTime;
					vec3f tgtvel = tstatus.vel + game.world.tank[i].model.acc * InterpTime;
					game.world.tank[i].model.setInterpParam(tgtpos,tgtvel,game.world.currentTime,InterpTime);
					*/
					if (i != LOCAL_PLAYER_IDX)
						game.world.tank[i].model.dir = tstatus.input.dir / 10.0;
					game.netclient.resetPlayerUpdatedFlag(i);
					game.world.tank[i].model.pos = tstatus.pos;
					game.world.tank[i].model.vel = tstatus.vel;

					if (tstatus.input.engine)
					{
						if (game.world.tank[i].hoverStatus == HOVER_OFF)
							game.world.tank[i].StartHover();
					}
					else
					{
						if (game.world.tank[i].hoverStatus == HOVER_GOING)
							game.world.tank[i].StopHover();
					}

					game.world.tank[i].armor = tstatus.armor;
					if (game.world.tank[i].state != tstatus.state)
					{
						printf("\nstate change (tank %d): %d -> %d\n",i,game.world.tank[i].state,tstatus.state);
						game.world.tank[i].SetState(tstatus.state);
						/*
						printf("\nSwitching state from %d to %d\n",game.world.tank[i].state,tstatus.state);
						game.world.tank[i].state = tstatus.state;
						game.world.tank[i].stateStartTime = game.world.currentTime;
						if (game.world.tank[i].state == TS_EXPLODING)
						*/
					}
					game.world.tank[i].kills = tstatus.kills;
					game.world.tank[i].deaths = tstatus.deaths;
				}

				if ((i==LOCAL_PLAYER_IDX)&&(!game.hud.IsEditingMessage()))
					//tinput.turnAmount = input.turnAmount;
					tinput = input;
				else
					ConvertCompactToStandardInput(tinput,tstatus.input);

				game.world.tank[i].HandleInput(game.world.dtime, tinput);

				if (tinput.pause)
					printf("\r* Player %d requesting pause ",i);

				pauseReq |= tinput.pause;
			}
		//game.world.tank[0].HandleInput(game.world.dtime, input);

		if (!pauseReq)
		{
			//-- 
			// Update based on dtime here
			game.update(game.world.dtime);
			//game.world.update(0.050);		// fix dtime
		}
		else
		{
			//printf("\r * PAUSE * ");
		}

		if (game.world.currentTime > nextServerUpdateTime && game.netclient.GetConnectionState() == HTCS_CONNECTED)
		{
			game.netclient.SendServerUpdate(input);
			nextServerUpdateTime = game.world.currentTime + serverUpdateDelay;
		}
	}
	else
		game.update(game.world.dtime);

	//printf("\r");
	TankObject& t = game.world.tank[LOCAL_PLAYER_IDX];
	//printf("H:%3.1f(%d:%d) A:%4d S:%2d ",t.GetHoverAmount(),t.hoverStatus,tstatus.input.engine,t.armor,t.state);
		/*
	for (int i=0;i<MAX_PLAYERS;i++)
		if (game.netclient.m_PlayerSlotValid[i])
			printf("P%d%c:%X ",i,game.world.tank[i].model.hasTargetPos?'*':' ',game.netclient.m_PlayerInfo[i].pid);

	MobileModel& m = game.world.tank[LOCAL_PLAYER_IDX].model;
	printf(" XP:[S:%.1f T:%.1f] XV:[S:%.1f T:%.1f C:%.1f] ",
		m.startPos[X],m.targetPos[X],m.startVel[X],m.targetVel[X],m.vel[X]);
		*/

	LARGE_INTEGER EndFrameTime;
	QueryPerformanceCounter(&EndFrameTime);
	float frameT = (EndFrameTime.QuadPart-timenow.QuadPart)/(float)timerfreq.QuadPart;
	/*
	DWORD EndFrameTime = timeGetTime();
	float frameT = (EndFrameTime - sysTimeNow)/1000.0;
	*/

	///* //------------------------------------------
	//-- Monitor framerate
	#define NFPS 50
	static float fps[NFPS];
	static float avgfps;
	static int fpsidx=0;
	avgfps -= fps[fpsidx];
	fps[fpsidx] = 1.0/game.world.dtime/NFPS;
	avgfps += fps[fpsidx++];
	fpsidx %= NFPS;
	vec3f dpos = game.world.tank[0].model.pos-game.world.eyepoint.pos + game.world.eyedyn.followerOffset;

	if (fpsidx==0)
	{
		/*
		printf("\rAvg fps: %5.1f  [%4.1f,%4.1f,%4.1f] dl=%4.1f  T1P[%4.f,%4.f] T2P[%4.f,%4.f] ",avgfps,
			game.world.eyepoint.vel[X],game.world.eyepoint.vel[Y],game.world.eyepoint.vel[Z], dpos.length(),
			game.world.tank[0].model.pos[X],game.world.tank[0].model.pos[Y],
			game.world.tank2.model.pos[X],game.world.tank2.model.pos[Y]);
		*/
		//printf(" Avg loop fps: %5.1f fps,  Update time: %5.1f us,  Render time: %5.1f ms \r",avgfps,1E6f*frameT,1E3f*renderTime);

		if (avgfps < 35 && gDesiredTris > 200)
			gDesiredTris *= 0.9;
		else if (avgfps > 40 && gDesiredTris < 5000)
			gDesiredTris *= 1.1;
		else if (avgfps > 60 && gDesiredTris < 10000)
			gDesiredTris *= 1.1;
		else if (avgfps > 80 && gDesiredTris < 20000)
			gDesiredTris *= 1.1;

		//printf(" Avg fps: %5.1f fps tgt Tris: %d \r",avgfps, gDesiredTris);
	}
	printf("\r");

	
	/*
	printf("\ra[%5.1f]v[%5.1f]p[%5.1f] v[%5.1f,%5.1f,%5.1f] dx[%5.1f,%5.1f,%5.1f] [%4.3f]",
		game.world.eyepoint.acc[X],game.world.eyepoint.vel[Y],game.world.eyepoint.pos[Z], 
		game.world.eyepoint.vel[X],game.world.eyepoint.vel[Y],game.world.eyepoint.vel[Z], 
		dpos[X],dpos[Y],dpos[Z], game.world.tank[0].model.pos[Z]);
	*/
	/*
	printf("\ra[%5.1f] v[%5.1f] p[%5.1f,%5.1f,%5.1f] t[%5.1f,%5.1f,%5.1f]  ",
		game.world.eyepoint.acc.length(),game.world.eyepoint.vel.length(), 
		game.world.eyepoint.pos[X],game.world.eyepoint.pos[Y],game.world.eyepoint.pos[Z], 
		game.world.tank[0].model.pos[X],game.world.tank[0].model.pos[Y],game.world.tank[0].model.pos[Z]);
	*/
	/*
	printf("\rv[%5.1f,%5.1f,%5.1f] p[%5.1f,%5.1f,%5.1f]  ",
		game.world.tank[0].model.vel[X],game.world.tank[0].model.vel[Y],game.world.tank[0].model.vel[Z],
		game.world.tank[0].model.pos[X],game.world.tank[0].model.pos[Y],game.world.tank[0].model.pos[Z]);
	*/
	/*
	printf("\ra[%5.1f] v[%5.1f]   T:v[%5.1f]  ",
		game.world.eyepoint.acc.length(),game.world.eyepoint.vel.length(), game.world.tank[0].model.vel.length());
	*/
	/*
	printf("\ra[%5.1f] v[%5.1f] p[%5.1f]  ",
		game.world.tank[0].model.acc[Z], game.world.tank[0].model.vel[Z], game.world.tank[0].model.pos[Z]-game.world.map.GetHeight(game.world.tank[0].model.pos) );
	*/
	/*
	int nactive  = 0;
	int i;
	for (i=0; i < MAX_PROJECTILES; i++)
		if (game.world.projectiles[i].active)
			nactive++;
	printf("\rActive bullets: %d ",nactive);
	*/
	//*/ //------------------------------------------

	// Time to redraw!
	glutarPostRedisplay();
	return;
}

int main(int argc, char* argv[])
{
	glutarInit(&argc, argv);

	//------------------------------------------------------------------------
	// SWITCH THE FOLLOWING LINES TO START IN FULL SCREEN MODE INSTEAD OF WINDOWED!
	//glutarInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUTAR_FULLSCREEN);
	glutarInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	//------------------------------------------------------------------------

	glutarInitWindowPosition(200,200);
	glutarConfig.bitdepth=32;
	glutarConfig.displayfreq=60;
	//*
	if (glutarConfig.fullscreen)	glutarInitWindowSize(800,600);
	else									glutarInitWindowSize(800,600);
	//*/
	//glutarInitWindowSize(400,300);

	// Register glut-based callbacks
	glutarKeyboardFunc(glutKeyboard);
	glutarMouseFunc(glutMouse);
	glutarDisplayFunc(display);
	glutarReshapeFunc(reshape);
	glutarIdleFunc(mainLoop);
	glutarFocusFunc(focusChange);		// Focus-change callback for DirectInput

	// Needs to be done sometime before any CoCreateInstance
	CoInitializeEx( NULL, COINIT_MULTITHREADED );

	// Initalize sound
	SetupSound();

	// Initialize game variables
	game.Initialize();
	game.LoadData();
	game.world.Reset();

	glutarCreateWindow("HoverTank");
	initGL();

	// Initialize DirectInput
	printf("Initializing DirectInput: ");
	setupInput();
	printf("done.");

	printf("\n>> Initializing server...\n");
	game.netserver.Initialize();
	/*
	if (game.netserver.StartServer())
		printf(">> Server started!\n");
	else
	  printf(">> Could not start server!  :(\n");
	*/

	printf("\n*\n");
	printf("* [m] toggles mouse capture, [k] toggles keyboard capture\n");
	printf("* [f] toggles fullscreen (lemme know if this works ;)\n");
	printf("* Once you start that game, the following keys work:\n");
	printf("* [e] toggles your engine status\n");
	printf("* [UP] thrusts, [DOWN] brakes, [LEFT] & [RIGHT] turn\n");
	printf("* [SHIFT] fires\n*\n");

	// Initialize the high-speed system timer
	QueryPerformanceFrequency(&timerfreq);
	QueryPerformanceCounter(&lasttime);

	nextServerUpdateTime = 0;

	// Grab keyboard and mouse input
	keyboardAcquire(true);
	mouseAcquire(true);

	game.world.settings.sound = true;

	// Unlike glut, my function will return when done
	glutarMainLoop();

	if (game.netserver.IsServerHosting())
	{
		printf("\n>> Shutting down server...\n");
		game.netserver.StopServer();
		printf("\n>> Server stopped.\n");
	}

	// Clean up
	cleanupInput();
	ShutdownSound();

	return 0;
}
