/***************************************************************************
 *   Copyright (C) 2004-2005 by                                            *
 *     Paolo Sacconier   <axa1981@tin.it>                                  *
 *     Francesco Tamagni <minchiahead@hacari.org>                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "game.h"

namespace gillo {

const float Game::possessionBarLength = 0.50;
const float Game::maxScoreBarLength = 0.7;
const float Game::maxTimerBarLength = 0.4;
const float Game::maxMagnetIntensity  = 100.0;
const float Game::scoreEpsilon = 0.0100;
const float Game::possessionEpsilon = 0.0100;
const float Game::splashCoordSet[][4] = {
	{0.0, 0.0, 1.0, 1.0},
	{0.0, 0.0, 1.0, 1.0},
	{0.25, 0.25, 0.75, 0.75},
	{0.52500001, 0.1250000, 0.72500001, 0.3250000},
	{0.12500000, 0.12500000, 0.32500000, 0.32500000},
	{0.4, 0.12500000, 0.6, 0.32500000}
};

Game::Game(Video& v, Sound& s, queue<Event>& eq)
: video(v), sound(s), equeue(eq), context(s), goalSplashDuration(0.0),
  ball((SimpleBall&) this->addEntity(new SimpleBall(this->context))),
  p1(),
  p2(),
  goal((Goal&) this->addEntity(new Goal(this->context))),
  goalkeeper((Goalkeeper&) this->addEntity(new Goalkeeper(this->context, this->goal, this->ball))),
  status(PAUSED), frameCount(0),
  goalRedSplash(0.5, 0.5, 0.5, 0.5, TEX_GOAL_RED_SPLASH),
  goalBlueSplash(0.5, 0.5, 0.5, 0.5, TEX_GOAL_BLUE_SPLASH),
  tieGoalSplash(0.5, 0.5, 0.5, 0.5, TEX_TIEGOAL_SPLASH),
  mainView(0, 0, 1, 1, new CameraTrack(this->context, &(this->entities))),
  playerTwoView(  0, 0.8, 0.2, 0.2, NULL),
  playerOneView(0.8, 0.8, 0.2, 0.2, NULL),
  attractionStautsP2(0.00, 0, 0.18, 0.08, TEX_ATTRACT),
  attractionStautsP1(0.82, 0, 1.00, 0.08, TEX_ATTRACT)
{
	this->context.setGravity(0,0,ODE_WORLD_GRAVITY);// @fixme remove this
	/* ODE setup */
// 	dWorldSetERP (context.getWid(), 0.9);
	dWorldSetCFM (context.getWid(), 0.01);
// 	dWorldSetContactMax0CorrectingVel (context.getWid(), 1);
// 	dWorldSetContactSurfaceLayer (context.getWid(), 0.001);
	contactgroup = dJointGroupCreate(0);
	
	// @fixme where to place these so can be read from the menu? public?
	p1.keys.up		= SDLK_UP;
	p1.keys.down	= SDLK_DOWN;
	p1.keys.left	= SDLK_LEFT;
	p1.keys.right	= SDLK_RIGHT;
	p1.keys.fire	= SDLK_m;
	p1.keys.jump	= SDLK_k;

	p2.keys.up		= SDLK_d;
	p2.keys.down	= SDLK_c;
	p2.keys.left	= SDLK_x;
	p2.keys.right	= SDLK_v;
	p2.keys.fire	= SDLK_z;
	p2.keys.jump	= SDLK_a;
	
	this->addEntity(new Field(this->context));
// 	this->addEntity(new BoxField(this->context));
	// magnetic interation
	ball.charge = 10;
	magneticEntities.push_back((Entity*) &ball);
	
	video.add(&this->mainView);
	
	// load overlay graphics kept in memory all the time
	timerBarPtr = new Img(0.295, 0.009, 0.705, 0.06, TEX_TIMER_BAR);
	scoreBarRedPtr  = new Img(       0.009, 0.085, 0.009+0.03, 0.085+0.7, TEX_SCORE_BAR_RED);
	scoreBarBluePtr = new Img(1-0.009-0.03, 0.085,    1-0.009, 0.085+0.7, TEX_SCORE_BAR_BLUE);
	mainOverlayPtr = new Img(0, 0, 1, 1, TEX_OVERLAY);
	possessionCursorPtr = new Img(0.21, 0.91, 0.23, 0.99, TEX_POSSESSION_CURSOR);
	
	// new game
// 	this->startNew();
	this->changeStatus(PAUSED);
}


Game::~Game()
{
	vector<Entity*>::iterator i = entities.begin();
	while (i != entities.end() ) {
		delete *i;
		i++;
	}
	printf("Game(%p): destroyed\n",this);
}

Entity& Game::addEntity(Entity* e)
{
	entities.push_back(e);
	return *(entities.back());
}

