/*
 * 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.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 - 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: 150, // 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);
