#include <allegro.h>
#include <alleggl.h>
#include <ode/ode.h>
#include <GL/glu.h>
#include <math.h>

#include "glode.h"
#include "geometry.h"
#include "intro.h"
#include "car.h"
#include "camera.h"
#include "trig.h"
#include "skybox.h"
#include "timer.h"
#include "loop.h"
#include "controls.h"
#include "player.h"
#include "menu.h"

void initialize(void);
void main_loop(void);
void splash(void);

dWorldID world;
dSpaceID space;
dJointGroupID contactgroup;

dGeomID *groundbox;
dGeomID *groundsphere;
int ngroundbox;
int ngroundsphere;

CPlayer player[8];
int players=1;

CGeometry terrain("data/testlevel1final.trb");
CGeometry wheel("data/wheel.trb");
CGeometry wheelshell("data/wheelshell.trb");
CGeometry carbody("data/testcar1.trb");

CLoop innerloop("data/testlevel1innerloop.cl1");
CLoop outerloop("data/testlevel1outerloop.cl1");

CSkybox skybox;

GLuint carbodytex, wheeltex, leveltex;

FONT *allegro_fnt;

int main()
{
  splash();
  
	// Set up OpenGL Rendering mode
	allegro_gl_begin();
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f, 1.3333, 0.1f, 500.0f);
	glMatrixMode(GL_MODELVIEW);
	glClearColor(0, 0, 0, 0);
	glShadeModel(GL_SMOOTH);
	glDepthFunc(GL_LEQUAL);
	glClearDepth(1.0);
	glEnable(GL_DEPTH_TEST);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	GLfloat LightAmbient[]= { 0.02, 0.02, 0.02, 1.0 };
	GLfloat LightDiffuse[]= { 0.8, 0.8, 0.8, 1.0 };
	GLfloat LightPosition[]= { 3.0, 10.0, 7.0, 1.0 };
	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
	glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHTING);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glEnable(GL_COLOR_MATERIAL);
	allegro_fnt = allegro_gl_convert_allegro_font(font, AGL_FONT_TYPE_TEXTURED, 16.0);
	allegro_gl_end();
	
	timer.install(1000);
	intro();

	main_menu();

	initialize();
	timer.install(596590.5);
	timer.reset();
	main_loop();

	allegro_gl_begin();
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glViewport(0, 0, SCREEN_W, SCREEN_H);
	glOrtho(-20,20,-15,15,-1,1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glDisable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);

	glClear(GL_COLOR_BUFFER_BIT);
	allegro_gl_printf(allegro_fnt, -2, 10, 0, makecol(0, 255, 0), "carterrain");
	allegro_gl_printf(allegro_fnt, -8, 7, 0, makecol(0, 0, 255), "Programming & Graphics:  Benny Kramek");
	allegro_gl_printf(allegro_fnt, -3, -3, 0, makecol(0, 128, 0), "Uses AllegroGL game programming library");
	allegro_gl_printf(allegro_fnt, -3, -5, 0, makecol(0, 128, 0), "Uses ODE real-time physics library");
	allegro_gl_printf(allegro_fnt, -7, -10, 0, makecol(255, 255, 0), "I hope you enjoyed playing!");
	glScalef(2, 2, 0);
	allegro_gl_printf(allegro_fnt, -7, 1, 0, makecol(255, 255, 255), "http://benny.kramekweb.com");

	glFinish();
	allegro_gl_flip();
	allegro_gl_end();


	rest(2000);
	while(!key[KEY_ESC]);

	return 0;
}
END_OF_MAIN();

