#include "global.h"

char WorldInformation::skyBaseFileName[MAX_PATH];
rgb_image_type WorldInformation::skyimage[2];
tex_object_2D  WorldInformation::skytex[2];

void WorldInformation::ReleaseModels()
{
	TankObject::ReleaseModelData();
	map.ReleaseModelData();
	particleEngine.ReleaseModels();

	for (int i=0; i < 6; i++)
		skytex[i].del();
}

void WorldInformation::RegisterModels()
{
	/*
	tank[1].RegisterModelData();
	tank[0].RegisterModelData();
	*/
	TankObject::RegisterModelData();
	map.RegisterModelData();
	particleEngine.RegisterModels();

	for (int i=0; i < 6; i++)
	{
		skytex[i].bind();
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D,		// 2D texture mode
				0,										// mipmap level
				GL_RGBA,								// internal format
				skyimage[i].get_width(),		// image width
				skyimage[i].get_height(),		// image height
				0,										// image border (0/1)
				GL_RGB,								// image pixel format
				GL_UNSIGNED_BYTE,					// image pixel size/type
				skyimage[i].get_pointer());	// ptr to image data
		skytex[i].unbind();
	}
}

#define INV_SQRT_2 (0.70710678118654752440084436210485)

void WorldInformation::DrawSkyBox()
{
	float scale = 1.5;
	float texScale = 0.5;
	float horizonRadius = max ( map.GetWidth(), map.GetLength() ) * scale;
	float midRadius = horizonRadius * INV_SQRT_2;
	int i;

	const int NUM_SPOKES=8;

	vec3f centerBot(map.GetWidth()/2.0,map.GetLength()/2.0, skyBoxLowerZ);
	vec3f centerTop(centerBot), centerMid(centerBot);
	centerTop[Z] = horizonRadius;
	centerMid[Z] = midRadius;

	vec3f offset(0,0,0);
	vec2f texpos(0.5,0.5);

	tex_object_2D::enable();
	glColor4f(1.0,1.0,1.0,1.0);

	// Draw ground first:
	skytex[SKY_FACE_BOTTOM].bind();
	glBegin(GL_TRIANGLE_FAN);
	glTexCoord(texpos);
	glVertex(centerBot);
	for (i=0; i <= NUM_SPOKES; i++)
	{
		texpos[X] = cos( i*2.0*M_PI/NUM_SPOKES );
		texpos[Y] = sin( i*2.0*M_PI/NUM_SPOKES );
		offset[X] = horizonRadius * texpos[X];
		offset[Y] = horizonRadius * texpos[Y];
		glTexCoord(texpos * texScale + vec2f(0.5,0.5));
		glVertex(centerBot+offset);
	}
	glEnd();
	skytex[SKY_FACE_BOTTOM].unbind();

	glDisable(GL_LIGHTING);
	// Draw Horizon
	skytex[SKY_FACE_TOP].bind();
	glBegin(GL_TRIANGLE_STRIP);
	for (i=0; i <= NUM_SPOKES; i++)
	{
		texpos[X] = (float)i/(float)NUM_SPOKES;
		texpos[Y] = 0.0;
		offset[X] = horizonRadius * cos( i*2.0*M_PI/NUM_SPOKES );
		offset[Y] = horizonRadius * sin( i*2.0*M_PI/NUM_SPOKES );
		glTexCoord(texpos);
		glVertex(centerBot+offset);

		texpos[X] = (float)i/(float)NUM_SPOKES;
		texpos[Y] = 0.5;
		offset[X] = midRadius * cos( i*2.0*M_PI/NUM_SPOKES );
		offset[Y] = midRadius * sin( i*2.0*M_PI/NUM_SPOKES );
		glTexCoord(texpos);
		glVertex(centerMid+offset);
	}
	glEnd();

	// Draw Zenith
	glBegin(GL_TRIANGLE_FAN);
	glTexCoord(vec2f(0.5,1.0));
	glVertex(centerTop);
	for (i=NUM_SPOKES; i >= 0; i--)
	{
		texpos[X] = (float)i/(float)NUM_SPOKES;
		texpos[Y] = 0.5;
		offset[X] = midRadius * cos( i*2.0*M_PI/NUM_SPOKES );
		offset[Y] = midRadius * sin( i*2.0*M_PI/NUM_SPOKES );
		glTexCoord(texpos);
		glVertex(centerMid+offset);
	}
	glEnd();
	skytex[SKY_FACE_TOP].unbind();

	tex_object_2D::enable();
	glEnable(GL_LIGHTING);

	return;

	//float scale = 4.5;
	float xsize = map.GetWidth()*scale;
	float ysize = map.GetLength()*scale;
	float zsize = (map.GetMaxHeight()-map.GetMinHeight())*scale*2;

	float xstart = map.GetWidth()/2 - xsize/2;
	float ystart = map.GetLength()/2 - ysize/2;
	float zstart = map.GetMinHeight() - 10;

	//glDisable(GL_CULL_FACE);
	glDisable(GL_LIGHTING);

	glColor4f(1.0,1.0,1.0,1.0);
	tex_object_2D::enable();
	skytex[SKY_FACE_TOP].bind();
	glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2i(0,0);		glVertex3f( xstart        , ystart        , zstart + zsize );
		glTexCoord2i(0,1);		glVertex3f( xstart        , ystart + ysize, zstart + zsize );
		glTexCoord2i(1,0);		glVertex3f( xstart + xsize, ystart        , zstart + zsize );
		glTexCoord2i(1,1);		glVertex3f( xstart + xsize, ystart + ysize, zstart + zsize );
	glEnd();
	skytex[SKY_FACE_TOP].unbind();

	skytex[SKY_FACE_LEFT].bind();
	glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2i(0,0);		glVertex3f( xstart        , ystart        , zstart         );
		glTexCoord2i(0,1);		glVertex3f( xstart        , ystart        , zstart + zsize );
		glTexCoord2i(1,0);		glVertex3f( xstart + xsize, ystart        , zstart         );
		glTexCoord2i(1,1);		glVertex3f( xstart + xsize, ystart        , zstart + zsize );
	glEnd();
	skytex[SKY_FACE_LEFT].unbind();

	skytex[SKY_FACE_FRONT].bind();
	glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2i(0,0);		glVertex3f( xstart + xsize, ystart        , zstart         );
		glTexCoord2i(0,1);		glVertex3f( xstart + xsize, ystart        , zstart + zsize );
		glTexCoord2i(1,0);		glVertex3f( xstart + xsize, ystart + ysize, zstart         );
		glTexCoord2i(1,1);		glVertex3f( xstart + xsize, ystart + ysize, zstart + zsize );
	glEnd();
	skytex[SKY_FACE_FRONT].unbind();

	skytex[SKY_FACE_RIGHT].bind();
	glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2i(0,0);		glVertex3f( xstart        , ystart + ysize, zstart         );
		glTexCoord2i(1,0);		glVertex3f( xstart + xsize, ystart + ysize, zstart         );
		glTexCoord2i(0,1);		glVertex3f( xstart        , ystart + ysize, zstart + zsize );
		glTexCoord2i(1,1);		glVertex3f( xstart + xsize, ystart + ysize, zstart + zsize );
	glEnd();
	skytex[SKY_FACE_RIGHT].unbind();

	skytex[SKY_FACE_BACK].bind();
	glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2i(0,0);		glVertex3f( xstart        , ystart        , zstart         );
		glTexCoord2i(1,0);		glVertex3f( xstart        , ystart + ysize, zstart         );
		glTexCoord2i(0,1);		glVertex3f( xstart        , ystart        , zstart + zsize );
		glTexCoord2i(1,1);		glVertex3f( xstart        , ystart + ysize, zstart + zsize );
	glEnd();
	skytex[SKY_FACE_BACK].unbind();

	skytex[SKY_FACE_BOTTOM].bind();
	glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2i(0,0);		glVertex3f( xstart        , ystart        , zstart );
		glTexCoord2i(1,0);		glVertex3f( xstart + xsize, ystart        , zstart );
		glTexCoord2i(0,1);		glVertex3f( xstart        , ystart + ysize, zstart );
		glTexCoord2i(1,1);		glVertex3f( xstart + xsize, ystart + ysize, zstart );
	glEnd();
	skytex[SKY_FACE_BOTTOM].unbind();
	tex_object_2D::disable();

	glEnable(GL_CULL_FACE);
}

