#include "AsteroidsField.h"
#include "Asteroid.h"
#include "Stone.h"
#include "Billboard.h"
#include "math.h"
#include "Material.h"
#include "TextureObject.h"
#include "AABoundingBox.h"
#include "BoundingSphere.h"
#include "Camera.h"
#include "Player.h"


AsteroidsField::AsteroidsField(	Vec3f position, float width, float height, float depth, 
								int number,int variations, float sizeMax, float sizeMin,
								Material* material, TextureObject* texture, Camera* cam)
{
	this->position = position;

	this->width = width;
	this->height = height;
	this->depth = depth;
	
	this->front = position.z + depth * 0.5;
	this->back = position.z - depth * 0.5;
	this->left = position.x - width * 0.5;
	this->right = position.x + width * 0.5;
	this->top = position.y + height * 0.5;
	this->bottom = position.y - height * 0.5;

	this->number = number;
	this->variations = variations;

	this->sizeMax = sizeMax;
	this->sizeMin = sizeMin;

	this->material = material;
	this->texture = texture;

	this->playerSpeed = 0;

	// Anlegen von geometrien
	float radius;
	for(int v=0; v<variations; v++)
	{
		stones.push_back(new Stone(1, 0.3f));
	}

	int geoNum;
	Stone* geo;

	this->cam = cam;

	// Asteroiden erzeugen
	for(int i=0; i<number; i++)
	{
		geoNum = rand()%variations;
		geo = stones[geoNum];
		radius = ((float)rand()/(float)RAND_MAX) * (sizeMax - sizeMin) + sizeMin;
		asteroids.push_back(new Asteroid( radius, Vec3f(0,0,0), geo));

		// Collionsflag Vector erzeugen
		asteroidCollisionTested.push_back(false);
	}

	//weitere Asteroiden die nur sichbar sind wenn eine Kollision statt fand
	for(int i=0; i<CHUNK_NUMBER; i++)
	{
		geoNum = rand()%variations;
		geo = stones[geoNum];
		//radius = sqrt(static_cast<float>(i+1)) + 1.0f;
		radius = (i%6) * 0.5f + 0.5f;
		Asteroid* newOne = new Asteroid( radius, Vec3f(0,0,100), geo, false);
		asteroids.push_back(newOne);
		chunks.push_back(newOne);

		// Collionsflag Vector erzeugen
		asteroidCollisionTested.push_back(false);
	}

	// Asteroiden positionieren
	for(int i=0; i<asteroids.size(); i++)
	{
		asteroids[i]->setPosition(calcPos(asteroids[i]->getRadius()));
	}

	// Bewegungsrichtung für Asteroiden
	for(int i=0; i<asteroids.size(); i++)
	{
		asteroids[i]->setMovingDirection(Vec3f(0,0,1));
		asteroids[i]->setMovingSpeed(((float)(rand()/(float)RAND_MAX))* MAX_MOVING_SPEED);
	}

	// nach tiefe sortieren
	qsort(&asteroids[0], asteroids.size(), sizeof(Asteroid*), comparatorZpos);

	// Explosionsbillboard erzeugen
	explosion = new Billboard("Textures\\explosion.png");
}
//-------------------------------------------------------------------------
AsteroidsField::~AsteroidsField()
{

}
//-------------------------------------------------------------------------
void AsteroidsField::setPlayerSpeed(float speed)
{
	this->playerSpeed = speed;
}
//-------------------------------------------------------------------------
std::vector<Stone*> AsteroidsField::getStones()
{
	return stones;
}
//-------------------------------------------------------------------------
Vec3f AsteroidsField::calcPos(float radius)
{
	float xPos = ((float)rand()/(float)RAND_MAX) * height - (height / 2.0f) + position.x;
	float yPos = ((float)rand()/(float)RAND_MAX) * width - (width / 2.0f) + position.y;
	float zPos = ((float)rand()/(float)RAND_MAX) * depth - (depth / 2.0f) + position.z;

	Vec3f pos = Vec3f(xPos, yPos, zPos);
	Vec3f distance;

	for (int i=0; i<asteroids.size(); i++)
	{
		distance = Vec3f(&pos, &(asteroids[i]->getPosition()));
		if(distance.length() <= (radius + asteroids[i]->getRadius()))
			return calcPos(radius);
	}

	return Vec3f(xPos, yPos, zPos);
}
//-------------------------------------------------------------------------
int AsteroidsField::comparatorZpos( const void * elem1, const void * elem2 )
{
	Asteroid* element1 = *(Asteroid**) elem1;
	Asteroid* element2 = *(Asteroid**) elem2;

	float element1z = element1->getPosition().z;
	float element2z = element2->getPosition().z;

	return (int) (element1z - element2z);
}
//-------------------------------------------------------------------------
bool AsteroidsField::collisionTestWithPlayer(AABoundBox* tested, float zMin, float zMax)
{
	if(playerImunTicks > 0)
	{
		playerImunTicks--;
		printf("playerImun %d\n",playerImunTicks);
		return false;
	}

	for(unsigned int i=0; i<asteroids.size(); i++)
	{
		if( !asteroids[i]->visible )
			continue;

		if(	asteroids[i]->getPosition().z > zMin &&
			asteroids[i]->getPosition().z < zMax && 
			tested->collisionTest(asteroids[i]->getBoundingVolume()))
		{
			asteroidDestruction(asteroids[i]);

			Vec3f dir(0,0,1);
			Vec3f up(0,1,0);
			explosion->showForTicks(asteroids[i]->getPosition(), dir, up, asteroids[i]->getRadius(), asteroids[i]->getRadius(), 10);
			asteroids[i]->visible = false;

			playerImunTicks = PLAYER_COLLISION_IMUN_TICKS;
			printf("Collision with Asteroid %d\n", i);

			qsort(&asteroids[0], asteroids.size(), sizeof(Asteroid*), comparatorZpos);
			return true;
		}
	}
	return false;
}
//-------------------------------------------------------------------------
void AsteroidsField::asteroidCollision(unsigned int fieldIndex)
{
	int start = fieldIndex - ASTEROID_COLLISION_TEST_RADIUS;
	if(start < 0)
		start = 0;
	int end = fieldIndex + ASTEROID_COLLISION_TEST_RADIUS;
	if(end >= asteroids.size())
		end = asteroids.size()-1;

	BoundingSphere* bv = asteroids[fieldIndex]->getBoundingVolume();

	for(int i = start; i <= end; i++)
	{
		// nicht mit sich selber und nicht mit schon getesteten prüfen
		if(i == fieldIndex || asteroidCollisionTested[i] || !asteroids[i]->visible)
			continue;

		// Testen auf Kollisionen
		if(bv->collisionTest(asteroids[i]->getBoundingVolume()))
		{
			Vec3f reflectDir(&(asteroids[fieldIndex]->getPosition()), &(asteroids[i]->getPosition()));
			reflectDir.normalize();

			float influence = asteroids[fieldIndex]->getRadius() / asteroids[i]->getRadius();
			float invInfluence = 1.0 / influence;

			Vec3f newDirTesten = asteroids[fieldIndex]->getMovingDirection();
			newDirTesten.x -= reflectDir.x * invInfluence;
			newDirTesten.y -= reflectDir.y * invInfluence;
			newDirTesten.z -= reflectDir.z * invInfluence;
			newDirTesten.normalize();
			asteroids[fieldIndex]->setMovingDirection(newDirTesten);
			//asteroids[fieldIndex]->setMovingSpeed(asteroids[fieldIndex]->getMovingSpeed() * invInfluence);

			Vec3f newDirOther = asteroids[i]->getMovingDirection();
			newDirOther.x += reflectDir.x * influence;
			newDirOther.y += reflectDir.y * influence;
			newDirOther.z += reflectDir.z * influence;
			newDirOther.normalize();
			asteroids[i]->setMovingDirection(newDirOther);
			//asteroids[i]->setMovingSpeed(asteroids[i]->getMovingSpeed() * influence);
		}
	}

	// Asteroid merken, damit er nicht von den anderen nochmal getestet wird
	asteroidCollisionTested[fieldIndex] = true;
}
//-------------------------------------------------------------------------
void AsteroidsField::asteroidDestruction( Asteroid* destroyed)
{
	float destroyedRadius = destroyed->getRadius(); 

	float volume = 1.3333 * PI * destroyedRadius * 2;


	for(int i=0; i< chunks.size(); i++)
	{
		if(volume > 0)
		{
			
			Vec3f p(	((float) rand() / (float) RAND_MAX) - 0.5,
						((float) rand() / (float) RAND_MAX) - 0.5,
						((float) rand() / (float) RAND_MAX) - 0.5);

			Vec3f movingDir = p;
			float movingSpeed = ((float) rand() / (float) RAND_MAX) * (CHUNK_SPEED_MAX - CHUNK_SPEED_MIN) + CHUNK_SPEED_MIN;
			
			p.add(destroyed->getPosition());
			p.z -= 10;

			chunks[i]->setPosition(p);
			chunks[i]->setMovingDirection(movingDir);
			chunks[i]->setMovingSpeed(movingSpeed);

			volume -= 1.3333 * PI * chunks[i]->getRadius();
			chunks[i]->visible = true;
		}
		else 
			return;
	}
}
//-------------------------------------------------------------------------
void AsteroidsField::update( Player* player)
{
	qsort(&asteroids[0], asteroids.size(), sizeof(Asteroid*), comparatorZpos);

	// Collionsvektor zurücksetzen
	for(int i=0; i < asteroidCollisionTested.size(); i++)
	{
		asteroidCollisionTested[i] = false;
	}

	for (int i=0; i< asteroids.size(); i++)
	{
		if( !asteroids[i]->visible )
			continue;

		asteroidCollision(i);
		asteroids[i]->update();
		Vec3f newPos = asteroids[i]->getPosition();

		// Raumschiff geschwindigkeit simulieren
		newPos.z += playerSpeed;

		if(newPos.x > this->right)
			newPos.x = left;
		if(newPos.x < this->left)
			newPos.x = right;

		if(newPos.y > this->top)
			newPos.y = bottom;
		if(newPos.y < this->bottom)
			newPos.y = top;

		if(newPos.z > this->front)
			newPos.z = back;
		if(newPos.z < this->back)
		{
			Vec3f moveDir = asteroids[i]->getMovingDirection();
			moveDir.z *= -1;
			asteroids[i]->setMovingDirection(moveDir);
		}


		asteroids[i]->setPosition(newPos);
	}
	explosion->update();

	for (int i=0; i< chunks.size(); i++)
	{
		if(chunks[i]->getPosition().z > 0)
			chunks[i]->visible = false;
	}

	findAsteroidIndexAtZ( player->getPosition().z);
}
//-------------------------------------------------------------------------
void AsteroidsField::renderBeforePlayer()
{
	if(texture)
		texture->activate();

	for (int i=0; i< asteroidIndexNearPlayer; i++)
	{
		if(!asteroids[i]->visible)
			continue;

		float alpha = 1;
		if(asteroids[i]->getPosition().z > -20.0f)
			alpha *= (asteroids[i]->getPosition().z / -20.0f);
		
		float distX = abs(asteroids[i]->getPosition().x);
		if( distX > (width*0.5 - XY_DISAPPEAR_RANGE) )
			alpha *= (width*0.5 - distX) / XY_DISAPPEAR_RANGE;

		float distY = abs(asteroids[i]->getPosition().y);
		if( distY > (height*0.5 - XY_DISAPPEAR_RANGE) )
			alpha *= (height*0.5 - distY) / XY_DISAPPEAR_RANGE;


		//float depth = 1.0f + ((asteroids[i]->getPosition().z) / back);

		if(material)
			material->activate( 1, alpha);
		
		asteroids[i]->render();
	}
	if (texture)
		texture->deactivate();
}
//-------------------------------------------------------------------------
void AsteroidsField::renderBehindPlayer()
{
	if(texture)
		texture->activate();

	for (int i=asteroidIndexNearPlayer; i< asteroids.size(); i++)
	{
		if(!asteroids[i]->visible)
			continue;

		float alpha = 1;
		if(asteroids[i]->getPosition().z > -20.0f)
			alpha *= (asteroids[i]->getPosition().z / -20.0f);

		float distX = abs(asteroids[i]->getPosition().x);
		if( distX > (width*0.5 - XY_DISAPPEAR_RANGE) )
			alpha *= (width*0.5 - distX) / XY_DISAPPEAR_RANGE;

		float distY = abs(asteroids[i]->getPosition().y);
		if( distY > (height*0.5 - XY_DISAPPEAR_RANGE) )
			alpha *= (height*0.5 - distY) / XY_DISAPPEAR_RANGE;


		//float depth = 1.0f + ((asteroids[i]->getPosition().z) / back);

		if(material)
			material->activate( 1, alpha);

		asteroids[i]->render();
	}
	if (texture)
		texture->deactivate();

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	explosion->render();
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
//-------------------------------------------------------------------------
int AsteroidsField::findAsteroidIndexAtZ( float zValue )
{
	asteroidIndexNearPlayer = findAsteroidIndexAtZRek( zValue, 0, asteroids.size() - 1);
	return asteroidIndexNearPlayer;
}

//-------------------------------------------------------------------------
int AsteroidsField::findAsteroidIndexAtZRek(float zValue, int lower, int upper)
{
	if( lower == upper)
		return lower;

	int half = (lower + upper) / 2;

	float zOfHalf = asteroids[half]->getPosition().z;
	if( zOfHalf >= zValue)
		return findAsteroidIndexAtZRek( zValue, lower, half);
	else
		return findAsteroidIndexAtZRek( zValue, half+1, upper);

}