Entity& Game::addEntityAtRandomPos(Entity* e, float duration)
{
	int signx = rand() % 2;
	int signy = rand() % 2;	
	int posx = ((rand() % 4) + 1) * (signx? -1 : 1);
	int posy = ((rand() % 4) + 1) * (signy? -1 : 1);
	
	e->timeOfRemoval = this->time + duration;
 	this->tempEntities.push_back(e);
	this->addEntity(e).setPos(posx,posy,0);
	return *e;
}

bool Game::removeMagneticEntity(Entity * e) {
	vector<Entity*>::iterator i = magneticEntities.begin();
	while (i != magneticEntities.end() ) {
		if (e == *i) {
			magneticEntities.erase(i);
			return true;
		}
		i++;
	}
	return false;
}


bool Game::removeEntity(Entity* e)
{
/*	if (e == NULL)
		return false;
*/
	vector<Entity*>::iterator i = entities.begin();
	while (i != entities.end() ) {
		if (e == *i) {
			this->removedEntities.push_back(*i);
			this->removeMagneticEntity(*i);
			this->entities.erase(i);
			return true;
		}
		i++;
	}
	return false;
}

void Game::removeEntitiesOfType(Entity::Type et)
{
	vector<Entity*>::iterator i = entities.begin();
	while (i != entities.end() ) {
		if ((*i)->getType() == et) {
			this->removedEntities.push_back(*i);
			this->removeMagneticEntity(*i);
			i = entities.erase(i);
		} else
			i++;
	}
}

void Game::startNew() {
	this->randomTimeInterval = 0;
	this->goalSplashDuration = 0;
	this->gameDuration = 60*3;
	this->score        = 0;
	this->topScore     = 1;
	this->time         = 0;
	this->goalStatus      = false;
	this->rotatingGravity = false;
	this->powerShoot      = false;
	
	this->context.setGravity(0,0,ODE_WORLD_GRAVITY);
	this->context.releaseBall(NULL);	
	this->ball.setSize(2*ODE_BALL_RADIUS);
	
// 	video.overlay.add(timerBarPtr);
// 	video.overlay.add(new Img(0.295, 0.009, 0.705, 0.06, TEX_TIMER));
	
// 	video.overlay.add(scoreBarRedPtr);
// 	video.overlay.add(scoreBarBluePtr);
// 	video.overlay.add(new Img(       0.0085, 0.009, 0.0085+0.04, 0.009+0.08, TEX_SCORE_BAR_TIED));
// 	video.overlay.add(new Img(1-0.0085-0.04, 0.009,    1-0.0085, 0.009+0.08, TEX_SCORE_BAR_TIED));

// 	video.overlay.add(mainOverlayPtr);
	
	// possession bar graphics setup
// 	video.overlay.add(new Img(0.21, 0.91, 0.21+this->possessionBarLength, 0.99, TEX_POSSESSION_BAR));
// 	video.overlay.add(possessionCursorPtr);
// 	
// 	video.overlay.add(&this->attractionStautsP1);
// 	video.overlay.add(&this->attractionStautsP2);

	video.overlay.rem(endSplash);
	if(endSplash != NULL) {
		delete endSplash;
		endSplash=NULL;
	}

	// temporary entities should be removed
	vector<Entity*>::iterator i = this->tempEntities.begin();
	while (i != tempEntities.end() ) {
		this->removeEntity(*i);
		i++;
	}
	this->tempEntities.clear();
	this->removeEntitiesOfType(Entity::PWRUP);
	this->removeEntity(p2.getEntityPtr());
	p2.releaseEntity();
	this->removeEntity(p1.getEntityPtr());
	p1.releaseEntity();
	
	Entity * ep = p1.getEntityPtr();
	if (ep == NULL) {
		switch (p1.getCar()) {
			case CAR_OMNICAR:
				ep = p1.setEntity(this->addEntity(new Omnicar(this->context, this->ball,  -0.07, 1, 3)));
				break;
			case CAR_FOURCAR:
				ep = p1.setEntity(this->addEntity(new Omnicar(this->context, this->ball,  -0.07, 1, 4)));
				break;
		}
		magneticEntities.push_back(ep);
		playerOneView.changeCameraPtr(new CameraBall(*p1.getEntityPtr(), (Entity&) ball));
	}
	ep->setPos(0, 1, -1);
	
	if (status == RUNNING) {
		ep = p2.getEntityPtr();
		if (ep == NULL) {
			switch (p2.getCar()) {
				case CAR_OMNICAR:
					ep = p2.setEntity(this->addEntity(new Omnicar(this->context, this->ball,  0.07, 2, 3)));
					break;
				case CAR_FOURCAR:
					ep = p2.setEntity(this->addEntity(new Omnicar(this->context, this->ball,  0.07, 2, 4)));
					break;
			}
			magneticEntities.push_back(ep);
			playerTwoView.changeCameraPtr(new CameraBall(*p2.getEntityPtr(), (Entity&) ball));
			
			video.overlay.add(&this->attractionStautsP2);
			video.overlay.add(scoreBarRedPtr);
			video.overlay.add(timerBarPtr);
			
		}
		ep->setPos(0, -1, -1);
	} else if (status == TRAINING){
		video.rem(&this->playerTwoView);
		
		video.overlay.rem(&this->attractionStautsP2);
		video.overlay.rem(scoreBarRedPtr);
		video.overlay.rem(timerBarPtr);
	}
	
	video.overlay.add(&this->attractionStautsP1);
	video.overlay.add(scoreBarBluePtr);
	video.overlay.add(mainOverlayPtr);
	video.overlay.add(possessionCursorPtr);
	
	mainView.changeCameraPtr(new CameraTrack(this->context, &this->entities));
	
	ball.setScore(0);
	ball.setPos(0, 2, 1);
	ball.setVel(0, 0, 0);
	this->updateTimer(0);
}