void initialize(void)
{
	world = dWorldCreate();
	dWorldSetGravity(world, 0, 0, -15);
	space = dHashSpaceCreate();
	contactgroup = dJointGroupCreate(10000);

	int i, j;
	float dummy, x, y, z, l, w, h, ax, ay, az, aa;
	
	FILE *levelfile;
	levelfile = fopen("data/testlevel1.ct1", "r");
	if (levelfile == NULL)
	{
		allegro_message("Cannot find Level File!\n");
		exit(-1);
	}

	fscanf(levelfile, "%d", &ngroundbox);
	fscanf(levelfile, "%d", &ngroundsphere);
	groundbox = new dGeomID[ngroundbox];
	groundsphere = new dGeomID[ngroundsphere];

	dMatrix3 R;
	for(i=0; i<ngroundbox; i++)
	{
		fscanf(levelfile, "%f", &x);
		fscanf(levelfile, "%f", &y);
		fscanf(levelfile, "%f", &z);
		fscanf(levelfile, "%f", &ax);
		fscanf(levelfile, "%f", &ay);
		fscanf(levelfile, "%f", &az);
		fscanf(levelfile, "%f", &aa);
		fscanf(levelfile, "%f", &l);
		fscanf(levelfile, "%f", &w);
		fscanf(levelfile, "%f", &h);
        			
		groundbox[i] = dCreateBox(space, l*1.2, w*1.2, h*1.2);
		
		dGeomSetPosition(groundbox[i], -x/100.0*1.2, -y/100.0*1.2, z/100.0*1.2);
		dRFromAxisAndAngle(R, ax, ay, -az, aa);
		dGeomSetRotation(groundbox[i], R);
	}
	for(i=0; i<ngroundsphere; i++)
	{
		fscanf(levelfile, "%f", &x);
		fscanf(levelfile, "%f", &y);
		fscanf(levelfile, "%f", &z);
		fscanf(levelfile, "%f", &l);
		fscanf(levelfile, "%f", &w);
		fscanf(levelfile, "%f", &h);
		groundsphere[i] = dCreateSphere(space, l/2.0*1.2);
		dGeomSetPosition(groundsphere[i], -x/100.0*1.2, -y/100.0*1.2, z/100.0*1.2);
	}
	fclose(levelfile);

	terrain.scale(1.2*0.01);
       	carbody.scale(0.1);
	wheel.scale(0.01);
	wheelshell.scale(0.01);

	BITMAP *bmp = load_tga("data/testcar1skin.tga", NULL);
	carbodytex = allegro_gl_make_texture(bmp);
	destroy_bitmap(bmp);
	bmp = load_tga("data/testwheelskin.tga", NULL);
	wheeltex = allegro_gl_make_texture(bmp);
	destroy_bitmap(bmp);
	bmp = load_tga("data/testlevel1skin.tga", NULL);
	leveltex = allegro_gl_make_texture(bmp);
	destroy_bitmap(bmp);
	
	if(players>=1)
	  player[0].car.init(world, space, -3, 0, 3);
	if(players>=2)
	  player[1].car.init(world, space, 3, 0, 3);
	if(players>=3)
	  player[2].car.init(world, space, -3, -5, 3);
	if(players>=4)
	  player[3].car.init(world, space, 3, -5, 3);
	if(players>=5)
	  player[4].car.init(world, space, -3, -10, 3);
	if(players>=6)
	  player[5].car.init(world, space, 3, -10, 3);
	if(players>=7)
	  player[6].car.init(world, space, -3, -15, 3);
	if(players>=8)
	  player[7].car.init(world, space, 3, -15, 3);

	
	for(i=0; i<players; i++)
	  player[i].camera.init(player[i].car.chassis);
	
	skybox.init("data/skybox/front.bmp",
	            "data/skybox/back.bmp",
	            "data/skybox/left.bmp",
	            "data/skybox/right.bmp",
	            "data/skybox/top.bmp",
	            "data/skybox/bottom.bmp");

	player[0].controls.set_keys(KEY_UP,     KEY_DOWN,   KEY_LEFT,   KEY_RIGHT,  KEY_RCONTROL,  KEY_RSHIFT, KEY_ENTER);
	player[1].controls.set_keys(KEY_W,      KEY_S,      KEY_A,      KEY_D,      KEY_LCONTROL,  KEY_LSHIFT, KEY_CAPSLOCK);
	player[2].controls.set_keys(KEY_Y,      KEY_H,      KEY_G,      KEY_J,      KEY_B,         KEY_T,      KEY_U);
	player[3].controls.set_keys(KEY_P,      KEY_COLON,  KEY_L,      KEY_QUOTE,  KEY_STOP,      KEY_O,      KEY_OPENBRACE);
	player[4].controls.set_keys(KEY_8_PAD,  KEY_2_PAD,  KEY_4_PAD,  KEY_6_PAD,  KEY_1_PAD,     KEY_7_PAD,  KEY_9_PAD);
	player[5].controls.set_keys(KEY_HOME,   KEY_END,    KEY_DEL,    KEY_PGDN,   KEY_BACKSPACE, KEY_INSERT, KEY_PGUP);

}

static void nearCallback (void *data, dGeomID o1, dGeomID o2)
{
	int i, n;


	const int N = 10;
	dContact contact[N];
	n = dCollide (o1, o2, N, &contact[0].geom, sizeof(dContact));
	if (n > 0)
	{
		for (i=0; i<n; i++)
		{
			contact[i].surface.mode = dContactBounce;
			contact[i].surface.mu = 2000; //5000;
			contact[i].surface.bounce = 0.3;
			contact[i].surface.bounce_vel = 0.2;
  			dJointID c = dJointCreateContact (world,contactgroup,&contact[i]);
			dJointAttach(c,dGeomGetBody(o1), dGeomGetBody(o2));
		}
	}
}

