/*
* This code is released under the GNU General Public License.  See COPYING for 
* details.  Copyright 2003 John Spray: spray_john@users.sourceforge.net
*/


#include <string.h>
#include <SDL_opengl.h>
#include <math.h>
#include <sys/time.h>
#include <unistd.h>

#include "Game.h"
#include "Events.h"
#include "SoundCore.h"
#include "Particle.h"
#include "Visual.h"
#include "Player.h"
#include "Missile.h"
#include "Config.h"
#include "Ufo.h"
#include "Menu.h"
#include "Obstacle.h"
#include "Vector.h"
#include "ScoreBoard.h"
#include "PowerUp.h"


extern Config GLOB_conf;
extern ScoreBoard GLOB_scores;

Game::Game(SDL_Surface* newscreen)
{
	quit=0;
	verbose=GLOB_conf.verbose;
	screen=newscreen;
	timescale=1.0f;
	score=0;
	g.y=-0.0001;
	spawnticker=0;
	spawndelay=5000;
}

Game::~Game()
{
}

int Game::Happen()
{
	long frames=0;

	struct timeval tv;
	struct timeval oldtv;

	player=new Player(this);
	if(GLOB_conf.twoplayer)
		player2=new Player(this);

	visual=new Visual(this);
	visual->InitGL();

	sound=new SoundCore;
	sound->SetEar(&visual->GetMainCam()->s,&visual->GetMainCam()->v,
		&visual->GetMainCam()->face,&visual->GetMainCam()->up);

	visual->ShowLoading(0.1f);
	visual->LoadTexture("arenamesh.png");
	visual->LoadTexture("arenabase.png");
	visual->LoadTexture("arenawall.png");
	visual->LoadTexture("skyup.png");
	visual->LoadTexture("skydown.png");
	visual->LoadTexture("skyleft.png");
	visual->LoadTexture("skyright.png");
	visual->LoadTexture("skyback.png");
	visual->LoadTexture("skyfront.png");
	visual->ShowLoading(0.5f);
	visual->LoadTexture("shield.png");
	visual->LoadTexture("explosion1.png");
	visual->LoadTexture("missiletrail.png");
	visual->LoadTexture("shadow.png");
	visual->ShowLoading(0.65f);
	visual->LoadTexture("driveparticle.png");
	visual->LoadTexture("lifeicon.png");
	visual->LoadTexture("skyscraper.png");
	visual->LoadTexture("menucursor.png");
	visual->LoadTexture("smoke1.png");
	visual->LoadTexture("powerupshell.png");
	visual->LoadTexture("tracer.png");
	visual->LoadTexture("muzzleflash.png");
	visual->LoadTexture("halfdriveparticle.png");
	visual->ShowLoading(0.8f);

	char *soundfiles[]={	"fire.wav","boom.wav","engine.wav","ufo.wav",
				"ufodie.wav","gotthelife.wav","cannon.wav",
				"spotted.wav","playerdie.wav","shield.wav",
				"mine.wav",NULL};

	for(int i=0;soundfiles[i];i++){
		if(verbose) printf("Game::Happen: Loading wavefile: %s\n",soundfiles[i]);
		sound->LoadSample(soundfiles[i]);
	}

	//Note that textures corresponding to meshes
	//don't need to be preloaded, LoadMesh does it
	visual->LoadMesh("player.mesh");
	visual->LoadMesh("ufo.mesh");
	visual->LoadMesh("mine.mesh");

	evs=new Events;
	evs->game=this;
	evs->keyboardtarget=player;

	arena=new Arena(this);
	arena->MakeObstacles();
	arena->GenerateGeometry();

	missilelist= new LList<Missile>;

	ufolist=new LList<Ufo>;

	minelist=new LList<Mine>;

	poweruplist=new LList<PowerUp>;

	visual->ShowLoading(0.9f);

	evs->targetcam=visual->GetMainCam();

	player->Respawn();
	if(GLOB_conf.twoplayer)
		player2->Respawn();

#ifdef RELEASE
	SDL_WM_GrabInput(SDL_GRAB_ON);
#endif

	visual->ShowLoading(1.0f);//yes,yes, it's pointless

#ifdef LINUX
	gettimeofday(&oldtv,NULL);
#else
	ticks=SDL_GetTicks();
#endif
	startticks=SDL_GetTicks();
	dtf=0.0f;

	while(!quit){
		sleep(0);

		evs->HandleEvents();

		visual->UpdateParticles();

		//3x detail on player physics: more detailed
		//observations by player than other physics
		dtf/=3.0f;
		player->Physics();
		player->Physics();
		player->Physics();
		dtf*=3.0f;

		if(GLOB_conf.twoplayer)
			player2->Physics();

		SpawnUfos();

		UpdateUfos();

		UpdateMines();

		UpdateMissiles();

		UpdatePowerUps();

		sound->Update();

		visual->Draw();

		frames++;

#ifdef LINUX
		gettimeofday(&tv,NULL);
		dtf=(tv.tv_sec-oldtv.tv_sec)*1000.0f+(tv.tv_usec-oldtv.tv_usec)/1000.0f;
		if(timescale!=1.0f)
			dtf*=timescale;
		oldtv=tv;
#else
		dtf=float(SDL_GetTicks()-ticks)*timescale;
		ticks=SDL_GetTicks();		
#endif
		if(GLOB_conf.actslow) SDL_Delay(28);
	}

	SDL_WM_GrabInput(SDL_GRAB_OFF);

	player->Unspawn();

	printf("Game::Happen: %d frames, %.2ffps, on average.\n",(int)frames,float(1000*frames)/float(SDL_GetTicks()-startticks));
	printf("Game::Happen: score=%d\n",score);

	delete poweruplist;

	delete missilelist;

	delete ufolist;

	delete minelist;

	delete player;
	if(GLOB_conf.twoplayer)
		delete player2;

	delete sound;

	delete evs;
	
	delete visual;

	delete arena;

	return quit;
}

