/**
 *        ParticleManager.h
 * description : fichier contenant la descriptions du manager de moteurs de particules.
 *
 *
 * Ajouts :
 * - 18/12/2004 : suppression du pattern singleton sur cette classe. La methode getInstance est dépréciée et à été
 * remplacée par getGlobal(). Il existe toujours un manager global, mais l'utilisateur peut aussi utiliser ses propres
 * manager.
 * - 20/12/2004 : ajout du mode de gestion memoire REMOVABLE : les moteur ajouté au manager en mode REMOVABLE doivent
 * etre détruit par l'utilisateur (comme pour le mode UNMANAGED) mais il peuvent aussi etre retirer de la liste des
 * moteurs de particules lorsqu'ils sont terminé.
 * - 30/12/2004 : ajout de la gestion de l'ajout des moteurs de particules à l'avant ou a l'arriere de la liste
 * de moteur de particules, afin de permettre une plus grande finesse dans la gestion des moteurs de particules 
 * additifs ou non additifs. Avant, l'utilisateur etait obligé de la ajouter dans l'ordre voulu (et ne pouvait
 * donc plus ajouter de moteur de particules non additifs tant qu'il y avait un moteur additis dans le manager.
 * - 31/12/2004 : mise a jour et correction d'erreure dans la documentation de la classe.
 * @author Michel de VERDELHAN <michel.deverdelhan@free.fr>
 */


#ifndef PARTICLEMANAGER_H
#define PARTICLEMANAGER_H

#include "stdio.h"
#include "ParticleEngine.h"
#include <list>
#include <vector>


#include "MemoryManager/DebugNew.h"


using namespace std;

/**
 * namespace contenant l'ensemble des classes du systeme de particules.
 */
namespace ParticleSystem{
/**
 * enumeration listant l'ensemble des mode de gestion de la memoire du particleManager.
 */
enum ParticleEngineMemoryManagment
{
	/**
	 * le moteur de particule est géré par le particleManager, c'est a dire que l'utilisateur ne doit pas le detruire.
	 * Par contre il peut demander a ce que le particleManager le detruise avec ParticleManager::removeEngine()
	 */
	MANAGED = 0,

	/**
	 * Le moteur de particule n'est pas gere par le particleManager, c'est a dire que c'est a l'utilisateur
	 * de la detruire. Par contre, il ne sera pas retiré du manager.
	 */
	UNMANAGED,

	/**
	 * Comme pour le mode UNMANAGED, c'est à l'utilisateur de détruire le moteur de particules, mais la difference est
	 * qu'il sera enlevé du manager quand il sera terminé.
	 */
	REMOVABLE,