extern float gFovX;

void WorldInformation::Draw()
{
	SetDisplayMode();
	glFrontFace(GL_CCW);

#ifdef USE_ROAM
	gFovX = settings.fov+10;
#endif

	//static float lastEyeTargetZ = tank[0].model.pos[Z];
	//vec3f eyeTarget = tank[0].model.pos;
	vec3f zOff(0,0,10);

	if (tank[0].state == TS_NORMAL)
	{
		eyeTarget[X] = tank[0].model.pos[X];
		eyeTarget[Y] = tank[0].model.pos[Y];
		eyeTarget[Z] *= 0.95;
		eyeTarget[Z] += (tank[0].model.pos[Z] + zOff[Z]) * 0.05;
	}
	else
	{
		eyeTarget *= 0.98;
		eyeTarget += (tank[0].model.pos + zOff) * 0.02;
	}

	eyeDirection = eyeTarget - eyepoint.pos;

	gluLookAt(eyepoint.pos[X], eyepoint.pos[Y], eyepoint.pos[Z],
			  eyeTarget[X], eyeTarget[Y], eyeTarget[Z],
			  0.0, 0.0, 1.0 );

	if (nightmode)
	{
		lightCol = vec4f( 0.1, 0.2, 0.3, 1.0 );
	}

	SetDisplayLighting();

	// Draw world's bounding box:
	/*
	glPushMatrix();
	glLineWidth(1.5);
	glScalef(map.GetWidth(),map.GetLength(),map.GetMaxHeight());
	glTranslatef(0.5,0.5,0.5);
	glColor3f(0.0,1.0,0.0);
	glutWireCube(1.0);
	glPopMatrix();
	*/

	glEnable(GL_FOG);
	GLint fogMode = GL_LINEAR;
	vec4f fogColor( 0.5, 0.5, 0.5, 1.0 );
	glFogfv(GL_FOG_COLOR, fogColor);
	glFogi(GL_FOG_MODE, fogMode);
	glFogf(GL_FOG_DENSITY, 0.25);
	glHint(GL_FOG_HINT, GL_FASTEST);
	glFogf(GL_FOG_START, 500);
	glFogf(GL_FOG_END, 12500);

	glDisable(GL_LIGHTING);
	DrawSkyBox();

	glFogf(GL_FOG_START, 500);
	glFogf(GL_FOG_END, 3500);

	if (input.shield)
	{
		glPolygonMode(GL_FRONT,GL_LINE);
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glDisable(GL_LIGHTING);
	}
	else
	{
		glPolygonMode(GL_FRONT,GL_FILL);
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glEnable(GL_LIGHTING);
	}

	glColor3f(1.0,1.0,1.0);
	map.DrawMap();

	int i;
	for (i=0; i < MAX_PLAYERS; i++)
		if (tank[i].active)
			tank[i].Draw();

	glDisable(GL_LIGHTING);

	glColor3f(1.0,1.0,0.0);
	for (i=0; i < MAX_PROJECTILES; i++)
		if (projectiles[i].active)
			projectiles[i].Draw();

	//glColor3f(1.0,1.0,1.0);
	particleEngine.Draw();

	glDisable(GL_FOG);
}

