
#include "GroundMap.h"

#include "DecorManager.h"
#include "GroupPathFinder.h"
#include "ActionGroupMove.h"

#include "MemoryManager/debugNew.h"


using namespace ground;
using namespace RenderSystem;

GroundMap* GroundMap::groundMap = NULL;

GroundMap::GroundMap()
{
	this->mapLoaded = false;
	this->height = 0;
	this->width = 0;
	this->graph = NULL;
	matrixCostAdd = NULL;
	currentUnit = NULL;
	pathFound = false;
	pathFinderMode = pathFinding::NORMAL;
}

GroundMap::~GroundMap()
{
	unloadMap();
}

GroundMap& GroundMap::getInstance()
{
	if(groundMap==NULL) {
		groundMap = new GroundMap();
	}
	return *groundMap;
}

void GroundMap::destroy()
{
	delete groundMap;
	groundMap = NULL;
}

bool GroundMap::oreeOfWood(int i, int j)
{
	if(map[i][j]->getGroundType() == FOREST){
		for(int i2=i-1;i2<=i+1;i2++){
			for(int j2=j-1;j2<=j+1;j2++){
				if(i2>=0 && j2>=0 && i2<(int)(width) && j2<(int)(height)){
					if( !(map[i2][j2]->getGroundType()==FOREST || map[i2][j2]->getGroundType()==OREE) )
						return true;
				}
			}
		}
	}
	return false;
}

bool GroundMap::isPassable(int i, int j)
{
	//on verifie qu'une des cases voisine en 8connexité n'a pas un hauteur relative supérieur à la limite fixé
	for(int i2=i-1;i2<=i+1;i2++){
		for(int j2=j-1;j2<=j+1;j2++){
			if(i2>=0 && j2>=0 && i2<(int)(width) && j2<(int)(height)){
				if( (map[i2][j2]->getElevation()-map[i][j]->getElevation())>=FRANCHISSABLE_DELTA_LIMIT )
					return false;
			}
		}
	}
	return true;
}

bool GroundMap::loadMap(char* heightpath,char* typepath)
{
	mapName	 = heightpath;
	ImageBMP bmpH;	//l'image heightmap
	ImageBMP bmpT;	//l'image type de terrain
	unsigned char *dataH;
	unsigned char *dataT;

	if(this->mapLoaded) this->unloadMap();

	if(!bmpH.load(heightpath))
		return false;
	if(!bmpT.load(typepath))
		return false;

	if(bmpH.getHeight()!=bmpT.getHeight() && bmpH.getWidth()!=bmpT.getWidth() ){
		return false;	//les deux bmp ne sont pas de la même taille donc la map n'est pas valide
	}
	this->width = bmpH.getWidth();
	this->height = bmpH.getHeight();
	dataH = bmpH.getDatasPointer();
	dataT = bmpT.getDatasPointer();

	//allocation mémoire de la map
	map = new Case**[width];
	for(unsigned int i=0;i<width;i++){
		map[i] = new Case*[height];
		for (unsigned int j = 0; j < height; j++){
			map[i][j] = new Case();
		}
	}

	for(unsigned int i=0;i<width;i++){
		for(unsigned int j=0;j<height;j++){
			map[i][j]->setCoord(i,j);

			map[i][j]->setElevation(dataH[(height*i + j)*3]*RAPPORT_HAUTEUR);	//on prend la composante rouge seulement

			//assignetion du type
			if(dataT[(height*i + j)*3]==255 && dataT[(height*i + j)*3+1]==255 && dataT[(height*i + j)*3+2]==0)
				map[i][j]->setGroundType(MEADOW);
			else if(dataT[(height*i + j)*3]==0 && dataT[(height*i + j)*3+1]==255 && dataT[(height*i + j)*3+2]==0)
				map[i][j]->setGroundType(FOREST);
			else if(dataT[(height*i + j)*3]==200 && dataT[(height*i + j)*3+1]==120 && dataT[(height*i + j)*3+2]==0)
				map[i][j]->setGroundType(ROAD);
			else if(dataT[(height*i + j)*3]==0 && dataT[(height*i + j)*3+1]==0 && dataT[(height*i + j)*3+2]==255)
				map[i][j]->setGroundType(WATER);
			else
				map[i][j]->setGroundType(MEADOW);//prairie par défaut
		}
	}
	
	//prise en compte de l'orée du bois
	for(unsigned int i=0;i<width;i++) {
		for(unsigned int j=0;j<height;j++) {
			if( oreeOfWood(i,j) ) {
				//la case est à l'orée du bois : on la transforme en terrain de type orée de foret
				map[i][j]->setGroundType(OREE);
			}
		}
	}

	//franchissabilité des case suivant le terrain(eau, dénivellation...)
	for(unsigned int i=0;i<width;i++) {
		for(unsigned int j=0;j<height;j++) {
			//TODO : hauteur de l'eau pour infranchissabilité????

			//si la case est franchissable, vérifion qu'elle l'est avec les hauteur du terrain
			if(map[i][j]->isPassable() && isPassable(i,j)==false)
				map[i][j]->setPassable(false);
		}
	}
	return true;
}