void main_loop(void)
{
	double m;
	CVector cam;

	while(!key[KEY_ESC])
	{
      	  	rest(1);
		m=timer.seconds();
		timer.reset();

		int i;
		
		for(i=0; i<players; i++)
		  player[i].controls.work(player[i].car, player[i].camera, m);
		
		dSpaceCollide (space,0,&nearCallback);
		dWorldStep(world, MIN(m/2.0, 1.0/30.0));
		dJointGroupEmpty(contactgroup);
		dSpaceCollide (space,0,&nearCallback);
		dWorldStep(world, MIN(m/2.0, 1.0/30.0));
		dJointGroupEmpty(contactgroup);

		for(i=0; i<players; i++)
			player[i].camera.update(m);
		
		allegro_gl_begin();
		glClear(GL_DEPTH_BUFFER_BIT);
		
		int n;
		for(n=0; n<players; n++)
		{
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		
		if(players<=2)
		  {
		    glViewport(0, (players-1-n)*(SCREEN_H/players), SCREEN_W, SCREEN_H/players);
		    gluPerspective(45.0f, SCREEN_W/(SCREEN_H/players), 0.2, 200.0);
		  }
		else if(players==3)
		  {
		    if(n==0)
		      {
			glViewport(0, SCREEN_H/2, SCREEN_W, SCREEN_H/2);
			gluPerspective(45.0f, SCREEN_W/(SCREEN_H/2), 0.2, 200.0);
		      }
		    else
		      {
			glViewport(SCREEN_W/2*(n-1), 0, SCREEN_W/2, SCREEN_H/2);
			gluPerspective(45.0f, SCREEN_W/(SCREEN_H), 0.2, 200.0);
		      }
		  }
		else if(players==4)
		  {
		    glViewport(SCREEN_W/2*(n%2), SCREEN_H/2*((3-n)/2), SCREEN_W/2, SCREEN_H/2);
		    gluPerspective(45.0f, SCREEN_W/(SCREEN_H), 0.2, 200.0);
		  }
		else if(players==5)
		  {
		    if(n<2)
		      {
			glViewport(SCREEN_W/2*n, SCREEN_H/2, SCREEN_W/2, SCREEN_H/2);
			gluPerspective(45.0f, SCREEN_W/(SCREEN_H), 0.2, 200.0);
		      }
		    else
		      {
			glViewport(SCREEN_W/3*(n-2), 0, SCREEN_W/3, SCREEN_H/2);
			gluPerspective(45.0f, (SCREEN_W)/(SCREEN_H)*(2.0/3), 0.2, 200.0);
		      }
		  }
		else if(players==6)
		  {
		    glViewport(SCREEN_W/2*(n%2), SCREEN_H/3*((5-n)/2), SCREEN_W/2, SCREEN_H/3);
		    gluPerspective(45.0f, (SCREEN_W)/(SCREEN_H)*(3.0/2), 0.2, 200.0);
		  }
		else if(players==7)
		  {
		    if(n<4)
		      {
			glViewport(SCREEN_W/2*(n%2), SCREEN_H/3*((5-n)/2), SCREEN_W/2, SCREEN_H/3);
			gluPerspective(45.0f, (SCREEN_W)/(SCREEN_H)*(3.0/2), 0.2, 200.0);
		      }
		    else
		      {
			glViewport(SCREEN_W/3*(n-4), 0, SCREEN_W/3, SCREEN_H/3);
			gluPerspective(45.0f, (SCREEN_W)/(SCREEN_H), 0.2, 200.0);
		      }
		  }
		else
		  {
		    glViewport(SCREEN_W/2*(n%2), SCREEN_H/4*((7-n)/2), SCREEN_W/2, SCREEN_H/4);
		    gluPerspective(45.0f, (SCREEN_W)/(SCREEN_H)*(2), 0.2, 200.0);
		  }


		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		cam.x=player[n].camera.target.x-player[n].camera.position.x;
		cam.y=player[n].camera.target.y-player[n].camera.position.y;
		cam.z=player[n].camera.target.z-player[n].camera.position.z;
		gluLookAt(0,0,0,player[n].camera.target.x-player[n].camera.position.x,player[n].camera.target.y-player[n].camera.position.y,player[n].camera.target.z-player[n].camera.position.z,0,0,1);
		skybox.draw(cam.unit());
		glLoadIdentity();
		gluLookAt(player[n].camera.position.x, player[n].camera.position.y, player[n].camera.position.z,   player[n].camera.target.x, player[n].camera.target.y, player[n].camera.target.z,0,0,1);
        
		/*		glEnable(GL_BLEND);
		glDisable(GL_DEPTH_TEST);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glColor4f(0.7, 0.4, 0, 0.8);		
		for(i=0; i<ngroundbox; i++)
		{
			dVector3 ss;
			glPushMatrix();
			position_gl(groundbox[i]);
			dGeomBoxGetLengths(groundbox[i], ss);
			draw_box(ss[0], ss[1], ss[2]);
			glPopMatrix();
		}
		for(i=0; i<ngroundsphere; i++)
		{
			glPushMatrix();
			position_gl(groundsphere[i]);
			draw_sphere(dGeomSphereGetRadius(groundsphere[i]), 12);
			glPopMatrix();
		}
		glEnable(GL_DEPTH_TEST);
		glDisable(GL_BLEND);
		//		glClear(GL_DEPTH_BUFFER_BIT);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glColor3f(0, 1, 0);
		glDisable(GL_LIGHTING);
		for(i=0; i<ngroundbox; i++)
		{
			dVector3 ss;
			glPushMatrix();
			position_gl(groundbox[i]);
			dGeomBoxGetLengths(groundbox[i], ss);
			draw_box(ss[0], ss[1], ss[2]);
			glPopMatrix();
		}
		for(i=0; i<ngroundsphere; i++)
		{
			glPushMatrix();
			position_gl(groundsphere[i]);
			draw_sphere(dGeomSphereGetRadius(groundsphere[i]), 12);
			glPopMatrix();
		}
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glEnable(GL_LIGHTING);*/
                

		glPushMatrix();
		glScalef(-1, -1, 1);
		glColor3f(1.0, 1.0, 1.0);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, leveltex);
		terrain.draw();
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_LIGHTING);
		glColor3f(.5, .1, .1);
		innerloop.draw();
		outerloop.draw();
		glEnable(GL_LIGHTING);
		glPopMatrix();

		glEnable(GL_CULL_FACE);

		for(int j=0; j<players; j++)
		{
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, carbodytex);
		glColor3f(1, 1, 1);		
		glPushMatrix();
		position_gl(player[j].car.chassis);
		glScalef(-1, -1, 1);
		carbody.draw();
		//		draw_box(2, 3, 1.5);
		glPopMatrix();

		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glBindTexture(GL_TEXTURE_2D, wheeltex);
		for(i=0; i<4; i++)
		{
			glPushMatrix();
			position_gl(player[j].car.wheel[i]);
			//			glRotatef(90, 0, 0, 1);
			//			draw_sphere(0.8, 12);
			glColor3f(1, 1, 1);
			wheel.draw();
			glDisable(GL_TEXTURE_2D);
			glEnable(GL_BLEND);
			glColor4f(0, 0.6, 1, 0.5);
			wheelshell.draw();
			glDisable(GL_BLEND);
			glEnable(GL_TEXTURE_2D);
			glPopMatrix();
		}
		}
		glDisable(GL_CULL_FACE);

		}

		//		CVector sp;
		//		const dReal *sh;
		//		sh = dBodyGetLinearVel(player[0].car.chassis);
		//		sp.x=sh[0];
		//		sp.y=sh[1];
		//		sp.z=sh[2];
		//      		glEnable(GL_BLEND);
		//		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR);
		//		glDisable(GL_DEPTH_TEST);
		//		glLoadIdentity();
		//		allegro_gl_printf(allegro_fnt, -10, 3, -20, makecol(0, 255, 0), "SPEED: %6.2f", sp.length());		
		//		glDisable(GL_BLEND);
		//		glEnable(GL_DEPTH_TEST);

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
    		glViewport(0, 0, SCREEN_W, SCREEN_H);
       		gluPerspective(45.0f, SCREEN_W/(SCREEN_H/2), 0.2, 200.0);
		glMatrixMode(GL_MODELVIEW);
      		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR);
		glDisable(GL_DEPTH_TEST);
		glLoadIdentity();
		allegro_gl_printf(allegro_fnt, -16, 8, -20, makecol(0, 255, 0), "FPS: %6.4f", 1/m);		
		glDisable(GL_BLEND);
		glDisable(GL_TEXTURE_2D);
		glEnable(GL_DEPTH_TEST);
		
		glFinish();
		allegro_gl_flip();
		allegro_gl_end();
	}
}