void WorldInformation::update(double dtime)
{
	int i;
	for (i=0; i < MAX_PLAYERS; i++)
		if (tank[i].active)
			 tank[i].update(dtime);

	eyedyn.followerOffset[X] = -cos(tank[LOCAL_PLAYER_IDX].model.dir * M_PI / 180.0) * 40; // * (1+model.vel.length()/MaxVelocity);
	eyedyn.followerOffset[Y] = -sin(tank[LOCAL_PLAYER_IDX].model.dir * M_PI / 180.0) * 40; // * (1+model.vel.length()/MaxVelocity);

	eyepoint.acc = calcFollow(eyepoint,tank[LOCAL_PLAYER_IDX].model,eyedyn);
	//if (!map.IsWithinBounds(eyepoint.pos))
	//	eyepoint.pos = vec3f(50,50,50);
	if (map.IsWithinBounds(eyepoint.pos))
	{
		if (eyepoint.pos[Z] < map.GetHeight(eyepoint.pos))
			eyepoint.pos[Z] = map.GetHeight(eyepoint.pos);
		if (eyepoint.pos[Z] < map.GetHeight(eyepoint.pos) + eyedyn.followerOffset[Z]/1.5)
			eyepoint.acc += calcHoverAccel(eyepoint, eyedyn.followerOffset[Z], 1000, 0, map);
	}

	/*
	if (eyepoint.pos[Z] < map.GetHeight(eyepoint.pos) + eyedyn.followerOffset[Z])
		eyepoint.vel[Z] += map.GetHeight(eyepoint.pos) + eyedyn.followerOffset[Z] - eyepoint.pos[Z];
	*/

	eyepoint.update(dtime);

	for (i=0; i < MAX_PROJECTILES; i++)
		if (projectiles[i].active != PS_INACTIVE)
			projectiles[i].update(dtime);

	particleEngine.update(dtime);

	for (i=0; i < map.treeInfo.size();i++)
	{
		if (map.treeInfo[i].onFireAndGonnaDieAHorribleFlamingDeath
			&& map.treeInfo[i].lifeleft > 0)
		{
			map.treeInfo[i].lifeleft -= dtime;
		}
	}
}

