#include "SpaceMap.h"
#include "streams\ErrorStream.h"
#include "..\gameSettings.h"

extern ExceptionStream< ostream&, PauseAndExitFunctor  > KODIAK_ERROR;
extern ExceptionStream< ostream&, ContinueFunctor  > KODIAK_WARNING;
extern KODIAK_DEBUG_STREAM KODIAK_DEBUG_COUT;



void renderObjectName( SoftProjRender& render, const string &name, const Point_3<double> &pos, const float offsetMultiplier,const unsigned int fontTexId, const float fontSize, const CRgbw &fontColor )
{
	const Vector_3<double> axisY(0,1,0);

	Font font;
	font.color = fontColor.col;
	font.alpha = fontColor.w;
	font.size = fontSize;
	font.texID = fontTexId;

	glDepthMask(GL_FALSE);

	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		render.renderText( CONVERT_TO_FLOAT( pos + axisY * offsetMultiplier), name, font );
	glDisable( GL_BLEND );
	glDepthMask(GL_TRUE);
}
void MapFunctionality::renderShipName( SoftProjRender &render, SpaceShip &ship, const float fontSize ) const
{
	const float textPositionOffsetFactor = ship.getSize() * 8.0;
	const CTriCol blueFont( 0.7,0.7,1.0);

	renderObjectName( render, ship.name, ship.getPos(), textPositionOffsetFactor, GameConstants::FONT_TEXTURE_ID, fontSize, GameConstants::SCIFI_MAGNETA  );

	/*
	Point_3<double> serverPos = GameConstants::MICRO_COORD_CONVERTOR.convertClientToServerPoint( ship.getPos() );
	string posStr = longlongToString( (long long)( serverPos.x() )) +", " + longlongToString( (long long)( serverPos.z() )) ;
	renderObjectName( render, posStr, ship.getPos(), textPositionOffsetFactor, GameConstants::FONT_TEXTURE_ID, fontSize, GameConstants::SCIFI_MAGNETA  );
	*/
}
void MapFunctionality::renderPlanetName( SoftProjRender &render, BasePlanetClass &planet, const float fontSize ) const
{
	const float textPositionOffsetFactor = planet.getSize() * 1.7;
	float distance = planet.getPos().get_distance( CONVERT_TO_DOUBLE(render.getCamera().pos) );

	renderObjectName( render, planet.name, planet.getPos(), textPositionOffsetFactor, GameConstants::FONT_TEXTURE_ID, fontSize, GameConstants::SCIFI_RED );

	/*
	Point_3<double> serverPos = GameConstants::MICRO_COORD_CONVERTOR.convertClientToServerPoint( planet.getPos() );
	string posStr = longlongToString( (long long)( serverPos.x() )) +", " + longlongToString( (long long)( serverPos.z() )) ;
	renderObjectName( render, posStr, planet.getPos(), textPositionOffsetFactor, GameConstants::FONT_TEXTURE_ID, fontSize, GameConstants::SCIFI_RED );
	*/
}

