#include "DecorManager.h"

#include <stdlib.h>
#include <sstream>
#include <fstream>

#include "ModelMD2.h"
#include "ModelStatic.h"
#include "TextureManager.h"
#include "Renderer.h"
#include "GroundMap.h"

// particle engines
#include "ParticleEngine.h"
#include "FireGenerator.h"
#include "SmokeGenerator.h"
#include "SpringGenerator.h"

#include "MemoryManager/debugNew.h"

using namespace RenderSystem;
using namespace std;
using namespace ParticleSystem;
DecorManager* DecorManager::instance = NULL;

DecorManager::DecorManager(void){
}

DecorManager::~DecorManager(void){
	unload();
}

DecorManager& DecorManager::getInstance(){
	if (instance == NULL){
		instance = new DecorManager();
	}
	return *instance;
}

void DecorManager::destroy(){
	delete instance;
	instance = NULL;
}

Model* DecorManager::getModel(std::string name,bool dynamic){
	// on cherche si le model existe dans la map
	map<string,Model*>::iterator it = modelsMap.find(name);
	if (it != modelsMap.end()){
		// le model existe dans la map : on le retourne
		return it->second;
	}
	// le model n'existe pas dans la map
	Model* model;
	if (dynamic){
		model = new ModelMD2();
	}else{
		model = new ModelStatic();
	}
	if (!model->load((char*)name.c_str(),(char*)name.c_str())){
		// impossible de charger le model... on le detruit et on retourne NULL
		delete model;
		return NULL;
	}
	// le model est chargé, on le met dans la map et on le retourne
	modelsMap[name] = model;
	return model;
}

void DecorManager::parseLine(std::string line){
	char buffer[512];
	strcpy(buffer,line.c_str());
	if (buffer[0] == '#'){
		return;
	}
	char* token = strtok( buffer, "|" );
	if (token != NULL){
		if (strcmp(token,"decor") == 0){
			token = strtok( NULL, "|" );
			parseDecor(token);
		}else if(strcmp(token,"fire")==0){
			token = strtok( NULL, "|" );
			parseFire(token);
		}else if(strcmp(token,"spring")==0){
			token = strtok( NULL, "|" );
			parseSpring(token);
		}
	}
}

RegistredTexture* DecorManager::getTexture(std::string name){
	map<string,RegistredTexture*>::iterator it = texturesMap.find(name);
	if (it != texturesMap.end()){
		// la texture existe deja dans la map : on la retourne
		return it->second;
	}
	// la texture n'existe pas : on la charge.
	if(!TextureManager::getInstance()->addTexture2D((char*)name.c_str(),NULL,0,GL_LINEAR,GL_LINEAR_MIPMAP_LINEAR,1,GL_REPEAT,GL_REPEAT,GL_REPEAT,false,0,0,0,0,0)){
		fprintf(stderr,"Warning : unable to load decor texture %s\n",name.c_str());
		return NULL;
	}
	RegistredTexture * texture = TextureManager::getInstance()->getTextureByName((char*)name.c_str());
	texturesMap[name] = texture;
	return texture;
}

void DecorManager::unload(){
	// les textures sont geré par le texture manager
	// on detruit les decors
	for(vector<Decor*>::iterator it = decors.begin(); it != decors.end(); ++it){
		delete *it;
	}
	decors.clear();

	// on detruit les models
	for (map<string,Model*>::iterator it = modelsMap.begin(); it != modelsMap.end(); ++it){
		delete it->second;
	}
	modelsMap.clear();
}

bool DecorManager::load(std::string path){
	ifstream fichier(path.c_str());
	if (fichier.is_open()){
		char buffer[512];
		while(!fichier.eof()){
			fichier.getline(buffer,511);
			this->parseLine(buffer);
		}
		return true;
	}else{
		return false;
	}
}

void DecorManager::parseDecor(char * buffer){
	char * token = buffer;
	int step = 0;
	string path;
	string texture;
	string dynamic;
	float posX;
	float posY;
	int angle;
	float scale;
	int anim = 0;
	while( token != NULL ){
		switch(step){
			case 0:
				// le chemin du model
				path = token;
				break;
			case 1:
				// texture
				texture = token;
				break;
			case 2:
				// dynamic ?
				dynamic = token;
				break;
			case 3:{
				// position X
				std::istringstream str(token);
				str >> posX;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : X position must be an integer\n"); 
					return;
				}
				}break;
			case 4:{
				// position Y
				std::istringstream str(token);
				str >> posY;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : Y position must be an integer\n"); 
					return;
				}
				}break;
			case 5:{
				// angle
				std::istringstream str(token);
				str >> angle;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : angle must be an integer\n"); 
					return;
				}
				}break;
			case 6:{
				// scale
				std::istringstream str(token);
				str >> scale;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : scale must be a float\n"); 
					return;
				}
				}break;
			case 7:{
				if(dynamic != "true"){
					fprintf(stderr,"DecorManager::Parsing error : can't have an animation number while model is not dynamic\n"); 
					return;
				}
				// anim
				std::istringstream str(token);
				str >> anim;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : anim must be an integer\n"); 
					return;
				}
				}break;
		}
		step++;
		// Get next token: 
		token = strtok( NULL, "|" );
	}
	if (step < 7 || step > 8){
		fprintf(stderr,"DecorManager::Parsing error : found %d token instead of 6\n",step);
		return;
	}
	Model * model= getModel(path,dynamic == "true");
	if (model == NULL){
		return;
	}
	if (scale != 1){
		model->scale(scale);
	}
	RegistredTexture * tex = getTexture(texture);
	if (tex == NULL){
		return;
	}
	Decor *decor = new Decor();
	decor->setPosition(Point2D(posX,posY));
	decor->setAngle((float)angle);
	decor->setTexturePath(texture);
	decor->setupModel(model,tex, anim);
	decors.push_back(decor);
	Renderer::getInstance().addDecor(*decor);
	// TODO : utiliser la bounding box pour calculer les cases bloquée.
	ground::GroundMap::getInstance().getCases()[(int)(posX+0.5f)][(int)(posY+0.5f)]->setPassable(false);

}