char* WorldInformation::skyfilesuffix[2] = {
//	"top","left","right","front","back","bottom"
//	"top","top","top","top","top","bottom2"
	"top2","bottom"
};


void WorldInformation::LoadData()
{
	static bool loaded = false;

	map.LoadMapByID(0);
	TankObject::LoadModelData();
	particleEngine.LoadData();

	if (loaded)
		return;

	loaded = true;

	strcpy(skyBaseFileName,"sunset_");
	char str[MAX_PATH];
	for (int i=0; i < 2; i++)
	{
		sprintf(str,"../data/%s%s.png",skyBaseFileName,skyfilesuffix[i]);
		printf("WorldInfo> Loading sky box texture \"%s\":",str);
		if (!read_png_rgb( str, skyimage[i] ))
			printf("<ERROR> Could not load file.\n");
		else
			printf("Loaded %dx%d image\n",skyimage[i].get_width(),skyimage[i].get_height());
	}

	/*
	tank[0].LoadModelData();
	tank[1].LoadModelData();

	tank[0].hoverStartupTime = GetSoundLen(HTEngineStartupSound);
	tank[0].hoverShutdownTime = GetSoundLen(HTEngineShutdownSound);
	tank[1].hoverStartupTime = GetSoundLen(HTEngineStartupSound);
	tank[1].hoverShutdownTime = GetSoundLen(HTEngineShutdownSound);
	*/
}

void WorldInformation::Reset()
{
	int i;

	currentTime = 0;

	for (i=0; i < MAX_PLAYERS; i++)
		tank[i].Reset(this);

	//eyepoint.pos = vec3f( 25, 25, 220 );
	eyepoint.pos = vec3f( 25, 25, map.GetHeight(25,25)+220 );
	eyepoint.vel = vec3f(0,0,0);
	//eyedyn = FollowerDynamics( vec3f( 20, 20, 30 ) , 500 , 150 , 100 , 0.95 );
	//eyedyn = FollowerDynamics( vec3f( 40, 40, 40 ) , 500 , 150 , 100 , 2.95 );
	//eyedyn = FollowerDynamics( vec3f( 40, 40, 40 ) , 600 , 250 , 300 , 0.9 );
	eyedyn = FollowerDynamics( vec3f( 40, 40, 40 ) , 600 , 250 , 300 , 1.1 );
	eyeTarget = tank[0].model.pos;

	settings.gravity = 200.0;
	settings.usingCubicSplineInterp = true;
	settings.sound = true;

	if (this == &game.netserver.m_world)
		particleEngine.inServer = true;
	else
		particleEngine.inServer = false;

	lightPos = vec4f(-1,0,1,0);
	lightCol = vec4f(1,1,1,1);
	lightAmb = vec4f(0,0,0,1);
}

char* msgs[5] = {
	"This is message #1!",
	"Captain Carnage: I will crush you",
	"DeathMan: You like that?",
	"Happy: \"Boom!\"",
	"System error!",
};