void MapFunctionality::render( SoftProjRender &render, PlanetDatabase &spacePlanets, ShipDatabase &spaceShips, SpaceShip *selectedShip )
{			
	//if some ship is selected then we center camera to the ship
	if ( selectedShip != NULL && !selectedShip->inBase && !selectedShip->inWarp  )
	{
		this->clampCamera( selectedShip->getPos(), this->lockedCamera );
	}
	else
	{
		this->clampCamera( this->cameraPivot, this->lockedCamera );
	}
	
	this->calculateProgressiveZoom( render );

	render.beginFrame( this->camera );
	
		glDepthMask( GL_TRUE );
		glDisable( GL_BLEND );
		glDisable( GL_DEPTH_TEST );
		glEnable( GL_CULL_FACE );
		glCullFace( GL_CCW );

		this->renderBackground( render );
		this->renderLodGrid( render, this->cameraPivot, this->getCameraZoom() );

		this->renderParticles( render );

		glEnable( GL_DEPTH_TEST );
		this->renderMissiles( render );
		this->renderSpaceShips( render, spaceShips );
		this->renderSpacePlanets( render, spacePlanets );

		glDisable( GL_DEPTH_TEST );
		this->renderAnimations( render );

		glEnable( GL_DEPTH_TEST );
		this->renderInvisiblePickPlane( render, this->cameraPivot, PICK_PLANE_SIZE ); //MUST BE THE LAST...it is using blending!!!
		
		// recalculate new destination point if it changed
		// must be here because we need Render object do be able to do unproject
	
		if ( this->newDstPoint && selectedShip!= NULL &&
			 !selectedShip->inBase && !selectedShip->inWarp )
		{
			Point_3<double> clickPos3D;
			int int2[2] = { this->dstPoint[0] , render.viewport.sizeY - this->dstPoint[1] };
			//cout<<endl<<"INT2: "<<int2[0]<<", "<<int2[1]<<endl;

			clickPos3D = render.unproject( int2 );
			clickPos3D.y() = 0; //ignore y ( only X,Z are relevant )

			selectedShip->destinationPoint = CONVERT_TO_DOUBLE(clickPos3D) ;

			if ( this->onShipNewDestinationPos != NULL )
			{
				this->onShipNewDestinationPos( clickPos3D );
			}

			Vector_3<double> newShipDir = selectedShip->getDir();
			selectedShip->setOrientation( newShipDir );

			this->newDstPoint = false;
		}
		
		glDisable( GL_DEPTH_TEST );
		this->renderFog( render, this->cameraPivot, VISIBLE_SIZE, PICK_PLANE_SIZE );

		if ( selectedShip != NULL && !selectedShip->inBase && /*!selectedShip->inWarp  &&*/  !selectedShip->isOrbiting )
		{
			//normal gizmo mode
			this->renderGizmo( render, selectedShip->getPos(), selectedShip->destinationPoint );
		}
		else if ( selectedShip != NULL && !selectedShip->inBase && !selectedShip->inWarp  && selectedShip->isOrbiting )
		{
			//orbiting gizmo mode
			this->renderGizmo( render, selectedShip->getPos(), selectedShip->orbitPivot );
		}


		this->renderSignalizer( render );
		this->renderScreenEffect( render );

	render.endFrame();
}
float MapFunctionality::getCameraZoom( void ) const
{
	//calculate current zoom lvl
	float zoomDistance = this->cameraPivot.get_distance( this->camera.pos );
	return zoomDistance;
}
int MapFunctionality::calculatePlanetLevelOfDetail( float distance ) const
{
	float details = log2( distance ) - 8.0;
	details = max( 1.0, details );

	//details = 1
	//details = 2  ...2-times less polycount
	//details = n ....N-time less polycount
	return details;
}
void MapFunctionality::calculateProgressiveZoom( SoftProjRender &render )
{
	//calculate current zoom lvl
	float zoomDistance = this->getCameraZoom();

	//set projection data ( near & far plane distances)
	//Warning: for current camera look direction are the folowing params optimal, 
	//plz do not change if camera look direction is the same
	render.getProjection().zFar = zoomDistance * 1.7; //farest object
	render.getProjection().zNear = zoomDistance / 1.45; //nearest object


	render.getProjection().zFar = zoomDistance * 10.0; //farest object
	render.getProjection().zNear = zoomDistance / 10.0; //nearest object
}
void MapFunctionality::clampCamera( const Point_3<double> &position, bool autoCenter )
{
	//Vector_3<double> cameraOffset = position - this->cameraPivot;

	if ( autoCenter )
	{
		float cameraZoom = this->getCameraZoom();
		this->cameraPivot = position;
		this->camera.pos = position - this->camera.look * cameraZoom;
	}
	
	/*
	double offsetLen = cameraOffset.get_length();
	if ( offsetLen > CAMERA_BOUNDARY && offsetLen !=0 )
	{
		cameraOffset /= offsetLen; //normalize offset vector
		cameraOffset *= offsetLen - CAMERA_BOUNDARY; //set offset magnitude according to the required camera, object distance
		this->camera.pos += cameraOffset;
		this->cameraPivot += cameraOffset;
	}
	*/

	this->camera.look =  MAKE_VECTOR<double>(0,-1,-1);
	this->camera.look.normalize();
	this->camera.right = MAKE_VECTOR<double>(1,0,0);
	this->camera.recalculate_up();
}
void MapFunctionality::simulate( PlanetDatabase &planetDatabase, ShipDatabase &shipDatabase, float dt )
{
	this->spaceSkyPlane.simulate( dt );
	this->simulateShips( shipDatabase, dt );
	this->simulatePlanets( planetDatabase, dt );
	this->simulateMissiles( planetDatabase, shipDatabase, dt );
	this->simulateAnimations( dt );
	this->simulateParticles( planetDatabase, shipDatabase, dt );
	this->simulateNoisyScreen( dt );
	this->simulateSignalizer( dt );
}
void MapFunctionality::renderBackground( SoftProjRender &render )
{
	//NOTE: use sky texture + procedural space generation together give best results
	this->skyPlane.render( render );
	this->spaceSkyPlane.render( render );
}
void MapFunctionality::renderGizmo( SoftProjRender &render, const Point_3<double> &gizmoPosition, const Point_3<double> &destinationPoint  )
{

	selectTextureUnit( 0 );
	glDisable( GL_TEXTURE_2D );
	glDisable( GL_DEPTH_TEST );

	this->renderSelectionFlag( render, gizmoPosition );
	this->renderDestinationFlag( render,destinationPoint);

	// dont display line start and stop point are in the selection circle radius
	const float outsideOffset = 1.0;
	if ( (gizmoPosition - destinationPoint).get_length_square() + EPSILON <= SELECTION_CIRCLE_SIZE * SELECTION_CIRCLE_SIZE + outsideOffset )
	{
		return;
	}

	// render path line which starts OUTSIDE the selection circle
	// it has to be outside in order to dont overlap the space model

	Vector_3<double> dir = destinationPoint - gizmoPosition;
	dir.normalize();
	this->renderPathFlag( render, gizmoPosition + dir * (SELECTION_CIRCLE_SIZE+ outsideOffset ),destinationPoint );
}
void MapFunctionality::renderScreenEffect( SoftProjRender &render )
{
	//-------------------------
	//display quick key info picture
	int width = getTextureWidth( this->hintTextureId );
	width *= 0.7; //make the texture smaller

	int height = getTextureHeight( this->hintTextureId );
	height *=0.7; //make the texture smaller

	int leftPos = render.viewport.fromX + render.viewport.sizeX - width;
	int downPos = render.viewport.fromY + render.viewport.sizeY - height;

	int leftDownPos[2] = {leftPos, downPos};
	int upRightPos[2] = { leftDownPos[0] + width, leftDownPos[1] + height};

	glEnable(GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glColor4f(	GameConstants::SCIFI_GREEN_BRIGHT.col.r, 
				GameConstants::SCIFI_GREEN_BRIGHT.col.g,
				GameConstants::SCIFI_GREEN_BRIGHT.col.b,
				GameConstants::SCIFI_GREEN_BRIGHT.w );
	render.renderImage2D( leftDownPos, upRightPos, this->hintTextureId );
	//----------------------------

	if ( this->noisyScreenEffect.isActive() )
	{
		this->noisyScreenEffect.render( render );
	}
}
void MapFunctionality::renderSignalizer( SoftProjRender &render )
{
	this->signalizer.render( render );
}
void MapFunctionality::renderAnimations( SoftProjRender &render )
{
	selectTextureUnit( 0 );
	glEnable( GL_TEXTURE_2D );

	//GLboolean useDepthTest;
	//glGetBooleanv( GL_DEPTH_TEST, &useDepthTest );
	//glDisable( GL_DEPTH_TEST );

	GLboolean useBlending;
	glGetBooleanv( GL_BLEND, &useBlending );

	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		
	for ( AnimationContainer::iterator it= this->animations.begin();  it != this->animations.end(); ++it )
	{
		it->render( render );
	}

	if ( useBlending ) glEnable( GL_BLEND ); else glDisable( GL_BLEND );
	//if ( useDepthTest ) glEnable( GL_DEPTH_TEST ); else glDisable( GL_DEPTH_TEST );
}
void MapFunctionality::renderLodGrid( SoftProjRender &render, const Point_3<double> &center, float zoom )
{
	//determine optimal grid and cell size
	if ( zoom > 1000000 )
	{
		this->renderMapGrid( render, center, VISIBLE_SIZE, 300000 );
	}
	else if ( zoom > 100000 )
	{
		this->renderMapGrid( render, center, VISIBLE_SIZE, 30000 );
	}
	else if ( zoom > 10000 )
	{
		this->renderMapGrid( render, center, 100000, 3000 );
	}
	else if ( zoom > 1000 )
	{
		this->renderMapGrid( render, center, 10000, 300 );
	}
	else
	{
		this->renderMapGrid( render, center, 1000, 30 );
	}
}
void MapFunctionality::renderMapGrid( SoftProjRender &render,  const Point_3<double> &center, const float gridSize, const float cellSize )
{
	const float EPSILON_FACTOR = 0.9;
	Point_3<double> from( -gridSize, -gridSize, 0 );
	Point_3<double> to( gridSize, gridSize, 0 );

	// clamp offset into multiple of cellSize
	// switch Z and Y ( grid will be rotated about X axis -> Z´ = Y, Y´ = Z
	Vector_3<double> offsetFloat(	int( center.x() / cellSize ) * cellSize, 
									int( center.z() / cellSize ) * cellSize,
									int( center.y() / cellSize ) * cellSize );

	from += offsetFloat;
	to	 += offsetFloat;

	float rot[3]={90,0,0};
	render.renderGrid2D( from, to, GameConstants::SCIFI_GREEN_DARK, cellSize, rot );
}
void MapFunctionality::renderInvisiblePickPlane( SoftProjRender &render, const Point_3<double> &center, const float planeSize )
{
	Point_3<double> from( -planeSize, 0, -planeSize );
	Point_3<double> to( planeSize, 0, planeSize );
	
	from += CONVERT_TO_VECTOR<double>(center);
	to += CONVERT_TO_VECTOR<double>(center);

	GLboolean useBlending;
	glGetBooleanv( GL_BLEND, &useBlending );

	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	render.bind();

	selectTextureUnit( 0 );
	glDisable( GL_TEXTURE_2D );

	glColor4f( 0,0,0,0); //invisible desk ( used for gluUnproject during picking )
	glBegin(GL_QUADS);
		glVertex3f( from.x(),  0.1, from.z() );
		glVertex3f( from.x(), 0.1, to.z() );
		glVertex3f( to.x(), 0.1 , to.z() );
		glVertex3f( to.x(),  0.1, from.z() );
	glEnd();
		

	if ( useBlending ) glEnable( GL_BLEND ); else glDisable( GL_BLEND );
}
void MapFunctionality::renderFog( SoftProjRender &render, const Point_3<double> &center, const float visibleSize, const float totalSize )
{
	GLboolean useBlending;
	glGetBooleanv( GL_BLEND, &useBlending );
	render.bind();


	//#########################################################################
	//non-occluding desk ( must be in front of occluding desk )
	Point_3<double> visibleFrom( -visibleSize, 0, -visibleSize );
	Point_3<double> visibleTo( visibleSize, 0, visibleSize );
	
	visibleFrom += CONVERT_TO_VECTOR<double>(center);
	visibleTo += CONVERT_TO_VECTOR<double>(center);

	float Z_FIGHT_OFFSET = 100.0;
	float offsetY = center.y() + Z_FIGHT_OFFSET;
	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	selectTextureUnit( 0 );
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, this->fogTextureId );
	glColor4f( 1,1,1,1);
	glBegin(GL_QUADS);
		glTexCoord2f(0,0);	glVertex3f( visibleFrom.x(),	offsetY,	visibleFrom.z() );
		glTexCoord2f(0,1);	glVertex3f( visibleFrom.x(),	offsetY,	visibleTo.z() );
		glTexCoord2f(1,1);	glVertex3f( visibleTo.x(),		offsetY,	visibleTo.z() );
		glTexCoord2f(1,0);	glVertex3f( visibleTo.x(),		offsetY,	visibleFrom.z() );
	glEnd();
	
	//#######################################################################
	//ocluding quads to occlude all except the visible quad

	Point_3<double> totalFrom( -totalSize, 0, -totalSize );
	Point_3<double> totalTo( totalSize, 0, totalSize );
	
	totalFrom += CONVERT_TO_VECTOR<double>(center);
	totalTo += CONVERT_TO_VECTOR<double>(center);


	glDisable( GL_BLEND );
	selectTextureUnit( 0 );
	glDisable( GL_TEXTURE_2D );
	glColor4f( 0,0,0,1);
	//UP OCCLUDING QUAD
	glBegin(GL_QUADS);
		glVertex3f( visibleFrom.x(),	offsetY,	visibleFrom.z() );
		glVertex3f( visibleTo.x(),		offsetY,	visibleFrom.z() );
		glVertex3f( totalTo.x(),		offsetY,	totalFrom.z() );
		glVertex3f( totalFrom.x(),		offsetY,	totalFrom.z() );
	glEnd();
	
	//RIGHT OCCLUDING QUAD
	glBegin(GL_QUADS);
		glVertex3f( visibleTo.x(),		offsetY,	visibleFrom.z() );
		glVertex3f( visibleTo.x(),		offsetY,	visibleTo.z() );
		glVertex3f( totalTo.x(),		offsetY,	totalTo.z() );
		glVertex3f( totalTo.x(),		offsetY,	totalFrom.z() );
	glEnd();

	//DOWN OCCLUDING QUAD
	glBegin(GL_QUADS);
		glVertex3f( visibleFrom.x(),	offsetY,	visibleTo.z() );
		glVertex3f( totalFrom.x(),		offsetY,	totalTo.z() );
		glVertex3f( totalTo.x(),		offsetY,	totalTo.z() );
		glVertex3f( visibleTo.x(),		offsetY,	visibleTo.z() );
	glEnd();

	//LEFT OCCLUDING QUAD
	glBegin(GL_QUADS);
		glVertex3f( visibleFrom.x(),	offsetY,	visibleFrom.z() );
		glVertex3f( totalFrom.x(),		offsetY,	totalFrom.z() );
		glVertex3f( totalFrom.x(),		offsetY,	totalTo.z() );
		glVertex3f( visibleFrom.x(),	offsetY,	visibleTo.z() );
	glEnd();
	//#######################################################################
	
	//restore previous blending state
	if ( useBlending ) glEnable( GL_BLEND ); else glDisable( GL_BLEND );

	return;
}
void MapFunctionality::renderHiglightPlanet( SoftProjRender &render, BasePlanetClass &planetToHighlightRender  )
{
	//make copy of space planet, modify it if neccessary and then delete the copy
	BasePlanetClass *modelCopy = NULL;
	switch ( planetToHighlightRender.getPlanetClassType() )
	{
	case BASIC_PLANET_CLASS:
		{
		BasicPlanet *basicPlanet = new BasicPlanet(*((BasicPlanet*) &planetToHighlightRender));

		//clamp to size limit before highlight ( because sun is afteer highlight too big !)
		double clampedObjectSize = basicPlanet->objectSize;
		clamp<double>( 0.0, PLANET_MAX_SIZE_BEFORE_HIGHLIGHT, clampedObjectSize);

		 
		float objectScaleFactor =PLANET_HIGHLIGHT_SCALE * this->getCameraZoom();

		//scale up or dont scale, do not scale down
		if ( objectScaleFactor < 1.0 )
		{
			//do not scale object ( scale down disabled )
		}
		else
		{
			basicPlanet->objectSize = max( basicPlanet->objectSize, clampedObjectSize * objectScaleFactor);
		}

		clamp<double>( 0.0, PLANET_HIGHLIGHT_MAX_SIZE, basicPlanet->objectSize);
		modelCopy = basicPlanet;
		}
		break;

	/*
	//Not supported for now...models must be normalized to some unit size!!!
	case MODEL_PLANET_CLASS:
		{
		ModelPlanet *modelPlanet = new ModelPlanet(*((ModelPlanet*) &planetToHighlightRender));
		modelPlanet->model.scale( 50.0 );  //make 50times bigger to highlight it
		modelCopy = modelPlanet;
		}
		break;
	*/

	default:
		modelCopy = NULL;
		assert(!"Unkown spacePlanet type!");
		break;
	}

	if ( modelCopy != NULL )
	{
		//render highlighted planet
		float distanceCameraShip =  this->camera.pos.get_distance( planetToHighlightRender.getPos());
		modelCopy->render( render, this->calculatePlanetLevelOfDetail( distanceCameraShip  ) );
	
		//render highlighted planet text
		float distance = planetToHighlightRender.getPos().get_distance( CONVERT_TO_DOUBLE(render.getCamera().pos) );
		const float currentFontSize =  distance / 30.0;
		const float MAX_HIGHLIGHT_PLANET_FONT_SIZE = 30000.0;
		const float finalFontSize = min( MAX_HIGHLIGHT_PLANET_FONT_SIZE, currentFontSize );
		this->renderPlanetName( render, *modelCopy, finalFontSize );

		delete modelCopy;
	}
}
void MapFunctionality::renderSpacePlanets( SoftProjRender &render, PlanetDatabase &spacePlanets )
{
	

	
	for ( PlanetDatabase::iterator it = spacePlanets.begin(); it != spacePlanets.end(); ++it)
	{
		if ( this->highlightPlanets )
		{
			//render highlighted planets ( bigger planets + including names)
			this->renderHiglightPlanet( render, *it->second );
		}
		else
		{
			//render standard planets
			float distanceCameraShip =  this->camera.pos.get_distance( it->second->getPos());
			it->second->render( render, this->calculatePlanetLevelOfDetail( distanceCameraShip ) );
		}
	}
}