dContact* Game::collide(Entity* e1, Entity* e2, dGeomID o1, dGeomID o2, dContact* c)
{
	Entity* pe = NULL;
	Entity* e  = NULL;
	
	if ((p1.getEntityPtr() == e1) || (p2.getEntityPtr() == e1)) {
		pe = e1;
		e  = e2;
	} else if ((p1.getEntityPtr() == e2) || (p2.getEntityPtr() == e2)) {
		pe = e2;
		e  = e1;
	} 
			
	if (pe && e) {
		if (e->isA(Entity::PWRUP)) {
			if (this->removeEntity(e)){
				switch (((PwrUp *)e)->getPType()){ // it would be nice to insert the effect into the powerup
					case PwrUp::BALL_INFLATOR :
						if (ball.getSize() < 4*ODE_BALL_RADIUS) {
							ball.setSize(8*ODE_BALL_RADIUS);
							this->sound.play(Sound::PWRUP_BALL_INFLATE);
						} else {
							ball.setSize(2*ODE_BALL_RADIUS);
							this->sound.play(Sound::PWRUP_BALL_DEFLATE);
						}
						break;
/*					case PwrUp::GRAVITY_INVERTER :
						this->sound.play(Sound::PWRUP_GRAVITY_INVERTER);
						this->context.invertGravity();
						break;*/
					case PwrUp::GRAVITY_ROTATOR :
						this->sound.play(Sound::PWRUP_GRAVITY_ROTATOR);
						this->rotatingGravity = true;
						break;
					case PwrUp::BALL_ADDER :
						this->sound.play(Sound::PWRUP_BALL_ADD);
						this->magneticEntities.push_back(&this->addEntityAtRandomPos(new SimpleBall(this->context, 3*ODE_BALL_RADIUS, this->context.getState(STA_ODDBALL)), 30.0));
						break;
					case PwrUp::CHARGE_INVERTER :
						this->sound.play(Sound::PWRUP_CHARGE_INVERTER);
						this->context.releaseBall(NULL);
						this->ball.charge = -this->ball.charge;
						this->powerShoot = false;
						//@fixme error
						break;
				}
				
			}
		} /*else if (e->isA(Entity::BALL)) {
			SimpleBall* b = (SimpleBall*) e;
			float diff;
			if (p1.getEntityPtr() == pe)
				diff = -0.01;
			else
				diff = +0.01;
			b->setScore(b->getScore() +diff);
		}*/
	} else { if (!e && pe ) printf("Wazzap?\n"); }
	
	static bool contactBallGoalHappened;
	static bool contactBallFieldHappened;
	static int lastContactBallGoalFrameNum;
	static int lastContactBallFieldFrameNum;
	if (Entity::isPair(e1, e2, Entity::BALL, Entity::GOAL) // this way balls can pass through the goal
		&& (goal.isScoreGeom(o1) || goal.isScoreGeom(o2))) {
		if (contactBallGoalHappened && (lastContactBallGoalFrameNum < this->frameCount-2) && ((e1 == &ball) || (e2 == &ball))) {
			contactBallGoalHappened = false;
		}
		if (!contactBallGoalHappened) {
			this->score += ball.getScore();
			this->goalStatus = true;
			contactBallGoalHappened = true;
		}
		lastContactBallGoalFrameNum = this->frameCount;
		return NULL;
	} else if (Entity::isPair(e1, e2, Entity::BALL, Entity::GOAL) // this way balls can pass through the area
		&& (goal.isAreaGeom(o1) || goal.isAreaGeom(o2))) {
		return NULL;
	} else if (Entity::isPair(e1, e2, Entity::GOALKEEPER, Entity::GOAL) // this way the goalkeeper can pass through the area
		&& (goal.isAreaGeom(o1) || goal.isAreaGeom(o2))) {
		return NULL;
	} else if ((Entity::isPair(e1, e2, Entity::CAR, Entity::GOAL)
			|| Entity::isPair(e1, e2, Entity::PWRUP, Entity::GOAL)) // car collides softly with area
		&& (goal.isAreaGeom(o1) || goal.isAreaGeom(o2))) {
		if (e1->isA(Entity::CAR) && ((Omnicar*)e1)->isPickerGeom(o1) ||
		    e2->isA(Entity::CAR) && ((Omnicar*)e2)->isPickerGeom(o2))
			return NULL;
		c->surface.mode =  dContactSoftERP | dContactSoftCFM ;
		c->surface.bounce = 0;
		c->surface.bounce_vel = 5;
		c->surface.mu = 0.1;
		c->surface.soft_erp = 0.5;
		c->surface.soft_cfm = 0.5;
	} else if (Entity::isPair(e1, e2, Entity::BALL, Entity::CAR)) {
		c->surface.mode = dContactBounce;
		c->surface.bounce = 0.1;
		c->surface.bounce_vel = 5;
		c->surface.mu = 100;
	} else if (Entity::isPair(e1, e2, Entity::BALL, Entity::BALL)) {
		c->surface.mode = dContactBounce;
		c->surface.bounce = 0.3;
		c->surface.bounce_vel = 5;
		c->surface.mu = 100;
	} else if (Entity::isPair(e1, e2, Entity::CAR, Entity::CAR) && (e1->isA(Entity::CAR) && ((Omnicar*)e1)->isPickerGeom(o1) ||
		    e2->isA(Entity::CAR) && ((Omnicar*)e2)->isPickerGeom(o2))) {
		return NULL;
	} else if (Entity::isPair(e1, e2, Entity::FIELD, Entity::CAR)
			|| Entity::isPair(e1, e2, Entity::FIELD, Entity::CUBE)) {
		
		if (e1->isA(Entity::CAR) && ((Omnicar*)e1)->isPickerGeom(o1) ||
		    e2->isA(Entity::CAR) && ((Omnicar*)e2)->isPickerGeom(o2))
			return NULL;
		c->surface.mode = dContactBounce | dContactApprox1;
		c->surface.bounce = 0.0;
		c->surface.bounce_vel = 5;
		c->surface.mu = 1000;
	} else if (Entity::isPair(e1, e2, Entity::FIELD, Entity::BALL)
			|| Entity::isPair(e1, e2, Entity::GOAL,  Entity::BALL)) {
		c->surface.mode = dContactBounce | dContactApprox1;
		c->surface.bounce = 0.3;
		c->surface.bounce_vel = 0;
		c->surface.mu = 100;
		if (contactBallFieldHappened && (lastContactBallFieldFrameNum < this->frameCount-4)) {
			contactBallFieldHappened = false;
		}
		if (!contactBallFieldHappened) {
			this->sound.play(Sound::BOING);
			contactBallFieldHappened = true;
		}
		lastContactBallFieldFrameNum = this->frameCount;
	} else if (Entity::isPair(e1, e2, Entity::PWRUP, Entity::FIELD)) {
		c->surface.mode = 0;
		c->surface.mu = 100000000;
	} else if (Entity::isPair(e1, e2, Entity::GOALKEEPER, Entity::FIELD)) {
		c->surface.mode = 0;
		c->surface.mu = 100000000;
	} else if ((Entity::isPair(e1, e2, Entity::GOALKEEPER, Entity::GOAL) ||
			    Entity::isPair(e1, e2, Entity::CAR, Entity::GOAL))
		&& (goal.isScoreGeom(o1) || goal.isScoreGeom(o2))) {
		return NULL;
	} else if (e1->isA(Entity::PWRUP) || e2->isA(Entity::PWRUP)) {
		return NULL;
	} else {
		c->surface.mode = dContactBounce;
		c->surface.bounce = 1;
		c->surface.bounce_vel = 5;
		c->surface.mu = 100;
	}
	
	return c;
}

