#include "PAppSdlOpenGl3.h"

///constante qui permet que le dt ne soit pas trop grand
const float DTFACTOR = 0.01;

/////////////////////////////////////////////////////////
//                                                     //
//   Fonctions publiques de la classe PAppSdlOpenGl3   //
//                                                     //
/////////////////////////////////////////////////////////


///Constructeur par défaut de PAppSdlOpenGl3
PAppSdlOpenGl3::PAppSdlOpenGl3(const std::string& titreFenetre, int largeurFenetre, int hauteurFenetre){
	this->initialisationPAppSdlOpenGl3(titreFenetre, largeurFenetre, hauteurFenetre);
}

///Constructeur de copier de PAppSdlOpenGl3
/**	@param other : PAppSdlOpenGl3 à copier
*/
PAppSdlOpenGl3::PAppSdlOpenGl3(const PAppSdlOpenGl3 & other){
	
}

///Destructeur de PAppSdlOpenGl3
PAppSdlOpenGl3::~PAppSdlOpenGl3(){
	SDL_Quit();
}

///fonction qui permet d'exécuter l'application
/**	@return 0 si l'exécution s'est bien passée, false sinon
*/
int PAppSdlOpenGl3::executer(){
	if(!p_isOpenGlSdlInit) return 1;
	p_isRun = true;
	while(p_isRun){
		p_start_time = SDL_GetTicks();
		this->clearKeyStatesNoRepeat();
		while(SDL_PollEvent(&p_evenements)){
// 			if(p_evenements.key.keysym.sym == SDLK_ESCAPE || p_evenements.type == SDL_QUIT){p_isRun = false;} //apparement ça génère des bugs donc on oublie
			this->updateWithEvent(&p_evenements);
			this->getEvent(&p_evenements);
		}
		// Nettoyage de l'écran
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		//on suppose que tout à bien fonctionne comme prévus et que le temps de calcul était égale à this->timeForOneFrame (en même temps on s'arange pour que se soit le cas)
		this->update(p_timeForOneFrame*DTFACTOR);   //on gère les évenements
		
		// Ré-initialisation de la matrice et placecement de la caméra
		p_matModelview.loadIdentity();
		p_cameraGL.see(p_matModelview);
		
		this->draw3d();               //on affiche tout
		
		SDL_GL_SwapWindow(p_fenetre);   //on fait le double buffering
		p_ellapsed_time = SDL_GetTicks() - p_start_time;
		if(p_ellapsed_time < p_timeForOneFrame){SDL_Delay(p_timeForOneFrame - p_ellapsed_time);}
	}
	return 0;
}

///////////////////////////////////////////////////////////////////
//                                                               //
//        Fonctions protégées de la classe PAppSdlOpenGl3        //
//                                                               //
///////////////////////////////////////////////////////////////////
		
void PAppSdlOpenGl3::updateWithEvent(SDL_Event* event){
	
}

///fonction qui permet de rajouter une touche à gérer durant l'execution de l'application
/**	@param action : nom de l'action à effectuer avec cette touche ("forward", "up", "down", "left", "right", "quit"...)
	@param key : touche sdl associée à cette action (flèche du haut : SDLK_UP, fléche du bas : SDLK_DOWN ...)
	@param initValue : valeur par défaut de cette touche (true : enfoncée ou false : relachée) 
*/
void PAppSdlOpenGl3::addKeyEventRepeat(const std::string & action, SDL_Keycode key, bool initValue){
	p_keyconfRepeat[action] = key;
	p_keystatesRepeat[p_keyconfRepeat[action]] = initValue;
}

///fonction qui permet de rajouter une touche à gérer durant l'execution de l'application (empèche la répatition de la touche tant qu'elle n'est pas relâchée
/**	@param action : nom de l'action à effectuer avec cette touche ("forward", "up", "down", "left", "right", "quit"...)
	@param key : touche sdl associée à cette action (flèche du haut : SDLK_UP, fléche du bas : SDLK_DOWN ...)
	@param initValue : valeur par défaut de cette touche (true : enfoncée ou false : relachée) 
*/
void PAppSdlOpenGl3::addKeyEventNoRepeat(const std::string & action, SDL_Keycode key, bool initValue){
	p_keyconfNoRepeat[action] = key;
	p_keystatesNoRepeat[p_keyconfNoRepeat[action]] = initValue;
}

///fonction qui met tous les keystatesNoRepeat à false
void PAppSdlOpenGl3::clearKeyStatesNoRepeat(){
	for(KeyStates::iterator it = p_keystatesNoRepeat.begin(); it != p_keystatesNoRepeat.end(); it++){
		it->second = false;
	}
}

///fonction qui permet d'envoyer la matrice de projection de l'application au shader
/**	@param idMatProjection : numéro d'identification de la matrice de projection à donner
*/
void PAppSdlOpenGl3::sendMatProjectionToShader(GLint idMatProjection){
	glUniformMatrix4fv(idMatProjection, 1, GL_TRUE, p_matProjection.getValue());
}

///fonction qui permet d'envoyer la matrice modelView de l'application au shader
/**	@param idMatModelView : numéro d'identification de la matrice modelView à donner
*/
void PAppSdlOpenGl3::sendMatModelViewToShader(GLint idMatModelView){
	glUniformMatrix4fv(idMatModelView, 1, GL_TRUE, p_matModelview.getValue());
}