bool GroundMap::loadMap(char* bmp)
{
	mapName = bmp;
	ImageBMP bmpi;	//l'image heightmap
	unsigned char *data;

	if(this->mapLoaded) this->unloadMap();

	if(!bmpi.load(bmp))
		return false;

	this->width = bmpi.getWidth();
	this->height = bmpi.getHeight();
	data = bmpi.getDatasPointer();

	//allocation mémoire de la map
	map = new Case**[width];
	for(unsigned int i=0;i<width;i++){
		map[i] = new Case*[height];
		for (unsigned int j = 0; j < height; j++){
			map[i][j] = new Case();
		}
	}

	//width-i-1 pour une bonne orientation de la carte
	for(unsigned int i=0;i<width;i++) {
		for(unsigned int j=0;j<height;j++) {
			map[width-i-1][j]->setCoord(i,j);

			map[width-i-1][j]->setElevation(data[(i + j*width)*3]*RAPPORT_HAUTEUR);	//on prend la composante rouge seulement

			//assignetion du type
			if(      data[(i + j*width)*3+1]==255 && data[(i + j*width)*3+2]==0)
				map[width-i-1][j]->setGroundType(MEADOW);
			else if( data[(i + j*width)*3+1]==100 && data[(i + j*width)*3+2]==0)
				map[width-i-1][j]->setGroundType(FOREST);
			else if( data[(i + j*width)*3+1]==100 && data[(i + j*width)*3+2]==100)
				map[width-i-1][j]->setGroundType(ROAD);
			else if( data[(i + j*width)*3+1]==0 && data[(i + j*width)*3+2]==255)
				map[width-i-1][j]->setGroundType(WATER);
			else
				map[width-i-1][j]->setGroundType(MEADOW);//prairie par défaut
		}
	}
	
	//prise en compte de l'orée du bois
	for(unsigned int i=0;i<width;i++) {
		for(unsigned int j=0;j<height;j++) {
			if( oreeOfWood(i,j) )
			{
				//la case est à l'orée du bois : on la transforme en terrain de type orée de foret
				map[i][j]->setGroundType(OREE);
			}
		}
	}

	//franchissabilité des case suivant le terrain(eau, dénivellation...)
	for(unsigned int i=0;i<width;i++) {
		for(unsigned int j=0;j<height;j++) {
			//TODO : hauteur de l'eau pour infranchissabilité????

			//si la case est franchissable, vérifion qu'elle l'est avec les hauteur du terrain
			if(map[i][j]->isPassable() && isPassable(i,j)==false)
				map[i][j]->setPassable(false);
		}
	}

	return true;
}

void GroundMap::unloadMap()
{
	//on libere le tableau de case
	for(unsigned int i=0;i<width;i++){
		for (unsigned int j = 0; j < height; j++){
			delete map[i][j];
		}
		delete [] map[i];
		delete [] matrixCostAdd[i];
	}
	delete [] map;
	delete [] matrixCostAdd;
	if (graph != NULL){
		delete graph;
		graph = NULL;
	}

	this->mapLoaded = false;
	pathFound = false;
}

unsigned int GroundMap::getHeight()
{
	return this->height;
}

unsigned int GroundMap::getWidth()
{
	return this->width;
}

Case*** GroundMap::getCases()
{
	return this->map;
}

Case* GroundMap::getCase(int x, int y){
	if (x < 0 || y < 0 || x >= (int)(width) || y >= (int)(height)){
		return NULL;
	}
	return map[x][y];
}

void GroundMap::drawDebugGraph(int caseSize){
	if (graph != NULL){
		graphBuilder.drawGraph(caseSize);
	}
}