void collide_callback (void *data, dGeomID o1, dGeomID o2) {
	Game* g = (Game*) data;
	Entity *e1=NULL, *e2=NULL; 
	dContact* co = NULL;
	dJointID c;

	if (dGeomIsSpace (o1) || dGeomIsSpace (o2)) {
		// colliding a space with something
		dSpaceCollide2 (o1,o2,data, collide_callback);
		// collide all geoms internal to the space(s)
		//@fixme why was this very necessary?
// 		if (dGeomIsSpace (o1))
// 			dSpaceCollide ((dSpaceID) o1,data, collide_callback);
// 		if (dGeomIsSpace (o2))
// 			dSpaceCollide ((dSpaceID) o2,data, collide_callback);
	} else if (g->contactcount < ODE_MAX_CONTACTS) { // @fixme check for every contact
		// colliding two non-space geoms, so generate contact
		// points between o1 and o2
		e1 = (Entity*) dGeomGetData(o1);
		e2 = (Entity*) dGeomGetData(o2);
		if (e1 == NULL || e2 == NULL) {
			// this should never happen!
			throw Error::Game("Error resolving a collision");
			return;
		} 
// 		printf("%10d - %s and %s collided (gids %10d <-> %10d)\n", g->frameCount, e1->toString(), e2->toString(), o1, o2);
		if (e1 == e2)
			return;
			
		int num_contact = dCollide(o1, o2, ODE_CONTACTS, &g->contacts[g->contactcount].geom, sizeof(dContact));
// 		printf("%10d - %s and %s collided %d times\n (gids %10d <-> %10d)\n", g->frameCount, e1->toString(), e2->toString(), num_contact, o1, o2);
		
		if (num_contact) {
			dBodyID b1, b2, tmp;
			b1 = dGeomGetBody(o1);
			b2 = dGeomGetBody(o2);
			if (b1 || b2) {
				for (int i = 0; i < num_contact; i++) {
					co = g->collide(e1, e2, o1, o2, &(g->contacts[g->contactcount]));
					if (co) {
						c = dJointCreateContact(g->context.getWid(), g->contactgroup, co);
						dJointAttach(c, b1, b2);
						co = NULL;
					}
					g->contactcount++;
				}
			}
		}
	} else {
// 		throw Error::Game("Too many contacts");
		e1 = (Entity*) dGeomGetData(o1);
		e2 = (Entity*) dGeomGetData(o2);
		printf("%10d - %s and %s collided (gids %10d <-> %10d) - IGNORED\n", g->frameCount, e1->toString(), e2->toString(), o1, o2);
		printf("Too many contacts, autopausing\n");
		g->changeStatus(Game::SELFPAUSED);
		return;
	}
}