void MapFunctionality::renderHiglightShip( SoftProjRender &render, SpaceShip &shipToHighlightRender  )
{
	//make copy of space planet, modify it if neccessary and then delete the copy
	SpaceShip modelCopy = shipToHighlightRender;

	float objectScaleFactor =  SHIP_HIGHLIGHT_SCALE * this->getCameraZoom();

	 //scale up or dont scale, do not scale down
	clamp<float>( 1.0, SHIP_HIGHLIGHT_MAX_SIZE, objectScaleFactor );

	modelCopy.model.scale( objectScaleFactor );
	modelCopy.render( render,1 );

	float distance = shipToHighlightRender.getPos().get_distance( CONVERT_TO_DOUBLE(render.getCamera().pos) );
	const float currentFontSize =  distance / 30.0;
	const float MAX_SHIP_FONT_SIZE = 100.0;
	const float finalFontSize = min( SHIP_HIGHLIGHT_MAX_SIZE, currentFontSize );
	this->renderShipName( render, modelCopy, finalFontSize );
}

void MapFunctionality::renderSpaceShips( SoftProjRender &render, ShipDatabase &spaceShips )
{
	//glEnable( GL_DEPTH_TEST );

	for ( ShipDatabase::iterator it = spaceShips.begin(); it != spaceShips.end(); ++it)
	{
		if ( it->second->inWarp || it->second->inBase )
		{
			//we do not visualise ship when its in warp or in base !
			//IMPROVEMENT TIP: visualise ship when its in warp
		}
		else
		{
		if ( this->highlightShips )
			{
				this->renderHiglightShip( render, *it->second );
			}
			else
			{
				this->renderHighlightDisc( render,  it->second->getPos() );
				it->second->render( render,1 );
			}
		}
	}
}

