#include "VisibilityChecker.h"

#include <math.h>
#include "Renderer.h"

#include "MemoryManager/debugNew.h"


// le min n'existe pas en standard en C...
#define MIN(a,b) a<b?a:b
#define MAX(a,b) a>b?a:b

using namespace std;
using namespace ground;

VisibilityChecker::VisibilityChecker(void){
	mapWidth = 0;
	mapHeight = 0;
	previousHeight = 0;
	previousSigne = 0;
	visibilityMatrix = NULL;

}

VisibilityChecker::~VisibilityChecker(void){
}

void VisibilityChecker::checkVisibility(bool** matrix,std::vector<VisibilityArea*>& entity){
	visibilityMatrix = matrix;
	clearMatrix();
	for(vector<VisibilityArea*>::iterator it = entity.begin(); it != entity.end(); ++it){
		computeVisibility(*it);
	}
}

void VisibilityChecker::setMatrixParameters(int width,int height){
	this->mapWidth = width;
	this->mapHeight = height;
}

void VisibilityChecker::clearMatrix(){
	// on initialise la matrice a false
	for(int i = 0; i < mapWidth; i++){
		for (int j = 0; j < mapHeight; j++){
			visibilityMatrix[i][j] = false;
		}
	}
}

void VisibilityChecker::rayTrace(VisibilityArea * entity){
	GroundMap & groundMap = GroundMap::getInstance();
	float radius = entity->getVisibilityRadius();
	float centerX = entity->getVisibilityPosition().getX();
	float centerY = entity->getVisibilityPosition().getY();
	float centerZ = groundMap.getCases()[(int)centerX][(int)centerY]->getElevation() + entity->getHeadOffset();
	float startAngle = (3.141*(entity->getViewAngle() - entity->getCutOffAngle()/2.0f))/180;
	float endAngle = (3.141*(entity->getViewAngle() + entity->getCutOffAngle()/2.0f))/180;
	float angleIncrement = ((3.141*35)/entity->getVisibilityRadius())/180;
	int test = 0;
	for (float angle = startAngle; angle <= endAngle; angle += angleIncrement){
		// le vecteur (vx,vy) est normalisé.
		float vx = cos(angle);
		float vy = sin(angle);
		// la position du vecteur sur laquelle on est situé
		float posX = centerX + vx;
		float posY = centerY + vy;
		float length = 0;
		previousSigne = 0;
		previousHeight = centerZ - entity->getHeadOffset();
		// on parcourt le rayon jusqu'a sortire du champ de vision ou de la carte
		while (length <= radius && posX >= 0 && posX < mapWidth && posY >= 0 && posY < mapHeight){
			test++;
			// on test si la case est visible
			if (checkPassable(posX,posY,centerZ)){
				// elle est bien visible
				visibilityMatrix[(int)posX][(int)posY] = true;
			}else{
				// elle n'est pas visible : on sort de la boucle de parcourt
				break;
			}
			// on avance sur le rayon
			posX += vx;
			posY += vy;
			// le vecteur (vx,vy) etant normalisé, on peut incrementer la distance parcouru
			length++;
		}
	}
	//printf("VisibilityChecker::rayTrace() : test : %d\n",test);
}

void VisibilityChecker::showBorders(VisibilityArea * entity){
	// on calcule les coordonnée pour qu'elle ne depassent pas de la matrice.
	float centerX = entity->getVisibilityPosition().getX();
	float centerY = entity->getVisibilityPosition().getY();
	int minX = MAX(0,centerX - 1);
	int minY = MAX(0,centerY - 1);
	int maxX = MIN(mapWidth-1,centerX+1);
	int maxY = MIN(mapHeight-1,centerY+1);
	// on met les coordonnée autoure de l'entité a visible.
	for (int i = minX; i <= maxX; i++){
		for (int j = minY; j <= maxY; j++){
			visibilityMatrix[i][j] = true;
		}
	}
}

void VisibilityChecker::computeVisibility(VisibilityArea* entity){
	GroundMap & groundMap = GroundMap::getInstance();
	int x = entity->getVisibilityPosition().getX();
	int y = entity->getVisibilityPosition().getY();
	// on test si l'entitée est bien dans la carte
	if (x < 0 || x >= mapWidth || y < 0 || y >= mapHeight){
		return;
	}
	if (groundMap.getCases()[x][y]->getGroundType() == ground::FOREST){
		// dans la foret on ne voit que les cases autour de sois
		showBorders(entity);
	}else{
		showBorders(entity);
		rayTrace(entity);
	}
}

bool VisibilityChecker::checkPassable(float x,float y, float baseHeight){
	Case *c = GroundMap::getInstance().getCases()[(int)x][(int)y];
	float elevation = RenderSystem::Renderer::getInstance().getElevation(x,y);
	bool elevationOk = false;
	switch(previousSigne){
		// on descendais avant
		case -1:
			if ((previousHeight - elevation) > 0){
				// on descend toujours
				elevationOk = true;
			}else{
				// on ne descend plus.
				if (baseHeight > elevation){
					// on est plus haut que le point : on le voit donc
					elevationOk = true;
					previousSigne = 1;
				}else{
					// on est situé en dessous du point : on ne le voit plus
					elevationOk = false;
				}
			}
			break;
		// on ne sait pas encore quel etait le type de monté precedent (premiere case testé)
		case 0:
			if (elevation > baseHeight){
				previousSigne = 1;
			}else if (elevation < baseHeight){
				previousSigne = -1;
			}
			// sinon on rest a 0
			elevationOk = true;
			break;
		// on montait avant
		case 1:
			if ((previousHeight - elevation) < 0){
				// on monte toujours
				elevationOk = true;
			}else{
				// on ne monte plus
				if (baseHeight > elevation){
					// on est au dessus du point : on le voit
					elevationOk = true;
					previousSigne = -1;
				}else{
					elevationOk = false;
				}
			}
			break;

	}
	previousHeight = elevation;
	bool visibleGround = c->getGroundType() != ground::FOREST && c->getGroundType() != ground::OREE;
	/*return (visibilityMatrix[(int)x][(int)y] && elevationOk) ||
		(elevationOk && visibleGround);*/
	return elevationOk && (visibilityMatrix[(int)x][(int)y] || visibleGround);
	// TODO : prendre en compte le type de terrain
}