void Game::NewMissile(Missile* newmissile)
{
	newmissile->game=this;
	missilelist->Add(newmissile);
}

void Game::NewPowerUp(PowerUp* newpowerup)
{
	newpowerup->game=this;
	poweruplist->Add(newpowerup);
}

void Game::NewUfo(Ufo* newufo)
{
	Ufo* created;
	newufo->game=this;
	created=ufolist->Add(newufo);	
	created->Live();
}

void Game::NewMine(Mine* newmine)
{
	Mine* created;
	newmine->game=this;
	created=minelist->Add(newmine);	
	created->Live();
}

void Game::SpawnUfos()
{
		int failcount;
		Vector place;
		Vector dist;

		spawnticker+=dtf;
		if(spawnticker<spawndelay) return;

		spawnticker=0;
		if(spawndelay>1000.0f)
			spawndelay*=0.95f;

		if(ufolist->count>=20)
			return;

		Ufo newufo;
		place.y=3.0f;
		place.x=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
		place.z=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
		dist=place-player->s;
		failcount=0;
		//be careful that sniffradius fits in the arena!
		while(failcount<50&&(dist.Mag2() < newufo.sniffradius*newufo.sniffradius || arena->Blocked(place,0) || !arena->Collision(place,player->s))){
			failcount++;
			if(verbose) printf("Game::SpawnUfos: place %f,%f,%f is unacceptable.  "
			"It is %f from the player, at %f,%f,%f\n",place.x,place.y,place.z,
			dist.Mag(),player->s.x,player->s.y,player->s.z);

			place.x=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
			place.z=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
			dist=place-player->s;
		}

		if(verbose)
			printf("Game::SpawnUfos: place=%f,%f,%f\n",place.x,place.y,place.z);

		newufo.s=place;
		NewUfo(&newufo);
}

void Game::UpdateMissiles()
{
	LListItem<Missile> *item;	
	Particle newpart;


	Vector temp,temp1,temp2,d;
	d=10000.0f;

	item=missilelist->head;
	while(item){
    if(item->data.collided){
			item=missilelist->Del(item);
			continue;
		}

		if(player->powerup==POWERUP_GUIDANCE && player->poweruptimer>0.0f){
			Ufo* target=NULL;
			LListItem<Ufo>* ufoitem;
			target=NULL;
			ufoitem=ufolist->head;
			while(ufoitem){
				temp=ufoitem->data.s-item->data.s;
				if(temp.Mag2() < 50.0f*50.0f && temp.Mag2() < d.Mag2()){
					target=&ufoitem->data;
					d=temp;
				}
				ufoitem=ufoitem->next;
			}

			if(target){
				d.Unitize();
				item->data.a=d*0.001f;
				//item->data.v*=pow(0.99f,dtf);
				temp1=item->data.v;
				temp1.Unitize();
				temp2=item->data.a;
				temp2.Unitize();
				item->data.v*=temp1|temp2;
			}
		}
		
		
		item->data.Physics();
		/*newpart.s=item->data.s;
		newpart.v=0.0f;
		strcpy(newpart.texfile,"missiletrail.png");
		newpart.rad=1;
		newpart.blendmode=GL_ONE;
		newpart.life=300;
		visual->NewParticle(&newpart);*/

		item=item->next;
	}

}

void Game::UpdatePowerUps()
{
	LListItem<PowerUp>* item;
	item=poweruplist->head;
	while(item){
		item->data.Update();
		if(item->data.done){
			item=poweruplist->Del(item);
			continue;
		}
		item=item->next;
	}
}

void Game::UpdateUfos()
{
	LListItem<Ufo>* ufoitem;
	ufoitem=ufolist->head;
	while(ufoitem){
		if(!ufoitem->data.alive){
			ufoitem=ufolist->Del(ufoitem);
			continue;
		}
		ufoitem->data.Physics();
		ufoitem->data.Pilot();
		ufoitem=ufoitem->next;
	}
}

