 // vim: set noet sw=8 cino=:
#define GL_GLEXT_PROTOTYPES

#include <fenv.h>

#include <stdio.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>
#include <SDL/SDL.h>
#include <math.h>
#include <string>
#include <vector>
#include <ctime>

using namespace std;

#ifdef __APPLE__ 
#include "definitions_mac.h" 
#else 
#include "definitions.h" 
#endif 

 
#include "font.h"
#include "utility.h"
#include "textures.h"
#include "backdrop.h"
#include "camera.h"
#include "weather.h"
#include "keyman.h"
#include "mouse.h"
#include "messageq.h"
#include "particles.h"
#include "settings.h"
#include "sound.h"
#include "timer.h"
#include "controls.h"
#include "vamosworld.h"
#include "replay.h"
#include "logo.h"
#include "gamestate.h"
#include "objects.h"
#include "multiplay.h"
#include "net.h"
#include "configfile.h"
#include "bezier.h"
#include "track.h"
#include "textures.h"
#include "exception.h"
#include "cardinfo.h"
#include "singleton.h"

using namespace VDrift;

#include <vamos/body/Gl_Car.h>
#include <vamos/geometry/Texture_Image.h>
#include <vamos/geometry/Three_Vector.h>
#include <vamos/body/Car.h>
#include <vamos/body/Fuel_Tank.h>
#include <vamos/body/Wheel.h>
#include <vamos/geometry/Conversions.h>
#include <vamos/world/World.h>

#include "gui/gui.h"

#ifdef _WIN32
#include <GL/glext.h>
#endif

//#define PERFORMANCE_PROFILE

//fps less than below will not tick the simulation

#ifdef PERFORMANCE_PROFILE
#include <sys/time.h>
suseconds_t GetMicroSeconds()
{
	struct timeval tv;
	struct timezone tz;
		
	tz.tz_minuteswest = 0;
	tz.tz_dsttime = 0;
	
	gettimeofday(&tv, &tz);
	
	return tv.tv_usec;
}
#endif


// Set up some booleans
#define TRUE  1
#define FALSE 0

// screen width, height, and bit depth
int SCREEN_WIDTH = 1024;
int SCREEN_HEIGHT = 768;
int SCREEN_BPP = 32;

//#define PATCH_DEBUG

bool verbose_output = false;

// This is our SDL surface
SDL_Surface *surface;

// Ambient Light Values ( NEW )
GLfloat LightAmbient[]  = { 0.15f, 0.15f, 0.15f, 1.0f };
// Diffuse Light Values ( NEW )
GLfloat LightDiffuse[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightSpecular[]  = { 0.0f, 0.0f, 0.0f, 0.0f };
// Light Position ( NEW )
GLfloat LightPosition[4];

KEYMAN keyman;
GAMECONTROLS gamecontrols;
SETTINGS settings;
TEXTURES textures;
CAMERA cam;
FONTMANAGER font;
UTILITY utility;
BACKDROP backdrop;
WEATHER weathersystem;
MOUSE mouse;
MESSAGEQ mq1;
PARTICLE particle;
SOUNDMANAGER sound;
TIMER timer;
REPLAY replay;
LOGO logo;
GAMESTATE state;
OBJECTS objects;
NET net;
MULTIPLAY multiplay;
VGUI::Gui* gui = Singleton<VGUI::Gui>::get_instance();
TRACK track;
CARDINFO gfxcard;

VAMOSWORLD world;

ofstream error_log;

#define CAR_Y_OFFSET 1.0

double day_time;
double abs_time;

//for game timing and FPS stuff
static GLint T0;
static GLint Frames;
static GLfloat fps;

bool showfps;

// should we run a benchmark test and then quit?
bool benchmode = false;

static int frameno;

float timefactor = 1.0f;

#define AVERAGEFRAMES 30

float pfps;
float lfps[AVERAGEFRAMES];
int lfpspos = 0;
bool lfpsfull = false;

#define FRAME_TIME 0.004
double execution_time = 0.0;

bool pauserequest = false;
bool unpauserequest = false;
bool esc_pressed = false;

float view_dist = 10000.0;
float field_of_view = 45.0;

string cur_tex_size = "";
int cur_screen_w = 0;
int cur_screen_h = 0;
int cur_screen_bpp = 0;
bool cur_fullscreen = false;

string data_dir;

#ifdef PATCH_DEBUG
BEZIER * thepatch;
#endif


float FrameTime()
{
	return FRAME_TIME;
}

int Screenshot(char *filename)
{
	SDL_Surface *screen;
	SDL_Surface *temp = NULL;
	unsigned char *pixels;
	int i;
	
	screen = surface;
	
	if (!(screen->flags & SDL_OPENGL))
	{
		SDL_SaveBMP(temp, filename);
		return 0;
	}
	
	temp = SDL_CreateRGBSurface(SDL_SWSURFACE, screen->w, screen->h, 24,
	#if SDL_BYTEORDER == SDL_LIL_ENDIAN
	0x000000FF, 0x0000FF00, 0x00FF0000, 0
	#else
	0x00FF0000, 0x0000FF00, 0x000000FF, 0
	#endif
	);
	if (temp == NULL)
		return -1;
	
	pixels = (unsigned char *) malloc(3 * screen->w * screen->h);
	if (pixels == NULL)
	{
		SDL_FreeSurface(temp);
		return -1;
	}
	
	glReadPixels(0, 0, screen->w, screen->h, GL_RGB, GL_UNSIGNED_BYTE, pixels);
	
	for (i=0; i<screen->h; i++)
		memcpy(((char *) temp->pixels) + temp->pitch * i, pixels + 3*screen->w * (screen->h-i-1), screen->w*3);
	free(pixels);
	
	SDL_SaveBMP(temp, filename);
	SDL_FreeSurface(temp);
	return 0;
}

void ResetWorld(bool fullreset);

/** Cleaup and exit.
 * @param returnCode the exit code
 *
 * This function cleans up the game state and restores the desktop environment
 * to it's previous state.
 */
void Quit(int returnCode)
{
	state.SetGameState(STATE_EXIT);

	if (replay.Recording() != -1)
		replay.Stop();

	gamecontrols.WriteControlFile();

	if (verbose_output)
		cout << "Quit called" << endl;

	error_log.close();

	/* clean up the window */
	SDL_Quit( );

	if (verbose_output)
		cout << "SDL_Quit finished" << endl;

	exit(returnCode);
}

// function to reset our viewport after a window resize
int resizeWindow( int width, int height )
{
	// Height / width ration
	GLfloat ratio;
 
	// Protect against a divide by zero
	if ( height == 0 )
		height = 1;

	ratio = ( GLfloat )width / ( GLfloat )height;

	// Setup our viewport.
	glViewport( 0, 0, ( GLint )width, ( GLint )height );

	// change to the projection matrix and set our viewing volume.
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );

	// Set our perspective
	gluPerspective( field_of_view, ratio, 0.1f, 10000.0f );

	// Make sure we're changing the model view and not the projection
	glMatrixMode( GL_MODELVIEW );

	// Reset The View
	glLoadIdentity( );

	return( TRUE );
}

void LoadingScreen(string loadtext);

