/*
 * jCoverflip - Present your featured content elegantly.
 * Version: 1.0.2
 * Copyright 2010 New Signature
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the 
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program.  
 * If not, see <http://www.gnu.org/licenses/>.
 *
 * You can contact New Signature by electronic mail at labs@newsignature.com 
 * or- by U.S. Postal Service at 1100 H St. NW, Suite 940, Washington, DC 20005.
 */
( function( $ ){
//
// Helpers
//
var undefined; // safeguards against anyone who wants to change the value of undefined in the global space

var nofn = function(){};

var proxy = function( context, fn ){
		return function( ){
			if( $.isFunction( fn ) ){
				return fn.apply( context, arguments );
			} else {
				return context[ fn ].apply( context, arguments );
			}
		};
	};




//
// Animation queue
//
animationqueue = { };

/**
 * Animation Queue
 * 
 * The AnimationQueue holds list of AnimationSets that each perform a set of animations. The sets
 * run in ordering when the AnimationQueue is animating. As well, the running time is distributed 
 * across all the sets when the AnimationQueue is animating.
 *
 * As well, the AnimationQueue can be stopped and started again at any time. AnimationSets can be 
 * removed when stopped when they are not needed anymore.
 *
 */
animationqueue.AnimationQueue = function(){
	this.sets = [];
	
	this.isRunning = false;
	this.current = 0;
	
	this.totalTime = 0;
	this.elapsedTime = 0;
	this.startTime = 1;
	this.poll = null;
};

animationqueue.AnimationQueue.prototype = {
	/**
	 * Add an AnimationSet to the end of the queue.
	 *
	 * @param animationSet
	 *   The AnimationSet object to add to the end of the queue.
	 */
	queue: function( animationSet ){
		this.sets.push( animationSet );
	},
	
	/**
	 * Start the animation.
	 *
	 * @param time
	 *   The total running time of the animation in milliseconds.
	 */
	start: function( time, callback ){
		this.totalTime = time;
		this.elapsedTime = 0;
		this.isRunning = true;
		
		callback = $.isFunction( callback )? callback : nofn;
		
		// calculate the how to divide the time between the sets
		// Each set gets 1 part of the time except for the first which can get less if "paused."
		var timeShare = 1; // total number of parts to divide the time by
		var firstTimeShare = 1; // the part for the first item
		if( this.sets.length > 0 ){
			timeShare = this.sets.length;
			firstTimeShare = this.sets[ 0 ].totalTime > 0? Math.max( 0, Math.min( 1, 1-(this.sets[ 0 ].elapsedTime / this.sets[ 0 ].totalTime) ) ): 1;
			//timeShare += firstTimeShare;
		} 
		var startTime = (new Date( )).getTime( );
		
		if( this.sets.length > 0 ){
			this.sets[ 0 ].start( firstTimeShare/timeShare*this.totalTime );
		}
		
		
		(function( self, totalTime, timeShare ){ 
			function poll( ){
				self.elapsedTime = (new Date( )).getTime( ) - startTime;
				
				if( self.sets.length && !self.sets[ 0 ].isRunning ){
					self.sets.shift( );
					if( self.sets.length ){
						self.sets[ 0 ].start( totalTime/timeShare );
					}
				}
				
				if( self.elapsedTime >= self.totalTime && self.sets.length == 0 ){
					callback( self.elapsedTime  );
					self.stop( );
				}
			}
			self.poll = setInterval( poll, 16 );
		} )( this, this.totalTime, timeShare );
	},
	
	/**
	 * Stop the animation.
	 */
	stop: function( ){
		if( this.isRunning ){
			this.isRunning = false;
			
			var i = this.sets.length;
			while( i-- ){
				this.sets[ i ].stop( );
			}
			
			clearInterval( this.poll );
		}
	},
	
	/**
	 * Remove an AnimationSet from the queue.
	 *
	 * @param animationSet
	 *   The AnimationSet to remove from the queue.
	 */
	remove: function( animationSet ){
		var i = this.sets.length;
		while( i-- ){
			if( this.sets[ i ] == animationSet ){
				var newSets = this.sets.slice( 0, i );
				this.animationSet =  newSets.push( this.sets.slice( i + 1 ) );
			}
		}
	},
	
	/**
	 * Get an array of all the AnimationSets in order. The array is not live, but the AnimationSets are.
	 *
	 * @return array of AnimationSets
	 */
	get: function( ){
		return this.sets.slice( 0 ); // .slice is to clone the array but not the objects
	}
};



/**
 *
 *
 *
 */
animationqueue.AnimationSet = function(){
	this.steps = [ ];
	this.isStepsSorted = true;
	this.currentStep = -1;
	this.animations = [ ];
	
	this.isRunning = false;
	this.totalTime = 0;
	this.elapsedTime = 0;
	
	this.poll = null;
	
	this.data = { };
};

animationqueue.AnimationSet.prototype = {
	/**
	 * Add an AnimationStep or Animation for this set.
	 *
	 * @param anim
	 *   The AnimationStep or Animation to add.
	 */
	add: function( anim ){
		if( anim instanceof animationqueue.AnimationStep ){
			this.steps.push( anim );
			this.isStepsSorted = false;
			++this.currentStep;
			
		} else if( anim instanceof animationqueue.Animation ){
			this.animations.push( anim );
		}
	},
	
	/**
	 * Start the set's animation.
	 *
	 * @param time
	 *   The total running time of the animation.
	 */
	start: function( time ){
		
		// Scale the previous elapsedTime to the new time
		this.elapsedTime = this.totalTime == 0? 0: this.elapsedTime/this.totalTime*time;
		this.totalTime = time;
		
		// prepare this to run
		if( !this.isStepsSorted ){
			// Reverse sort
			this.steps.sort( function( a, b ){
				return b.moment - a.moment;
			} );
			this.isStepsSorted = true;
		}
		
		this.isRunning = true;
		
		// Start the time up here right before any of the animation starts
		this.startTime = (new Date()).getTime() - this.elapsedTime;
		// Start up the animations
		var i = this.animations.length;
		while( i-- ){
			this.animations[ i ].start( this.totalTime );
		}
		
		// The polling function: this will run the steps at the right time and 
		// check for when the animations are finished.
		var self = this;
		var animationsIndex = this.animations.length-1;
		function poll( timeSince ){
			self.elapsedTime = (new Date()).getTime() - self.startTime;
			// Run any steps that should be run
			while( self.currentStep >= 0 && self.steps[ self.currentStep ].getTime( self.totalTime ) <= self.elapsedTime ){
				self.steps[ self.currentStep ].doIt( );
				--self.currentStep;
			}
			
			// Check if all the animations are finished
			if( self.elapsedTime >= self.totalTime && self.currentStep < 0){
				while( animationsIndex >= 0 && !self.animations[ animationsIndex ].isRunning ){
					--animationsIndex;
				}
				
				if( animationsIndex < 0 ){
					// finished
					self.reset( self.elapsedTime );
					
				}
			}
		}
		
		this.poll = setInterval( poll, 16 );
		
	},
	
	/**
	 * Stop the animation.
	 */
	stop: function( ){
		if( this.isRunning ){
			this.isRunning = false;
			if( this.poll ){
				clearInterval( this.poll );
			}
			var i = this.animations.length;
			while( i-- ){
				this.animations[ i ].stop( );
			}
		}
	},
	/**
	 * Set meta data for the set.
	 *
	 * @param key
	 *   The key for the meta data.
	 *
	 * @param data
	 *   The data.
	 */
	setData: function( key, data ){
		this.data[ key ] = data;
	},
	
	/**
	 * Get meta data for the set.
	 *
	 * @param key
	 *   The key used to save the meta data.
	 * 
	 * @return The value of the meta data
	 */
	getData: function( key ){
		return this.data[ key ];
	},
	
	/**
	 * Resets the set to beginning.
	 */
	reset: function( ){
		this.stop( );
		this.elapsedTime = 0;
		this.currentStep = this.steps.length-1;
	}
};







/**
 * A single step to occur during a set's animation.
 *
 * This is useful for handling one off things during a set's animation such 
 * as switch the z-index.
 *
 * @param $element
 *   The jQuery object for the element(s) to update their CSS
 * 
 * @param cssParams
 *   A key/value object of style properties. @see http://docs.jquery.com/CSS/css#properties
 *
 * @param moment
 *   A number from 0 to 1 for when as a percentage of the set's running time the 
 *   step should happen.
 */
animationqueue.AnimationStep = function( $element, cssParams, moment ){
	this.$element = $element;
	this.cssParams = cssParams;
	this.moment = Math.min( 1, Math.max( 0, moment ) );
};

animationqueue.AnimationStep.prototype = {
	
	/** 
	 * Get the time of execution
	 *
	 * @param totalTime
	 *   The total of time for the set this belongs to in milliseconds.
	 *
	 * @return The time in milliseconds this step needs to execute.
	 */
	getTime: function( totalTime ){
		return this.moment * totalTime;
	},
	
	/**
	 * Does the step action.
	 */
	doIt: function( ){
		this.$element.css( this.cssParams );
	}
};




/**
 * A single animation object for a jQuery object.
 *
 * @param $element
 *   The jQuery object for the element(s) to animate
 *
 * @param animateParams
 *   The object of CSS values to animate. @see http://docs.jquery.com/Effects/animate
 */
animationqueue.Animation = function( $element,  animateParams ){
	this.$element = $element;
	this.animateParams = animateParams;
	this.isRunning = false;
};

animationqueue.Animation.prototype = {
	/**
	 * Start the animation.
	 *
	 * @param time
	 *   The running time of the animation (and the step) in milliseconds.
	 */
	start: function( time ){
		this.$element.stop( );
		
		if( time === 0 ){
			this.$element.css( this.animateParams );
			self.isRunning = false;
		} else {
			this.isRunning = true;
			this.$element.animate( this.animateParams, time, proxy( this, function( ){ 
				this.isRunning = false; 
			} ) );
		}
	},
	
	/**
	 * Stop the animation.
	 */
	stop: function( ){
		this.$element.stop( );
		this.isRunning = false;
	}
};

























//
// The widget
//
//












// Static methods
$.jcoverflip = {
	/**
	 * Used for wrapping the animation for an element for returned by beforeCss, afterCss and 
	 * currentCss options.
	 *
	 * @param element
	 *   The jQuery element to run the animation on.
	 *
	 * @param animate
	 *   An object with CSS keys and values to animate to.
	 *
	 * @param steps
	 *   An object with keys from 0 to 1 (0 to 100%) for how far along in the animation (0: start,
	 *   0.5: half way through, 1: end) with the value being an object of CSS keys and values to change.
	 *   This is for discrete values that need to change such as z-index.
	 *
	 */
	animationElement: function( element, animate, steps ){ 
		return { element: element, animate: animate, steps: steps };
	},
	
	/**
	 * Find the item element and index number that the element is associated.
	 *
	 * @param element
	 *   The element that either is the item element or descendant element of the item element.
	 *
	 * @return 
	 *   null - if no item element is found
	 *   { element: <the item element>, index: <the item index> }
	 * 
	 */
	getItemFromElement: function( element ){
		element = $( element );
		var item = element.hasClass( 'ui-jcoverflip--item' )? element : element.parents( '.ui-jcoverflip--item' );
		
		if( item.size( ) == 0 ){
			return null;
		} else {
			return { element: item, index: item.data( 'jcoverflip__index' ) };
		}
	}
};



// The widget
$.widget( 'ui.jcoverflip', {
	_init: function( ){
		// init some internal values
		this.animationQueue = new animationqueue.AnimationQueue( );
		this.isInit = false; // used for setting up the CSS
		
		// Used to queue up overlapping goTo() calls since they come in async
		this.goToPoll = { id: null };
		this.goToQueue = [ ];
		
		
		// Setup the elements
		var items = this.items( );
		
		// add classes
		this.element.addClass( 'ui-jcoverflip' );
		items.addClass( 'ui-jcoverflip--item' );
		
		// Get the title for each item
		var i = items.size( );
		while( i-- ){
			var el = items.eq( i );
			
			// Tell the item what its index is
			el.data( 'jcoverflip__index', i );
			
			// Create the titles for the coverflow items
			var title = this.options.titles.create( el );
			title.css( { display: 'none' } ).addClass( 'ui-jcoverflip--title' ).appendTo( this.element );
			
			el.data( 'jcoverflip__titleElement', title );
		}
		
		// Bind the click action for when the user clicks on the item to change the current
		this.element.mouseover( proxy( this, this._clickItem ) );
		
		// setup the positioning of the elements, pass 0 for time, pass true to flag to init
		this._goTo( this.options.current, 0, true );
		
		// Add any addition controls (such as a scroll bar)
		this.options.controls.create( this.element, this.length() );
	},
	
	
	
	/**
	 * The click event for an item. If the item is not current,
	 * then it calls the current() and stops the event.
	 */
	_clickItem: function( event ){
		if( this.options.disabled == true ){
				return;
			}
		
		var item = $.jcoverflip.getItemFromElement( event.target );
		
		if( item !== null && item.index != this.current( ) ){
			this.current( item.index, event );
			event.preventDefault();
			return false;
		}
		return true;
	},
	
	
	/**
	 * Parses the parameters for next and previous methods. Any of the parameters are optional.
	 */
	_nextAndPrevParameters: function( by, wrapAround, callback, originalEvent ){
		
		
		// originalEvent is an object
		if( typeof by == 'object' ){
			originalEvent = by;
		} else if( typeof wrapAround == 'object' ){
			originalEvent = wrapAround;
		} else if( typeof callback == 'object' ){
			originalEvent = callback;
		} else if( typeof originalEvent == 'object' ){
			originalEvent = originalEvent;
		} else {
			originalEvent = { };
		}
		
		// callback is a function
		if( $.isFunction( by ) ){
			callback = by;
		} else if( $.isFunction( wrapAround ) ){
			callback = wrapAround;
		} else if( $.isFunction( callback ) ){
			callback = callback;
		} else {
			callback = nofn;
		}
		
		// wrapAround is boolean
		if( typeof( by ) == 'boolean' ) {
			wrapAround = by;
		} else if( typeof( wrapAround ) == 'boolean' ){
			wrapAround = wrapAround;
		} else {
			wrapAround = true;
		}
		
		// by is a number
		by = isNaN( parseInt( by ) )? 1 : parseInt( by );
		
		return { by: by, wrapAround: wrapAround, callback: callback, originalEvent: originalEvent };
	},
	
	
	/**
	 * Step to the right from the current.
	 *
	 * @param by
	 *   (optional) An integer to step to the right by. Defaults to 1.
	 *
	 * @param wrapAround
	 *   (optional) A boolean flag to wrap around if moving past the end. Defaults to true.
	 *
	 * @return
	 *   New current number
	 */
	next: function( by, wrapAround, callback, originalEvent ){
		if( this.options.disabled == true ){
				return;
			}
		
		var params = this._nextAndPrevParameters( by, wrapAround, callback, originalEvent );
		
		return this._nextAux( params.by, params.wrapAround, params.callback, params.originalEvent, 'next' );
	},
	
	
	
	_nextAux: function( by, wrapAround, callback, originalEvent, eventType ){
		by = by === undefined && isNaN( by ) ? 1 : parseInt( by );
		wrapAround = wrapAround !== false;
		
		var current = this.current( );
		var oldCurrent = current;
		var length = this.length( );
		
		if( wrapAround ){
			current = (current + by) % length;
			// If "current + by" is negative, then the result of "%" is between -(length-1) and -1.
			// Add the length, if negative, to bring the index back to a valid number
			current = current < 0 ? current + length : current; 
		} else {
			current = Math.min( length-1, Math.max( 0, current + by ) );
		}
		
		if( current != this.current( ) ){
			this.current( current, originalEvent );
		}
		
		if( eventType && oldCurrent != current ){
			var event = $.Event( originalEvent );
			event.type = this.widgetEventPrefix + eventType;
			callback.call( this.element, event, { from: oldCurrent, to: current } );
			this._trigger( eventType, originalEvent, { from: oldCurrent, to: current } );
		}
		
		return current;
	},
	
	
	
	/**
	 * Step to the left from the current.
	 *
	 * @param by
	 *   (optional) An integer to step to the left by. Defaults to 1.
	 *
	 * @param wrapAround
	 *   (optional) A boolean flag to wrap around if moving past the end. Defaults to true.
	 *
	 * @return
	 *   New current number
	 *
	 */
	previous: function( by, wrapAround, callback, originalEvent ){
		if( this.options.disabled == true ){
				return;
			}
		
		var params = this._nextAndPrevParameters( by, wrapAround, callback, originalEvent );
		
		return this._nextAux( -1*params.by, params.wrapAround, params.callback, params.originalEvent, 'previous' );
	},
	
	
	
	/**
	 * Go all the way to the left.
	 */
	first: function( callback, originalEvent ){
		if( this.options.disabled == true ){
				return;
			}
		
		if( typeof callback == 'object' ){
			originalEvent = callback
		} else if( typeof originalEvent == 'object' ){
			originalEvent = originalEvent;
		} else {
			originalEvent = { };
		}
		
		callback = $.isFunction( callback ) ? callback : nofn;
		
		var from = this.current( );
		var to = this.current( 0, originalEvent );
		if( from != to ){
			var event = $.Event( originalEvent );
			event.type = this.widgetEventPrefix + 'first';
			callback.call( this.element, event, { from: from, to: to } );
			this._trigger( 'first', originalEvent, { from: from, to: to } ); 
		}
	},
	
	
	
	/**
	 * Go all the way to the right.
	 */
	last: function( callback, originalEvent ){
		if( this.options.disabled == true ){
				return;
			}
		
		if( typeof callback == 'object' ){
			originalEvent = callback
		} else if( typeof originalEvent == 'object' ){
			originalEvent = originalEvent;
		} else {
			originalEvent = { };
		}
		
		callback = $.isFunction( callback ) ? callback : nofn;
		
		var from = this.current( );
		var to = this.current( this.length( ) - 1, originalEvent );
		if( from != to ){
		var event = $.Event( originalEvent );
			event.type = this.widgetEventPrefix + 'last';
			callback.call( this.element, event, { from: from, to: to } );
			this._trigger( 'last', originalEvent, { from: from, to: to } ); 
		}
	},
	
	
	
	/**
	 * Gets or sets the current item.
	 * 
	 * @param originalEvent (optional)
	 *   Pass an event object along to be assigned to the originalEvent for the event object passed
	 *   along with the triggered events of start, stop and change.
	 */
	current: function( newCurrent, originalEvent ){
		
		if( newCurrent !== undefined && !isNaN( newCurrent ) && !this.options.disabled && newCurrent != this.options.current ){
			this._goTo( newCurrent, undefined, false, originalEvent );
		}
		
		return this.options.current;
	},
	
	
	
	destroy: function( ){
		if( this.options.disabled == true ){
				return;
			}
		
		// let others clean up first
		this._trigger( 'destroy', {} );
		
		// container element
		this.element.removeClass( 'ui-jcoverflip' );
		
		
		// titles
		var items = this.items( );
		var titleEl;
		var i = items.length;
		while( i-- ){
			titleEl = items.eq( i ).data( 'jcoverflip__titleElement' );
			this.options.titles.destroy( titleEl );
		}
		
		
		// items
		// aggressively remove all inline styles
			items
				.removeClass( 'ui-jcoverflip--item' )
				.find( '*' ).add( items.get( ) )
				.each( function( ){
					this.removeAttribute( 'style' );
					
				} );
		
		
		// controls
		this.options.controls.destroy( this.element );
		
		
		// default action
		$.widget.prototype.destroy.apply( this, arguments );
	},
	
	
	enable: function( ){
		$.widget.prototype.enable.apply( this, arguments );
		this._trigger( 'enable', {} );
	},
		
	
	disable: function( ){
		$.widget.prototype.disable.apply( this, arguments );
		this._trigger( 'disable', {} );
	},
	
	
	option: function( name, value ){
		
		// getter
		if( typeof value == 'undefined' ){
			return $.widget.prototype.option.apply( this, arguments );
		}
		
		// setter
		
		// current
		if( name == 'current' ){
			return this.current( value );
		}
		
		// TODO: dynamic changing of the options: items, titles, controls
		// items, titles, controls
		if( name in { 'items': '', 'titles': '', 'controls': '' } ){
			return this.options.items;
		}
		
		// beforeCss, afterCss, currentCss
		if( name in { 'beforeCss': '', 'afterCss': '', 'currentCss': '' } ){
			this.options[ name ] = value;
			// force update positioning
			this._goTo( this.current( ), 0, true );
		}
		
		// time
		if( name == 'time' && isNaN( parseInt( value ) ) && parseInt( value ) < 0 ){
			return this.options.time;
		}
		
		// Default action
		return $.widget.prototype.option.apply( this, arguments );
	},
	
	
	
	/**
	 * Go to a particular coverflow item.
	 *
	 * @param index
	 *   The item index.
	 *
	 * @param time
	 *   Optional. The time to do the animation to the new item in.
	 *
	 */
	_goTo: function( index, time, force, originalEvent ){
			if( this.options.disabled == true ){
				return;
			}
			
			force = !!force;
			originalEvent = originalEvent == undefined? { } : originalEvent;
			
			// Get the time to run
			time = time === undefined? this.options.time: parseInt( time );
			
			// Setup current and oldCurrent
			var oldCurrent = this.options.current;
			var current = Math.floor( Math.max( 0, Math.min( index, this.length( )-1 ) ) );
			this.options.current = current;
			
			
			// Start working on the animation queue
			// 1. Stop the current animation
			// 2. Remove sets that are moving away from the current item
			// 3. Add needed sets to move towards the current item
			// 4. Start the animation queue
			this.animationQueue.stop( );
			
			// Clear out any sets that are moving away from the current item
			var animationSets = this.animationQueue.get( );
			var i = animationSets.length;
			while( i-- ){
				var to = animationSets[ i ].getData( 'to' );
				var goingToTheRight =  animationSets[ i ].getData( 'goingToTheRight' );
				var rightOfCurrent = to > current;
				if( rightOfCurrent != goingToTheRight ){
					this.animationQueue.remove( animationSets[ i ] );
				}
			}
			
			animationSets = this.animationQueue.get( ); // update it since we may have changed the it by removing sets above
			// How many steps from the old current item to the new current item
			var stepsToCurrent = animationSets.length > 0? animationSets.pop( ).getData( 'to' ) : oldCurrent;
			var goingToTheRight = stepsToCurrent < current; // direction of movement
			stepsToCurrent += goingToTheRight? 1: -1; // advance to the next since we don't need to animate to our current position
			
			
			// Special case for the first run
			if( force ){
				stepsToCurrent = current;
			}
			
			var items = this.items( );
			// Add sets for each step
			// The test works for moving in both directions
			while( ( goingToTheRight && stepsToCurrent <= current ) || ( !goingToTheRight && stepsToCurrent >= current ) || ( force && stepsToCurrent == current ) ){
				// Create a set
				var animationSet = new animationqueue.AnimationSet( );
				this.animationQueue.queue( animationSet );
				animationSet.setData( 'goingToTheRight', goingToTheRight );
				animationSet.setData( 'to', stepsToCurrent );
				
				// Setup animation for all the items
				var i = items.length;
				while( i-- ){
					var el = items.eq( i );
					if( i < stepsToCurrent ){
						var css = this.options.beforeCss( el, this.element, stepsToCurrent-i-1 );
						
					} else if( i > stepsToCurrent ){
						var css = this.options.afterCss( el, this.element, i-stepsToCurrent-1 );
						
					} else { // i == stepsToCurrent
						var css = this.options.currentCss( el, this.element, i-stepsToCurrent-1 );
					}
					
					// Push all the animation info onto the animation queue
					var j = css.length;
					while( j-- ){
						var cssI = css[ j ];
						animationSet.add( new animationqueue.Animation( cssI.element, cssI.animate ) ); 
						for( var step in cssI.steps ){
							animationSet.add( new animationqueue.AnimationStep( cssI.element, cssI.steps[ step ], parseFloat( step ) ) );
						}
					}
				} // endwhile( i-- ) End the looping through all the items
				stepsToCurrent += goingToTheRight? 1: -1;
			} // endwhile( ) End looping through all the steps from current to i
			
			// hide/show the title
			var titleElement = items.eq( current ).data( 'jcoverflip__titleElement' );
			if( titleElement ){
				this.options.titleAnimateIn( titleElement, time, goingToTheRight );
			}
			
			if( current != oldCurrent ){ // prevent the case where current and oldCurrent are the same
				
				var titleElement = items.eq( oldCurrent ).data( 'jcoverflip__titleElement' );
				
				if( titleElement ){
					this.options.titleAnimateOut( titleElement, time, goingToTheRight );
				}
			}
			
			if( !force ){
				// Trigger the start event 
				this._trigger( 'start', originalEvent, { to: current, from: oldCurrent } );
				// run the animation and set a callback to trigger the stop event
				this.animationQueue.start( time, proxy( this, function( timeElapsed ){
						this._trigger( 'stop', originalEvent, { to: current, from: oldCurrent, time: timeElapsed} );
					} ) ); 
				
				this._trigger( 'change', originalEvent, { to: current, from: oldCurrent } );
			} else {
				this.animationQueue.start( time, nofn );
			}
			
			// Used to create the functions for creating AnimationSteps
			function stepFactory( el, css ){
				return function( ){
						el.css( css );
					};
			};
			
			
		},
	
	
	/**
	 * Get the item elements
	 *
	 * Returns the items based on the selector string found in options.items, if not defined, then
	 * the children of the jcoverflip element will be the items.
	 *
	 * @param reload - boolean flag to clear the cache of elements that are the items
	 *
	 * @return jQuery object of items
	 */
	items: function( reload ){
			if( this.itemsCache === undefined || !!reload ){
				if( this.options.items ){
					this.itemsCache = this.element.find( this.options.items );
				} else {
					this.itemsCache = this.element.children( );
				}
			}
			
			return this.itemsCache;
		},
	
	length: function( ){
		var items = this.items( );
		return items.length;
	}
} ) ;









$.ui.jcoverflip.defaults = {
	items: '',
	beforeCss: function( el, container, offset ){
		return [
			$.jcoverflip.animationElement( el, { left: ( container.width( )/2 - 210 - 110*offset )+'px', bottom: '20px' }, { } ),
			$.jcoverflip.animationElement( el.find( 'img' ), { opacity: 0.5, width: '100px' }, {} )
		];
	},
	afterCss: function( el, container, offset ){
		return [
			$.jcoverflip.animationElement( el, { left: ( container.width( )/2 + 110 + 110*offset )+'px', bottom: '20px' }, { } ),
			$.jcoverflip.animationElement( el.find( 'img' ), { opacity: 0.5, width: '100px' }, {} )
		];
	},
	currentCss: function( el, container ){
		return [
			$.jcoverflip.animationElement( el, { left: ( container.width( )/2 - 100 )+'px', bottom: 0 }, { } ),
			$.jcoverflip.animationElement( el.find( 'img' ), { opacity: 1, width: '200px' }, { } )
		];
	},
	time: 500, // half a second
	
	titles: {
		/** 
		 *
		 * @param el - item element
		 *
		 * @return jQuery element object of the title
		 *
		 * Order for finding the title
		 * 1) An element with a class of "title"
		 * 2) The title attribute of the item
		 * 3) The alt attribute of the item
		 * 4) The first title or alt attribute of a child element of the item
		 */
		create: function( el ){
			var titleText = '';
			var title = $( [] );
			var titleEl = el.find( '.title:first' );
			if( titleEl.size( ) == 1 ){
				title = titleEl.clone( true );
				titleEl.css( 'display', 'none' );
				title.data( 'jcoverflip__origin', 'cloned' );
				title.data( 'jcoverflip__source', titleEl );
			} else if( el.attr( 'title' ) ) {
				titleText = el.attr( 'title' );
			} else if( el.attr( 'alt' ) ) {
				titleText = el.attr( 'alt' );
			} else {
				titleEl = el.find( '[title], [alt]' ).eq( 0 );
				if( titleEl.size( ) == 1 ){
					titleText = titleEl.attr( 'title' ) || titleEl.attr( 'alt' ) || '';
				}
			}
			
			if( title.size( ) ){
				title.css( { 'opacity': 0, 'display': 'block' } );
			} else {
				title = $( '<span class="title">' + titleText + '</span>' );
				title.data( 'jcoverflip__origin', 'attribute' );
			}
			return title;
		},
		/**
		 * 
		 * @param el - title element
		 */
		destroy: function( el ){
			if( el.data( 'jcoverflip__origin' ) == 'cloned' ){
				el.data( 'jcoverflip__source' ).css( 'display', '' );
			}
			el.remove( );
		}
	},
	
	titleAnimateIn: function( titleElement, time, offset ){
		if( titleElement.css( 'display' ) == 'none' ){
			titleElement.css({opacity: 0, display: 'block'});
		}
		titleElement.stop( ).animate({opacity: 1}, time );
	},
	titleAnimateOut: function( titleElement, time, offset ){
		titleElement.stop( ).animate( {opacity: 0 }, time, function(){ 
			$(this).css('display', 'none'); 
		} );
	},
	controls: {
		/**
		 * @param containerElement - the jQuery object for the jcoverflip
		 * @param length - the number of items
		 */
		create: nofn,
		/**
		 * @param containerElement - the jQuery object for the jcoverflip
		 */
		destroy: nofn
	},
	current: 0
};


// specify  the getters
$.ui.jcoverflip.getter = [ 'length', 'current'  ];




} )( jQuery );
