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


#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <SDL_opengl.h>

#include "Player.h"
#include "Missile.h"
#include "Particle.h"
#include "Visual.h"
#include "SoundCore.h"
#include "Menu.h"

#ifndef M_2PI
#define M_2PI 6.28318
#endif

#ifndef M_PI
#define M_PI 3.14159
#endif

Player::Player(Game* newgame)
{
	game=newgame;	
	alive=0;
	collideradius=7;
	collideradius2=25;  
	lives=3;
	highlightlives=0;
	cannonperiod=50.0f;
	//most of the init happens in Respawn
}

void Player::Respawn()
{

	if(!lives)
		return;
	if(alive)
		return;
	if(!SafeToRespawn())
		return;

	lives--;

	s.x=s.z=0;
	s.y=50.1f;
	v=0;
	a=0;
	thrust=0.0f;
	bear=0.0f;
	bearv=0.0f;
	beara=0.0f;
	strafea=0.0f;
	soundid = game->sound->Play("engine.wav",-1,&s,&v);
	alive=1;
	hp=1.0f;
	shieldeffect=0;
	bouncecycle=((float)rand()/(float)RAND_MAX)*M_2PI;
	powerup=POWERUP_NONE;
	poweruptimer=0.0f;
	maxpoweruptimer=0.0f;
	cannonfiring=0;
	radarrange=200.0f;

	TriggerShield();
}




void Player::Physics()
{
	if(!alive) return;
	
	//hacky way of dealing with fundamental flaws in keyboard event handling:
	//basically this lets the user fix 'stuck' keys by pressing the opposite key
	if(thrust<-1.0f)
		thrust=-1.0f;
	if(thrust>1.0f)
		thrust=1.0f;
	if(strafea<-1.0f)
		strafea=-1.0f;
	if(strafea>1.0f)
		strafea=1.0f;

	float afactor;
	float maxv=0.18f;
	Particle newpart;

	if(shieldeffect){
		shieldeffect-=game->dtf;
		if(shieldeffect<0.0f)
			shieldeffect=0.0f;
	}

	if(highlightlives){
		highlightlives-=game->dtf;
		if(highlightlives<0.0f)
			highlightlives=0.0f;
	}	

	if(poweruptimer){
		poweruptimer-=game->dtf;
		if(poweruptimer<0.0f)
			poweruptimer=0.0f;
	}	

	bouncecycle+=game->dtf/100.0f;
	//printf("%f\n",bouncecycle);
	if(bouncecycle>M_2PI)
		bouncecycle-=M_2PI;
	if(cannonfiring){
		if(cannonwait) cannonwait -= game->dtf;
		while(cannonwait<0){
			cannonwait+= cannonperiod;
			ShootCannon();
		}
	}

	s.y=3+0.3f*sin((float)bouncecycle);
	a.y=0.0f;
	//thrust should be (0|-1.0f|1.0f)
	a.z=cos(bear)*thrust*0.005f;
	a.x=sin(bear)*thrust*0.005f;

	a.z+=cos(bear+M_PI/2)*strafea*0.005;
	a.x+=sin(bear+M_PI/2)*strafea*0.005;

	a.Unitize();
	a*=0.005f;

	bearv+=beara*game->dtf;
	bearv*=pow(0.99f,game->dtf);
	bear+=bearv*game->dtf;
	if(bear>M_2PI)
		bear-=M_2PI;

	afactor=1.0-(v.Mag()/maxv);

	v+=(game->dtf*afactor)*a;
	v*=pow(0.99f,game->dtf);
	Collide();
	//Collide();
	//no, it's not a typo.  To understand why we call that twice, imagine
	//what happens when the player is in a 90 degree corner of two quads
	// - we need to collide with BOTH: deflect the vector off one, and then
	//deflect the resulting vector off the other
	s+=v*game->dtf;

	//Smoke when damaged
	if(hp<0.25f){
		Particle newpart;
		newpart.s=s;
		newpart.s.x-=sin(bear)*2.0f;
		newpart.s.z-=cos(bear)*2.0f;
		newpart.v=v*0.5f;
		newpart.a.y=0.0001f;
		newpart.diffuse=0.000001f;
		newpart.res=0.99f;
		newpart.life=750.0f;
		newpart.rad=3.0f;
		newpart.blendmode=GL_ONE_MINUS_SRC_ALPHA;
		strcpy(newpart.texfile,"smoke1.png");
		game->visual->NewParticle(&newpart);
	}
}