void ChangeDisplay(int w, int h, int bpp, bool fullscreen, bool reloadtextures)
{
	SCREEN_WIDTH = w;
	SCREEN_HEIGHT = h;
	SCREEN_BPP = bpp;

	// the flags to pass to SDL_SetVideoMode
	int videoFlags = SDL_OPENGL;       // Enable OpenGL in SDL
	videoFlags |= SDL_GL_DOUBLEBUFFER; // Enable double buffering
	videoFlags |= SDL_HWPALETTE;       // Store the palette in hardware
	videoFlags |= SDL_RESIZABLE;       // Enable window resizing

	// This checks to see if surfaces can be stored in memory
	/*if ( videoInfo->hw_available )
		videoFlags |= SDL_HWSURFACE;
	else
		videoFlags |= SDL_SWSURFACE;

	if ( videoInfo->blit_hw )
		videoFlags |= SDL_HWACCEL;*/

	if (fullscreen)
	{
		videoFlags |= SDL_HWSURFACE|SDL_ANYFORMAT|SDL_FULLSCREEN;  
	}	
	else
	{
		videoFlags |= SDL_SWSURFACE|SDL_ANYFORMAT;
	}
	
	 /*// destroy old surface before creating a new one.
	 if (surface != NULL) 	 
	 { 	 
			 SDL_FreeSurface(surface); 	 
			 surface = NULL; 	 
	 }*/
		
	surface = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, videoFlags );

	resizeWindow( SCREEN_WIDTH, SCREEN_HEIGHT );

	if (reloadtextures)
	{
		if( gui->GetEnabled() ) gui->SetEnabled( false );
		LoadingScreen("Loading...\nTextures");
		textures.ReloadAll();
		LoadingScreen("Loading...\nFont");
		font.Load();
		LoadingScreen("Loading...\nBackdrop");
		backdrop.Init();
		LoadingScreen("Loading...\nGame Controls");
		gamecontrols.LoadControls();
		LoadingScreen("Loading...\nParticles");
		particle.Load();
		LoadingScreen("Loading...\nTimer");
		timer.Load();
		LoadingScreen("Loading...\nHUD");
		world.UnloadHUD();
		world.LoadHUD();
		LoadingScreen("Loading...\nUser interface");
		gui->ReInit();
	}

	gui->UpdateScreenSize();
}

bool ReloadDisplay(bool test)
{
	static int times = 0;
	string tex_size;
	int w, h, bpp;
	bool fullscreen, show_fps, change_display, reload_tex;

	settings.Get( "display.width", w );
	settings.Get( "display.height", h );
	settings.Get( "display.depth", bpp );
	settings.Get( "display.fullscreen", fullscreen );
	settings.Get( "display.show_fps", show_fps );
	settings.Get( "display.texture_size", tex_size );
	settings.Get( "display.view_distance", view_dist );
	settings.Get( "display.FOV", field_of_view );

	times++;

	#if defined(__APPLE__) || defined(WIN32)
	change_display = ( tex_size == cur_tex_size ) &&
	             ( w == cur_screen_w ) &&
	             ( h == cur_screen_h ) &&
	             ( fullscreen == cur_fullscreen ) &&
	             ( bpp == cur_screen_bpp ) ? false : true;
	#else
	change_display = true;
	#endif

	if (test == true) // if test is true we return if a display change would be neccesary
		return (change_display && (times > 1));
			
	showfps = show_fps;
	world.UpdateSettings();
	objects.UpdateSettings();
	
	if (change_display)
	{
		#if defined(__APPLE__) || defined(WIN32)
			reload_tex = true;
		#else
			//reload_tex = false;
			reload_tex = true;
		#endif

		ChangeDisplay( w, h, bpp, fullscreen, reload_tex );
	
		cur_tex_size = tex_size;
		cur_screen_w = w;
		cur_screen_h = h;
		cur_screen_bpp = bpp;
		cur_fullscreen = fullscreen;
	}
	
	return true;
}

bool UnloadWorld()
{
	//these should have error catching such that if they're called before
	// init, it's OK
	backdrop.DeInit();
	world.DeInit();
	sound.Unload();
	
	return true;
}

void Update()
{
	//if unpaused and getting a reasonable framerate
	if (fps > 0.0f && timefactor != 0.0f && state.GetGameState() != STATE_INITIALMENU)
	{
		execution_time += timefactor / fps;
		int num_updates = (int) (execution_time / FRAME_TIME);
		double this_frame = (double) num_updates * FRAME_TIME;

		mouse.Update(cam, SCREEN_WIDTH, SCREEN_HEIGHT, timefactor, fps);
		weathersystem.Update(abs_time);

		while (execution_time > FRAME_TIME)
		{
			//timer tick
			timer.TickTimer(1.0, 1.0/FRAME_TIME);

			cam.Update();

			//handle input
			keyman.DoHeldKeys(1.0, 1.0/FRAME_TIME, cam);

			try
			{
				world.Update(1.0, 1.0/FRAME_TIME, gamecontrols.Get_js());
			}
			catch( string &s )
			{
				cout << s << endl;
				if( !benchmode ) gui->ProcessAction( "SimError" ); 
				UnloadWorld();
				return;
			}

			keyman.DoOneTimeKeys(cam);

			float timepassed = (FRAME_TIME)/86400.0;
			abs_time += timepassed;
			day_time += timepassed;
			if (day_time > 1.0f)
				day_time -= 1.0f;

			multiplay.Send(FRAME_TIME);

			execution_time -= FRAME_TIME;
			num_updates++;
		}
		particle.Update(1.0, 1.0/this_frame);
	}
	else
	{
		//print "paused"
		if (state.GetGameState() != STATE_INITIALMENU)
			world.Update(timefactor, fps, gamecontrols.Get_js());
	}

	if (pauserequest)
	{
		if (timefactor != 0.0f)
			timefactor = 0.0f;
		pauserequest = false;
	}
	if (unpauserequest)
	{
		if (timefactor == 0.0f)
			timefactor = 1.0f;
		unpauserequest = false;
	}

	sound.Update();
}

void snap_screenshot()
{
	char scrname[64];
	int num_shots;
	settings.Get( "game.num_shots", num_shots );
	sprintf(scrname, "%s/screenshots/shot%03d.bmp", (settings.GetSettingsDir()).c_str(), num_shots );
	Screenshot(scrname);
	settings.Set( "game.num_shots", num_shots + 1 );
}

void MainPause()
{
	if( !gui->GetEnabled() && !multiplay.NumConnected() > 0 )
	{
		pauserequest = true;
	}

	gui->ProcessAction( "Pause" );
}

void MainUnpause()
{
	unpauserequest = true;
}

// function to handle key press events
void handleKeyPress( SDL_keysym *keysym )
{
	if( !gui->GetEnabled() )
	{
		switch ( keysym->sym )
		{
		case SDLK_ESCAPE:
			// ESC key was pressed
			esc_pressed = true;
			break;
		default:
			break;
		}
	}

	return;
}

void handleKeyRelease( SDL_keysym *keysym )
{
	if( !gui->GetEnabled() )
	{
		switch ( keysym->sym )
		{
		case SDLK_ESCAPE:
			// ESC key was released
			if( esc_pressed )
			{
				esc_pressed = false;
				MainPause();
			}
			break;
		default:
			keyman.OneTime(keysym->sym);
			break;
		}
	}

	return;
}

void glSetup()
{
	// Enable Texture Mapping ( NEW )
	glEnable( GL_TEXTURE_2D );
	// Enable smooth shading
	glShadeModel( GL_SMOOTH );
	// Set the background black
	glClearColor(0,0,0,0);
	// Depth buffer setup
	glClearDepth( 1.0f );
	// Enables Depth Testing
	glEnable( GL_DEPTH_TEST );
	// The Type Of Depth Test To Do
	glDepthFunc( GL_LEQUAL );
	// Really Nice Perspective Calculations
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	// Enable Lighting
	glEnable( GL_LIGHTING );
}