///fonction qui arrète l'application
void PAppSdlOpenGl3::stop(){p_isRun = false;}

///fonction qui dit si Sdl et OpenGl ont bien été initialisées
/**	@return true si Sdl et OpenGl ont bien été initialisées, false sinon
*/
bool PAppSdlOpenGl3::isOpenGlSdlInit() const{return p_isOpenGlSdlInit;}

///définit le nombre de frames par secondes à afficher (n'est pas forcément un nombre entier)
/** @param framePerSecond : nombre de frames par secondes à afficher
*/
void PAppSdlOpenGl3::setFPS(Uint32 framePerSecond){
	//le facteur 1000 vient du fait que le temps donnée par SDL_getTicks est en millisecondes
	p_timeForOneFrame = 1000.0/framePerSecond;
}

///fonction qui renvoie le ratio de la fenêtre
/**	@return ratio de la fenêtre
*/
float PAppSdlOpenGl3::getRatio() const{
	return p_largeurFenetre / p_hauteurFenetre;
}

///fonction qui initialise a perspective avec un angle en radian
/**	@param angleDeg : angle de vue en degrée (70°)
 * 	@param near : position la plus prés de la caméra à afficher
 * 	@param far : position la plus loin de la caméra à afficher
*/
void PAppSdlOpenGl3::setPerpectiveDeg(float angleDeg, float near, float far){
	p_matProjection.perspectiveDeg(angleDeg, getRatio(), near, far);
}

///fonction qui initialise a perspective avec un angle en radian
/**	@param angleRad : angle de vue en radian
 * 	@param near : position la plus prés de la caméra à afficher
 * 	@param far : position la plus loin de la caméra à afficher
*/
void PAppSdlOpenGl3::setPerpectiveRad(float angleRad, float near, float far){
	p_matProjection.perspectiveRad(angleRad, getRatio(), near, far);
}

///fonction qui permet d'empècher le démarage de l'application (par exemple lors d'un problème d'initialisation)
void PAppSdlOpenGl3::dontRun(){
	p_isOpenGlSdlInit = false;
}

///////////////////////////////////////////////////////
//                                                   //
//   Fonctions private de la classe PAppSdlOpenGl3   //
//                                                   //
///////////////////////////////////////////////////////




///Fonction d'initialisation de la classe PAppSdlOpenGl3
/**	@param titreFenetre : titre de la fenêtre
 * 	@param largeurFenetre : largeur de la fenêtre
 * 	@param hauteurFenetre : hauteur de la fenêtre
*/
void PAppSdlOpenGl3::initialisationPAppSdlOpenGl3(const std::string& titreFenetre, int largeurFenetre, int hauteurFenetre){
	p_titreFenetre = titreFenetre;
	p_largeurFenetre = largeurFenetre;
	p_hauteurFenetre = hauteurFenetre;
	if(SDL_Init(SDL_INIT_VIDEO) < 0){  // Initialisation de la SDL
		std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
		p_isOpenGlSdlInit = false;
// 		SDL_Quit();  //on quitte dans le destructeur
		return;
	}else{
		std::cout << "Mode vidéo de SDL initialisé." << std::endl; 
	}
		
	// Version d'OpenGL
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
		
	// Double Buffer
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
		
	// Création de la fenêtre
	p_fenetre = SDL_CreateWindow(p_titreFenetre.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, p_largeurFenetre, p_hauteurFenetre, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
	if(p_fenetre == NULL){
		std::cerr << "Imposible de créer la fenêtre : "  << SDL_GetError() << std::endl;
		p_isOpenGlSdlInit = false;
		dontRun();
// 		SDL_Quit();
		return;
	}else{
		std::cout << "Création de la fénêtre." << std::endl;
	}
	p_contexteOpenGL = SDL_GL_CreateContext(p_fenetre);
	
	//on dit qu'il faut faire un test de profondeur (pour l'affichage 3D)
	glEnable(GL_DEPTH_TEST);
	
	p_isOpenGlSdlInit = true;
	p_matModelview.loadIdentity();
	p_matProjection.loadIdentity();
	setFPS(30.0);
	p_cameraGL.setWindowID(p_fenetre);
}


///fonction qui récupère les différents évenements (appuis des touches)
/**	@param event : évenement de la lib sdl
*/
void PAppSdlOpenGl3::getEvent(SDL_Event* event){
	//on test d'abord les touches qui peuvent se répéter
	for (KeyStates::iterator it = p_keystatesRepeat.begin();it != p_keystatesRepeat.end();it++){
		//est-ce que la touche responsable de l'événement est celle du keystate ?
		if (event->key.keysym.sym == it->first){
			it->second = (event->motion.type == SDL_KEYDOWN); //true si enfoncé, false si relâché
			break; //la touche responsable de l'événement a été utilisée, on quitte le for
		}
	}
	//maintenant les touches qui ne se répètent pas
	for (KeyStates::iterator it = p_keystatesNoRepeat.begin();it != p_keystatesNoRepeat.end();it++){
		//est-ce que la touche responsable de l'événement est celle du keystate ?
		if (event->key.keysym.sym == it->first){
			it->second = (event->motion.type == SDL_KEYUP); //true si enfoncé, false si relâché
			break; //la touche responsable de l'événement a été utilisée, on quitte le for
		}
	}
}