	/**
	 * utilisée par la methode ParticleManager::exist() pour savoir si un moteur de particule existe ou n'exista pas
	 * dans le manager.
	 */
	NOT_EXISTING,
};

/**
 * La classe de gestion des moteurs de particules.
 *
 * Cette classe est faite pour gerer facilement de nombreux moteurs de particules. Pour cela, elle permet
 * à l'utilisateur de pouvoir stocker les moteurs de particule et d'en faciliter l'utilisation (en regroupant
 * les mises a jours par exemple.)
 *
 * L'utilisateur a aussi la possibilitée de laisser le manager s'occuper de la destruction des moteurs de particules 
 * ou alors de se reserver cette destruction (par la suite on parlera dans la documentation de moteurs de particules
 * MANAGED lorque le manager se chargera de la destruction memoire et de moteurs de particules UNMANAGED lorsque
 * le manager ne prendra pas cela en compte (c'est donc l'utilisateur qui aura la charge de la memoire)).
 *
 * [Ajout] : En plus des mode MANAGED et UNMANAGED, il existe maintenant le mode REMOVABLE, c'est a dire que l'utilisateur
 * doit gerer la mamoire, mais le moteur de particules peut etre enlevé de la liste de moteurs de particules lorsqu'il est
 * terminé. Ce mode est a utiliser pour des moteur de particules qui ne boucles pas mais qui peuvent etre reutilisé
 * plusieurs fois (moyennant un redemarrage du moteur.). Comme pour les moteur MANAGED, les REMOVABLE doivent imperativement
 * etres demarré avant d'etre ajouté au manager.
 *
 * Un autre point important dans l'utilisation d'un manager de particules, c'est que l'utilisateur ne doit JAMAIS
 * presuposser de la position d'un moteur de particules dans la liste. En effet la position d'un moteur de particules
 * peut changer au cours du temp avec l'ajout ou la suppression de moteur quand il sont supprimé (ou par l'utilisateur 
 * lui même) De plus un systeme de tri des moteur de particules sera peut etre implementé par la suite, il ne fauc
 * pas se fier a la position d'un moteur de particule dans l'iteration pour le retouver...
 *
 * [Ajout] : On peut ajouter les moteurs de particules a l'avant de la liste ou a l'arriere de la liste pour permettre
 * une plus grande finnesse dans la gestion de l'affichage. Ainsi les moteur de particules en mode additifs doivent
 * de preference etre ajouté a l'arriere de la liste alors que les moteurs de particules non additifs (alpha blended, 
 * color blended ...) dont l'ordre d'affichage est important (order dependant transparancy) doivent etre ajouté
 * de preference au debut de la liste. Ceci ne resoud pas l'ensemble des probleme d'ordre de transparence mais resoud
 * celui de l'affichage des moteurs additifs sur les autres...
 *
 * TODO : voir si une methode de trie des moteur de particules en fonction de leur mode d'affichage et de leur position
 * par rapport a la camera ne reoudrait pas l'ensemble des problemes d'ordre de transparance par manager par contre
 * ca ne resoudrait pas le probleme entre plusieurs managers... Pour resoudre le probleme entre plusieurs manager, il
 * faudrait changer la methode de gestion de l'affichage des moteurs de particules, et ne plus les afficher par manager
 * mais crée par exemple un classe stockant plusieurs manager (ceux dont les moteurs de particules sont potentiellement
 * visibles), recuperer les moteurs de particules dans une liste triée en fonction du mode d'affichage et de la distance a
 * la camera du moteur.
 * @deprecated getInstance() : cette methode à été remplacée par getGlobal() et a été retirée de l'interface de la
 * classe.
 */
class ParticleManager
{
private:
	struct PEngine
	{
		ParticleEngine * engine;
		ParticleEngineMemoryManagment mode;
		/**
		 * detruit le moteur de particule en fonction du mode de gestion de la memoire.
		 * 
		 * On utilise pas le destructeur car celui ci pourrait etre appelé en cas d'allocation dans la pile...
		 */
		void destroy()
		{
			switch(mode)
			{
			case MANAGED:
				delete engine;
				break;
			case UNMANAGED:
			case REMOVABLE:
			case NOT_EXISTING:
				// do nothing
				break;
			}
		}
	};

	/**
	 * la liste de moteurs de particules.
	 */
	list<PEngine> *particleEngines;

	/**
	 * l'iterateur utilisée pour parcourire l'ensemble de moteurs de particules.
	 */
	list<PEngine>::iterator iterator;

	/**
	 * l'instance global de particleManager.
	 *
	 * Cette instance est utilisée pour stocker les moteurs de particules globaux, c'est a dire
	 * ceux qui ne sont ratachée a aucune entitée logique du moteur (salles, objets...).
	 * 
	 * Pour les moteurs ratachée a des entitée logique, il est conseillé de leur crée un particle manager par
	 * groupe d'entitée (par exemple un manager par salle et un manager qui gere les moteurs de particules
	 * des bonus.
	 */
	static ParticleManager * global;

	/**
	 * methode retournant le premier PEngine contenant le ParticleEngine passé en parametre.
	 * @param e: le moteur de particule correspondant au PEngine recherché.
	 * @return PEngine: le PEngine correspondant au ParticleEngine passé en parametre.
	 * Si le manager ne contient pas le ParticleEngine passé en parametre, retourne NULL.
	 */
	PEngine * foundEngine(ParticleEngine * e);

public:

	/**
	 * le constructeur par defaut
	 */
	ParticleManager(void);

	/**
	 * Le destructeur par defaut, nettoie la liste de moteurs de particule et libere la memoire de ceux mis
	 * en mode MANAGED.
	 */
	virtual ~ParticleManager(void);

	/**
	 * methode retournant l'instance global de manager.
	 *
	 * Si l'instance globale n'existe pas, elle est crée puis ensuite, elle est retournée sous forme de reference
	 * afin d'empecher l'utilisateur de la detruire.
	 * @return l'unique instance de manager
	 */
	static ParticleManager & getGlobal();

