// Declarations de variables globales externes
/*global window */
// Declarations de variables globales internes à Easy (tools.js, timeline.js)
/*global SAFARI, EV, genericNavigator, getStyleValue, Timeline, TimelineEventType, TimelineListener */

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

/** On ne fait rien si le Slider a déjà été initialisé */
if(!EV.effects.Slider){
	EV.effects.Slider=(function(){
		// Méthode anonyme définissant un espace privé pour éviter de
		// créer des variables globales inutiles en dehors de ce fichier.

		/**
		 * This private constant defines the delay of the timeline internally used 
		 * by the slider. CAUTION: modifying this constant can cause a strong 
		 * resource consumption.
		 */
		var INTERVAL=60;

		/**
		 * private constant which defines the different sliding actions.
		 */
		var Direction={
			/** no sliding action. */
			NONE: 0,
			/** opening sliding action. */
			OPEN: 1,
			/** closing sliding action. */
			CLOSE: 2
		};

		/**
		 * This is the Slider's constructor.
		 * 
		 * This class manages a slider, i.e. a block that can change fluently its 
		 * dimensions vertically and/or horizontally. The size of the bloc evolve from
		 * min-height to max-height and from min-width to max-width applied to the 
		 * block. CAUTION: Those values MUST be specified with px unity, ortherwise they 
		 * are not read by slider. 
		 * If min-height or max-height (resp. min-width or max-width) is missing, the 
		 * block is not moving vertically (resp. horizontally).
		 * To open/close the block we must use {@link #open()}, {@link #close()}, or 
		 * {@link #slide()}.
		 * A SliderListener can be added to the Slider, in order to add behaviours drove
		 * by SliderEvent @see {@link EV.effects.SliderEvent} for further informations.
		 * 
		 * Usage:
		 * 	<!-- The following block has min-width and max-width and then will slide
		 * 		horizontally, not vertically. Those styles can be placed in a style
		 * 		tag, or in a CSS file.
		 * 	 -->
		 * 	<div id="x" style="min-width: 100px; max-width: 400px;">...</div>
		 * 	<script type="text/javascript">
		 * 		// Defines the block's DOM element.
		 * 		var block=document.getElementById("x");
		 * 		// Defines the slider affected to the block, it will open/close in 3s.
		 * 		var slider=new EV.effects.Slider(block,3000);
		 * 		// Defines additional behavior of slider in an overwriting 
		 * 		// SliderListener class.
		 *		function MySliderListener() {
		 *			this.throwSliderEvent=function(sliderEvent) {
		 *				// Implement here behaviour of listener
		 *			}
		 *		}
		 *		slider.addSliderListener(new MySliderListener());
		 *	</script>
		 * 	<!-- A click on the following block toggles between open and close -->
		 * 	<span onclick="slider.slide()">slide</span>
		 * 
		 * @param _paneNode A DOM element designing the block to slide.
		 * @param _delay The delay interval (in ms) for the block to open or close.
		 * @param _opened A boolean which defines if the block is initially opened (if
		 * true). This parameter is optional, default is false (block closed).
		 * @throws If _paneNode is undefined.
		 * @throws If _paneNode is null.
		 * @throws If _delay is undefined.
		 * @throws If _delay is null.
		 * @throws If _delay is not a number.
		 * @throws If _delay is negative.
		 */
		return function(_paneNode, _delay, _opened){
			if(!_paneNode){throw new Error("paneNode is not valid");}
			if(typeof(_delay)!=='number'){throw new Error("delay is not a number");}
			if(_delay<0){throw new Error("delay is negative");}
			if(_delay<200){throw new Error("delay is too small (minimal value is 200ms)");}

			/**
			 * This property defines if the sliding block is initially opened.
			 * Its default value is 'false'.
			 * '!!_opened' means 'NOT NOT OPENED'. This should transform the parameter,
			 * whatever its type is, to a boolean value :
			 *   - null, undefined, 0, '', false => false
			 *   - object, number != 0, true => true
			 */
			_opened=!!_opened;
		
			/**
			 * Internal reference (using closure) to be accessed by the sliderTimelineListener.
			 */
			var thisSlider=this;

			/**
			 * This private property holds the actual or precedent slide type (defined
			 * by on of the three precedent constants).
			 */
			var direction=Direction.NONE;

			/**
			 * The timeline used by this slider to manage the effect 
			 * FIXME [ygally] rendre unique et globale (à tout slider d'une même page)
			 * @see {@link Timeline}. 
			 */
			var timeline=new Timeline(INTERVAL);

			/**
			 * This property defines the {@link SliderListener} associated with this 
			 * Slider, default is null which indicates there is no SliderListener 
			 * defined for this instance.
			 */
			var sliderListener=null;

			/**
			 * Gets the pane node, i.e; the sliding block.
			 */
			this.getPaneNode=function(){
				return _paneNode;
			};
		
			/**
			 * This property holds the minimum height of the sliding block (of the 
			 * closed block). It is defined by min-height style if defined, null 
			 * otherwise. If null, the block does not open vertically. This size must be 
			 * defined in px unit. 
			 */
			var minHeight=getStyleValue(_paneNode,"min-height");// equals "none" if not defined 
			if(minHeight!="none"&&minHeight!="-1px"&&minHeight.match(/[0-9]+px/)){
				minHeight=parseInt(minHeight.replace(/px/,""),10);
			}
			else{
				minHeight=null;
			}
		
			/**
			 * This property holds the maximum height of the sliding block (of the 
			 * opened block). It is defined by max-height style if defined, null 
			 * otherwise. If null, the block does not open vertically. This size must be 
			 * defined in px unit. 
			 */
			var maxHeight=getStyleValue(_paneNode,"max-height");
			if(maxHeight!="none"&&maxHeight!="-1px"&&maxHeight.match(/[0-9]+px/)){
				maxHeight=parseInt(maxHeight.replace(/px/,""),10);
			}
			else{
				maxHeight=null;
			}
		
			/**
			 * This property holds the minimum width of the sliding block (of the 
			 * closed block). It is defined by min-width style if defined, null 
			 * otherwise. If null, the block does not open horizontally. This size must 
			 * be defined in px unit. 
			 */
			var minWidth=getStyleValue(_paneNode,"min-width");
			if(minWidth!="none"&&minWidth!="-1px"&&minWidth.match(/[0-9]+px/)){
				minWidth=parseInt(minWidth.replace(/px/,""),10);
			}
			else{
				minWidth=null;
			}
		
			/**
			 * This property holds the maximum width of the sliding block (of the 
			 * closed block). It is defined by max-width style if defined, null 
			 * otherwise. If null, the block does not open horizontally. This size must 
			 * be defined in px unit. 
			 */
			var maxWidth=getStyleValue(_paneNode,"max-width");
			if(genericNavigator.navigator.id==SAFARI){
				// Bug sur SAFARI : max-width retourne la valeur de max-height => il faut procéder autrement
				if(minHeight===null||maxHeight===null){
					// si 1 des paramètres min-height et max-height n'est pas défini,
					// on utilise la largeur de l'élément (width) comme max-width et on
					// on construit donc un Slider qui fait varier la largeur
					maxWidth=getStyleValue(_paneNode,"width");
				}
				else{
					maxWidth="none";
				}
			}
	
			if(maxWidth!="none"&&maxWidth!="-1px"&&maxWidth.match(/[0-9]+px/)){
				maxWidth=parseInt(maxWidth.replace(/px/,""),10);
			}
			else{
				maxWidth=null;
			}
		
			/**
			 * This private property holds the effective height of the sliding block as 
			 * it evolves during opening and closing operations. If the block does not 
			 * slide vertically, this property is undefined.  
			 */	
			var height;
		
			/**
			 * This property defines the vertical speed of sliding box, defined in term 
			 * of increment applied to height on every slider's timeline delay.
			 */
			var incrementHeight;
			function initHeightParameters(){
				if(minHeight!==null&&maxHeight!==null){
					// If minHeight AND maxHeight are defined the block slides vertically
					// and height and incrementHeight can be defined. Initial value of 
					// height is defined by opened property.
					if(_opened){
						height=maxHeight;
					}
					else{
						height=minHeight;
					}
					_paneNode.style.height=height+"px";
					incrementHeight=(maxHeight-minHeight)/_delay*INTERVAL;
				}
				else{
					_paneNode.style.height=getStyleValue(_paneNode,"height");
				}
			}
			initHeightParameters();
		
			/**
			 * This private property holds the effective width of the sliding block as 
			 * it evolves during opening and closing operations. If the block does not 
			 * slide horizontally, this property is undefined.  
			 */	
			var width;
		
			/**
			 * This property defines the horizontal speed of sliding box, defined in 
			 * term of increment applied to width on every slider's timeline delay.
			 */
			var incrementWidth;
			function initWidthParameters() {
				if(minWidth!==null&&maxWidth!==null){
					// See over, same considerations.
					if(_opened){
						width=maxWidth;
					}
					else{
						width=minWidth;
					}
					_paneNode.style.width=width+"px";
					incrementWidth=(maxWidth-minWidth)/_delay*INTERVAL;
				}
				else{
					_paneNode.style.width=getStyleValue(_paneNode,"width");
				}
			}
			initWidthParameters();

			/**
			 * Sets minHeight (force the value defined by style min-height).
			 * @param _minHeight Number of pixels.
			 */
			this.setMinHeight=function(_minHeight) {
				if(typeof(_minHeight)!=='number'){throw new Error("minHeight is not a number");}
				minHeight=_minHeight;
				initHeightParameters();
			};
		
			/**
			 * Sets maxHeight (force the value defined by style max-height).
			 * @param _maxHeight Number of pixels.
			 */
			this.setMaxHeight=function(_maxHeight) {
				if(typeof(_maxHeight)!=='number'){throw new Error("maxHeight is not a number");}
				maxHeight=_maxHeight;
				initHeightParameters();
			};
		
			/**
			 * Sets minWidth (force the value defined by style min-width).
			 * @param _minWidth Number of pixels.
			 */
			this.setMinWidth=function(_minWidth) {
				if(typeof(_minWidth)!=='number'){throw new Error("minWidth is not a number");}
				minWidth=_minWidth;
				initWidthParameters();
			};
		
			/**
			 * Sets maxWidth (force the value defined by style max-width).
			 * @param _maxWidth Number of pixels.
			 */
			this.setMaxWidth=function(_maxWidth) {
				if(typeof(_maxWidth)!=='number'){throw new Error("maxWidth is not a number");}
				maxWidth=_maxWidth;
				initWidthParameters();
			};
		
			/**
			 * Internal instance of {@link TimelineListener}, containing the 
			 * particular behavior of this slider.
			 * 
			 * When a slider is opening or closing, its timeline starts (it stops when
			 * this operation ends), then during sliding
			 * {@link TimelineListener.throwTimelineEvent} is called, this method then
			 * manages all aspects of sliding.
			 */
			var sliderTimelineListener=new TimelineListener();
			
			/**
			 * Overrides the default {@link TimelineListener.throwTimelineEvent} method.
			 * This method defines the particular behavior of this slider, (i.e. managing
			 * the sliding period).
			 * 
			 * @param {TimelineEvent} _e : the TimelineEvent object, generated by the timeline
			 */
			sliderTimelineListener.throwTimelineEvent=function(_e){
				// Sliding only append when the timeline is running (starting is 
				// useless).
				if(_e.getType()==TimelineEventType().RUNNING){
					if(direction===Direction.OPEN){
						if(minHeight!==null&&maxHeight!==null){
							if(height<maxHeight){
								height=height+incrementHeight;
								if (height>maxHeight){
									_paneNode.style.height=maxHeight+"px";
								}
								else{
									_paneNode.style.height=Math.ceil(height)+"px";
								}
							}
						}
						if(minWidth!==null&&maxWidth!==null){
							if(width<maxWidth){
								width=width+incrementWidth;
								if(width>maxWidth){
									_paneNode.style.width=maxWidth+"px";
								}
								else{
									_paneNode.style.width=Math.ceil(width)+"px";
								}
							}
						}
						if((minHeight===null||maxHeight===null||height>=maxHeight)&&(minWidth===null||maxWidth===null||width>=maxWidth)) {
							timeline.stop();
							if(minHeight!==null&&maxHeight!==null){
								height=maxHeight;
								_paneNode.style.height=Math.ceil(height)+"px";
							}
							if(minWidth!==null&&maxWidth!==null){
								width=maxWidth;
								_paneNode.style.width=Math.ceil(width)+"px";
							}
							if(sliderListener!==null){
								sliderListener.throwSliderEvent(new EV.effects.SliderEvent(EV.effects.SliderEventType.OPEN_STOP,height,width,thisSlider));
							}
						}
						else{
							if(sliderListener!==null){
								sliderListener.throwSliderEvent(new EV.effects.SliderEvent(EV.effects.SliderEventType.OPEN_RUNNING,height,width,thisSlider));
							}
						}
					}
					else if(direction===Direction.CLOSE){
						if(minHeight!==null&&maxHeight!==null){
							if(height>minHeight){
								height=height-incrementHeight;
								if(height<0){
									_paneNode.style.height="0px";
								}
								else{
									_paneNode.style.height=Math.ceil(height)+"px";
								}
							}
						}
						if(minWidth!==null&&maxWidth!==null){
							if(width>minWidth){
								width=width-incrementWidth;
								if(width<0){
									_paneNode.style.width="0px";
								}
								else{
									_paneNode.style.width=Math.ceil(width)+"px";
								}
							}
						}
						if((minHeight===null||maxHeight===null||height<=minHeight)&&(minWidth===null||maxWidth===null||width<=minWidth)) {
							timeline.stop();
							if(minHeight!==null&&maxHeight!==null){
								height=minHeight;
								_paneNode.style.height=Math.ceil(height)+"px";
							}
							if(minWidth!==null&&maxWidth!==null){
								width=minWidth;
								_paneNode.style.width=Math.ceil(width)+"px";
							}
							if(sliderListener!==null){
								sliderListener.throwSliderEvent(new EV.effects.SliderEvent(EV.effects.SliderEventType.CLOSE_STOP,height,width,thisSlider));
							}
						}
						else{
							if(sliderListener!==null){
								sliderListener.throwSliderEvent(new EV.effects.SliderEvent(EV.effects.SliderEventType.CLOSE_RUNNING,height,width,thisSlider));
							}
						}
					}
				}
			};
		
			/**
			 * Adds the instance of {@link SliderTimelineListener} to listen the timeline's events
			 * and operate the Slider's movements.
			 */
			timeline.addTimelineListener(sliderTimelineListener);

			/**
			 * Method that adds a SliderListener to this Slider.
			 * @param _sliderListener The sliderListener to give to this instance.
			 * @throws If _sliderListener is undefined.
			 * @throws If _sliderListener is null.
			 * @throws If _sliderListener is not an instance of SliderListener.
			 * FIXME [ygally] ajouter possibilité d'utiliser plusieurs listeners
			 */
			this.addSliderListener=function(_sliderListener) {
				if(!_sliderListener){throw new Error("sliderListener is not valid");}
				if(typeof(_sliderListener.throwSliderEvent)!=='function'){throw new Error("sliderListener does not implement #throwSliderEvent(SliderEvent) method");}
				sliderListener=_sliderListener;
				sliderListener.throwSliderEvent(new EV.effects.SliderEvent(EV.effects.SliderEventType.INIT_LISTENER,height,width,thisSlider));
			};

			/**
			 * This method opens the sliding block (if not already opened).
			 */
			this.open=function() {
				if(direction!==Direction.OPEN&&sliderListener!==null){
					sliderListener.throwSliderEvent(new EV.effects.SliderEvent(EV.effects.SliderEventType.OPEN_START,height,width,thisSlider));
				}
				direction=Direction.OPEN;
				//FIXME [ygally] demarrage dans SliderManager
				if(!timeline.isRunning()){
					timeline.start();
				}
			};
	
			/**
			 * This method closes the sliding block (if not already closed).
			 */
			this.close=function() {
				if(direction!==Direction.CLOSE&&sliderListener!==null){
					sliderListener.throwSliderEvent(new EV.effects.SliderEvent(EV.effects.SliderEventType.CLOSE_START,height,width,thisSlider));
				}
				direction=Direction.CLOSE;
				//FIXME [ygally] demarrage dans SliderManager
				if(!timeline.isRunning()){
					timeline.start();
				}
			};
	
			/**
			 * Slides the block, i.e:
			 * if the block is closed or is closing, opens it,
			 * if the block is opened or is opening, closes it.
			 */
			this.slide=function() {
				if(direction===Direction.NONE){
					if((minHeight!==null&&maxHeight!==null&&height<=minHeight)||(minWidth!==null&&maxWidth!==null&&width<=minWidth)){
						thisSlider.open();
					}
					else if((minHeight!==null&&maxHeight!==null&&height>=maxHeight)||(minWidth!==null&&maxWidth!==null&&width>=maxWidth)){
						thisSlider.close();
					}
					else{// weird behaviour => open the slider
						thisSlider.open();
					}
				}
				else if(direction===Direction.CLOSE) {
					thisSlider.open();
				}
				else {
					thisSlider.close();
				}	
			};
	
			this.toString=function() {
				return "EV.effects.Slider{paneNode="+_paneNode+",delay="+_delay+"}";
			};
		};
	})();// Exécution de la fonction anonyme
	
	/**
	 * SliderEvent's constructor.
	 * 
	 * Defines the event produced by a Slider instance when it invokes 
	 * SliderListener#throwSliderEvent(). This event contains its occurring date, the 
	 * height (can be undefined if block is not sliding vertically), the width (same
	 * as height), the source slider, and the type of event. Event type is a 
	 * constant read from {@link SliderEventType}:
	 * INIT_LISTENER when the listener is added to the slider,
	 * OPEN_START when the slider is asked to open,
	 * OPEN_RUNNING when the slider is sliding open,
	 * OPEN_STOP when the slider is ending its opening process,
	 * CLOSE_START when the slider is asked to close,
	 * CLOSE_RUNNING when the slider is sliding close,
	 * CLOSE_STOP when the slider is ending its closing process.
	 * @param _type Instance of SliderEventType holding this SliderEvent's type.
	 * @param _source The Slider instance who produced this event.
	 * @throws if _type is undefined.
	 * @throws if _type is null.
	 * @throws if _type is not a number.
	 * @throws if _height is defined but not a number.
	 * @throws if _width is defined but not a number.
	 * @throws if _source is undefined.
	 * @throws if _source is null.
	 * @throws if _source is not a Slider.
	 */
	EV.effects.SliderEvent=function(_type,_height,_width,_source){
		if(typeof(_type)!=='number'){throw new Error("type is not a number");}
		if(!EV.effects.SliderEventType.isValid(_type)){throw new Error("type is not valid");}
		if(_height!==undefined&&_height!==null&&typeof(_height)!=='number'){throw new Error("height is not a number");}
		if(_width!==undefined&&_width!==null&&typeof(_width)!=='number'){throw new Error("width is not a number");}
		if(!_source){throw new Error("source is not valid");}
		if(!(_source instanceof EV.effects.Slider)){throw new Error("source is not instance of Timeline");}
		var date=new Date();
		
		/**
		 * Gets the type of the event one of the SliderEventType class constants.
		 */
		this.getType=function(){
			return _type;
		};
	
		/**
		 * Gets the date of occurrence.
		 */
		this.getDate=function(){
			return date;
		};
	
		/**
		 * Gets the height of the sliding block at the time of the event. undefined
		 * if the block does not slide vertically.
		 */
		this.getHeight=function(){
			return _height;
		};
	
		/**
		 * Gets the width of the sliding block at the time of the event. undefined
		 * if the block does not slide horizontally.
		 */
		this.getWidth=function(){
			return _width;
		};
	
		/**
		 * The slider interface which throws the event.
		 */
		this.getSource=function(){
			return _source;
		};
	
		this.toString=function(){
			return "EV.effects.SliderEvent{type="+EV.effects.SliderEventType.toString(_type)+", date="+date+", height="+_height+", width="+_width+", source="+_source+"}";
		};
	};
	
	/**
	 * SliderEventType enumeration.
	 * 
	 * This enumeration contains each slider's event type.
	 */
	EV.effects.SliderEventType={
		INIT_LISTENER: 0,
		OPEN_START: 1,
		OPEN_RUNNING: 2,
		OPEN_STOP: 3,
		CLOSE_START: 4,
		CLOSE_RUNNING: 5,
		CLOSE_STOP: 6,
		/**
		 * A method that returns whether the given event type is valid or not.
		 * @return a string
		 */
		isValid: function(value){
			switch(value){
				case this.INIT_LISTENER: return !0;
				case this.OPEN_START: return !0;
				case this.OPEN_RUNNING: return !0;
				case this.OPEN_STOP: return !0;
				case this.CLOSE_START: return !0;
				case this.CLOSE_RUNNING: return !0;
				case this.CLOSE_STOP: return !0;
				default: return !1;
			}
		},
		/**
		 * A method that returns the name of the given event type.
		 * @return a string
		 */
		toString: function(value){
			switch(value){
				case this.INIT_LISTENER: return "INIT_LISTENER";
				case this.OPEN_START: return "OPEN_START";
				case this.OPEN_RUNNING: return "OPEN_RUNNING";
				case this.OPEN_STOP: return "OPEN_STOP";
				case this.CLOSE_START: return "CLOSE_START";
				case this.CLOSE_RUNNING: return "CLOSE_RUNNING";
				case this.CLOSE_STOP: return "CLOSE_STOP";
				default: return "n.c";
			}
		}
	};
	
	/**
	 * FIXME : ceci assure seulement la compatibilité
	 */
	window.SliderESV=EV.effects.Slider;
	
	/**
	 * FIXME : ceci assure seulement la compatibilité
	 */
	window.SliderListener=function(){
		this.throwTimelineEvent=function(){
			throw new Error("#throwTimelineEvent() has not been implemented");
		};
		this.throwSliderEvent=function(e){
			this.throwTimelineEvent(e);
		};
	};
	
	/**
	 * FIXME : ceci assure seulement la compatibilité
	 */
	window.SliderEvent=EV.effects.SliderEvent;
	
	/**
	 * FIXME : ceci assure seulement la compatibilité
	 */
	window.SliderEventType=function(){
		return EV.effects.SliderEventType;
	};

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