void Game::update(float dt)
{
	if (status == RUNNING || status == TRAINING) {
		this->ballScore = ball.getScore();
		this->time += dt;
		this->frameCount++;
		if (status == RUNNING)
			this->updateTimer(dt);
		this->updatePossession(dt);
		this->updateScore(dt);
		this->updatePlayers(dt);
		
		static float targetAngle = 0;
		static float currentAngle = 0;
		static int sign = 0;
		static sgVec3 savedGravity;
		if (this->rotatingGravity) {
			if ((targetAngle == 0) && (currentAngle == 0)) {
				targetAngle =  90 + 90*(rand() %3);
				sign = rand() %2 ? -1 : 1;
				sgCopyVec3(savedGravity, this->context.getGravity());
			}
			float inc = 70.0*dt;
			this->context.rotateGravity(sign*inc);
			currentAngle += inc;
			if (currentAngle > targetAngle) {
				this->rotatingGravity = false;
				this->context.setGravity(savedGravity);
				this->context.rotateGravity(sign*targetAngle);
				targetAngle = 0;
				currentAngle = 0;
			}
		}

		static float goalTime = 0;
		static float possessionAtGoalTime = 0;
		static float splashCoord[4];
		static Img* splash = NULL;
		if (this->goalStatus) {
			this->context.releaseBall(NULL);
			if (this->powerShoot) {
				this->ball.setScore(0.0);
				this->powerShoot = false;
			}
			this->goalStatus = false;
			possessionAtGoalTime = this->ballScore;
			if (sgAbs(possessionAtGoalTime) < Game::possessionEpsilon) {
				if ( splash != &this->tieGoalSplash) {
					this->video.overlay.rem(splash);
					splash = &this->tieGoalSplash;
					for (int i = 0; i < 4; i++)
						splashCoord[i] = 0.5;
					splash->setCoords(splashCoord);
					this->video.overlay.add(splash);
					goalTime = 0;
					goalSplashDuration = 0;
				}
				this->sound.play(Sound::TIEGOAL);
			} else {
				if ((possessionAtGoalTime < -Game::possessionEpsilon) && (splash != &this->goalBlueSplash)) {
					this->video.overlay.rem(splash);
					splash = &this->goalBlueSplash;
					for (int i = 0; i < 4; i++)
						splashCoord[i] = 0.5;
					splash->setCoords(splashCoord);
					this->video.overlay.add(splash);
					goalTime = 0;
					goalSplashDuration = 0;
				} else if ((possessionAtGoalTime > Game::possessionEpsilon) && (splash != &this->goalRedSplash)) {
					this->video.overlay.rem(splash);
					splash = &this->goalRedSplash;
					for (int i = 0; i < 4; i++)
						splashCoord[i] = 0.5;
					splash->setCoords(splashCoord);
					this->video.overlay.add(splash);
					goalTime = 0;
					goalSplashDuration = 0;
				}
				this->sound.play(Sound::GOAL);
			}
			goalSplashDuration += 5;
		} else if (goalTime < goalSplashDuration) {
			goalTime += dt;
			int target = 2;
			if (possessionAtGoalTime < -Game::possessionEpsilon) {
				target = 0;
			} else if (possessionAtGoalTime > Game::possessionEpsilon) {
				target = 1;
			} else {
				target = 2;
			}
			if (goalTime > 1)
				target += 3;
// 			else if (goalTime > 5)
// 				target += 
			for (int i = 0; i < 4; i++)
				splashCoord[i] += 4*(splashCoordSet[target][i] -splashCoord[i])*dt;
			splash->setCoords(splashCoord);
		} else if (goalTime != 0) {
			goalTime = 0;
			goalSplashDuration = 0;
			this->video.overlay.rem(splash);
			splash = NULL;
		}
		
		
		// generate some random events
// 		printf("random timer %f of %f\n", time, random_time);
		if (this->time > randomTimeInterval) {
			// random event occurred
			randomTimeInterval = this->time + 10 + rand() % 10;
			PwrUp::PType ptype;
			switch(rand() % 4) { // @fixme we shouldn't remeber the number of powerup types
// 			switch(3) {
// 				case 0:
// 					ptype = PwrUp::GRAVITY_INVERTER;
// 					break;
				case 0: 
					ptype = PwrUp::BALL_INFLATOR;
					break;
				case 1:
					ptype = PwrUp::GRAVITY_ROTATOR;
					break;
				case 2:
					ptype = PwrUp::BALL_ADDER;
					break;
				case 3:
					ptype = PwrUp::CHARGE_INVERTER;
					break;
			}
			this->sound.play(Sound::PWRUP_NEW);
			this->addEntityAtRandomPos(new PwrUp(this->context, ptype), 15.0);
		}
		
		for (int c = 0; c < ODE_PASSES; c++) {
			this->contactcount = 0;
			dSpaceCollide (context.getSid(), (void *) this, collide_callback);
// 			dWorldStep(context.getWid(), dt/ODE_PASSES);
			dWorldQuickStep(context.getWid(), dt/ODE_PASSES);
			dJointGroupEmpty(contactgroup);
	// 		if (this->contactcount)
	// 			printf("Created %d joints in this pass\n", this->contactcount);
		}
		// update active entities
		vector<Entity*>::iterator i = this->entities.begin();
		while (i != entities.end() ) {
			(*i)->update(dt);
			i++;
		}
		// temporary entities disappear automatically
		i = this->tempEntities.begin();
		while (i != tempEntities.end() ) {
			if (this->time > (*i)->timeOfRemoval) {
				this->removeEntity(*i);
				i = this->tempEntities.erase(i);
			} else
				i++;
		}
		// remove entities no longer needed (because destroyed by some collision in previous frame)
		i = this->removedEntities.begin();
		while (i != this->removedEntities.end()) {
			delete *i;
			i++;
		}
		removedEntities.clear();
		
		// magnetic interaction forces generation
		sgVec3 zero;
		sgZeroVec3(zero);
		// @fixme optimize this loop!!!
		i = this->magneticEntities.begin();
		while (i != this->magneticEntities.end()) {
			vector<Entity*>::iterator j = i +1;
			while(j != this->magneticEntities.end()) {
				sgVec3 posI;
				sgVec3 posJ;
				sgVec3 dirI;
				sgVec3 dirJ;
				sgVec3 diff;
				(*i)->getMagnetPos(posI);
				(*j)->getMagnetPos(posJ);
				(*i)->getMagnetDir(dirI);
				(*j)->getMagnetDir(dirJ);
				sgSubVec3(diff, posJ, posI);
				float l = sgLengthVec3(diff);
				sgNormalizeVec3(diff);
				
				if (Entity::isPair((*i), (*j), Entity::BALL, Entity::BALL)) {
					float intensity = (*i)->charge*(*j)->charge/l/l;
					if (sgAbs(intensity) > Game::maxMagnetIntensity)
						intensity = intensity > 0 ? Game::maxMagnetIntensity : -Game::maxMagnetIntensity;
					sgScaleVec3(diff, intensity);
					(*i)->addForce(diff);
				}
				if     (sgEqualVec3(zero, dirI) &&
					sgEqualVec3(zero, dirJ)) {
					// do nothing
				} else if (sgEqualVec3(zero, dirJ)) {
					float directional = sgScalarProductVec3(diff, dirI);
					if (directional > 0)
						directional = 0;
					float intensity = -directional*(*i)->charge*(*j)->charge/l/l;
					if (sgAbs(intensity) > Game::maxMagnetIntensity)
						intensity = intensity > 0 ? Game::maxMagnetIntensity : -Game::maxMagnetIntensity;
					sgScaleVec3(diff, intensity);
					(*j)->addForce(diff);
				} else if (sgEqualVec3(zero, dirI)) {
					float directional = sgScalarProductVec3(diff, dirJ);
					if (directional > 0)
						directional = 0;
					float intensity = -directional*(*i)->charge*(*j)->charge/l/l;
					if (sgAbs(intensity) > Game::maxMagnetIntensity)
						intensity = intensity > 0 ? Game::maxMagnetIntensity : -Game::maxMagnetIntensity;
					sgScaleVec3(diff, intensity);
					(*i)->addForce(diff);
				}
				j++;
			}
			i++;
		}
		if (sgAbs(this->ballScore) >= 1) {
			powerShoot = true;
			this->sound.play(Sound::POWERSHOOT);
		}
		if (this->powerShoot) {
			sgVec3 posB;
			sgVec3 posG;
			sgVec3 diff;
			this->ball.getMagnetPos(posB);
			this->goalkeeper.getMagnetPos(posG);
			sgSubVec3(diff, posG, posB);
			float l = sgLengthVec3(diff);
			sgNormalizeVec3(diff);
			float intensity = 1000/l;
			sgScaleVec3(diff, intensity);
			ball.addForce(diff);
// 			sgNegateVec3(diff);
// 			goalkeeper.addForce(diff);
		}
	}
	
	video.draw(this->context.getScenePtr(), this->context.getAlphaScenePtr()); // @fixme remove pointers and use references
}