void SelectCar(string cfile, bool trim)
{	
	world.clear_cars();
	state.SetCarName(0, cfile);

	if (replay.Playing() == -1)
	{
		bool abs;
		settings.Get( "game.antilock", abs );
		if (abs)
			state.SetCarABS(0, abs);
		
		bool tcs;
		settings.Get( "game.traction_control", tcs );
		if (tcs)
			state.SetCarTCS(0, tcs);
	}

	int i;
	Vamos_Body::Gl_Car* car = 0;
	car = new Vamos_Body::Gl_Car (Vamos_Geometry::Three_Vector (11.0, 0.0, 0.6));
	car->read (data_dir, cfile);
	car->SetPaint(state.GetCarPaint(0));
	car->start_engine ();
	car->set_controller(1);
	car->SetABS( state.GetCarABS(0) );
	car->SetTCS( state.GetCarTCS(0) );
	world.add_car (car);

	if (state.GetGameMode() == MODE_SINGLERACE)
	{
		string opponents = "";
		settings.Get( "game.opponents", opponents );
		std::istringstream is;
		is.str( opponents );
		string car_name = "";
		vector<string> ai_cars;
		while( is )
		{
			is >> car_name;
			if( car_name != "" )
			{
				ai_cars.push_back( car_name );
				car_name = "";
			}
		}

		timer.InitOpponentTimer( ai_cars.size() );

		int i = 1;
		for( vector<string>::const_iterator ai_car = ai_cars.begin(); ai_car != ai_cars.end(); ++ai_car )
		{
			car = new Vamos_Body::Gl_Car( Vamos_Geometry::Three_Vector( 11.0, 0.0, 0.6 ) );
			car->read( data_dir, *ai_car );
			int paintindex = rand() % car->GetNumPaintjobs();
			car->SetPaint( paintindex );
			car->start_engine();
			car->set_controller( CONT_AI );
			car->SetABS( true );
			car->SetTCS( true );
			state.SetCarName( i, *ai_car );
			state.SetCarPaint( i, paintindex );
			state.SetCarABS( i, true );
			state.SetCarTCS( i, true );
			world.add_car( car );
			i++;
		}
	}

	for (i = 0; i < multiplay.NumConnected()+1; i++)
	{
		if (i != multiplay.MyNum())
		{
			int idx = i;
			if (idx == 0)
			idx = 1;
			car = new Vamos_Body::Gl_Car (Vamos_Geometry::Three_Vector (11.0, 0.0, 0.6));
			car->read (data_dir, state.GetCarName(idx));
			car->SetPaint(state.GetCarPaint(idx));
			car->start_engine ();
			car->set_controller(2);
			car->SetABS( state.GetCarABS(idx) );
			car->SetTCS( state.GetCarTCS(idx) );
			world.add_car (car);
		}
	}

	if (replay.GhostCar())
	{
		car = new Vamos_Body::Gl_Car (Vamos_Geometry::Three_Vector (11.0, 0.0, 0.6));
		car->read (data_dir, replay.ReplayCar());
		car->SetPaint(replay.ReplayPaint());
		car->chassis ().translate (Vamos_Geometry::Three_Vector (10.0*multiplay.MyNum(), 0.0, 
			-car->chassis ().lowest_contact_position () + CAR_Y_OFFSET));
		car->start_engine ();
		car->set_controller(3);
		car->SetABS( replay.ReplayABS() );
		car->SetTCS( replay.ReplayTCS() );
		world.add_car (car);
	}

	world.reset();

	if (trim)
	{
		ResetWorld(false);
	}
}

bool LoadWorld()
{
	UnloadWorld();

	//begin loading world
	timefactor = 1.0;

	LoadingScreen("Loading...\nConfiguration files");

	string selected_car = "";
	string track_name = "";
	int car_paint = 0;
	int number_of_laps = 1;
	bool reverse = false;

	settings.Get( "game.selected_car", selected_car );
	settings.Get( "game.car_paint", car_paint );
	settings.Get( "game.track", track_name );
	settings.Get( "game.number_of_laps", number_of_laps );
	settings.Get( "game.reverse", reverse );

	state.SetCarName( 0, selected_car );
	state.SetCarPaint( 0, car_paint );
	state.SetTrackName( track_name );
	state.SetLaps ( number_of_laps );

	if (state.GetCarName(0) == "")
		state.SetCarName(0, "XS");

	data_dir = settings.GetDataDir() + "/";

	LoadingScreen("Loading...\nTrack");
	track.Load(state.GetTrackName(), reverse);
	
	LoadingScreen("Loading...\nBackdrop");
	backdrop.Init();
	
	LoadingScreen("Loading...\nScenery objects");
	string objectpath = settings.GetDataDir() + "/tracks/" + state.GetTrackName() + "/objects";
	objects.LoadObjectsFromFolder(objectpath);

	#ifdef COLLISION_TESTING
	if (0)
	{
		bool col = false;
		VERTEX origin, dir, colpt, normal;
		bool closest = true;
		origin.Set(496.334,1000,-1.11335);
		dir.Set(0,-1,0);
		BEZIER * colpatch;
		ROADSTRIP * colstrip;
		ofstream deb;
		deb.open("debug.log");
		deb << "Debug log start" << endl;
		deb.close();
		
		col = track.CollideRoads(origin, dir, colpt, closest, colstrip, colpatch, normal);
		deb.open("debug.log", ofstream::out | ofstream::app);
		deb << "col: " << col << endl;
		deb << "origin: "; origin.DebugPrint(deb);
		deb << "dir: "; dir.DebugPrint(deb);
		deb << "colpt: "; colpt.DebugPrint(deb);
		deb << "closest: " << closest << endl;
		deb << "normal: "; normal.DebugPrint(deb);
		deb << "seglen: " << 10000.0f << endl;
		deb << "-------------" << endl;
		deb.close();
		
		col = objects.Collide(origin, dir, colpt, closest, normal, 10000.0f);
		deb.open("debug.log", ofstream::out | ofstream::app);
		deb << "col: " << col << endl;
		deb << "origin: "; origin.DebugPrint(deb);
		deb << "dir: "; dir.DebugPrint(deb);
		deb << "colpt: "; colpt.DebugPrint(deb);
		deb << "closest: " << closest << endl;
		deb << "normal: "; normal.DebugPrint(deb);
		deb << "seglen: " << 10000.0f << endl;
		deb << "-------------" << endl;
		deb.close();
	}
	#endif
	
	LoadingScreen("Loading...\nSound");
	sound.Reload();

	LoadingScreen("Loading...\nWorld");
	world.Init(&track);

	LoadingScreen("Loading...\nCar");
	SelectCar(state.GetCarName(0), true);

	timer.Clear();
	particle.Clear();

	LoadingScreen("Loading...\nDone");

	mq1.AddMessage("Simulation start");

	#ifdef PATCH_DEBUG
	{
		VERTEX v[4];
		v[0].Set(-2148.81,583.537,-1395.68);
		v[1].Set(-2151.04,583.525,-1396.96);
		v[2].Set(-2145.45,583.378,-1401.53);
		v[3].Set(-2147.67,583.341,-1402.81);

		thepatch = track.GetPatch(v);
		if (thepatch != NULL)
		{
			cout << "Got patch" << endl;
		
			VERTEX p1,p2;
			p1 = (v[0] + v[1]).ScaleR(0.5);
			p2 = (v[2] + v[3]).ScaleR(0.5);
			VERTEX dir;
			dir = p2-p1;
			dir = dir.normalize();
			dir.Scale(7.0);
			p1 = p1 - dir.ScaleR(0.5);
			int steps = 100;
			cout << "c(";
			for (int i = 0; i < steps; i++)
			{
				VERTEX p;
				p = p1 + dir.ScaleR((float)i/(float)steps);
				VERTEX dir;
				dir.y = -1000;
				p.y += 1000;
				VERTEX tvert, tnorm;
				bool col = thepatch->CollideSubDivQuadSimpleNorm(p, dir, tvert, tnorm);
				if (col)
					cout << tvert.y << ",";
				else
					cout << 600 << ",";
			}
			cout << ")" << endl;
		}
		else
			cout << "couldn't find patch" << endl;
	}
	#endif
	
	//triangle-triangle collision testing
	/*if (0)
	{
		VERTEX tri1[3];
		VERTEX tri2[3];
		tri1[0].Set(0,0,0);
		tri1[1].Set(1,0,0);
		tri1[2].Set(1,0,1);
		
		tri2[0].Set(0,1,0);
		tri2[1].Set(1,1,0);
		tri2[2].Set(1,-1,1);
		
		VERTEX colpt, colseg;
		int whichtri;
		
		bool col = utility.BruteForceTriangleIntersectionF(tri1, tri2, colpt, 
				colseg, whichtri);
		if (col)
		{
			colpt.DebugPrint();
			colseg.DebugPrint();
			cout << whichtri << endl;
		}
		else
			cout << "No col" << endl;
	}
	
	if (0)
	{
		VERTEX tri1[3];
		VERTEX tri2[3];
		tri1[0].Set(132.422,-1.18925,1.84825);
		tri1[1].Set(131.82,-0.16846,-0.154412);
		tri1[2].Set(131.803,-0.173352,1.84551);
		
		tri2[0].Set(220.398,220.398,-3.08942e-06);
		tri2[1].Set(-220.398,220.398,1.19984e-05);
		tri2[2].Set(220.398,-220.398,-2.23572e-05);
		
		VERTEX colpt, colseg;
		int whichtri;
		
		bool col = utility.BruteForceTriangleIntersectionF(tri1, tri2, colpt, 
				colseg, whichtri);
		if (col)
		{
			colpt.DebugPrint();
			colseg.DebugPrint();
			cout << whichtri << endl;
		}
		else
			cout << "No col" << endl;
	}*/
	
	return true;
}

