/*********************************************************************
*	System controler
*	SOURCE FILE
*	Autor:	Michal Jirouš
*	Datum: 1.9.2008
*	Soubor: sys_controller.cpp
*	Popis: Modul pracuje s udalostmi, ktere posila prislusnym soucastem
*			podle toho, v jakem stavu se prave nachazi. Dale provadi
*			vykreslovani a volani funkce beh stejnym zpusobem.
**********************************************************************/

#include "sys_controller.h"
#include "sys_console.h"
#include "ctrl_gamecontrol.h"
#include "game_mainmenu.h"
#include "mathematic.h"
#include "game.h"
#include "game_loadingscreen.h"
#include "sys_config.h"
#include "HUD.h"

using namespace systemController;

//Casove promenne pro periodicke opakovani vykreslovani a behu
Uint32	g_uiRunTimePeriod	= 0,	//Run period variable
		g_uiDrawTimePeriod	= 0;	//draw period variable

//Vypocty FPS
std::string g_sTime, g_sFps, g_sFrames;	//Times and FPS
Uint32 g_iNumFrames, g_iFrameTime;		//pouzivaji se ke zjisteni fps

//promenne se pouzivaji ke zjistovani konstanty, ktera vyjadruje zpozdeni pohybu dle FPS
float g_fDelayMultiplicator = 0.0f;
Uint32 g_uiDelayStartTime = 0;
float g_fTotalDelay = 0;

//function definition
void SysCtrl_SDL_Controller( SDL_Event &event );
void SysCtrl_SDL_Run();
bool SysCtrl_SDL_Draw();	//returns true if draw something

//Funkce pro nastavovani stavu systemu
void stopRunningLevel( int level, int newlevel );
void startRunningLevel( int level );

//promenne stavu systemu
int g_iRunningLevel = LOADGAME;
int g_iLastRunningLevel = g_iRunningLevel;

//nastavi novy stav systemu
void systemController::setRunningLevel( int level, bool overwrite_last )
{
	if( level == g_iRunningLevel || !isInRange( level, (int)LOADGAME, (int)HUD_SET ) )
		return;
	
	stopRunningLevel( g_iRunningLevel, level );
	startRunningLevel( level );
	if( overwrite_last )
		g_iLastRunningLevel	= g_iRunningLevel;
	g_iRunningLevel = level;
}

int systemController::getRunningLevel()
{
	return g_iRunningLevel;
}

Uint32 g_uiTotalFps = 0;