double frand();
bool WorldInformation::HandleInput(InputEvent_type e)
{
	if (e.type == IE_KB)
	{
		switch(e.key)
		{
			case 'e':
			case 'E':
				if (game.world.tank[0].hoverStatus == HOVER_OFF)
					game.world.tank[0].StartHover();
				else if (game.world.tank[0].hoverStatus == HOVER_GOING)
					game.world.tank[0].StopHover();
				printf("\nToggling hover!\n");
				return true;
			case 'x':
				game.netclient.SendServerCommand(HT_CMDID_SUICIDE);
				return true;
			case 'X':
				eyepoint.pos = tank[0].model.pos - eyedyn.followerOffset * 3;
				eyeTarget = tank[0].model.pos;
				eyepoint.vel = vec3f(0,0,0);
				return true;
			case 'b':
				printf("\r inc'ing tank model from %d to ",tank[LOCAL_PLAYER_IDX].modelnum);
				if ( ++(tank[LOCAL_PLAYER_IDX].modelnum) >= NUM_TANK_MODELS)
					tank[LOCAL_PLAYER_IDX].modelnum = 0;
				tank[LOCAL_PLAYER_IDX].SetTankModel(tank[LOCAL_PLAYER_IDX].modelnum);
				printf("%d   \n",tank[LOCAL_PLAYER_IDX].modelnum);
				break;
			case 'B':
				printf("\r dec'ing tank model from %d to ",tank[LOCAL_PLAYER_IDX].modelnum);
				if (--(tank[LOCAL_PLAYER_IDX].modelnum) < 0)
					tank[LOCAL_PLAYER_IDX].modelnum = NUM_TANK_MODELS-1;
				tank[LOCAL_PLAYER_IDX].SetTankModel(tank[LOCAL_PLAYER_IDX].modelnum);
				printf("%d   \n",tank[LOCAL_PLAYER_IDX].modelnum);
				break;
			case 'n':
				nightmode = !nightmode;
				return true;

			default:
				break;
		}
	}
	else
	{
		// Mouse event - ignore for now
	}
	return false;
}

void WorldInformation::SetDisplayMode()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	float speedfactor = tank[LOCAL_PLAYER_IDX].model.vel.length() / tank[LOCAL_PLAYER_IDX].MaxVelocity;
	speedfactor -= 0.3;
	if (speedfactor < 0)	speedfactor = 0;
	settings.fov = 80 + 40*speedfactor;
	gluPerspective(settings.fov, game.config.aspect, 0.1, 25000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
}

void WorldInformation::SetDisplayLighting()
{
	// Enable lighting
	//GLfloat lightpos[] = { 1.0, 1.0, -1.0, 0.0 };
	//vec4f lightpos( -1.0, 0.0, 1.0, 0.0 );
	//GLfloat lightcol[] = { 1.0, 1.0, 1.0, 1.0 };
	//GLfloat lightamb[] = { 0.0, 0.0, 0.0, 1.0 };
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightCol);
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmb);
	/*
	// Nighttime!!
	GLfloat lightpos[] = { 1.0, -0.5, 1.0, 0.0 };
	GLfloat lightcol[] = { 0.1, 0.1, 0.3, 1.0 };
	GLfloat lightamb[] = { 0.1, 0.1, 0.1, 1.0 };
	glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightcol);
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightamb);
	*/

	//glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,light0_dir);
	glLightf (GL_LIGHT0,GL_SPOT_CUTOFF,180);
	glEnable(GL_LIGHTING);
}

void WorldInformation::StartTreeFire(int treeIdx)
{
	ParticleEmitter* ppe = particleEngine.NewEmitter();
	if (ppe)
	{
		vec3f pos( map.treeInfo[treeIdx].pos[0], map.treeInfo[treeIdx].pos[1], 0 );
		pos[2] = map.GetHeight(pos);

		ParticleEmitter& pe = *ppe;
		pe.InitEmitter(PARTICLE_EMITTER_FIRE);
		pe.sourceParticle.pos = pos;
		pe.sourceParticle.vel = vec3f(0,0,0);
		pe.sourceParticle.size = 25;
		pe.k_drag = 0.2;
		pe.wind = vec3f(0,0,0);
		pe.particleEmissionRate = 12.5;
		pe.particlesToEmit = 20;

		map.treeInfo[treeIdx].onFireAndGonnaDieAHorribleFlamingDeath=true;
		map.treeInfo[treeIdx].lifeleft = pe.lifetime;

		printf("\rStarting fire!  \n");
	}
}