void InitGameData()
{
	utility.Init();

	LoadingScreen("Loading...\nSetting time");
	LightPosition[0] = 2.0f;
	LightPosition[1] = 2.0f;
	LightPosition[2] = 2.0f;
	LightPosition[3] = 0.0f;

	day_time = abs_time = 0.1;

	LoadingScreen("Loading...\nInitializing weather");
	weathersystem.Init();
	weathersystem.Update(abs_time);

	LoadingScreen("Loading...\nSetting camera position");
	cam.Move(0,-5,0);
	cam.Update();
	cam.LoadVelocityIdentity();

	LoadingScreen("Loading...\nStarting message queue");
	mq1.SetPersist(5.0f);
	mq1.SetDepth(1);
	mq1.SetPos(0.01,0.10,5,1);
	mq1.SetBuildUp(true);

	LoadingScreen("Loading...\nGame control config");
	keyman.Load();
	gamecontrols.LoadControls();

	LoadingScreen("Loading...\nInitializing particle engine");
	particle.Load();
	VERTEX wind;
	wind.x = 0.3;
	wind.z = 0.1;
	particle.SetWind(wind);

	LoadingScreen("Loading...\nLoading Timer");
	timer.Load();

	LoadingScreen("Loading...\nInitializing Network");
	net.Init();

	string track;
	settings.Get( "game.track", track );
	state.SetTrackName( track );
	if( state.GetTrackName() == "" )
		state.SetTrackName("ruudskogen");

	string car_name;
	int car_paint;
	settings.Get( "game.selected_car", car_name );
	settings.Get( "game.car_paint", car_paint );

	state.SetCarName( 0, car_name );
	state.SetCarPaint( 0, car_paint );
	// XXX use a better way to pick the default car
	if (state.GetCarName(0) == "")
	{
		state.SetCarName(0, "MI");
	}
	
	LoadingScreen("Loading...\nInitializing sound");
	sound.Init();
	float sound_volume = 0.5;
	settings.Get( "sound.volume", sound_volume );
	sound.SetMasterVolume( sound_volume );
}

// general OpenGL initialization function
void InitGL( GLvoid )
{
	//warning:  this must be done BEFORE anything important
	//or else it will override it
	font.Load();
	
	// Enable Texture Mapping ( NEW )
	glEnable( GL_TEXTURE_2D );

	// Enable smooth shading
	glShadeModel( GL_SMOOTH );

	// Set the background black
	//glClearColor( 0.46f, 0.54f, 0.64f, 0.0f );
	glClearColor(0,0,0,0);

	// Depth buffer setup
	glClearDepth( 1.0f );

	// Enables Depth Testing
	glEnable( GL_DEPTH_TEST );

	// The Type Of Depth Test To Do
	glDepthFunc( GL_LEQUAL );

	// Really Nice Perspective Calculations
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

	// Position The Light
	glLightfv( GL_LIGHT1, GL_POSITION, LightPosition );
	
	// Setup The Diffuse Light
	glLightfv( GL_LIGHT1, GL_DIFFUSE, LightDiffuse );
	glLightfv( GL_LIGHT1, GL_SPECULAR, LightSpecular );
	
	// Setup The Ambient Light
	glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient );

	// Enable Light One
	glEnable( GL_LIGHT1 );
	
	// Enable Lighting
	glEnable( GL_LIGHTING );
	
	//our perspective matrix
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );
	gluPerspective( field_of_view, (float)SCREEN_WIDTH/SCREEN_HEIGHT, 0.1f, 10000.0f );
	glMatrixMode( GL_MODELVIEW );
	
	// Enable anti-aliasing
	int fsaa;
	settings.Get( "display.antialiasing", fsaa );
	if ( ( fsaa != 0 ) && ( gfxcard.GetCapability( CARDINFOTYPE::ANTIALIASING ) == 1 ) )
	{
		glEnable(GL_MULTISAMPLE_ARB);
	}
}

void LoadingScreen(string loadtext)
{
	if (verbose_output)
    	cout << loadtext << endl;

	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glPushMatrix();

	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);

	glSetup();

	glEnable(GL_TEXTURE_2D);

	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer

	Frames++;

	font.Print(0,0,loadtext.c_str(),1,6,1,1,1);

	// Draw it to the screen
	SDL_GL_SwapBuffers( );

	GLint t = SDL_GetTicks();
	GLfloat seconds = (t - T0) / 1000.0;
	fps = 1 / seconds;
	T0 = t;
	
	glPopMatrix();
	glPopAttrib();
}