int Player::SafeToRespawn()
{
	LListItem<Ufo>* item;

	item=game->ufolist->head;
	while(item){
		if(item->data.s.Mag2()<item->data.sniffradius*item->data.sniffradius)
			{
				/*s.x=s.z=0.0f;
				bear=-asin(item->data.s.x/item->data.s.Mag())+M_PI;*/
				return 0;
			}
		item=item->next;
	}

	return 1;
}

Player::~Player()
{
	if(alive)
		Unspawn();
}

void Player::Unspawn()
{
	if(!alive) return;
	game->sound->StopEffect(soundid);
	if(cannonfiring)
		StopCannon();
	alive=0;
}

void Player::FireMissile()
{	
	if(!alive) return;

	game->sound->Play("fire.wav",0,s,v);

	Missile newmis;
	newmis.s=s;
	newmis.v=v;
	newmis.a.y=-0.0001;
	newmis.v.y+=0.015f;
	newmis.v.z+=0.5f*cos(bear);
	newmis.v.x+=0.5f*sin(bear);
	newmis.res=0.999f;
	newmis.owner=this;
	game->NewMissile(&newmis);

	if(powerup==POWERUP_TRIPLESHOT && poweruptimer>0){
		newmis.v=v;
		newmis.v.y+=0.01f;
		newmis.v.z+=0.5f*cos(bear+M_PI/20.0f);
		newmis.v.x+=0.5f*sin(bear+M_PI/20.0f);

		game->NewMissile(&newmis);

		newmis.v=v;
		newmis.v.y+=0.01f;
		newmis.v.z+=0.5f*cos(bear-M_PI/20.0f);
		newmis.v.x+=0.5f*sin(bear-M_PI/20.0f);
		game->NewMissile(&newmis);
	}                                        
  
	Particle newpart;
	for(int i=0;i<5;i++){
		newpart.s=newmis.s;
		newpart.v=v+newmis.v*0.5f;
		newpart.a.y=0.0001f;
		newpart.res=0.99f;
		newpart.rad=2.5f;
		newpart.diffuse=0.000003f;
		newpart.life=400;
		strcpy(newpart.texfile,"smoke1.png");
		game->visual->NewParticle(&newpart);
	}
}