	/**
	 * methode detruisant l'instance globale de manager.
	 *
	 * L'ensemble de la memoire utilisée par le manager est libérée, mis a part les moteur de particule passée
	 * en mode UNMANAGED.
	 * 
	 * L'utilisateur pourra par la suite recrée une instance de manager en faisant appel à la methode getInstance().
	 */
	static void destroyGlobal();

	/** 
	 * ajoute un moteur de particule au manager.
	 *
	 * ce moteur de particule doit etre initialisé (et alloué) par l'utilisateur avant de faire appel à cette methode.
	 *
	 * L'utilisateur ne peut pas ajouter 2 fois le même moteur de particule (même pointeur)
	 * 
	 * ATTENTION : si le moteur de particules est ajouté en mode MANAGED ou REMOVABLE, il doit etre demarré par l'utilisateur
	 * afin de pouvoir prendre en compte les moteurs de particules qui bouclent. Si l'utilisateur ne lance pas le 
	 * moteur avant l'appel a la methode update(), il risque d'etre supprimé car terminé.
	 *
	 * Si une iteration sur le manager est en cours, l'appel a cette methode va relancer l'iteration.
	 *
	 * L'utilisateur doit aussi bien veiller a ne pas passer en parametre des moteur de particule alloué par new[].
	 * Par exemple le code
	 *<PRE>
	 using namespace ParticleSystem;
	 ParticleEngine * p = new ParticleEngine[10];
	 ParticleManager::getGlobal().addEngine(&p[3]);
	 </PRE>
	 * Va provoquer une erreure car l'operateur de supression appelé par le manager lors de la suppression de moteur
	 * (qui peut intervenire lors d'une methode update(), removeEngine(), removeAll() ou destroy() ) est delete et 
	 * non pas delete[].
	 * @param e: le moteur de particule a ajouter au manager.
	 * @param end: definit si le moteur est ajouté a la fin de la liste ou au debut. Les moteur de particules additifs
	 * doivent etre de preference ajouté apres les moteur non additifs (alpha blending par exemple). Ceci permet 
	 * d'eviter des probleme d'ordre dans la transparence (sans pour autant les resoudres tous, par exemple quand
	 * 2 moteur en alpha blending se croisent...).
	 * Une bonne utilisation est donc de faire :
	 *<PRE>
	 ParticleEngine * additif;
	 ParticleEngine * alphaBlended;
	 [...]// initialisation des moteurs de particules
	 ParticleManager manager;
	 // on est obligé d'ajouter le mode de gestion memoire si on veut ajouter au debut.
	 manager.add(alphaBlended,MANAGED,false);
	 // pas besoin de specifier le mode d'ajout, c'est false par defaut.
	 manager.add(additif); 
	 </PRE>
	 * @param mode: le mode de gestion de la memoire utilisée. Par defaut, le moteur de particule appartient au manager
	 * qui se charge donc de le detruire. En mode unmanaged, c'est l'utilisateur qui doit detruire le moteur de
	 * particules. Le mode de gestion memoire ne doit PAS etre NOT_EXISTING. Si le mode NOT_EXISTING est specifié, la
	 * methode arrete le programme.
	 * @deprecated end: ce parametre est inutil des lors qu'on utilise un particle drawer pour afficher les moteurs de
	 * particules. Il faut desormais preferer l'utilisation de ParticleDrawer pour afficher les moteurs de particules
	 * plutot que d'utiliser ce parametre. Il rest dans l'interface pour compatibilitée.
	 */
	void addEngine(ParticleEngine * e,ParticleEngineMemoryManagment mode = MANAGED,bool end = true);
	
	/**
	 * enleve un moteur de particule de manager.
	 *
	 * Si le moteur de particule à été ajouté en mode managed, il est alors detruit.
	 * Si le moteur de particule n'existe pas, rien n'est fait.
	 *
	 * ATTENTION: appeler cette methode redemmare l'iteration en cours, il ne faut pas l'appeler au cours
	 * d'une iteration.
	 * @param e: le moteur de particules a enlever.
	 */
	void removeEngine(ParticleEngine * e);