void DecorManager::parseFire(char* buffer){
	char * token = buffer;
	int posX;
	int posY;
	int step = 0;
	while(token != NULL){	
		switch(step){
			case 0:{
				std::istringstream str(token);
				str >> posX;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : fire X position must be an integer\n"); 
					return;
				}
				}break;
			case 1:{
				std::istringstream str(token);
				str >> posY;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : fire Y position must be an integer\n"); 
					return;
				}
				}break;
		}
		step++;
		token = strtok( NULL, "|" );
	}
	if (step != 2){
		fprintf(stderr,"DecorManager::parseFire : error : found %d token instead of 2\n",step);
	}

	// on ajoute le moteur de particules
	ParticleEngine* fire = new ParticleEngine();
	FireGenerator * fireGenerator = new FireGenerator(CASE_SIDE_SIZE/6.0f);
	fireGenerator->pos[0] = (posX+0.5f)*CASE_SIDE_SIZE;
	fireGenerator->pos[1] = Renderer::getInstance().getElevation(posX+0.5f,posY+0.5f);
	fireGenerator->pos[2] = (posY+0.5f)*CASE_SIDE_SIZE;
	fire->setGenerator(fireGenerator);
	fire->getPhysique()->setPhysique(0,0,0);
	fire->createParticles(300);
	fire->setSortType(NO_SORT);
	fire->setBlendFunc(GL_SRC_ALPHA,GL_ONE);
	fire->start();
	ParticleEngine* smoke = new ParticleEngine();
	SmokeGenerator * smokeGenerator = new SmokeGenerator(CASE_SIDE_SIZE/2.0f);
	smokeGenerator->pos[0] = (posX+0.5f)*CASE_SIDE_SIZE;
	smokeGenerator->pos[1] = Renderer::getInstance().getElevation(posX+0.5f,posY+0.5f); 
	smokeGenerator->pos[2] = (posY+0.5f)*CASE_SIDE_SIZE;
	smoke->setGenerator(smokeGenerator);
	smoke->getPhysique()->setPhysique(0,0,0);
	smoke->createParticles(30);
	smoke->setSortType(NO_SORT);
	smoke->setBlendFunc(GL_SRC_COLOR,GL_ONE_MINUS_SRC_COLOR);
	smoke->start();
	ParticleManager::getGlobal().addEngine(smoke);
	ParticleManager::getGlobal().addEngine(fire);

	// on ajoute les moteurs de particules au moteur de rendu
	Renderer::getInstance().addFireParticle(fire,smoke);
	// et on bloque la case
	ground::GroundMap::getInstance().getCases()[posX][posY]->setPassable(false);
}

void DecorManager::parseSpring(char* buffer){
	char * token = buffer;
	int posX;
	int posY;
	int step = 0;
	while(token != NULL){	
		switch(step){
			case 0:{
				std::istringstream str(token);
				str >> posX;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : fire X position must be an integer\n"); 
					return;
				}
				}break;
			case 1:{
				std::istringstream str(token);
				str >> posY;
				if ( str.fail() ) {
					str.clear();  
					fprintf(stderr,"DecorManager::Parsing error : fire Y position must be an integer\n"); 
					return;
				}
				}break;
		}
		step++;
		token = strtok( NULL, "|" );
	}
	if (step != 2){
		fprintf(stderr,"DecorManager::parseFire : error : found %d token instead of 2\n",step);
	}

	// on ajoute le moteur de particules
	ParticleEngine* spring = new ParticleEngine();
	SpringGenerator * generator = new SpringGenerator(CASE_SIDE_SIZE/6.0f);
	generator->pos[0] = (posX+0.5f)*CASE_SIDE_SIZE;
	generator->pos[1] = Renderer::getInstance().getElevation(posX+0.5f,posY+0.5f);
	generator->pos[2] = (posY+0.5f)*CASE_SIDE_SIZE;
	spring->setGenerator(generator);
	spring->getPhysique()->setPhysique(0,-1,0);
	spring->createParticles(300);
	spring->setSortType(NO_SORT);
	spring->setBlendFunc(GL_SRC_ALPHA,GL_ONE);
	spring->start();
	ParticleManager::getGlobal().addEngine(spring);
	// on ajoute les moteurs de particules au moteur de rendu
	Renderer::getInstance().addSpringParticle(spring);
	// et on bloque la case
	ground::GroundMap::getInstance().getCases()[posX][posY]->setPassable(false);
}