void ApplyScaleBias(float scale, float bias)
{
	//add a brightness/contrast adjustment
	glClear (GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glDisable(GL_FOG);
	glDisable(GL_LIGHTING);
	glTranslatef(0,0,-40.0f);
	glBlendFunc(GL_ONE, GL_SRC_ALPHA);

	glColor4f(bias, bias, bias, scale);
	float x1, y1, x2, y2;
	x1 = -300;
	y1 = -300;
	x2 = 300;
	y2 = 300;

	if (scale > 1.0)
	{
		float remainingScale;
		
		remainingScale = scale;
		glBlendFunc(GL_DST_COLOR, GL_ONE);
		if (remainingScale > 2.0) 
		{
			glColor4f(1, 1, 1, 1);
			while (remainingScale > 2.0) 
			{
				glRectf(x1,y1,x2,y2);
				remainingScale /= 2.0;
			}
		}
		glColor4f(remainingScale - 1,
			remainingScale - 1, remainingScale - 1, 1);
		glRectf(x1,y1,x2,y2);
		glBlendFunc(GL_ONE, GL_ONE);
		if (bias != 0)
		{
			if (bias > 0) 
			{
				glColor4f(bias, bias, bias, 0.0);
			} 
			else 
			{
				glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
				glColor4f(-bias, -bias, -bias, 0.0);
			}
			glRectf(x1,y1,x2,y2);
		}
	}
	else
	{
		if (bias > 0) 
		{
			glBlendFunc(GL_ONE, GL_SRC_ALPHA);
			glColor4f(bias, bias, bias, scale);
			glRectf(x1,y1,x2,y2);
		}
		else
		{
			glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
			glColor4f(1,1,1, scale);
			glRectf(x1,y1,x2,y2);
			
			glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
			glColor4f(1,1,1, -bias);
			glRectf(x1,y1,x2,y2);
		}
	}
	
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);
	glEnable(GL_FOG);
	glEnable(GL_LIGHTING);
	glColor4f(1,1,1,1);
}

// Here goes our drawing code
int drawGLScene( GLvoid )
{
	glViewport(0,0,SCREEN_WIDTH, SCREEN_HEIGHT);
	
	if (state.GetGameState() != STATE_INITIALMENU)
	{			
		backdrop.SetFogStrength(weathersystem.GetFogDensity());
		backdrop.RefreshFog();
	}
	
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );

	gluPerspective( field_of_view, (float)SCREEN_WIDTH/SCREEN_HEIGHT, 0.1f, view_dist );
	glMatrixMode( GL_MODELVIEW );
	
	glSetup();
	
	glClearColor( 0.3750f, 0.4830f, 0.718750f, 0.0f );
	
	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);
	
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer
	
	GLdouble temp_matrix[16];
	cam.PutTransformInto(temp_matrix);
	glLoadMatrixd(temp_matrix);
	
	//reset sun position so it's in the correct frame
	float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	glLightfv( GL_LIGHT1, GL_POSITION, lp );
	
	if (state.GetGameState() != STATE_INITIALMENU)
	{
		glTranslatef(cam.GetPosition().x,cam.GetPosition().y,cam.GetPosition().z);
	
		cam.ExtractFrustum();

		objects.Draw(track.GetCullFaces());
		
		#ifdef PATCH_DEBUG
		track.VisualizeRoads(true, false, NULL);
		#endif
		
		world.Draw();

		glPolygonOffset(-10.0,-10.0);
		glEnable(GL_POLYGON_OFFSET_FILL);
		world.DrawShadows();
		glDisable(GL_POLYGON_OFFSET_FILL);

		world.DrawCars();
		
		world.DrawTopLevel();

		#ifdef PATCH_DEBUG
		VERTEX c;
		c.Set(0,1,0);
		if (thepatch != NULL)
			thepatch->Visualize(true,true,c);
		#endif
	}

	if (fps > 0.0f)
		mq1.Draw(timefactor, fps, font);


	if( gui->GetEnabled() )
		gui->Draw();

	Frames++;
	frameno++;
	if (frameno >= 30011)
		frameno -= 30011;
	lfps[lfpspos] = pfps;
	lfpspos++;
	if (lfpspos >= AVERAGEFRAMES)
	{
		lfpspos = lfpspos % AVERAGEFRAMES;
		lfpsfull = true;
	}

	int i;

	float tfps = 0.0f;
	int tnum = 0;
	for (i = 0; i < AVERAGEFRAMES; i++)
	{
		if (!(!lfpsfull && i >= lfpspos))
		{
			tfps += lfps[i];
			tnum++;
		}
	}
	fps = tfps / (float) tnum;

	char tempchar[1024];
	sprintf(tempchar, "Frames per second:  %f\n", 
		fps);

	if (showfps)
		font.Print( 0.75, 0.0, tempchar, 1, 5, 1.0 );

#ifdef REVISION
	if (state.GetGameState() == STATE_INITIALMENU)
	{
		sprintf(tempchar, "Revision: %s", REVISION);
		font.Print( 0.85, 0.95, tempchar, 1, 5, 1.0 );
	}

	SDL_GL_SwapBuffers( );
#endif

	GLint t = SDL_GetTicks();
	GLfloat seconds = (t - T0) / 1000.0;
	pfps = 1 / seconds;
	T0 = t;

	return( TRUE );
}