	/**
	 * Enleve tout les moteurs de particules du manager.
	 *
	 * Tout les moteur mis en mode MANAGED sont detruit, les autre ne le sont pas.
	 *
	 * Attention a ne pas confondre destruction de la memoire (operateur delete) et suppression du moteur
	 * de particule. Les instances en mode MANAGED seront detruites de la memoire alors que celles en mode
	 * UNMANAGED ou REMOVABLE ne seront pas detruites de la memoire mais seront neanmoins supprimée du manager (c'est l'utilisateur
	 * qui a la charge de les detruires.)
	 */
	void removeAll();

	/**
	 * met a jour l'ensemble des moteurs de particules en fonction du temps.
	 *
	 * l'utilisateur peut utiliser cette methode si il veut mettre a jour l'ensemble des moteur de particules en même
	 * temps. Par contre si il souhaite une plus grande flexibilitée, il peut faire
	 <PRE>
	// recuperation du temps ecoulé dans elapsedTime
	using namespace ParticleSystem;
	ParticleManager &pm = ParticleManager::getGlobal();
	for (pm.startIteration(); !pm.iterationFinished(); pm.next())
	{
		pm.current().update(elapsedTime);
	}
	 </PRE>
	 * @param elapsedTime: le temps ecoulé depuis le dernier appel a cette methode.
	 */
	void update(float elapsedTime);

	/**
	 * demarre l'iteration sur l'ensemble des moteurs de particules du manager
	 *
	 * une iteration sur les moteurs de particule type est de la forme
	 *<PRE>
	 using namespace ParticleSystem;
	 ParticleManager &pm = ParticleManager::getGloabal();
	 for (pm.startIteration(); !pm.iterationFinished(); pm.next())
	 {
		ParticleEngine &engine = pm.current();
		// do somethings with engine
	 }
	 </PRE>
	 */
	void startIteration();

	/**
	 * retourne le moteur de particule courant dans l'iteration.
	 *
	 * La methode retourne une reference afin d'empecher l'utilisateur de detruire l'instance retournée.
	 *
	 * Attention a ne pas utiliser la valeure retournée par cette methode quand la methode iterationFinished()
	 * retourne true. En effet, l'iterateur en fin d'iteration n'est plus valide, l'utiliser entrainerait alors
	 * une erreure.
	 *
	 * Pour avoir un exemple d'iteration sur les moteurs de particules, se referer a la documentation de
	 * la methode startIteration()
	 * @return ParticleEngine: le moteur de particule courant
	 */
	ParticleEngine& current();

	/**
	 * passe l'iteration au moteur de particules suivant.
	 *
	 * Si l'iteration est finie, l'iterateur ne change pas. pour savoir si l'iterateur est arrivé à la
	 * fin, il faut utiliser la methode iterationFinished()
	 *
	 * Pour avoir un exemple d'iteration sur les moteurs de particules, se referer a la documentation de
	 * la methode startIteration()
	 */
	void next();

	/**
	 * permet de savoir si l'iteration est terminée.
	 *
	 * quand cette methode retourne true, c'est que l'iteration est terminée. L'iterateur a ce moment la n'est plus
	 * valide.
	 * @return bool: true si l'iteration courante est terminée, false sinon.
	 */
	bool iterationFinished();

	/**
	 * methode permettant de savoir si un moteur de particules existe dans le manager
	 *
	 * Cette methode retourne le mode de gestion memoire du moteur de particule passé en parametre. Si le moteur de
	 * particule n'existe pas dans le manager, la methode retourne NOT_EXISTING. Pour savoir si le manager contient
	 * un moteur de particules, il suffit de faire :
	 <PRE>
	int val = manager.exist(myParticleEngine);
	if (val != NOT_EXISTING)
	{
		// do something...
	}
	 </PRE>
	 * Cette methode permet de savoir si un moteur de particules va etre detruit par le manager lorsqu'il sera terminé ou
	 * encore lorsque l'utilisateur voudra le retirer du manager (avec la methode removeEngine() par exemple)
	 *
	 * @param e: pointeur sur le moteur de particules dont on veut tester l'existance dans le manager.
	 * @return Le type de gestion memoire du moteur de particules passé en parametre. Si le moteur de particules
	 * n'existe pas dans le manager, celui ci retourne NOT_EXISTING.
	 */ 
	ParticleEngineMemoryManagment exist(ParticleEngine * e);

};

}// namespace particleSystem

#include "MemoryManager/DebugNewOff.h"



#endif
