/*
 * jCoverflip - Present your featured content elegantly.
 * Version: 1.0.1
 * 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.click( 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 - 70*offset )+'px', bottom: '10px' }, { } ),
                        $.jcoverflip.animationElement( el.find( 'img' ), { opacity: 0.5, maxHeight: '80px', maxWidth: '90px'}, {} )
                ];
        },
        afterCss: function( el, container, offset ){
                return [
                        $.jcoverflip.animationElement( el, { left: ( container.width( )/2 + 10 + 110*offset )+'px', bottom: '10px' }, { } ),
                        $.jcoverflip.animationElement( el.find( 'img' ), { opacity: 0.5, maxHeight: '80px', maxWidth: '90px'}, {} )
                ];
        },
        currentCss: function( el, container ){
                return [
                        $.jcoverflip.animationElement( el, { left: ( container.width( )/2 - 110 )+'px', bottom: '10px' }, { } ),
                        $.jcoverflip.animationElement( el.find( 'img' ), { opacity: 1,  maxHeight: '100px', maxWidth:'110px'}, { } )
                ];
        },
        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 );


