

/**
 * Fonction anonyme de déclaration des classes gérant
 * les chargements RJS.
 * Certaines fonctions ne sont visible que par les
 * classes concernées (déclarées ici).
 * Ce mécanisme de fonction anonyme permet de reproduire
 * un système d'encapsulation digne d'un langage de
 * programmation évolué (comme le Java).
 * Classes et éléments visibles seront stockés dans le
 * namespace 'ev.rjs'.
 */
(function() {
	var WIN = this,
			EASY = WIN.ev,
			TIME,
			LOG,
			RJS;
	// Si les namespaces/classes nécessaires ne sont pas chargées : exception
	if (!EASY) {throw "Le namespace 'ev' doit exister";}
	if (!EASY.tools) {throw "Le namespace 'ev.tools' doit exister";}
	if (!EASY.time) {throw "Le namespace 'ev.time' doit exister";}

	TIME = EASY.time;
	LOG = EASY.log;

	if (!LOG) {throw "Le namespace 'ev.log' doit exister";}
	if (!TIME.Timeline) {throw "La classe 'ev.time.Timeline' doit exister";}
	if (!EASY.Hashtable) {throw "La classe 'ev.Hashtable' doit exister";}
	// Si le namespace ev.rjs est déjà déclaré, on sort
	if (EASY.rjs) {return;}
	RJS = EASY.rjs = {
		/**
		 * Constante globale qui identifie l'URL racine des
		 * fichiers rjs statiques.
		 */
		URL_ROOT: EASY.path.cdn + '/rjs',
		/**
		 * Constante globale qui identifie l'URL racine des fichiers rjs spécifiques
		 * à l'ERA (Easyvoyage Remote API).
		 */
		URL_ERA_ROOT: EASY.path.era + '/' + EASY.lang.current,
		/**
		 * Multiple resource holder for multiple Locator use.
		 */
		multiple: {}
	};

	LOG.debug('ev.rjs.js: ev.rjs.URL_ROOT=' + RJS.URL_ROOT);
	LOG.debug('ev.rjs.js: ev.rjs.URL_ERA_ROOT=' + RJS.URL_ERA_ROOT);

	/**
	 * Instance privée de {@link ev.time.Timeline}.
	 * Cette variable est disponible seulement ici
	 * pour le fonctionnement des RJS d'une page.
	 */
	var TIMELINE = new TIME.Timeline('RJS', 20);

	/**
	 * Instance privée de {@link ev.Hashtable}.
	 * Cette map agit comme un cache en stockant
	 * les ressources déjà téléchargées. (Table
	 * ne contenant que des ressources)
	 * Cette variable est disponible seulement ici
	 * pour le fonctionnement des RJS d'une page.
	 */
	var MAP = new EASY.Hashtable();

	/**
	 * Enumération des différents états existants
	 * d'un localiseur de RJS.
	 * Cette énumération est disponible seulement ici.
	 */
	var LocatorState = {
		/**
		 * Etat d'attente d'une ressource à invoquer.
		 */
		WAITING: 0,
		/**
		 * Etat d'invocation d'une ressource.
		 * (en attente d'invocation, de réponse ou du timeout)
		 */
		RUNNING: 1,
		/**
		 * Etat de remise à zéro après invocation d'une ressource.
		 */
		UNLOADING: 2,
		/**
		 * Etat de transition après remise à zéro, avant de revenir
		 * à un état d'attente.
		 */
		UNRUNNING: 3
	};

	/**
	 * FIXME Doc
	 * @param {Object} _resource la ressource téléchargée.
	 * @param {Object} _returned la valeur retournée par la ressource.
	 * @param {Object} _source le locator ayant créé l'événement.
	 */
	RJS.Event = function(_resource, _returned, _source) {
		// TODO resource event's type of instance and nullity
		this.getResource = function() {
			return _resource;
		};
		this.getReturned = function() {
			return _returned;
		};
		this.getSource = function() {
			return _source;
		};
		this.toString = function() {
			return 'ev.rjs.Event{' + _resource.toString() + ', ' + _returned + ', ' + _source.toString() + '}';
		};
	};

	/**
	 * This class holds a resource, i.e. an URL to a RJS class file and a method to
	 * invoke in the RJS class that will be loaded.
	 * @param {!string} _url The URL to the file.
	 * @param {boolean=} _force [optionel] true pour forcer la récupération (au lieu de récupérer
	 *   une version cachée de la ressource), sinon false [par défaut false].
	 */
	RJS.Resource = function(_url, _force) {
		this.getUrl = function() {
			if (!_force) {
				return _url;
			}
			else {
				if (_url.indexOf('?') != -1) {
					return _url + '&nocache=' + (new Date()).getTime();
				}
				else {
					return _url + '?nocache=' + (new Date()).getTime();
				}
			}
		};

		/**
		 * Méthode qui sera exécutée après invocation
		 * d'un script distant.
		 * Elle peut retourner un objet ou une valeur
		 * si besoin.
		 */
		this.execute = function() {
			// rien à faire ici (sera surchargée par script distant);
			return undefined;
		};
		/**
		 * Equality (used in cache) is defined by URL which correspond to a
		 * particular instance.
		 */
		this.equals = function(other) {
			return _url === other.getUrl();
		};
		this.toString = function() {
			return 'ev.rjs.Resource{' + _url + '}';
		};
	};

	/**
	 * This class manages buffered invokations to RJS.<br>
	 * Moreover this class can instanciate a RJS class
	 * over a particular URL and invokes in this instance
	 * the method #execute() (that should be implemented,
	 * though no error will be thrown if it is not).<br>
	 * <br>
	 * Example:<br>
	 *  <code>
	 *  function MyRJSListener() {<br>
	 *    this.onRJSEvent=function(){<br>
	 *      // implement here<br>
	 *    };<br>
	 *  }<br>
	 *  var listener=new MyRJSListener();<br>
	 *  var rjsLocator=new ev.rjs.Locator(3000);// timeout de 3s<br>
	 *  rjsLocator.addListener(listener);<br>
	 *  var resource=new ev.rjs.Resource("http://www.xyz.nwh/aaa.rjs");<br>
	 *  rjsLocator.invoke(resource);// This will invoke #onRJSEvent on listener, when the resource's #execute() method is terminated<br>
	 *<br>
	 *  // the resource 'aaa.rjs' (located at 'http://www.xyz.nwh/')<br>
	 *  // should contain something like following, otherwise it will be<br>
	 *  // considered as missing (by reaching the chosen timeout delay) :<br>
	 *  ev.rjs.RJS=function(){<br>
	 *    this.execute=function(){<br>
	 *      ...<br>
	 *    };<br>
	 *  };<br>
	 *  </code>
	 *  <br>
	 *  This means that the remote resource must define a resource
	 *  holder 'ev.rjs.RJS' as a function that creates an
	 *  #execute() method in the current context 'this'.<br>
	 *  The #invoke() function will use the ResourceHolder with
	 *  the given resource as current context.<br>
	 *  Then the resource will possess an #execute() method
	 *  (if correctly defined, like shown above) that will
	 *  called by the locator's timeline listener.<br>
	 *  <br>
	 *  This class uses a cache based on a hashtable to
	 *  prevent from re-loads of RJS instances. This cache
	 *  is held by the MAP hashtable.
	 *  @param {Integer} _timeout : The timeout in ms for a
	 *      resource to load. If timeout is reached we
	 *      consider the resource does not exist.
	 *  @param {String} _name (optional) : the name of the
	 *      locator, which allows to get more than one
	 *      resource at the same time (each resource
	 *      should be added in in respective
	 *      'ev.rjs.multiple[<_name>]' object)
	 *  TODO Create a process for no method invokation?
	 */
	RJS.Locator = function(_timeout, _name) {
		if (_timeout === undefined) {throw 'timeout is undefined';}
		if (_timeout === null) {throw 'timeout is null';}
		if (typeof(_timeout) !== 'number') {throw 'timeout is not a number';}
		if (_timeout <= 0) {throw 'timeout is <=0';}

		/**
		 * This private property defines this instance reference
		 * that can be used in inner methods.
		 */
		var thisLocator = this,

				/**
				 * Private reference of node HEAD where 'script'
				 * nodes will be added.
				 */
				headNode = EASY.dom.tags('HEAD')[0],

				/**
				 * Private reference of node 'script'.
				 * (house for RJS)
				 */
				scriptNode = (function() {
					var s = EASY.dom.create('script');
					// Adding empty SCRIPT node to HEAD.
					s.type = 'text/javascript';
					s.charset = 'iso-8859-1';
					headNode.appendChild(s);
					return s;
				}()),

				/**
				 * Private reference of RJS invokations buffer. In this array are pushed all
				 * resources, and then this class synchronized by its Timeline pops those
				 * resources one by one, preventing rebounds.
				 */
				buffer = [],

				/**
				 * State property, synchronizes all process.
				 * Default is WAITING of course.
				 */
				state = LocatorState.WAITING,

				/**
				 * This property holds the timestamp of last RUNNING state, it is used
				 * to compute eventual timeout.
				 */
				timestamp,

				/**
				 * This property holds the last resource asked to invoke.
				 * @see ev.rjs.Resource
				 */
				resource,

				/**
				 * Internal TIMELINE listener class managing buffered resources invokations.
				 */
				timelineListener = new TIME.TimelineListener(),

				/**
				 * Tableau de listeners du client RJS actuel.
				 */
				listeners = [];

		/**
		 * @return {!Object} the object that should contains the last resource called
		 * according to the name of the current Locator.
		 * If the Locator does not have any name, the resource holder will
		 * be 'ev.rjs.RJS' (In that case, there should be only one Locator
		 * in the current page).
		 */
		function getResourceHolder() {
			if (_name) {
				//        LOG.debug("getResourceHolder(): ev.rjs.multiple."+_name+" ; "+RJS.multiple[_name]+" ; RJS:"+RJS.RJS);
				return RJS.multiple[_name];
			}
			//      LOG.debug("getResourceHolder(): ev.rjs.RJS ; "+RJS.RJS);
			return RJS.RJS;
		}

		/**
		 * Reset the object that should contains the last resource called
		 * to the value 'undefined'.
		 */
		function resetResourceHolder() {
			if (_name) {
				//        LOG.debug("resetResourceHolder(): ev.rjs.multiple."+_name+" ; "+RJS.multiple[_name]+" ; RJS:"+RJS.RJS);
				RJS.multiple[_name] = undefined;
			}
			else {
				//        LOG.debug("resetResourceHolder(): ev.rjs.RJS ; "+RJS.RJS);
				RJS.RJS = undefined;
			}
		}

		/**
		 * Méthode d'activation du Locator.<br>
		 * Cette méthode s'occupe d'enregistrer le listener de
		 * l'objet Locator dans la TIMELINE et démarre cette
		 * TIMELINE si elle n'est pas encore démarrée.
		 */
		function resume() {
			// on lance l'écoute de la TIMELINE
			TIMELINE.addTimelineListener(timelineListener);
			// démarrage de la TIMELINE si ce n'est déjà fait
			if (!TIMELINE.isRunning()) {
				TIMELINE.start();
			}
		}

		/**
		 * Méthode de désactivation du Locator.<br>
		 * Cette méthode s'occupe de supprimer le listener de
		 * l'objet Locator de la TIMELINE.
		 */
		function suspend() {
			// suppression du listener de l'objet de la TIMELINE
			TIMELINE.removeTimelineListener(timelineListener);
		}

		/**
		 * @return {!Object} the locator's name (if defined).
		 */
		this.getName = function() {
			return _name;
		};

		/**
		 * Permet d'ajouter un listener à la fin du
		 * tableau de listeners.
		 * @param {Object} _listener : le listener à ajouter.
		 */
		this.addListener = function(_listener) {
			if (!_listener) {throw 'Un listener ne doit pas être nul.';}
			if (typeof(_listener.onRJSEvent) !== 'function') {throw 'Un listener doit posséder une méthode #onRJSEvent().';}
			listeners.push(_listener);
		};

		/**
		 * Permet de supprimer le listener donné, du
		 * tableau de listeners (s'il existe).
		 * @param {Object} _listener : le listener à supprimer.
		 */
		this.removeListener = function(_listener) {
			if (!_listener) {return;}
			var cnt = listeners.length;
			for (var i = 0; i < cnt; ++i) {
				if (listeners[i] === _listener) {
					// suppression du listener 'i' dans le tableau
					listeners.splice(i, 1);
					// et on sort
					return;
				}
			}
		};

		/**
		 * This method which overrides TimelineListener's manages
		 * synchronization of invokations of resources stored in buffer. Method
		 * {@link #invoke(ev.rjs.Resource)} has pushed resources in buffer, each
		 * TIMELINE's step, for each of those resources the resource is invoked
		 * (state RUNNING), then asked to unload (state UNLOADING), then really
		 * unload (state UNRUNNING). If no more resource is present in the
		 * buffer, the TIMELINE waits for a new resource to come (state
		 * WAITING).
		 * @param {Object} e : timeline event.
		 */
		timelineListener.throwTimelineEvent = function(e) {
			switch (state) {
				case LocatorState.WAITING:

					// In WAITING state, buffer was empty, and we test if a new resource
					// was pushed in the buffer and is to be load and runned
					if (!buffer.length) {
						// If buffers contains no data, there is nothing to load.
						suspend();
						break;
					}
					// The resource property is defined with the just popped
					// resource from buffer
					resource = buffer[0];
					buffer.shift();
					// Removes node referenced by scriptNode from HEAD node.
					// Replaces it by a new 'script' node with src matching to the
					// resource's URL.
					headNode.removeChild(scriptNode);
					scriptNode = EASY.dom.create('script');
					scriptNode.type = 'text/javascript';
					scriptNode.charset = 'iso-8859-1';
					scriptNode.src = resource.getUrl();
					// May cause a non blocking JS error in IE when src does not
					// exist. this error cannot be catch.
					headNode.appendChild(scriptNode);
					// tiemstamp is renewed
					timestamp = new Date().getTime();
					// state is changed to RUNNING until the RJS is loaded
					state = LocatorState.RUNNING;
					break;
				case LocatorState.RUNNING:
					var errEvent, j;
					// A new 'script' node with its scr set to the resource URL has been
					// added to HEAD node. Navigator will take some time to load the
					// file and run it. Trying to instanciate RJS will throw an
					// exception until the file is arrived. So when all loaded no
					// exception is to be catched.

					//LOG.info('LocatorState: RUNNING ['+resource.getUrl()+']');
					// if timeout is reached we unload the 'script' node if not we try
					// to instanciate a new RJS
					if (new Date().getTime() - timestamp >= _timeout) {
						LOG.error('RJSTimeoutException after ' + (_timeout / 1000) + ' seconds on resource : ' + resource);
						errEvent = new RJS.Event(resource, new Error('RJSTimeoutException after ' + (_timeout / 1000) + ' seconds'), thisLocator);
						for (j = 0; j < listeners.length; j++) {
							//LOG.warn('FIRE After Timeout error (post-invoke) : '+j);
							// We asure here that the listener doesn't make the event fail
							try {
								listeners[j].onRJSError(errEvent);
							}
							catch (eOnErrorEvent) {
								LOG.error('RJSListener Exception (on Timeout error): ' + eOnErrorEvent);
							}
						}
						state = LocatorState.UNLOADING;
						break;
					}
					if (!getResourceHolder()) {break;}
					// If the RJS is not ready that will throw an exception
					try {
						//LOG.debug('BEFORE ResourceHolder execution...');
						if (typeof(getResourceHolder()) === 'function') {
							getResourceHolder().call(resource);
						}
						else if (getResourceHolder().execute && typeof(getResourceHolder().execute) === 'function') {
							resource.execute = getResourceHolder().execute;
						}
						//LOG.debug('AFTER  ResourceHolder execution... ['+resource.getUrl()+']');
						// If the RJS has been correctly instanciated its method
						// is invoked, and state is set to unload the 'script'
						// node
						MAP.put(resource, resource);
						state = LocatorState.UNLOADING;
						var event;
						if (typeof(resource.execute) === 'function') {
							event = new RJS.Event(resource, resource.execute(), thisLocator);
						}
						else {
							event = new RJS.Event(resource, undefined, thisLocator);
						}
						for (j = 0; j < listeners.length; j++) {
							//LOG.info('FIRE (post-invoke) ['+resource.getUrl()+'] : '+j);
							// We asure here that the listener doesn't make the event fail
							try {
								listeners[j].onRJSEvent(event);
							}
							catch (eOnEvent) {
								LOG.error('RJSListener Exception: ' + eOnEvent);
							}
						}
					}
					catch (eRunning) {
						LOG.error(eRunning);
						resetResourceHolder();
						errEvent = new RJS.Event(resource, eRunning, thisLocator);
						for (j = 0; j < listeners.length; j++) {
							//LOG.warn('FIRE After error (post-invoke) : '+j);
							// We asure here that the listener doesn't make the event fail
							try {
								// FIXME supprimer le premier des que ready!
								listeners[j].onRJSEvent(errEvent);
								listeners[j].onRJSError(errEvent);
							}
							catch (eOnErrorEvent2) {
								LOG.error('RJSListener Exception (on error): ' + eOnErrorEvent2);
							}
						}
						state = LocatorState.UNLOADING;
					}
					break;
				case LocatorState.UNLOADING:
					// Unloading replaces the precedent 'script' node by a new one which
					// throws an Error when completely loaded (this behaviour is opposed
					// to the formal 'script' node that was NOT throwing an exception).
					// When this node is changed the state is changed to UNRUNNING that
					// will try to run the RJS class (the one that throws the error)
					//LOG.info('LocatorState: UNLOADING ['+resource.getUrl()+']');
					headNode.removeChild(scriptNode);
					//          scriptNode=EASY.dom.create("script");
					//          scriptNode.type="text/javascript";
					//          scriptNode.charset="iso-8859-1";
					//          if(_name){
					//            scriptNode.text=
					//              "ev.rjs.multiple[\""+_name+"\"]=function(){" +
					//              "  throw new Error(\"NULL\");" +
					//              "};";
					//          }
					//          else{
					//            scriptNode.text=
					//              "ev.rjs.RJS=function(){" +
					//              "  throw new Error(\"NULL\");" +
					//              "};";
					//          }
					//          headNode.appendChild(scriptNode);
					//          state=LocatorState.UNRUNNING;
					// Adding empty SCRIPT node to HEAD.
					scriptNode = EASY.dom.create('script');
					scriptNode.type = 'text/javascript';
					scriptNode.charset = 'iso-8859-1';
					headNode.appendChild(scriptNode);
					resetResourceHolder();
					state = LocatorState.WAITING;
					break;
				case LocatorState.UNRUNNING:
					//FIXME revoir commentaires si on supprime vraiment cela !!!
					LOG.fatal('LocatorState.UNRUNNING : should NEVER be here!');
					// Tries to catch the error made by the RJS loaded in the new
					// 'script' node. When this script is loaded the error is thrown
					// catched and state is changed to WAITING
					//LOG.info('LocatorState: UNRUNNING ['+resource.getUrl()+']');
					try {
						if (getResourceHolder()) {
							//LOG.debug('BEFORE unrunning ResourceHolder execution...');
							getResourceHolder()();
							//LOG.debug('AFTER  unrunning ResourceHolder execution...');
						}
					}
					catch (eUnload) {
						//LOG.debug('Exception (normal) on unrunning ResourceHolder execution... ['+(resource? resource.getUrl():"null")+']');
						resetResourceHolder();
						state = LocatorState.WAITING;
					}
					break;
				default:
					break;
			}
		};

		/**
		 * This method invokes the given resource.
		 * @param {!Object} resource An instance of RJSResource to invoke.
		 * @param {boolean=} force [optionel] parametre permettant de préciser
		 *   s'il on souhaite absoluement faire la requete (true) ou
		 *   s'il on souhaite utiliser le cache (false) lorsque la
		 *   requête a déjà été exécutée.
		 */
		this.invoke = function(resource, force) {
			var lastResource = MAP.get(resource);
			if (lastResource !== null && !force) {
				// If resource is cached we use it
				var event;
				if (typeof(lastResource.execute) === 'function') {
					event = new RJS.Event(resource, lastResource.execute(), thisLocator);
				}
				else {
					event = new RJS.Event(resource, undefined, thisLocator);
				}
				for (var i = 0; i < listeners.length; i++) {

					listeners[i].onRJSEvent(event);
				}
			}
			else {
				// else push the resource in buffer, TIMELINE will do the job next time
				buffer.push(resource);
				// on lance l'écoute de la TIMELINE
				resume();
			}
		};

		/**
		 * Méthode donnant un aperçu bref du locator.
		 */
		this.toString = function() {
			return 'ev.rjs.Locator{' + (_name ? _name : 'no name') + ', timeout: ' + _timeout + '}';
		};
	};

	/**
	 *
	 * @param callback fonction à appeler sur succès (ou objet contenant une méthode onSuccess et une méthode onError).
	 */
	RJS.simpleInvoke = function(loc, url, callback, force) {
		if (!loc) {throw 'argument 1 : a Locator must be specified';}
		if (!(loc.constructor && loc.constructor === RJS.Locator)) {throw 'argument 1 : the Locator must be of type ev.rjs.Locator';}
		if (!url) {throw 'argument 2 : an URL must be specified';}

		// Gestion des fonctions de callback
		if (!callback) { // pas de callback
			callback = {};
		}
		else if (typeof(callback) === 'function') {
			// pour support de l'ancien callback (onAfter)
			callback = {
				onSuccess: callback
			};
		}

		var res = new RJS.Resource(url, force), r;
		var t0, t1;
		loc.addListener({
			/**
			 * @param {ev.rjs.Event} e
			 */
			onRJSEvent: function(e) {
				if (!e) {throw 'argument 1 : an Event must be specified';}
				if (!(e.constructor && e.constructor === RJS.Event)) {throw 'argument 1 : the Event must be of type ev.rjs.Event';}
				// s'il ne s'agit pas de la ressource attendue, on sort
				if (e.getResource() !== res) {return;}
				t1 = new Date().getTime();
				// ici, on peut dire que la ressource attendue est arrivée, on n'écoute plus les événements du Locator
				loc.removeListener(this);
				r = e.getReturned();
				if (t1 - t0 > 500) {
					LOG.debug('rjs.simpleInvoke()> Ressource \'' + res + '\' reçue [' + (t1 - t0) + ' ms]');
				}
				// S'il y a un exception dans la ressource, on l'affiche
				if (r && r.exception) {
					LOG.error('rjs.simpleInvoke()> Ressource \'' + res + '\' - Exception : ' + r.exception);
				}
				// une fois l'événement contrôlé, on le transfère à la fonction de callback donnée, le cas échéant
				if (typeof(callback.onSuccess) === 'function') {
					callback.onSuccess(r);
				}
			},
			/**
			 * @param {ev.rjs.Event} e
			 */
			onRJSError: function(e) {
				// s'il ne s'agit pas de la ressource attendue, on sort
				if (e.getResource() !== res) {return;}
				if (typeof(callback.onError) === 'function') {
					callback.onError(e);
				}
			}
		});
		t0 = new Date().getTime();
		loc.invoke(res, force);
	};

	LOG.fatal('rjs#<init>: ev.rjs est DEPRECATED => utiliser ev.core.RjsHttpRequest (+ ev.core.requestManager) !');
}()); // exécution de la fonction anonyme, ici