int drawGLScene_reflected( QUATERNION & direction, GLenum cube_target, VERTEX position, int sizex, int sizey, GLuint texture_id, bool reflection_effects )
{
	if (state.GetGameState() == STATE_INITIALMENU)
		return 0;
	
	CAMERA oldcam = cam;
	
	if (!reflection_effects)
	{
		cam.dir.LoadMultIdent();
	}
	cam.dir.PostMultiply(direction);
	cam.dir.Rotate(3.141593, 1,0,0);
	
	cam.position = position;

	glViewport(0,0,sizex,sizey);
	
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );

	gluPerspective( 90, 1.0, 0.1f, view_dist );
	glMatrixMode( GL_MODELVIEW );
	
	glSetup();
	
	glClearColor( 0.3750f, 0.4830f, 0.718750f, 1.0f );
	
	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);
	
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer
	
	GLdouble temp_matrix[16];
	cam.PutTransformInto(temp_matrix);
	glLoadMatrixd(temp_matrix);
	
	//reset sun position so it's in the correct frame
	float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	glLightfv( GL_LIGHT1, GL_POSITION, lp );
	
	glTranslatef(cam.GetPosition().x,cam.GetPosition().y,cam.GetPosition().z);
	
	cam.ExtractFrustum();
	
	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);

	objects.Draw(track.GetCullFaces(), 100.0, 90.0, (float)sizex/sizey, true, !reflection_effects);
	
	if (reflection_effects)
	{
		glPolygonOffset(-10.0,-10.0);
		glEnable(GL_POLYGON_OFFSET_FILL);
		world.DrawShadows();
		glDisable(GL_POLYGON_OFFSET_FILL);
	}

	float scale;
	float bias;
	
	if (reflection_effects)
	{
		scale = 0.25;
		bias = 0.0;
	}
	else
	{
		scale = 1.0;
		bias = 0.0;
	}
	
	ApplyScaleBias(scale,bias);

	bool fancystuff = true;
	int smallsize = 32;
	bool usesmallsize = !reflection_effects && fancystuff;

	if (!reflection_effects && fancystuff)
	{
		GLuint temp_tex;
		glGenTextures(1, &temp_tex);
		
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
		
		glBindTexture(GL_TEXTURE_2D, temp_tex);
		glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, sizex, sizey, 0);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
		
		glViewport(0,0,smallsize,smallsize);
		
		glEnable(GL_TEXTURE_2D);
		glDisable(GL_LIGHTING);
		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_ONE);
		glDisable(GL_DEPTH_TEST);

		glMatrixMode(GL_PROJECTION);
		glPushMatrix( );
		glLoadIdentity( );
		glOrtho( -1, 1, -1, 1, -1, 1 );
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix( );
		glLoadIdentity( );
		glBindTexture(GL_TEXTURE_2D, temp_tex);
		
		int iterations = 32;
		float boxsize = 1.0;
		float iterationsize = boxsize / iterations;
		float smalliterationsize = 0.1*iterationsize;
		float colormod = 2.5/iterations;
		
		glColor4f(0,0,0,1);
		glDisable(GL_BLEND);
		glBegin(GL_QUADS);			// Start Drawing Quads
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  0.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  0.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  0.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  0.0f);	// Top Left Of The Texture and Quad
		glEnd();
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		
		glColor4f(colormod,colormod,colormod,1);
		glBegin(GL_QUADS);			// Start Drawing Quads
		float xoffset = 0;
		float yoffset = 0;
		xoffset += -boxsize*0.05;
		yoffset += -boxsize*0.5;
		for (int i = 0; i <= iterations; i++)
		{
			yoffset += iterationsize;
			xoffset += smalliterationsize;
			glTexCoord2f(0.0f+xoffset, 0.0f+yoffset); glVertex3f(-1.0f, -1.0f,  0.0f);	// Bottom Left Of The Texture and Quad
			glTexCoord2f(1.0f+xoffset, 0.0f+yoffset); glVertex3f( 1.0f, -1.0f,  0.0f);	// Bottom Right Of The Texture and Quad
			glTexCoord2f(1.0f+xoffset, 1.0f+yoffset); glVertex3f( 1.0f,  1.0f,  0.0f);	// Top Right Of The Texture and Quad
			glTexCoord2f(0.0f+xoffset, 1.0f+yoffset); glVertex3f(-1.0f,  1.0f,  0.0f);	// Top Left Of The Texture and Quad
		}
		glEnd();
		
		glColor4f(1,1,1,1);
		
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
		glDepthMask(1);
		
		glMatrixMode( GL_PROJECTION );
		glPopMatrix( );
		glMatrixMode( GL_MODELVIEW );
		glPopMatrix( );
		
		glDeleteTextures(1, &temp_tex);
	}

	// Before we copy the screen to a texture, we need to specify the current
	// texture to draw to by calling glBindTexture() with the appropriate texture 
	glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, texture_id);
	
	// Now comes the moment we have all been waiting for, we render the screen
	// to the texture.  We pass in the texture type, detail level (0), pixel format,
	// the x and y position to start from, the width and height to grab, and a border.
	// If you only want a part of the screen, this works great for that.
	if (usesmallsize)
		glCopyTexImage2D(cube_target, 0, GL_RGB, 0, 0, smallsize, smallsize, 0);
	else
		glCopyTexImage2D(cube_target, 0, GL_RGB, 0, 0, sizex, sizey, 0);
	
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);

	// Here we clear the screen and depth bits of the small viewport
	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	cam = oldcam;
	
	return( TRUE );
}

void RenderQuad()
{
	glBegin(GL_QUADS);
	glNormal3f( 0.0f, 0.0f, 1.0f);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  0.0f);	// Bottom Left Of The Texture and Quad
	glTexCoord2f(2.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  0.0f);	// Bottom Right Of The Texture and Quad
	glTexCoord2f(2.0f, 2.0f); glVertex3f( 1.0f,  1.0f,  0.0f);	// Top Right Of The Texture and Quad
	glTexCoord2f(0.0f, 2.0f); glVertex3f(-1.0f,  1.0f,  0.0f);	// Top Left Of The Texture and Quad
	glEnd();
}

int drawGLScene_reflected( QUATERNION & direction, GLenum cube_target, VERTEX position, int sizex, int sizey, int faceid )
{
	if (state.GetGameState() == STATE_INITIALMENU)
		return 0;
	
	CAMERA oldcam = cam;
	
	cam.dir.PostMultiply(direction);
	cam.dir.Rotate(3.141593, 1,0,0);
	
	cam.position = position;

	glViewport(0,0,sizex,sizey);
	
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );

	gluPerspective( 90, 1.0, 0.1f, view_dist );
	glMatrixMode( GL_MODELVIEW );
	
	glSetup();
	
	glClearColor( 0.3750f, 0.4830f, 0.718750f, 0.0f );
	
	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);
	
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer
	
	GLdouble temp_matrix[16];
	cam.PutTransformInto(temp_matrix);
	glLoadMatrixd(temp_matrix);
	
	//reset sun position so it's in the correct frame
	float lp[4];
	lp[0] = LightPosition[0];
	lp[1] = LightPosition[1];
	lp[2] = LightPosition[2];
	lp[3] = 0;
	glLightfv( GL_LIGHT1, GL_POSITION, lp );
	
	glTranslatef(cam.GetPosition().x,cam.GetPosition().y,cam.GetPosition().z);
	
	cam.ExtractFrustum();
	
	utility.Tex2D(3, false);
	utility.Tex2D(2, false);
	utility.Tex2D(1, false);
	utility.Tex2D(0, true);

	objects.Draw(track.GetCullFaces(), 100.0, 90.0, (float)sizex/sizey);
	
	glPolygonOffset(-10.0,-10.0);
	glEnable(GL_POLYGON_OFFSET_FILL);
	world.DrawShadows();
	glDisable(GL_POLYGON_OFFSET_FILL);

	ApplyScaleBias(0.25,0.0);

	if (world.DynamicCubeReflectionMapping())
	{
		glEnable(GL_TEXTURE_CUBE_MAP_ARB);
		
		// Before we copy the screen to a texture, we need to specify the current
		// texture to draw to by calling glBindTexture() with the appropriate texture 
		glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, world.GetReflectionTexture(0));
		
		// Now comes the moment we have all been waiting for, we render the screen
		// to the texture.  We pass in the texture type, detail level (0), pixel format,
		// the x and y position to start from, the width and height to grab, and a border.
		// If you only want a part of the screen, this works great for that.
		glCopyTexImage2D(cube_target, 0, GL_RGB, 0, 0, sizex, sizey, 0);
		
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
	}

	ApplyScaleBias(4.0,0.0);
	ApplyScaleBias(1.5,0.0);
	ApplyScaleBias(1.0,0.2);

	if (world.DynamicCubeLighting())
	{
		GLuint format = GL_RGB;
		
		int lightsize = world.GetLightPixelsSize();
		unsigned char * lightpixels = world.GetLightPixels(faceid);
		float wscale = (sizex-1)/(float)(lightsize-1);
		float hscale = (sizey-1)/(float)(lightsize-1);
		glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, world.GetLightingTexture(0));
		glReadBuffer(GL_BACK);
		glFlush();
		unsigned char pixel[3];
		for (int ix = 0; ix < lightsize; ix++)
		{
			for (int iy = 0; iy < lightsize; iy++)
			{
				glReadPixels((int)(ix*wscale), (int)(iy*hscale), 1, 1, format, GL_UNSIGNED_BYTE, pixel);
				
				int avgpix = (pixel[0]+pixel[1]+pixel[2])/3;
				for (int ic = 0; ic < 3; ic++)
				{
					pixel[ic] = (pixel[ic]+avgpix)/2;
					lightpixels[(iy*lightsize+ix)*3+ic] = (unsigned char) (lightpixels[(iy*lightsize+ix)*3+ic]*0.9+pixel[ic]*0.1);
				}
			}
		}
		
		glTexImage2D( cube_target, 0, GL_RGB, lightsize, lightsize, 0, format, GL_UNSIGNED_BYTE, lightpixels );
		
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
	}

	cam = oldcam;

	return( TRUE );
}