void MapFunctionality::renderHighlightDisc( SoftProjRender &render, const Point_3<double> &position )
{
	double distance = position.get_distance(render.getCamera().pos );

	const float currentFontSize =  distance / 100.0;
	const float MAX_DISC_SIZE = 12000.0;
	const float MIN_DISC_SIZE = 0.0;

	const float finalFontSize = getClamped( MIN_DISC_SIZE, MAX_DISC_SIZE, currentFontSize );
	

	//glDisable( GL_DEPTH_TEST );
	Point_3<float> point[4];
	point[0] = CONVERT_TO_FLOAT<double>( position ) + Vector_3<float>(-1, 0, -1) * finalFontSize;
	point[1] = CONVERT_TO_FLOAT<double>( position ) + Vector_3<float>(-1, 0, 1) * finalFontSize;
	point[2] = CONVERT_TO_FLOAT<double>( position ) + Vector_3<float>(1,  0, 1) * finalFontSize;
	point[3] = CONVERT_TO_FLOAT<double>( position ) + Vector_3<float>(1,  0, -1) * finalFontSize;

	//CRgbw color(1.0);

	render.renderQuad( point[0], point[1], point[2], point[3], GameConstants::SCIFI_MAGNETA, this->discTextureId );
}

void MapFunctionality::renderMissiles( SoftProjRender &render  )
{
	//glEnable( GL_DEPTH_TEST );

	for ( MissileContainer::iterator it = this->missiles.begin(); it != this->missiles.end(); ++it)
	{
		it->missileData.render( render,1 );
	}
}
void MapFunctionality::renderParticles( SoftProjRender &render )
{
	for (	ParticleEmittorMap::iterator it = this->particleEmittor.begin(); it != this->particleEmittor.end(); ++it )
	{
		render.renderParticles( it->second->particles );
	}
}
void MapFunctionality::renderPathFlag( SoftProjRender &render, const Point_3<double> &from, const Point_3<double> &to )
{
	render.renderLine( CONVERT_TO_FLOAT<double>(from), CONVERT_TO_FLOAT<double>(to), GameConstants::SCIFI_GREEN_BRIGHT, LINE_WIDTH );
}