void Game::UpdateMines()
{
	LListItem<Mine>* mineitem;
	mineitem=minelist->head;
	while(mineitem){
		if(!mineitem->data.alive){
			mineitem=minelist->Del(mineitem);
			continue;
		}
		mineitem->data.Physics();
		mineitem=mineitem->next;
	}
}

//blatant repetitousness lies herein....
void Game::IncScore(int more)
{
	PowerUp newpup;
	Vector place,dist;
	int failcount;

	if((score+more)-(score+more)%40000>score-score%40000){
		newpup.nature=POWERUP_LIFE;
		place.y=newpup.s.y;
		place.x=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
		place.z=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
		dist=place-player->s;
		failcount=0;
		while(failcount<50&&arena->Blocked(place,0)){
			failcount++;
			place.x=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
			place.z=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
			dist=place-player->s;
		}
		newpup.s=place;
  	NewPowerUp(&newpup);
	}

	if((score+more)-(score+more)%3000>score-score%3000){
		int rannum=rand();
  	if(rannum<RAND_MAX/3)
			newpup.nature=POWERUP_GUIDANCE;
		else if(rannum<(RAND_MAX/3)*2)
			newpup.nature=POWERUP_TRIPLESHOT;
		else
			newpup.nature=POWERUP_SUPERSHIELD;
		place.y=newpup.s.y;
		place.x=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
		place.z=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
		dist=place-player->s;
		failcount=0;
		while(failcount<50&&arena->Blocked(place,0)){
			failcount++;
			place.x=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
			place.z=((float)rand()/(float)RAND_MAX)*arena->halfwidth*2-arena->halfwidth;
			dist=place-player->s;
		}
		newpup.s=place;
  	NewPowerUp(&newpup);
  }



	score+=more;
}

void Game::ShowMenu()
{
	menufunc menureturn;

	SDL_GrabMode grabcache=SDL_WM_GrabInput(SDL_GRAB_QUERY);
	SDL_WM_GrabInput(SDL_GRAB_OFF);

	float tempvolume=sound->GetVolume();

	long start=SDL_GetTicks();
	sound->SetPaused(1);

	Menu* menu;
	char* labels[]={"Continue","Volume:","Abort game","Exit Excido",NULL};
	menufunc functions[]={MENU_CONTINUE,MENU_FLOATONE,MENU_QUIT,MENU_QUITFULL};
	void* targets[]={NULL,(void*)&tempvolume,NULL,NULL};


	menu=new Menu(labels,functions,targets,visual);
	menu->solidbackground=0;
	menureturn=menu->Happen();
	delete menu;
	if(menureturn==MENU_QUIT)
		GameOver(1);
	else if(menureturn==MENU_QUITFULL)
		GameOver(2);


	sound->SetVolume(tempvolume);
	
	sound->SetPaused(0);

	startticks+=SDL_GetTicks()-start;//for benefit of avg frame rate calculation in Game::Happen
	ticks=SDL_GetTicks();//look at where Events::HandleEvents is called in Happen, doing this is sane-ish
	dtf=0;//ie skip a simulation step after returning - it's that or a real big one :-)*/

	SDL_WM_GrabInput(grabcache);
}

void Game::Resize(int w,int h)
{
	GLOB_conf.screenx=w;
	GLOB_conf.screeny=h;
	screen=SDL_SetVideoMode(GLOB_conf.screenx,GLOB_conf.screeny,GLOB_conf.bpp,SDL_OPENGLBLIT | SDL_RESIZABLE);
	visual->ResetCams();
	evs->targetcam=visual->GetMainCam();
	sound->SetEar(&visual->GetMainCam()->s,&visual->GetMainCam()->v,
		&visual->GetMainCam()->face,&visual->GetMainCam()->up);
}


void Game::GameOver(int quitstate)
{
	char name[64]="Player";

	if(GLOB_scores.IsHigh(score)){
		Menu* menu;
		char* labels[]=	{	"Game over!  High Score!",
											"Your Name:",
											"[Enter your name and press return]",NULL
										};
		menufunc functions[]={MENU_NOSELECT,MENU_STRING,MENU_NOSELECT};
		void* targets[]={NULL,name,NULL};

		menu=new Menu(labels,functions,targets,visual);
		menu->solidbackground=0;
		menu->Happen();
		delete menu;

		GLOB_scores.NewScore(score,name);
		visual->LoadTexture("menuback.png");
		GLOB_scores.Display(visual);
		visual->UnLoadTexture("menuback.png");
	}
	else if(quitstate!=2){
		Menu* menu;
		char* labels[]={"Game over!",NULL};
		menufunc functions[]={MENU_CONTINUE};
		void* targets[]={NULL};

		menu=new Menu(labels,functions,targets,visual);
		menu->solidbackground=0;
		menu->Happen();
		delete menu;
	}

	quit=quitstate;
}