void Game::updatePossession(float dt) {
	float targetPos = this->possessionBarLength/2*(-this->ballScore+1)+0.24;
	float posX = possessionCursorPtr->getPosX();
	float diff = targetPos -posX;
	if (diff != 0)
		this->possessionCursorPtr->setPos(posX +2*diff*dt, 0.915);
}

void Game::updateScoreBarHeight(Rect* r, float h, float dt) {
	float oh = r->getHeight();
	float diff = h -oh;
	if (diff != 0)
		r->setHeight(oh +2*diff*dt);
}

void Game::updateScore(float dt) {
	float abss = sgAbs(this->score);
	if (abss > topScore)
		topScore = abss;
	float targetHeight = Game::maxScoreBarLength*abss/topScore;
	if (this->score > 0) {
		this->updateScoreBarHeight(this->scoreBarBluePtr,            0, dt);
		this->updateScoreBarHeight(this->scoreBarRedPtr,  targetHeight, dt);
	} else {
		this->updateScoreBarHeight(this->scoreBarRedPtr,             0, dt);
		this->updateScoreBarHeight(this->scoreBarBluePtr, targetHeight, dt);
	}
}

void Game::updateTimer(float dt) {
	this->timerBarPtr->setWidth(Game::maxTimerBarLength*time/this->gameDuration);
	if (time > this->gameDuration) {
		this->end();
// 		time = 0;
	}
}