void MapFunctionality::renderSelectionFlag( SoftProjRender &render, const Point_3<double> &pos )
{
	render.renderCircle( CONVERT_TO_FLOAT<double>(pos), CIRCLE_AXIS, GameConstants::SCIFI_GREEN_BRIGHT, SELECTION_CIRCLE_SIZE, CIRCLE_SEGMENT_COUNT, LINE_WIDTH );
}

void MapFunctionality::renderDestinationFlag( SoftProjRender &render, const Point_3<double> &pos )
{
	//pulsing circle animation

	const float pulsingSpeed = 0.01;
	const float maxPulsingCircleSize = 0.8;
	const float minPulsingCircleSize = 0.4;

	this->circleSize+= pulsingSpeed;

	if ( this->circleSize > maxPulsingCircleSize)
	{
		this->circleSize = minPulsingCircleSize;
	}

	render.renderCircle( CONVERT_TO_FLOAT<double>(pos), CIRCLE_AXIS, GameConstants::SCIFI_GREEN_BRIGHT, this->circleSize, CIRCLE_SEGMENT_COUNT, LINE_WIDTH );
}

void MapFunctionality::setBackground(  unsigned int backgroundTexID )
{
	this->skyPlane.tex = backgroundTexID;
}
void MapFunctionality::newExplosion( const Point_3<double> &pos )
{
	this->animations.push_back( EXPLOSION_PROTOTYPE );
	this->animations.back().pos = CONVERT_TO_FLOAT<double>(pos);
}
void MapFunctionality::newWarpEffect( const Point_3<double> &pos )
{
	this->animations.push_back( WARP_PROTOTYPE );
	this->animations.back().pos = CONVERT_TO_FLOAT<double>(pos);
}
MapFunctionality::~MapFunctionality() 
{
}