#include "error_handler.h"
//hlavni smycka systemu
void systemController::main()
{
	CONSOLE_PRINT_LN("Entering game loop.");
	bool bNoQuit = true;
	while( bNoQuit )
	{
		Uint32 startTime = SDL_GetTicks();
		Uint32 uiNoGameFrameStartTime = SDL_GetTicks();
		
		SDL_Event event;
		while( SDL_PollEvent(&event) )
		{
			if( event.type == SDL_QUIT )
			{
				bNoQuit = false;
				break;
			}

			SysCtrl_SDL_Controller( event );
		}

		
		SysCtrl_SDL_Run();
		if( !SysCtrl_SDL_Draw() )
			continue;

		g_iNumFrames++;

		//vypocet delay multiplicatoru (konstanta vyjadruje zpozdeni pohybu pri vyssich FPS
		Uint32 uiTotalTime = SDL_GetTicks() - g_uiDelayStartTime;	//zjistime cas od zacatku behu hry
	
		//na uvod: delay (zpozdeni) na cas GAME_DELAY je presne 1...pokud se ale stihnou vykreslit za dobu GAME_DELAY
		//2 snimky, tak nam vzniknou 2x delay o hodnote 0.5, cimz dosahneme napriklad plynulejsiho pohybu animaci
		//tuto hodnotu musime pocitat kazde kolo podle casu jednoho snimku. Ucelem je synchronizovat pohyb nezavisle
		//na FPS.

		//spocitame vysledne zpozdeni = kolik framu se vejde do celkoveho casu, kde 1 frame se ma zobrazovat
		//v periode GAME_DELAY
		float fTargetDelay = (float)uiTotalTime / (float)GAME_DELAY;	
	
		//delay = delaymultiplicator = zpozdeni, zde je to konstanta, kterou se musi nasobit kazdy pohyb
		//je zavisla primo na FPS
		//potom zjistime kolik delay nam chybi pricist k dosud provedemu, aby to dalo dobromady celkovy delay
		//tim dosahneme temer dokonale presnosti - nezalezi pouze na case 1 snimku, kde se da udelat chyba, 
		//ale pocitame s cilovou hodnotou a dosud sectenymi hodnotami. Jejich rozdil je vysledek.
		g_fDelayMultiplicator = fTargetDelay - g_fTotalDelay;

		//vysledny delay pricteme k celkovemu souctu, abychom dalsi kolo
		//mohli pokracovat ve vypoctu stejnym zpusobem
		g_fTotalDelay += g_fDelayMultiplicator;	

		//zde provedeme korekci kvuli sice velice nepravdepodobnemu, ale moznemu preteceni hodnot
		//pokud splnuji nasledujici podminku, muzeme upravit vztazne hodnoty tak, ze vysledek se nezmeni
		if( g_fTotalDelay > 1.0f && uiTotalTime > GAME_DELAY )
		{
			g_fTotalDelay -= 1.0f;
			g_uiDelayStartTime += GAME_DELAY;
		}
		

		//realny vypocet doby snimku
		Uint32 uiNoGameFrameTime = SDL_GetTicks() - uiNoGameFrameStartTime;
		
		if( g_iRunningLevel != GAME )
			SDL_Delay(DRAWTIME_PERIOD - min(DRAWTIME_PERIOD,uiNoGameFrameTime) );	
	

		//vypocty FPS
		Uint32 totalTime =  SDL_GetTicks() - startTime;

		//cas jednoho snimku ve stavu GAME, v jinem stavu je
		//to soucet casu snimku a klidoveho casu aplikace
		{
			ostringstream buf;
			buf << totalTime;
			g_sTime = buf.str();
			buf.clear();
		}

		//vypocet FPS z casu jednoho snimku
		if( totalTime != 0 )
		{
			ostringstream buf;
			Uint32 uiFps = 1000 / totalTime;
			buf << uiFps;
			g_sFps = buf.str();
			buf.clear();
		}

		//vypocet FPS dle poctu snimku za 1000ms = 1s
		if( g_iFrameTime + 1000 < SDL_GetTicks() )
		{
			g_uiTotalFps = g_iNumFrames;
			ostringstream buf;
			buf << g_iNumFrames;
			g_sFrames = buf.str();
			buf.clear();
			g_iNumFrames = 0;
			g_iFrameTime = SDL_GetTicks();
		}

		
	}
	CONSOLE_PRINT_LN("Leaving game loop.");
}
float g_frames_per_cycle = 0;
float g_delay_multiplicator = 0;
float systemController::getTimeDelayMultiplicator()
{
	return g_delay_multiplicator;
}

Uint32 systemController::getCurrentFps()
{
	return g_uiTotalFps;
}

void systemController::setLastRunningLevel()
{
	setRunningLevel( g_iLastRunningLevel );
}

//funkce zpracovava udalosti SDL a posila je modulum podle stavu systemu
void SysCtrl_SDL_Controller( SDL_Event &event )
{
	switch( g_iRunningLevel )
	{
		case LOADGAME:
		case LOADMAP:
			break;
		case CONSOLE:
			systemConsole::sys_console.HUDConsole_SDL_controller( event );
			break;
		case MAINMENU:
			mainMenu::main_menu.SDL_controller( event );
			break;
		case GAME:
			gameControl::game_control.eventController( event );
			break;
		case HUD_SET:
			HUD::hud.HUD_SDL_controller( event );
			break;

	}
}
Uint32 totaltime = 0;
Uint32 totalruns = 1;



//funkce beh - zde se periodicky volaji moduly zalozene na GFG, ktere jsou implementovany
//idealne pro interval 30ms, coz je konstanta RUNTIME_PERIOD
void SysCtrl_SDL_Run()
{
	g_frames_per_cycle++;
	totaltime = SDL_GetTicks();
	if( totalruns * RUNTIME_PERIOD < totaltime )
	{
		totalruns++;
		clock_t time = clock();
		g_delay_multiplicator = 1.0f / g_frames_per_cycle;
		g_frames_per_cycle = 0;
		loadingScreen::loading_screen.use_this_run();
		systemConsole::sys_console.HUDConsole_use_this_run();
		mainMenu::main_menu.use_this_run();
		HUD::hud.use_this_run();
		g_uiRunTimePeriod = SDL_GetTicks();
		//game.run();
		clock_t end = clock() - time;
		end = end;
	}	
	
}