void Player::ShootCannon()
{
 if(!alive) return;
 Vector shoot,hit,path;
 Vector tempv;

 shoot.x=sin(bear)*1000.0f+100.0f*float(rand())/float(RAND_MAX)-50.0f;
 shoot.z=cos(bear)*1000.0f+100.0f*float(rand())/float(RAND_MAX)-50.0f;
 shoot.y=0.0f+50.0f*float(rand())/float(RAND_MAX)-25.0f;

 tracersource=s;
 tracersource.x+=sin(bear)*collideradius;
 tracersource.z+=cos(bear)*collideradius;

 hit=game->arena->CollisionNearest(tracersource,tracersource+shoot);

 path=hit-tracersource;

 tempv=path;
 tempv.Unitize();
 v-=tempv*0.01f; //recoil

 Vector hittemp,pathtemp;
 Vector testpoint,dist;

 LListItem<Ufo>* ufoitem;
 Ufo* ufohit=NULL;
 ufoitem=game->ufolist->head;
 while(ufoitem){
  if(!ufoitem->data.alive){
   ufoitem=ufoitem->next;
   continue;
  }

  testpoint=PerpLinePoint(tracersource,tracersource+path,ufoitem->data.s);

  dist=testpoint-ufoitem->data.s;

  if(dist.Mag2()<ufoitem->data.collideradius2 && PointOnLine(tracersource,tracersource+path,testpoint)){
   tempv=path;
   tempv.Unitize();
   
	 hittemp=testpoint-tempv*ufoitem->data.collideradius*
					cos(dist.Mag()/ufoitem->data.collideradius);
   pathtemp=hittemp-tracersource;

   if(pathtemp.Mag2()<path.Mag2()){
	  hit=hittemp;
	  path=pathtemp;
	  ufohit=&ufoitem->data;
	 }
  }
  ufoitem=ufoitem->next;
 }

 LListItem<Mine>* mineitem;
 Mine* minehit=NULL;
 mineitem=game->minelist->head;
 while(mineitem){
  if(!mineitem->data.alive){
   mineitem=mineitem->next;
   continue;
  }

  testpoint=PerpLinePoint(tracersource,tracersource+path,mineitem->data.s);

  dist=testpoint-mineitem->data.s;

  if(dist.Mag2()<mineitem->data.collideradius2 && PointOnLine(tracersource,tracersource+path,testpoint)){
   tempv=path;
   tempv.Unitize();
   
	 hittemp=testpoint-tempv*mineitem->data.collideradius*
					cos(dist.Mag()/mineitem->data.collideradius);
   pathtemp=hittemp-tracersource;

   if(pathtemp.Mag2()<path.Mag2()){
		 hit=hittemp;
		 path=pathtemp;
		 minehit=&mineitem->data;
		 ufohit=NULL;
	 }
  }
  mineitem=mineitem->next;
 }

 tempv=path;
 tempv.y=0.0f;
 tempv.Unitize();

 if(ufohit){
	 ufohit->v+=tempv*0.15f;
	 ufohit->Hurt(0.15f);
 }

 tracer=path;

 Particle newpart;
 if(ufohit){
  for(int i=0;i<5;i++){
   newpart.s=hit;
   newpart.v=0.0f;
   newpart.v.x+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.v.y+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.v.z+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.a=game->g;
   newpart.rad=1;
   newpart.life=200.0f;
   newpart.blendmode=GL_ONE;
   strcpy(newpart.texfile,"explosion1.png");
		 game->visual->NewParticle(&newpart);
  }
 }
 else{
  for(int i=0;i<5;i++){
   newpart.s=hit;
   newpart.v=game->arena->GetLastQuad()->n*0.02;
   newpart.v.x+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.v.y+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.v.z+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.a=game->g;
   newpart.rad=0.25;
   newpart.life=200.0f;
   newpart.blendmode=GL_ONE;
   strcpy(newpart.texfile,"explosion1.png");
   game->visual->NewParticle(&newpart);
  }

  for(int i=0;i<5;i++){
   newpart.s=hit;
   newpart.v=game->arena->GetLastQuad()->n*0.02;
   newpart.v.x+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.v.y+=0.02*((float)rand()/(float)RAND_MAX)-0.01+0.01f;
   newpart.v.z+=0.02*((float)rand()/(float)RAND_MAX)-0.01;
   newpart.a=game->g*0.1f;
   newpart.rad=2;
   newpart.diffuse=0.000003;
   newpart.res=0.99f;
   newpart.life=500.0f;
   newpart.blendmode=GL_ONE_MINUS_SRC_ALPHA;
   strcpy(newpart.texfile,"smoke1.png");
   game->visual->NewParticle(&newpart);
  }
 }



	newpart.s=tracersource;
	newpart.v=v;
	newpart.a.y=0.0001f;
	newpart.res=0.99f;
	newpart.rad=1.5f;
	newpart.diffuse=0.000003f;
	newpart.life=400;
	strcpy(newpart.texfile,"smoke1.png");
	game->visual->NewParticle(&newpart);
 
	//teh riskeh
	game->sound->Play("cannon.wav",0,&s,&v);
}

void Player::TriggerShield()
{
	shieldeffect=maxshieldeffect=800.0f;
	game->sound->Play("shield.wav",0,s,v);
}

void Player::Hurt(float pain)
{
	if(hp<=0) return;
	if(powerup==POWERUP_SUPERSHIELD&&poweruptimer>0.0f) return;

	hp-=pain;

	if(hp<=0){
		Explode();
		Unspawn();
		if(lives==0){
			game->GameOver(1);
		}
	}
	else
		TriggerShield();
}