void MapFunctionality::simulateNoisyScreen(  float dt )
{
	if ( this->noisyScreenEffect.isActive() )
	{
		this->noisyScreenEffect.simulate( dt );
	}
	else
	{
		this->noisyScreenEffect.alpha = 0.0;
		this->noisyScreenEffect.alphaOffset = 0.01;
	}
}
void MapFunctionality::simulateSignalizer( float dt )
{
	this->signalizer.simulate( dt );
}
void MapFunctionality::simulateShips( ShipDatabase &shipDatabase, float dt )
{
	// simulate all Ship objects
	for ( ShipDatabase::iterator it = shipDatabase.begin(); it!= shipDatabase.end(); ++it )
	{
		if ( !it->second->inBase /*&& !it->second->inWarp*/ )
		{
			//distance to the target
			float remainingDistance = (it->second->getPos() - it->second->destinationPoint).get_length();

			if ( remainingDistance < EPSILON_DISTANCE )
			{
				//turn motor off, target is reached
				//Note: simulation still needed in case that the ship is orbiting, so dont skip ship::simulate()
				it->second->accleration = 0;
				it->second->speed = 0;
			}

			it->second->simulate( dt );
		}
	}
}

void MapFunctionality::simulatePlanets( PlanetDatabase &planetDatabase, float dt )
{
	// simulate all Planet objects
	for ( PlanetDatabase::iterator it= planetDatabase.begin(); it!= planetDatabase.end(); ++it )
	{
		it->second->simulate( dt );
	}
}

void MapFunctionality::simulateParticles( PlanetDatabase &planetDatabase, ShipDatabase &shipDatabase, float dt )
{
	this->particleTimeCounter += dt;

	//######################################################################
	// emit new particles on ship engine position
	for ( ShipDatabase::const_iterator it = shipDatabase.begin(); it != shipDatabase.end(); ++it)
	{
		if ( it->second->inBase )
		{
			//ignore if its in base
			continue;
		}

		//distance to the target
		float remainingSquareDistance = (it->second->getPos() - it->second->destinationPoint).get_length_square();

		if ( remainingSquareDistance < EPSILON_DISTANCE )
		{
			//do not emit new particles...motor is turned off, target is reached
			continue;
		}

		// Using Roussian roulette to generate new particles
		if ( getRandomNumberFrom0To1() < dt )
		{
			ParticleEmittorMap::iterator emittorIt = this->particleEmittor.find(it->second->type);
		
			//if we founded proper particle emittor then we use him for entire ship
			if ( emittorIt != this->particleEmittor.end() )
			{
				emittorIt->second->emitParticles( CONVERT_TO_FLOAT<double>(it->second->getPos()), CONVERT_TO_FLOAT<double>(it->second->getOrientation())  );
			}
		}
	}

	//######################################################################
	// emit new particles on missiles position
	for ( MissileContainer::iterator it = this->missiles.begin(); it != this->missiles.end(); ++it)
	{
		
		// Using Roussian roulette to generate new particles
		if ( getRandomNumberFrom0To1() < dt )
		{
			ParticleEmittorMap::iterator emittorIt = this->particleEmittor.find( (ShipType)1 ); //TODO, FIXME, USE MISSILE TYPE!!!
		
			//if we founded proper particle emittor then we use him for entire missile
			if ( emittorIt != this->particleEmittor.end() )
			{
				emittorIt->second->emitParticles( CONVERT_TO_FLOAT<double>(it->missileData.getPos()), CONVERT_TO_FLOAT<double>(it->missileData.getDir())  );
			}
		}
	}

	//######################################################################
	// emit new particles if ship is minining on from planet during orbiting
	for ( ShipDatabase::const_iterator it = shipDatabase.begin(); it != shipDatabase.end(); ++it)
	{
		if ( it->second->isOrbiting && it->second->isMiningFromPlanet )
		{
			const unsigned int MINING_MACHINE = 6;
			ParticleEmittorMap::iterator miningEmittorIt = this->particleEmittor.find( (ShipType) MINING_MACHINE );
		
			//if we founded proper particle emittor then we use him for entire ship
			if ( miningEmittorIt != this->particleEmittor.end() )
			{
				Vector_3<double> particleDir = it->second->orbitPivot - it->second->getPos();
				miningEmittorIt->second->emitParticles( CONVERT_TO_FLOAT<double>( it->second->getPos() ), CONVERT_TO_FLOAT<double>( particleDir));
			}
		}

	}
	//######################################################################
	// simulate existing particles
	for ( ParticleEmittorMap::iterator it = this->particleEmittor.begin(); it != this->particleEmittor.end(); ++it )
	{
		it->second->simulate( dt );
	}

	//######################################################################
	// remove old particles
	if ( particleTimeCounter > PARTICLE_GARBAGE_TIME_LIMIT )
	{
		for ( ParticleEmittorMap::iterator it = this->particleEmittor.begin(); it != this->particleEmittor.end(); ++it )
		{
			it->second->garbageRequest();
		}

		this->particleTimeCounter = 0;
	}
}

void MapFunctionality::newMissile( const Point_3<double> &fromPos, const Point_3<double> &toPos, const MissileTarget&  target  )
{
	assert( this->missilesPaternMap[ missile0 ] );
	const Model & missileModel = *this->missilesPaternMap[ missile0 ];

	SpaceMissile missileData( missileModel, toPos );
	missileData.setPos( fromPos );
	missileData.dstPos = toPos;
	missileData.type = missile0;
	missileData.speed = 2.0;
	missileData.setOrientation( missileData.getDir() );
	
	MissileInfo newMissile( missileData, target );
	this->missiles.push_back( newMissile );
}

void MapFunctionality::signalize(  const Point_3<double> &center, const float maxSignalRadius, const float radiusOffset )
{
	this->signalizer = Signalizer( center, maxSignalRadius, radiusOffset);
}
void MapFunctionality::simulateAnimations( float dt )
{
	// Explosions simulation: animation + remove finished explosions

	AnimationContainer::iterator it= this->animations.begin();
	while (  it != this->animations.end() )
	{
		it->simulate( dt *0.2 );

		// finished explosion will be removed
		if ( !it->isActive() )
		{
			it = this->animations.erase( it );
			continue;
		}

		++it;
	}
}