void Game::updatePlayers(float dt) {
	static ssgTexture attractTex(TEX_ATTRACT, 0, 0, 0);
	static ssgTexture repulseTex(TEX_REPULSE, 0, 0, 0);
	
	ssgTexture* newTex;
	float ballCharge = this->ball.charge;
	
	float p1Charge   = this->p1.getEntityPtr()->charge;
	newTex = ballCharge*p1Charge >= 0 ? &attractTex : &repulseTex;
	this->attractionStautsP1.setExternalTexture(newTex);
	
	if (status == RUNNING) {
		float p2Charge   = this->p2.getEntityPtr()->charge;
		newTex = ballCharge*p2Charge >= 0 ? &attractTex : &repulseTex;
		this->attractionStautsP2.setExternalTexture(newTex);
	}
	if (this->p1.getEntityPtr()->hasBall()) {
		video.rem(&this->playerTwoView);
		video.add(&this->playerOneView);
	} else if (status == RUNNING && this->p2.getEntityPtr()->hasBall()) {
		video.rem(&this->playerOneView);
		video.add(&this->playerTwoView);
	} else {
		video.rem(&this->playerOneView);
		video.rem(&this->playerTwoView);
	}
}

void Game::end() {
	this->changeStatus(FINISHED);
	printf("SCORE: %f\n", this->score);
	if (this->score < -Game::scoreEpsilon) {
		this->sound.play(Sound::END);
		endSplash = new Img(0,0,1,1,TEX_P1WINS_SPLASH);
	} else if (this->score > Game::scoreEpsilon) {
		this->sound.play(Sound::END);
		endSplash = new Img(0,0,1,1,TEX_P2WINS_SPLASH);
	} else {
		this->sound.play(Sound::ENDTIE);
		endSplash = new Img(0,0,1,1,TEX_PARITY_SPLASH);
	}
	video.overlay.add(endSplash);
}

void Game::react(SDL_Event *event)
{
	switch( event->type ) {
		case SDL_KEYDOWN:
			handleKeyDown(&event->key.keysym);
			break;
		case SDL_KEYUP:
			handleKeyUp(&event->key.keysym);
			break;
		case SDL_QUIT:
			{
				Event ev(Event::APP, Event::QUIT);
				equeue.push(ev);
			}
			break;
		default:
//			throw Error::Event("Unhandled event");
			break;
	}
	p1.react(event);
	p2.react(event);
}