void GroundMap::computePath(Point2D& start, Point2D& end){
	unsigned int startX = (int)(start.getX()+0.5f);
	unsigned int startY = (int)(start.getY()+0.5f);
	unsigned int endX = (int)(end.getX()+0.5f);
	unsigned int endY = (int)(end.getY()+0.5f);
	// on ne doit pas essayer de chercher un chemin si on a un des point sur l'eau
	// car les cases d'eau n'ont pas de case du graph liée (retourne un pointeur NULL)
	// les autres cas sont pris en compte par le pathfinder sur matrice
	if (startX < 0 || startX >= width || startY < 0 || startY >= height ||
		endX < 0 || endX >= width || endY < 0 || endY >= height){
		pathFound = false;
		return;
	}
	// le cas ou les cases sont bien dans le terrain mais ne sont pas passables
	if (map[startX][startY]->getGroundType() == ground::WATER || map[endX][endY]->getGroundType() == ground::WATER ||
		!map[startX][startY]->isPassable() || !map[endX][endY]->isPassable() ||
		map[startX][startY]->getSquareNode() == NULL || map[endX][endY]->getSquareNode() == NULL){
		pathFound = false;
		matrixPathFinder.enableIncrementMap(false);
		matrixPathFinder.findPath(Point2D((int)startX,(int)startY),Point2D((int)endX,(int)endY));
		return;
	}

	// on est bien sur des cases valides de la map.
	// on effectue le pathfinding sur le graph.
	graphPathFinder.findPath(map[startX][startY]->getSquareNode(),map[endX][endY]->getSquareNode());
	// on test si on a trouvé un chemin dans le graph
	if (graphPathFinder.haveFoundPath()){
		// on calcule la matrice de ponderation
		graphBuilder.setupPonderationMatrix(matrixCostAdd,graphPathFinder.getPath());
		// on configure la pathfinder pour utiliser la matrice de ponderation
		matrixPathFinder.enableIncrementMap(true);
		matrixPathFinder.setIncrementMap(matrixCostAdd);
		// on recherche le chemin
		matrixPathFinder.findPath(Point2D((int)startX,(int)startY),Point2D((int)endX,(int)endY));
		// et on desactive la matrice de ponderation
		matrixPathFinder.enableIncrementMap(false);
		// puis on verifie si on a bien trouvé un chemin
		// en toute logique on doit toujours trouver un chemin car le pathfinder sur graph en a trouvé un...
		// mais par securitée on verifie quand même
		if (matrixPathFinder.haveFoundPath()){
			pathFound = true;
		}
	}else{
		matrixPathFinder.enableIncrementMap(false);
		matrixPathFinder.findPath(Point2D((int)startX,(int)startY),Point2D((int)endX,(int)endY));
		pathFound = false;
	}
}

void GroundMap::moveGroupTo(GroupPathFinder& group,Point2D& destination){
	if (pathFinderMode != ground::pathFinding::FURTIF){
		// mode de deplacement de groupe non furtif
		Point2D center = group.getCenter();
		// si le centre n'est pas passable, on utilise la position de la premiere unité du groupe sur une case passable
		Case * c = getCase(center.getX()+0.5f,center.getY()+0.5f);
		std::map<void*,Unit*>::iterator it = group.getUnits().begin();
		while(c == NULL || (!c->isPassable() && it != group.getUnits().end())){
			center = it->second->getPosition();
			++it;
		}
		setCurrentUnit(NULL);
		computePath(center,destination);
		// on a maintenant un chemin allant du centre du groupe au point voulu
		std::vector<Point2D>& path = matrixPathFinder.getPath();
		matrixPathFinder.enableIncrementMap(true);
		for (std::map<void*,Unit*>::iterator it = group.getUnits().begin(); it != group.getUnits().end(); ++it){
			it->second->pushAction(new ActionGroupMove(it->second,unitEnumeration::MOVE,&group,path,pathFinderMode));
		}
		matrixPathFinder.enableIncrementMap(false);
		
	}else{
		// deplacement de groupe furtif
		for (std::map<void*,Unit*>::iterator it = group.getUnits().begin(); it != group.getUnits().end(); ++it){
			computeUnitPath(it->second,destination + group.getOffsetMap()[it->second]);
		}
	}
}

void GroundMap::computeUnitPath(Unit* unit, Point2D& destination){
	currentUnit = unit;
	computePath(unit->getPosition(),destination);
	currentUnit = NULL;
}

void GroundMap::setPathFinderMode(pathFinding::PathFinderMode mode){
	pathFinderMode = mode;
}

pathFinding::PathFinderMode GroundMap::getPathFinderMode(){
	return pathFinderMode;
}

Unit* GroundMap::getCurrentUnit(){
	return currentUnit;
}

void GroundMap::setCurrentUnit(Unit* unit){
	currentUnit = unit;
}

bool GroundMap::haveFoundPath(){
	return pathFound;
}

std::vector<Point2D> GroundMap::getLastPath(){
	return matrixPathFinder.getPath();
}

float GroundMap::getGroundTypeCost(GroundType gt){
	return groundTypeToCost[pathFinderMode][gt];
}