// This function returns true if the extension is there.
bool isExtensionSupported(string extstring)
{
	char * temp = (char *) glGetString(GL_EXTENSIONS);
	if (temp == 0)
	{
		cout << "Error getting extensions.  Continuing anyway, errors may follow!" << endl;
		return true;
	}

	string s = temp;
	string::size_type temppos = s.find(extstring);

	bool hasext = (temppos <= s.length());

	if (!hasext)
	{
		cout << "Extension not supported: " << extstring << endl;
	}

	return hasext;
}

void ResetWorld(bool fullreset)
{
	if (fullreset)
	{
		SelectCar(state.GetCarName(0), true);
		sound.Reload();
		timer.Clear();
	}

	world.reset();
}

int main( int argc, char **argv )
{
	const vector<string> args(argv, argv + argc);

	// Flags to pass to SDL_SetVideoMode
	int videoFlags = 0;
	// main loop variable
	int done = FALSE;
	// used to collect events
	SDL_Event event;
	// this holds some info about our display
	const SDL_VideoInfo *videoInfo;
	// whether or not the window is active
	int isActive = TRUE;
	string benchmark_replay = "";

	surface = NULL;
	
	/*{
		CONFIGFILE testfile;
		testfile.Load("data/settings/options.config");
		string opname = "INVALID";
		testfile.GetParam("num_options", opname);
		cout << "***********" << opname << endl;
	}*/

	//initialize the pseudo-random number generator
	srand(time(NULL));

	//load error log
	error_log.open((settings.GetSettingsDir() + "/logs/main.log").c_str());

	for (vector<string>::const_iterator i = args.begin(); i != args.end(); ++i)
	{
		if (*i == "-verbose")
			verbose_output = true;
		if( *i == "-benchmark")
		{
			benchmode = true;
			benchmark_replay = "benchmark";
		}
	}
	
	#ifdef ENABLE_NLS
 	if (verbose_output)
 		cout << settings.GetLocaleDir () << std::endl;
 	setlocale (LC_MESSAGES, ""); // only LC_MESSAGES, LC_ALL breaks menus
 	bindtextdomain (PACKAGE, (settings.GetLocaleDir ()).c_str ());
 	textdomain (PACKAGE);
 	#endif

	// parse options file
	settings.ParseOptionFile();

	settings.Get( "display.view_distance", view_dist );
	settings.Get( "display.FOV", field_of_view );

	float sound_volume = 0.5;
	settings.Get( "sound.volume", sound_volume );
	sound.SetMasterVolume( sound_volume );

	mouse.UpdateSettings();
	world.UpdateSettings();
	state.UpdateSettings();
	objects.UpdateSettings();

	for (vector<string>::const_iterator i = args.begin(); i != args.end(); ++i)
	{
		if (*i == "-nosound")
			sound.DisableAllSound();
	}

	//set frame stats to zero
	Frames = 0;
	T0 = 0;
	fps = 0;
	frameno = 0;

        try
        {
		// initialize SDL
#ifdef DEBUG
		if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE) < 0 )
#else
		if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0 )