void Game::handleKeyDown( SDL_keysym* keysym )
{
	static GLboolean fullscreen = GL_FALSE;
	
	switch( keysym->sym ) {
		case SDLK_ESCAPE:
			if (status == RUNNING || status == TRAINING || status == FINISHED) {
				Event ev(Event::MENU, Event::SHOW);
				equeue.push(ev);
				changeStatus(PAUSED);
			}
			break;
		case SDLK_RSHIFT:
		case SDLK_LSHIFT:
			break;
		case SDLK_SPACE:
			if (status == RUNNING || status == TRAINING)
				this->startNew();
			break;
		case SDLK_1:
			mainView.changeCameraPtr(new CameraTrack(this->context, &this->entities));
			break;
		case SDLK_2:
			mainView.changeCameraPtr(new CameraBall(*p1.getEntityPtr(), (Entity&) ball));
			break;
		case SDLK_3:
			if (status == RUNNING)
				mainView.changeCameraPtr(new CameraBall(*p2.getEntityPtr(), (Entity&) ball));
			break;
		case SDLK_4:
			if (status == RUNNING)
				mainView.changeCameraPtr(new CameraBall(*p1.getEntityPtr(), *p2.getEntityPtr()));
			break;
		case SDLK_5:
			if (status == RUNNING)
				mainView.changeCameraPtr(new CameraBall(*p2.getEntityPtr(), *p1.getEntityPtr()));
			break;
		case SDLK_6:	
			mainView.changeCameraPtr(new CameraBall((Entity&) ball, (Entity&) goal));
			break;
		case SDLK_7:
			mainView.changeCameraPtr(new CameraBall((Entity&) goal, (Entity&) ball));
			break;
		case SDLK_f:
			switch (video.getMode()) {
				case Video::FULLSCREEN:
					video.switchToMode(Video::WINDOW);
					break;
				default:
					video.switchToMode(Video::FULLSCREEN);
					break;
			}
			break;
		case SDLK_s:
			sound.toggle();
			break;
		case SDLK_q:
			{
				Event ev(Event::APP, Event::QUIT);
				equeue.push(ev);
			}
			break;
/*		case SDLK_b:
			this->context.invertGravity();
			break;
		case SDLK_r:
			this->rotateGravityToggle();
			break;*/
		case SDLK_p:
			if (status == SELFPAUSED)
				changeStatus(previousStatus);
			else {
				changeStatus(SELFPAUSED);
			}
			break;
		case SDLK_h:
			this->context.toggleDebug();
			break;
		default:
			break;    
    }
}

void Game::handleKeyUp( SDL_keysym* keysym ) {
    switch( keysym->sym ) {
		default:
			break;
	}
}

void Game::react(const Event::Type et)
{
	switch (et) {
		case Event::START_TRAINING:
// 			this->training = true;
			printf("starting training\n");
			this->changeStatus(TRAINING);
			this->startNew();
			break;
		case Event::START:
// 			this->training = false;
			printf("starting a new game\n");
			this->changeStatus(RUNNING);
			this->startNew();
			break;
		case Event::RESUME:
			this->changeStatus(previousStatus);
			break;
		case Event::PAUSE:
			this->changeStatus(PAUSED);
			break;
		case Event::CV_P1_OMNICAR:
			p1.setCar(CAR_OMNICAR);
			break;
		case Event::CV_P2_OMNICAR:
			p2.setCar(CAR_OMNICAR);
			break;
		case Event::CV_P1_FOURCAR:
			p1.setCar(CAR_FOURCAR);
			break;
		case Event::CV_P2_FOURCAR:
			p2.setCar(CAR_FOURCAR);
			break;
		default:
			break;
	}
}

bool Game::changeStatus(Status s)
{
	previousStatus = status;
	if ((status == PAUSED) && (s == RUNNING)) {
		status = s;
		sound.music(Sound::GAME1);
	} else if ((status == PAUSED) && (s == TRAINING)) {
		status = s;
		sound.music(Sound::TRAINING);
	} else if ((status == RUNNING || status == TRAINING) && (s == SELFPAUSED)) {
		status = s;
	} else if ((status == SELFPAUSED) && (s == RUNNING || s == TRAINING)) {
		status = s;
	} else if ((status == RUNNING || status == TRAINING) && (s == PAUSED)) {
		status = s;
	} else if ((status == RUNNING) && (s == FINISHED)) {
		status = s;
	} else if ((status == FINISHED) && (s == PAUSED)){
		status = s;
	} else if ((status == PAUSED) && (s == FINISHED)){
		status = s;
	}else {
		//@fixme throw something
	}
}
};