void Player::GivePowerUp(poweruptype newpup)
{
	string pupdesc;
	if(newpup==POWERUP_LIFE){
		lives++;
		highlightlives=1000.0f;
		pupdesc="Extra Life!";
	}
	else if(newpup==POWERUP_TRIPLESHOT){
		powerup=newpup;
		maxpoweruptimer=poweruptimer=15000.0f;
		pupdesc="TripleShot!";
	}
	else if(newpup==POWERUP_GUIDANCE){
		powerup=newpup;
		maxpoweruptimer=poweruptimer=15000.0f;
		pupdesc="Guidance!";
	}
	else if(newpup==POWERUP_SUPERSHIELD){
		powerup=newpup;
		maxpoweruptimer=poweruptimer=15000.0f;
		pupdesc="Super Shield!";
	}
	else{
		printf("Player::GivePowerUp: unhandled powerup!  newpup=%d\n",newpup);
		return;
	}

  game->visual->GetMainCam()->SetMessage(pupdesc);
	game->sound->Play("gotthelife.wav");
}

void Player::Collide()
{
	Vector path;
	float part;

	if(game->arena->Collision(s,s+v*game->dtf,collideradius)){
		path=game->arena->GetLastQuad()->PlaneIntersectionPoint(s,s+v*game->dtf)-s;
		part=path.Mag()-collideradius;
	    	path.Unitize();
		s+=path*part;
		//s=game->arena->GetLastQuad()->PlaneIntersectionPoint(s,s+v*game->dtf);
		//printf("Collided: v=(%f,%f,%f),",v.x,v.y,v.z );
		/*printf("Collided with %f,%f,%f\n",game->arena->GetLastQuad()->n.x,
																		game->arena->GetLastQuad()->n.y,
																		game->arena->GetLastQuad()->n.z);*/

		v=game->arena->GetLastQuad()->SlideVector(v);
		//v=game->arena->GetLastQuad()->BounceVector(v);
		//printf("v_new=(%f,%f,%f)\n",v.x,v.y,v.z );
		collideflag=1;
	}
	else{
		collideflag=0;
	}
}

void Player::Explode()
{
	Particle newpart;
	newpart.s=s;
	newpart.rad=collideradius;
	newpart.diffuse=0.000003;
	newpart.res=0.999f;
	newpart.blendmode=GL_ONE;
	strcpy(newpart.texfile,"explosion1.png");
	newpart.life=400;
	for(int i=0;i<6;i++)
		game->visual->NewParticle(&newpart);



	strcpy(newpart.texfile,"smoke1.png");
	//newpart.v=v;
	newpart.life=2000;
	newpart.rad=collideradius*1.2;
	newpart.diffuse=0.0000005;
	newpart.res=0.99f;
	newpart.a=game->g*0.1f;
	newpart.blendmode=GL_ONE_MINUS_SRC_ALPHA;
	for(int i=0;i<8;i++){
		newpart.rad=collideradius*(1.0f+1.0f*((float)rand()/(float)RAND_MAX));
		game->visual->NewParticle(&newpart);
	}

	strcpy(newpart.texfile,"explosion1.png");
	newpart.blendmode=GL_ONE;
	newpart.rad=0.25f;
	newpart.life=2000;
	newpart.diffuse=0.0f;
	newpart.res=1.0f;
	newpart.a=game->g;
	newpart.bounce=PARTICLE_BOUNCE_ONE;
	newpart.collide=1;
	for(int i=0;i<30;i++){
		newpart.v.Randomize();
		newpart.v+=v;
		newpart.v.Unitize();
		newpart.v*=0.1f;
		game->visual->NewParticle(&newpart);
	}

	game->sound->Play("playerdie.wav",0,s,v);
}

void Player::StartCannon()
{
	if(cannonfiring) return;
	cannonfiring=1;
	cannonwait=cannonperiod;
	ShootCannon();
}

void Player::StopCannon()
{
	if(!cannonfiring) return;
	cannonfiring=0;
}
