/**
 * Declarations de variables globales externes.
 */
/*global window */
/**
 * Declarations de variables globales internes.
 */
/*global
	EV, Classe, Timeline, TimelineEvent, TimelineListener
*/

/** On s'assure que le namespace EV existe */
if(!window.EV){ window.EV={}; }

/** On ne fait rien si la classe Timeline a deja ete initialisee */
if(!EV.Timeline){
	/**
	 * This is the EV.Timeline constructor.
	 * 
	 * Timeline works as a Java thread, and manages to periodicaly invoke a 
	 * TimelineLister method. The timeline instance offers the methods to start and 
	 * stop the process. Implementation of behaviour is given to the implementation 
	 * of TimelineLister.
	 *
	 * Usage:
	 *	var timeline=new Timeline(100);
	 *	function MyTimelineListener() {
	 *		this.throwTimelineEvent=function(timelineEvent){
	 *			// Implement here behaviour of listener
	 *		}
	 *	}
	 *	MyTimelineListener.prototype=new TimelineListener();
	 *	var myTimelineListener=new MyTimelineListener();
	 *	timeline.addTimelineListener(myTimelineListener);
	 *		...
	 *	// starts timeline
	 *	timeline.start();
	 *		...
	 *	// stops timeline
	 *	timeline.stop();
	 *
	 *
	 *
	 * @param _delay Delay in ms between each invokation of 
	 * 	TimelineListener#throwTimelineEvent(TimelineEvent).
	 * @throws If _delay is undefined.
	 * @throws If _delay is null.
	 * @throws If _delay is not a number.
	 * @throws If _delay is negative or null.
	 * @throws If _delay is < 20.
	 */
	EV.Timeline=function(_delay){
		if(!window.Classe){throw new Error("Namespace 'Classe' is not defined");}
		if(typeof(_delay)!=='number'){throw new Error("delay is not a number");}
		if(_delay<=0){throw new Error("delay is negative");}
		if(_delay<20){throw new Error("delay is too small (minimal value is 20)");}

		/**
		 * This private field refers to the TimelineListener Array used by this instance 
		 * to manage their behaviour. Each delay (ms), #throwTimelineEvent(TimelineEvent) 
		 * method of each instance of TimelineListener is invoked, providing this timeline
		 * is running.
		 */
		var timelineListenerArray=[];
		/**
		 * This method adds a TimelineListener instance as a listener of this 
		 * Timeline.
		 *
		 * @param _timelineListener a TimelineListener that will listen to this 
		 * Timeline.
		 * @throws If _timelineListener is undefined.
		 * @throws If _timelineListener is null.
		 * @throws If _timelineListener is not an instance of TimelineListener.
		 */
		this.addTimelineListener=function(_timelineListener) {
			if(!_timelineListener){throw new Error("timelineListener is not valid");}
			if(!(Classe.isInstanceOf(_timelineListener, TimelineListener))){throw new Error("timelineListener is not a TimelineListener instance");}
			timelineListenerArray.push(_timelineListener);
		};
		/**
		 * This method removes a TimelineListener instance from this 
		 * Timeline if it exists. If it doesn't the method does nothing.
		 *
		 * @param _timelineListener a TimelineListener that should be
		 *				listening to this Timeline.
		 * @throws If _timelineListener is undefined.
		 * @throws If _timelineListener is null.
		 * @throws If _timelineListener is not an instance of TimelineListener.
		 */
		this.removeTimelineListener=function(_timelineListener) {
			if(!_timelineListener){throw new Error("timelineListener is not valid");}
			var l=timelineListenerArray.length;
			for(var i=0; i<l; ++i){
				if(timelineListenerArray[i]===_timelineListener){
					timelineListenerArray.splice(i, 1);
					break;
				}
			}
		};
		/**
		 * This private flag defines if this Timeline is running (i.e. started).
		 */
		var running=!1;
		/**
		 * Indicates if the timeline is allready running.
		 */
		this.isRunning=function(){
			return running;
		};

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

		/**
		 * this private field holds the count of 
		 * timelineListener#throwTimelineEvent(TimelineEvent) invokations since last 
		 * #start(), this count is given in TimelineEvent instances.
		 */
		var executionCount=0;
	
		/**
		 * This private method executes itself recursively while this instance is running. 
		 * Mainly it invokes the #throwTimelineEvent(TimelineEvent) method of each
		 * TimelineListener of this instance.
		 * 
		 * @throws If an error occured while invoking #throwTimelineEvent(TimelineEvent)
		 * method on one of the TimelineListener Array
		 */
		function execute(){
			var eventType=EV.TimelineEventType.RUNNING;
			if(!running){
				// Si l'état n'est plus 'running', l'evenement est STOP
				eventType=EV.TimelineEventType.STOP;
			}
			else if(!executionCount){
				// Si le nombre d'execution est nul, l'evenement est START
				eventType=EV.TimelineEventType.START;
			}
			var eventTmp=new TimelineEvent(executionCount, eventType, thisTimeline);
			var l=timelineListenerArray.length;
			function fireEvent(_timelineListener, _event){
				if(!_timelineListener){ return; }
				try{
					_timelineListener.throwTimelineEvent(_event);
				}
				catch(e){
					window.setTimeout(function(){ throw e; }, 0);
				}
			}
			for(var i=0; i<l; ++i){
				fireEvent(timelineListenerArray[i], eventTmp);
			}
			if(eventType!==EV.TimelineEventType.STOP){
				window.setTimeout(function(){ execute(); }, _delay);
				++executionCount;
			}
		}

		/**
		 * Starts this Timeline, i.e. this Timeline becomes running.
		 * @throws If thi Timeline is allready running.
		 */
		this.start=function() {
			if(running){throw new Error("timeline allready running");}
			running=!0;
			executionCount=0;
			execute();
		};
		/**
		 * Stops this Timeline. It becomes not running. If this Timeline is allready 
		 * not running, the method does nothing.
		 */
		this.stop=function() {
			// WARNING: This will stop this Timeline only during next delay step.
			running=!1;
		};

		/**
		 * overrides #toString()
		 */
		this.toString=function(){
			return "Timeline{delay="+_delay+", "+(running? "running": "stopped")+"}";
		};
	};
		
	/**
	 * Singleton declared to hold timeline's events type enumeration, i.e.
	 * START, RUNNING, and STOP.
	 */
	EV.TimelineEventType={
		START: 0,
		RUNNING: 1,
		STOP: 2,
		toString: function(value){
			switch(value){
				case this.START: return "START";
				case this.RUNNING: return "RUNNING";
				case this.STOP: return "STOP";
				default: return "n.c";
			}
		}
	};

	/**
	 * Abstract class designed to control a Timeline, this controls is a listener 
	 * whom #throwTimelineEvent(TimelineEvent) abstract method is invoked periodically by 
	 * the Timeline instance it controls. All TimelineListener subclass must 
	 * implement this method.
	 */
	window.TimelineListener=function(){
		/**
		 * The method periodically invoked by the controled Timeline, it defines the 
		 * Timeline's behaviour.
		 * @param timlineEvent Contains a TimelineEvent instance produced by the 
		 * 	controled Timeline.
		 * @throws If the method has not been implemented.
		 */
		this.throwTimelineEvent=function(timelineEvent){
			throw new Error("#throwTimelineEvent() must be overridden");
		};
	};
	
	/**
	 * Defines the event produced by a Timeline instance when it invokes 
	 * TimelineListener#throwTimelineEvent(TimelineEvent).
	 * @param _executionCount Step number from Timeline's instance last #start() 
	 * 	invokation.
	 * @param _type Instance of TimelineEventType holding this TimelineEvent's type.
	 * @param _source The Timeline instance who produced this event.
	 */
	window.TimelineEvent=function(_executionCount,_type,_source){
		if(_executionCount===undefined||_executionCount===null){throw new Error("executionCount is not valid");}
		if(typeof(_type)!=='number'){throw new Error("type is not a number");}
		if(_type!==EV.TimelineEventType.START&&_type!==EV.TimelineEventType.RUNNING&&_type!==EV.TimelineEventType.STOP){throw new Error("type is not START, RUNNING, or STOP");}
		if(!_source){throw new Error("source is not valid");}
		if(!(_source instanceof Timeline||_source instanceof EV.Timeline)){throw new Error("source is not instance of Timeline");}
		var date=new Date();
		this.getType=function() {
			return _type;
		};
		this.toString=function() {
			return "TimelineEvent{executionCount="+_executionCount+", type="+EV.TimelineEventType.toString(_type)+", date="+date+", source="+_source+"}";
		};
		this.getDate=function() {
			return date;
		};
		this.getCount=function() {
			return _executionCount;
		};
		this.getSource=function() {
			return _source;
		};
	};

	/**
	 * FIXME : ceci assure seulement la compatibilite
	 */
	window.Timeline=EV.Timeline;

	/**
	 * FIXME : ceci assure seulement la compatibilite
	 */
	window.TimelineEventType=function(){
		return EV.TimelineEventType;
	};

	EV.tools.onFileLoad('timeline.js');
}