void MapFunctionality::simulateMissiles( PlanetDatabase &planetDatabase, ShipDatabase &shipDatabase, float dt )
{
	//########################################################
	//emit new missiles from ships
	for ( ShipDatabase::const_iterator it = shipDatabase.begin(); it != shipDatabase.end(); ++it)
	{
		if ( it->second->enemyTarget.targetType != NO_TARGET  && getRandomNumberFrom0To1() < dt * 0.001 * it->second->rateOfFire )
		{
			BaseSpaceObject *targetObject = NULL;

			if ( it->second->enemyTarget.targetType == SHIP_TARGET )
			{
				targetObject  = GalaxyPart::findShip( shipDatabase, it->second->enemyTarget.enemyTargetId );
			}
			else if ( it->second->enemyTarget.targetType == PLANET_TARGET )
			{
				targetObject  = GalaxyPart::findPlanet( planetDatabase, it->second->enemyTarget.enemyTargetId );
			}

			if ( targetObject != NULL )
			{
				this->newMissile( it->second->getPos(), targetObject->getPos(), it->second->enemyTarget);
			}
			else
			{
				//target not found -> ignore
				continue;
			}
		}
	}
	//########################################################
	//emit new missiles from planets
	for ( PlanetDatabase::const_iterator it = planetDatabase.begin(); it != planetDatabase.end(); ++it)
	{
		for (	vector< MissileTarget >::iterator targetId = it->second->enemyTargetIdContainer.begin(); 
				targetId != it->second->enemyTargetIdContainer.end(); ++targetId )
		{
			if ( targetId->targetType != NO_TARGET  && getRandomNumberFrom0To1() < dt * 0.0001 * it->second->rateOfFire )
			{

				BaseSpaceObject *targetObject = NULL;

				if ( targetId->targetType == SHIP_TARGET )
				{
					targetObject  = GalaxyPart::findShip( shipDatabase, targetId->enemyTargetId );
				}
				else if ( targetId->targetType == PLANET_TARGET )
				{
					targetObject  = GalaxyPart::findPlanet( planetDatabase, targetId->enemyTargetId );
				}


				if ( targetObject != NULL )
				{
					this->newMissile( it->second->getPos(), targetObject->getPos(), *targetId );
				}
				else
				{
					//target not found -> ignore
					continue;
				}
			}
		}
	}

	//########################################################
	//move or remove existing missiles
	MissileContainer::iterator it = this->missiles.begin();
	while ( it != this->missiles.end() )
	{

		//reset missile destination position : find for the missile target
		//and set  missile destination position to its position
		if ( it->missileData.lastSynchronizationTime > 20 * dt ) //every 20th simulation loop
		{
			BaseSpaceObject *targetObject = NULL;
			if ( it->target.targetType == SHIP_TARGET )
			{
				targetObject  = GalaxyPart::findShip( shipDatabase, it->target.enemyTargetId );
			}
			else if  ( it->target.targetType == PLANET_TARGET )
			{
				targetObject  = GalaxyPart::findPlanet( planetDatabase, it->target.enemyTargetId );
			}

			if ( targetObject != NULL )
			{
				it->missileData.dstPos = targetObject->getPos();
				it->missileData.lastSynchronizationTime = 0;
			}
		}
		
		// move missiles
		it->missileData.simulate( dt );

		// if the missile is closer than some threshold from its target
		// we make the missile explode and remove from database
		if ( it->missileData.getPos().get_distance( it->missileData.dstPos) < EXPLOSION_RADIUS )
		{
			this->newExplosion( it->missileData.getPos() ); 
			it = this->missiles.erase( it );
			continue;
		}
		else
		{
			++it;
		}
	}



}
MapFunctionality::MapFunctionality( unsigned int backgroundTexID, const AnimationEffect &explosionPrototype, 
	const AnimationEffect &warpPrototype, int pickPlaneSize, 
	int maxVisibleAreaSize, int cameraBoundary, int particleGarbageLimit, int nearCameraZoom, int farCameraZoom, 
	float mouseSensitivity, float keyboardSensitivity ): 
	EXPLOSION_PROTOTYPE( explosionPrototype ), WARP_PROTOTYPE( warpPrototype ),
	PICK_PLANE_SIZE(pickPlaneSize ), VISIBLE_SIZE( maxVisibleAreaSize ),
	EXPLOSION_RADIUS( 10.0 ), CAMERA_BOUNDARY( cameraBoundary  ), NEAR_CAMERA_ZOOM( nearCameraZoom ), FAR_CAMERA_ZOOM( farCameraZoom ),


	PLANET_HIGHLIGHT_MAX_SIZE( 50000.0 ), PLANET_HIGHLIGHT_SCALE( 0.003 ), PLANET_MAX_SIZE_BEFORE_HIGHLIGHT( 100 ),
	SHIP_HIGHLIGHT_MAX_SIZE( 50000.0), SHIP_HIGHLIGHT_SCALE( 0.005 ) , 
	
	MISSILE_HIGHLIGHT_SCALE( 1000.0 ),
	PARTICLE_GARBAGE_TIME_LIMIT( 100 ), CIRCLE_SEGMENT_COUNT( GameConstants::CIRCLE_SEGMENT_COUNT ), LINE_WIDTH( GameConstants::LINE_WIDTH ), 
	MOUSE_SENSITIVITY( mouseSensitivity ),  KEYBOARD_SENSITIVITY( keyboardSensitivity ), 
	CIRCLE_AXIS( 0, 1.0, 0), SELECTION_CIRCLE_SIZE( 6.0 ), EPSILON_DISTANCE( 1.0 ), signalizer( MAKE_POINT<double>(0,0,0), 0, 0)
{
	this->newDstPoint = false;

	//generates procedural Space Sky with 500 planets
	generateSpaceSkyPlanets( 500, this->spaceSkyPlane.skyPlane );

	this->skyPlane.size=0.4;

	this->skyPlane.tex = backgroundTexID;

	//init camera to NEAR_LIMIT distance
	this->camera.look =  Vector_3<double>(0,-1,-1);
	this->camera.look.normalize();
	this->camera.pos = this->cameraPivot - this->camera.look * FAR_CAMERA_ZOOM * 0.1;

	this->particleTimeCounter = 0;
	this->circleSize = 0;
	this->onShipNewDestinationPos = NULL;

	this->highlightPlanets = false;
	this->highlightShips = false;
	this->lockedCamera = false;
}