//funkce pro vykreslovani
bool SysCtrl_SDL_Draw()
{
	//pouze ve hre potrebujeme plny vykon, jinak staci ~30fps vykreslovat periodicky - bude to tak brat mene vykonu
	if( (g_uiDrawTimePeriod + DRAWTIME_PERIOD) < SDL_GetTicks() || g_iRunningLevel == GAME )
	{
		//glClear( GL_COLOR_BUFFER_BIT );
		g_uiDrawTimePeriod = SDL_GetTicks();
		
		game.gameLoop();
		//game.drawscene();
		mainMenu::main_menu.use_this_draw();
		HUD::hud.use_this_draw();
		systemConsole::sys_console.HUDConsole_use_this_draw();
		loadingScreen::loading_screen.use_this_draw();
		
		
		/*glPushAttrib( GL_ENABLE_BIT );
			glDisable( GL_CULL_FACE );
			glDisable(GL_DEPTH_TEST);
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(0, systemconf::getSystemInfo().m_iResolutionWidth, 0, systemconf::getSystemInfo().m_iResolutionHeight, -1, 1);
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
		
			fontLibrary::simpleDrawText( "Frame time:" + g_sTime, 10.0f, 200.0f, "SERIF16" );
			fontLibrary::simpleDrawText( "Fps 1stM: " + g_sFps, 10.0f, 250.0f, "SERIF16" );
			fontLibrary::simpleDrawText( "Fps 2ndM: " + g_sFrames, 10.0f, 300.0f, "SERIF16" );
		glPopAttrib();*/
		
		glFlush();
		SDL_GL_SwapBuffers();
		
		
		return true;
	}	
	return false;
}



//pri zmene stavu se nejprve zrusi aktualni stav
void stopRunningLevel( int level, int newlevel )
{
	switch( g_iRunningLevel )
	{
		case LOADGAME:
		case LOADMAP:
			loadingScreen::loading_screen.fadeout();
			totaltime = SDL_GetTicks();
			totalruns = totaltime/RUNTIME_PERIOD+1;
			break;
		case CONSOLE:
			systemConsole::sys_console.fadeoutAnimated();
			break;
		case MAINMENU:
			mainMenu::main_menu.fadeout();
			break;
		case HUD_SET:
			HUD::hud.setState( HUD::NORMAL );
			HUD::hud.fadeout();
			break;
		case GAME:
			SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); 
			HUD::hud.fadeout();
			break;
	}
}

//pote se spusti novy stav
void startRunningLevel( int level )
{
	switch( level )
	{
		case LOADGAME:
		case LOADMAP:
			loadingScreen::loading_screen.fadein();
			break;
		case CONSOLE:
			SDL_EnableUNICODE( SDL_ENABLE );
			SDL_ShowCursor( SDL_ENABLE );
			systemConsole::sys_console.fadeinAnimated();
			break;
		case MAINMENU:
			mainMenu::main_menu.refresh();
			SDL_EnableUNICODE( SDL_ENABLE );
			SDL_ShowCursor( SDL_ENABLE );
			mainMenu::main_menu.fadein();
			break;
		case GAME:
			gameControl::game_control.resetMouse();
			systemconf::getSystemInfo().m_bResumeAllowed = true;
			SDL_ShowCursor( SDL_DISABLE );
			SDL_EnableUNICODE( SDL_DISABLE );
			SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL); 

			//inicializace pro vypocet delay multiplicatoru
			g_fDelayMultiplicator = 0.0f;
			g_uiDelayStartTime = SDL_GetTicks();
			g_fTotalDelay = 0.0f;
			HUD::hud.setState( HUD::NORMAL );
			HUD::hud.fadein();
			break;
		case HUD_SET:
			SDL_ShowCursor( SDL_ENABLE );
			HUD::hud.setState( HUD::SETTING );
			HUD::hud.fadein();
			break;
	}
}