float GroundMap::getGroundCaseFactor(const Point2D& position,const Point2D& destination){
	unsigned int posX = (unsigned int)(position.getX() + 0.5f);
	unsigned int posY = (unsigned int)(position.getY() + 0.5f);
	if (posX < 0 || posX >= width || posY < 0 || posY >= height){
		// on sort des limites de la carte
		return 1.0f;
	}
	Case * current = map[posX][posY];
	float factor = groundTypeFactor[map[posX][posY]->getGroundType()];
	// on calcule la position du prochain point
	unsigned int nextX= (int)(destination.getX() + 0.5f);
	unsigned int nextY= (int)(destination.getY() + 0.5f);
	if (nextX >= 0 && nextX < width && nextY >= 0 && nextY < height){
		// le prochain point est bien valide
		Case * next = map[nextX][nextY];
		// si la prochaine case n'est pas passable, on prend juste le facteur de la case
		// HACK : ce n'est pas logique de faire ca, mais sinon l'unité se bloque
		if (!next->isPassable()){
			return factor;
		}
		// FRANCHISSABLE_DELTA_LIMIT est la limite maximum franchissable par une unitée
		float elevationDelta = next->getElevation() - current->getElevation();
		// elevationDelta est positif si on monte et negatif si on descend
		if (fabs(elevationDelta) < FRANCHISSABLE_DELTA_LIMIT){
			if (elevationDelta < 0){
				elevationDelta /=2.0f;
			}
			// ici on a -FRANCHISSABLE_DELTA_LIMIT < elevationDelta < FRANCHISSABLE_DELTA_LIMIT
			// donc elevationDelta / FRANCHISSABLE_DELTA_LIMIT appartient à [-1..1]
			factor *= (1 - (elevationDelta / FRANCHISSABLE_DELTA_LIMIT));
		}else{
			// vitesse normale pour les cases sur lequelle on ne peut pas passer.
			factor = 1.0f;
		}
	}
	return factor;
}

void GroundMap::computeGraph(){
	// une fois la map charger, on peut calculer le graph
	std::string graphPath = mapName;
	graphPath += ".graph";
	FILE * graphFile = fopen(graphPath.c_str(),"r");
	if (graphFile != NULL){
		fclose(graphFile);
		printf("loading graph from file \"%s\"\n",graphPath.c_str());
		graphBuilder.loadFromFile(graphPath,map,width,height);
		this->graph = static_cast<SquareGraph*>(graphBuilder.createGraph());
		printf("graph loaded from file\n");
	}else{
		printf("Creation du graph de zones\n");
		graphBuilder.computeZones(map,width,height);
		this->graph = static_cast<SquareGraph*>(graphBuilder.createGraph());
		printf("fin de la creation du graph de zones\n");
		graphBuilder.saveToFile(graphPath);
	}

	// initilisation du pathfinding
	matrixPathFinder.setMatrix((AbstractCase***)map,width,height);
	matrixCostAdd = new float*[width];
	for (unsigned int i = 0; i < width; i++){
		matrixCostAdd[i] = new float[height];
	}
	graphPathFinder.setGraph((Graph*)graph);
	pathFound = false;
}

void GroundMap::loadDecors(){
	std::string decor = mapName;
	decor += ".decor";
	if (!DecorManager::getInstance().load(decor)){
		fprintf(stderr,"impossible de charger le fichier de decors\n");
	}
}

bool GroundMap::pathExist(Point2D& start, Point2D& end){
	unsigned int startX = (int)start.getX();
	unsigned int startY = (int)start.getY();
	unsigned int endX = (int)end.getX();
	unsigned int endY = (int)end.getY();
	// on ne doit pas essayre de chercher un chemin si on a un des point sur l'eau
	// car les cases d'eau n'ont pas de case du graph liée (retourne un pointeur NULL)
	// les autres cas sont pris en compte par le pathfinder sur matrice
	if (startX < 0 || startX >= width || startY < 0 || startY >= height ||
		endX < 0 || endX >= width || endY < 0 || endY >= height ||
		map[startX][startY]->getGroundType() == ground::WATER || map[endX][endY]->getGroundType() == ground::WATER){
		return false;
	}

	// on est bien sur des cases valides de la map.
	// on effectue le pathfinding sur le graph.
	graphPathFinder.findPath(map[startX][startY]->getSquareNode(),map[endX][endY]->getSquareNode());
	// on test si on a trouvé un chemin dans le graph
	return graphPathFinder.haveFoundPath();

}

MatrixPathFinder& GroundMap::getMatrixPathFinder(){
	return matrixPathFinder;
}