void MapFunctionality::handleMouse( Mouse &mouse)
{
	if ( mouse.pressedButtons[  Mouse::RIGHT ] )
	{
		Vector_3<double> moveVector;

		moveVector.x() = mouse.getDiffX() * this->getCameraZoom() * MOUSE_SENSITIVITY;
		moveVector.z() = mouse.getDiffY() * this->getCameraZoom() * MOUSE_SENSITIVITY;

		this->cameraPivot += moveVector;
		this->camera.pos += moveVector;
	}
	else if ( mouse.pressedButtons[  Mouse::LEFT ] )
	{
		this->newDstPoint = true;
		this->dstPoint[0] = mouse.getPosX();
		this->dstPoint[1] = mouse.getPosY();
	}

	const float MOUSE_WHEEL_SENSITIVE_REDUCTION = 0.001;
	this->zoomCamera(  mouse.wheel * MOUSE_WHEEL_SENSITIVE_REDUCTION * this->getCameraZoom() ); 
}

void MapFunctionality::handleKeyboard( Keyboard &keyboard  )
{
	Vector_3<double> moveVector;

	//move camera pivot if LEFT, RIGHT, UP, DOWN is pressed
	if ( keyboard.isPressed( Qt::Key_Left ) )
	{
		moveVector.x() = -KEYBOARD_SENSITIVITY * this->getCameraZoom();
	}
	if ( keyboard.isPressed( Qt::Key_Right ) )
	{
		moveVector.x() = KEYBOARD_SENSITIVITY * this->getCameraZoom();
	}
	if ( keyboard.isPressed( Qt::Key_Up ) )
	{
		moveVector.z() = -KEYBOARD_SENSITIVITY * this->getCameraZoom();
	}
	if ( keyboard.isPressed( Qt::Key_Down ) )
	{
		moveVector.z() = KEYBOARD_SENSITIVITY * this->getCameraZoom();
	}

	if ( keyboard.isPressed( Qt::Key_Plus ) )
	{
		this->zoomCamera( KEYBOARD_SENSITIVITY * this->getCameraZoom() );
	}

	if ( keyboard.isPressed( Qt::Key_Minus ) )
	{
		this->zoomCamera( -KEYBOARD_SENSITIVITY * this->getCameraZoom() );
	}

	if ( keyboard.isPressed( Qt::Key_A ) )
	{
		this->highlightPlanets = true;
	}
	else
	{
		this->highlightPlanets = false;
	}

	if ( keyboard.isPressed( Qt::Key_S) )
	{
		this->highlightShips = true;
	}
	else
	{
		this->highlightShips = false;
	}


	if ( keyboard.isPressed( Qt::Key_D ) )
	{
		this->lockedCamera = true;
	}
	else
	{
		this->lockedCamera = false;
	}

	this->cameraPivot += moveVector;
	this->camera.pos += moveVector;
}
void MapFunctionality::zoomCamera( float zoom )
{
	if ( zoom == 0 )
	{
		return;
	}

	Vector_3<double> halfPlaneVecBefore = this->cameraPivot - this->camera.pos;

	if ( halfPlaneVecBefore == Vector_3<double>(0,0,0) )
	{
		//this->camera.pos = this->cameraPivot - this->camera.look * FAR_CAMERA_ZOOM;
		KODIAK_WARNING<<"MapFunctionality::zoomCamera -> Vector_3<double> halfPlaneVecBefore = this->cameraPivot - this->camera.pos cannot be normalized, is zero vector!"; 
		return;
	}

	halfPlaneVecBefore.normalize();

	this->camera.move_camera( zoom );
	
	Vector_3<double> halfPlaneVecAfter = this->cameraPivot - this->camera.pos;
	halfPlaneVecAfter.normalize();

	//if the vector has changed, then camera is now in other halfplane!
	if ( halfPlaneVecBefore != halfPlaneVecAfter )
	{
		 //move camera to NEAR_LIMIT distance
		this->camera.pos = this->cameraPivot - this->camera.look * NEAR_CAMERA_ZOOM;
	}


	//at this moment is the camera in correct halfplane, but
	//near and far camera zoom limit is still not garanted
	float distance = this->getCameraZoom();

	if ( distance < NEAR_CAMERA_ZOOM )
	{
		this->camera.pos = this->cameraPivot - this->camera.look * NEAR_CAMERA_ZOOM; //move camera to NEAR_LIMIT distance
	}
	else if ( distance > FAR_CAMERA_ZOOM )
	{
		this->camera.pos = this->cameraPivot - this->camera.look * FAR_CAMERA_ZOOM; //move camera to NEAR_LIMIT distance
	}
}