#endif
		{
			throw INIT_ERROR(string("SDL initilization failed (") + SDL_GetError() + ")");
		}

		cout << _c("Run with -verbose for troubleshooting.") << endl;
		cout << _c("Run with -nosound to disable sound.") << endl;
		cout << _c("Run with -benchmark to play a replay and output benchmark data.") << endl;

		printf("%i joystick(s) found:\n", SDL_NumJoysticks() );
		for(int i=0; i < SDL_NumJoysticks(); i++ ) 
		{
			printf("    %i. %s\n", i, SDL_JoystickName(i));
		}

		SDL_JoystickEventState(SDL_ENABLE);

		gamecontrols.InitJoy();

		SDL_WM_SetCaption(_c("VDrift - open source drift racing simulation - powered by Vamos"), NULL);
		SDL_WM_SetIcon(IMG_Load(settings.GetFullDataPath("textures/small/icons/vdrift-32x32.png").c_str()), NULL);
		SDL_ShowCursor(SDL_DISABLE);

		// Fetch the video info
		videoInfo = SDL_GetVideoInfo( );

		if ( !videoInfo )
		{
			throw INIT_ERROR(string("Video query failed (") + SDL_GetError() + ")");
		}

		// the flags to pass to SDL_SetVideoMode
		videoFlags  = SDL_OPENGL;          // Enable OpenGL in SDL
		videoFlags |= SDL_GL_DOUBLEBUFFER; // Enable double buffering
		videoFlags |= SDL_HWPALETTE;       // Store the palette in hardware
		videoFlags |= SDL_RESIZABLE;       // Enable window resizing

		// This checks to see if surfaces can be stored in memory
		if ( videoInfo->hw_available )
			videoFlags |= SDL_HWSURFACE;
		else
			videoFlags |= SDL_SWSURFACE;

		// This checks if hardware blits can be done
		if( videoInfo->blit_hw )
			videoFlags |= SDL_HWACCEL;

		// Sets up OpenGL double buffering
		SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
	    
		//set up the depth buffer to be 16 bits, this is enough
		SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
		
		//set up anti-aliasing
		int fsaa;
		settings.Get( "display.antialiasing", fsaa );
		//if( ( fsaa != 0 ) && ( gfxcard.GetCapability( CARDINFOTYPE::ANTIALIASING ) == 1 ) )
		if( fsaa != 0 ) //gfxcard class isn't set up yet
		{
			SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 1 );
			SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, fsaa );
		}

		// set up our screen
		settings.Get( "display.width", cur_screen_w );
		settings.Get( "display.height", cur_screen_h );
		settings.Get( "display.depth", cur_screen_bpp );
		settings.Get( "display.texture_size", cur_tex_size );
		settings.Get( "display.fullscreen", cur_fullscreen );

		if( SDL_VideoModeOK( cur_screen_w, cur_screen_h, cur_screen_bpp, videoFlags ) != 0 )
		{
			ChangeDisplay( cur_screen_w, cur_screen_h, cur_screen_bpp, cur_fullscreen, true );
		}
		else
		{
			surface = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, videoFlags );
		}

		// Verify there is a surface
		if( !surface )
		{
			throw INIT_ERROR("Surface creation failed.");
		}
		
		gfxcard.BuildCardInfo();
		gfxcard.PrintCardInfo(cout);

		// initialize OpenGL
		LoadingScreen( "Loading...\nInitializing OpenGL" );
		InitGL();
		LoadingScreen( "Loading...\nInitializing Game Data" );
		InitGameData();

		// resize the initial window
		resizeWindow( SCREEN_WIDTH, SCREEN_HEIGHT );

		if( !( isExtensionSupported( "GL_ARB_multitexture" ) ) )
		{
			throw INIT_ERROR("Required OpenGL extensions not present.");
		}
	}
	catch (const INIT_ERROR& e)
	{
		cerr << "Error initilizing VDrift: " << e.what() << "\n";
		Quit(1);
	}

	// now we have set up a window etc. - we can get video modes now so it's ok to parse the option file
	LoadingScreen( "Loading...\nParsing Dynamic Options" );
	settings.ParseDynamicOptions();

	timefactor = 1.0f;

	if( benchmode )
	{
		LoadingScreen( "Loading...\nDone" );
		state.SetGameState(STATE_INITIALMENU);
		//run a benchmark and quit
		if( !replay.PlayStart( benchmark_replay, false, true ) )
		{
			cout << "Can't find the benchmark replay file (" << benchmark_replay << ")!" << endl;
			Quit( 1 );
		}

		int total_frames = 0;
		Uint32 start_ticks = SDL_GetTicks();
		while( !done )
		{
			while( SDL_PollEvent( &event ) )
			{
				if( ( event.type == SDL_KEYUP ) && ( event.key.keysym.sym == SDLK_ESCAPE ) )
				{
					done = 1;
				}
			}

			Update();
			if (world.DynamicCubeReflectionMapping())
			{
				QUATERNION cubedir;
				VERTEX refpos;
				if (world.Loaded())
				{
					Vamos_Body::Car * playercar = world.GetPlayerCar();
					if (playercar != NULL)
					{
						Vamos_Geometry::Three_Vector cm = playercar->chassis().position();
						refpos.Set(-cm[0], -cm[2], cm[1]);		
					}
				}
				else
					refpos = cam.position;
			}
			drawGLScene();
			total_frames++;

			if( replay.Playing() == -1 )
			{
				done = 1;
			}
		}
		Uint32 end_ticks = SDL_GetTicks();

		float start_time = start_ticks * 0.001;
		float end_time = end_ticks * 0.001;
		float total_time = end_time - start_time;
		float mean_fps = total_frames / total_time;
		cout << "Replay total running time: " << total_time << " seconds" << endl;
		cout << "Average framerate: " << mean_fps << " frames per second" << endl;

		Quit( 0 );
	}

	LoadingScreen( "Loading...\nUser Interface" );
	gui->Init();
	LoadingScreen( "Loading...\nDone" );

	state.SetGameState(STATE_LOGO);
	logo.run();
	state.SetGameState(STATE_INITIALMENU);
	// enable the user interface
	gui->SetEnabled( true );
	gui->ChangePage( "Main" );

	// see if the framerate counter is on
	settings.Get( "display.show_fps", showfps );

	// wait for events
	while( !done )
	{
		// handle the events in the queue
		while( SDL_PollEvent( &event ) )
		{
			float v;
			
			switch( event.type )
			{
			case SDL_ACTIVEEVENT:
				// Something's happend with our focus
				// If we lost focus or we are iconified, we
				// shouldn't draw the screen

				if ( event.active.gain == 0 )
					isActive = FALSE;
				else
					isActive = TRUE;
				break;			    
			case SDL_VIDEORESIZE:
				// handle resize event
				surface = SDL_SetVideoMode( event.resize.w,
							event.resize.h,
							16, videoFlags );
				if ( !surface )
				{
					fprintf( stderr, "Could not get a surface after resize: %s\n", SDL_GetError( ) );
					Quit(1);
				}
				resizeWindow( event.resize.w, event.resize.h );
				break;
			case SDL_KEYDOWN:
				// handle key presses
				if( gui->GetEnabled() )
					gui->KeyPress( event.key.keysym.sym );
				handleKeyPress( &event.key.keysym );
				keyman.KeyDown(event.key.keysym.sym);
				break;
			case SDL_KEYUP:
				if( gui->GetEnabled() )
					gui->KeyRelease( event.key.keysym.sym );
				handleKeyRelease( &event.key.keysym );
				keyman.KeyUp(event.key.keysym.sym);
				break;
			case SDL_QUIT:
				// handle quit requests
				gui->ProcessAction( "Quit" );
				break;
			case SDL_MOUSEBUTTONDOWN:
				if( gui->GetEnabled() )
					gui->MousePress();
				break;
			case SDL_MOUSEBUTTONUP:
				if( gui->GetEnabled() )
					gui->MouseRelease();
				break;
			case SDL_JOYBUTTONDOWN:
				if( gui->GetEnabled() )
					gui->JoyPress( event.jbutton.which, event.jbutton.button );
				if (timefactor == 0.0f && !gui->GetEnabled())
					MainUnpause();
				break;
			case SDL_JOYBUTTONUP:
				if( gui->GetEnabled() )
					gui->JoyRelease( event.jbutton.which, event.jbutton.button );
				break;
			case SDL_JOYAXISMOTION:
				v = event.jaxis.value / 32768.0f;
				if( gui->GetEnabled() )
					gui->JoyMove( event.jaxis.which, event.jaxis.axis, v );
				break;
			default:
				break;
			}
		}

		if( gui->GetEnabled() )
			gui->JoyUpdate();

		Update();

		if (state.GetGameState() == STATE_ENDRACE)
		{
			gui->ProcessAction( "RaceResults");
		}

		if (MP_DBGDEEP)
			cout << "main draw start" << endl;
		
		if (world.DynamicCubeReflectionMapping() || world.DynamicCubeLighting())
		{
			QUATERNION carorientation;
			QUATERNION cubedir;
			VERTEX refpos;
			if (world.Loaded())
			{
				Vamos_Body::Car * playercar = world.GetPlayerCar();
				if (playercar != NULL)
				{
					Vamos_Geometry::Three_Vector cm = playercar->chassis().position();
					refpos.Set(-cm[0], -cm[2], cm[1]);		
					
					double angle;
					Vamos_Geometry::Three_Vector axis = world.GetCar(CONT_PLAYERLOCAL)->car->chassis().axis_angle (&angle);
					carorientation.SetAxisAngle(angle*(3.141593/180), -axis[0], -axis[2], axis[1]);
					carorientation.Rotate(3.141593/2.0,0,1,0);			
				}
			}
			else
				refpos = cam.position;
			
			if (world.DynamicCubeReflectionMapping())
			{
				GLuint texture_target = world.GetReflectionTexture(0);
				int size = 128;
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB, refpos, size, size, texture_target, true);
				cubedir.Rotate(3.141593, 0,1,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB, refpos, size, size, texture_target, true);
				cubedir.LoadMultIdent();
				cubedir.Rotate(-3.141593*0.5, 1,0,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB, refpos, size, size, texture_target, true);
				cubedir.LoadMultIdent();
				cubedir.Rotate(3.141593*0.5, 1,0,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB, refpos, size, size, texture_target, true);
				cubedir.LoadMultIdent();
				cubedir.Rotate(3.141593*0.5, 0,1,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB, refpos, size, size, texture_target, true);
				cubedir.LoadMultIdent();
				cubedir.Rotate(-3.141593*0.5, 0,1,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, refpos, size, size, texture_target, true);
			}
			
			if (world.DynamicCubeLighting())
			{
				GLuint texture_target = world.GetLightingTexture(0);
				int size = 128;
				cubedir = carorientation;
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB, refpos, size, size, texture_target, false);
				cubedir.Rotate(3.141593, 0,1,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB, refpos, size, size, texture_target, false);
				cubedir = carorientation;
				cubedir.Rotate(-3.141593*0.5, 1,0,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB, refpos, size, size, texture_target, false);
				cubedir = carorientation;
				cubedir.Rotate(3.141593*0.5, 1,0,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB, refpos, size, size, texture_target, false);
				cubedir = carorientation;
				cubedir.Rotate(3.141593*0.5, 0,1,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB, refpos, size, size, texture_target, false);
				cubedir = carorientation;
				cubedir.Rotate(-3.141593*0.5, 0,1,0);
				drawGLScene_reflected(cubedir, GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, refpos, size, size, texture_target, false);
			}
		}
		drawGLScene( );
		if (MP_DBGDEEP)
			cout << "main draw done" << endl;
	}

	gamecontrols.DeinitJoy();

	Quit(0);
	return 0; // never executed
}
