/*!
 * Waterwheel Carousel
 * Version 1.0
 * http://www.bkosolutions.com
 *
 * Copyright 2010 Brian Osborne
 * Licensed under GPL version 3
 * http://www.gnu.org/licenses/gpl.txt
 *
 * Plugin written by Brian Osborne
 * for use with the jQuery JavaScript Framework
 * http://www.jquery.com
 *
 */
(function ($) {

    $.fn.waterwheelCarousel = function (orientation, options) {

        // override the default options with user defined options
        options = $.extend({}, $.fn.waterwheelCarousel.defaults, options || {});

        // make sure the passed in orientation is valid
        if (orientation != "horizontal" || orientation != "veritical") {
            orientation = "horizontal";
        }

        return $(this).each(function () {

            /* These are univeral values that are used throughout the plugin. Do not modify them
            * unless you know what you're doing. Most of them feed off the options
            * so most customization can be achieved by modifying the options values */
            var data = {
                itemsContainer: $(this),
                totalItems: $(this).children().length,
                containerWidth: $(this).width(),
                containerHeight: $(this).height(),
                items: [],
                itemDistances: [],
                waveDistances: [],
                itemOpacities: [],
                containerIDTag: "#" + $(this).attr("id"),
                carouselRotationsLeft: 0,
                currentlyMoving: false,
                itemsAnimating: 0,
                currentSpeed: options.speed
            }

            // Setup the carousel
            beforeLoaded();
            preload(function () {
                setupDistanceArrays();
                setupCarousel();
                setupStarterRotation();
            });

            // Gets the feature container based on the number
            function getItem(itemNum) {
                return data.itemsContainer.children().eq(itemNum - 1);
            }

            // get previous feature number - accounts for wrap around
            function getPreviousNum(num) {
                var newNum = (num == 1) ? null : num--;
                return newNum;
            }

            // get next feature number - accounts for wrap around
            function getNextNum(num) {
                var newNum = (num == data.totalItems) ? null : num++;
                return newNum;
            }

            // We want to hide all the images to make sure the viewer doesn't
            // see them before the carousel moves them to proper positions
            function beforeLoaded() {
                data.itemsContainer.find("img").hide();
            }

            /**
            * This function will preload all the images in the carousel before
            * calling the passed in callback function. This is VERY necessary for
            * the carousel to function properly because it depends heavily on the
            * width and height of images (which can only be determined post-load)
            */
            function preload(callback) {
                var $imageElements = data.itemsContainer.find("img");
                var loadedImages = 0;
                var totalImages = $imageElements.length;

                $imageElements.each(function () {
                    // Attempt to load the images
                    $(this).load(function () {
                        // Add to number of images loaded and see if they are all done yet
                        loadedImages++;
                        if (loadedImages == totalImages) {
                            // All done, perform callback
                            callback();
                        }
                    });
                    // The images may already be cached in the browser, in which case they
                    // would have a 'true' complete value and the load callback would never be
                    // fired. This will fire it manually.
                    if (this.complete) {
                        $(this).trigger('load');
                    }
                });
            }

            /**
            * This function will setup the various distance arrays that are used in the carousel
            * These values are calculated beforehand to reduce calculation time while the
            * carousel is moving
            */
            function setupDistanceArrays() {
                // Start each array with the first starting value from the options
                data.itemDistances[0] = options.startingItemSeparation;
                data.waveDistances[0] = options.startingWaveSeparation;
                data.itemOpacities[0] = 1 * .75;
                // Then go thru and calculate the rest of the values all the way up to
                // either edge and beyond 1 (to account for the hidden items)
                for (var i = 1; i < options.flankingItems + 1; i++) {
                    data.itemDistances[i] = data.itemDistances[i - 1] * options.itemSeparationFactor;
                    data.waveDistances[i] = data.waveDistances[i - 1] * options.waveSeparationFactor;
                    data.itemOpacities[i] = data.itemOpacities[i - 1] * options.opacityDecreaseFactor;
                }
                // The last opacity should be zero
                data.itemOpacities[data.itemOpacities.length - 1] = 0;
            }

            /**
            * This function will perform the necessary steps to setup the carousel and the items
            * within it. This mostly means positioning the elements properly and setting
            * their data values
            */
            function setupCarousel() {
                // Fill in a data array with jQuery objects of all the images
                data.items = data.itemsContainer.children('img');
                for (var i = 0; i < data.items.length; i++) {
                    data.items[i] = $(data.items[i]);
                }

                data.itemsContainer
                // Want the container to have relative positioning
                    .css('position', 'relative')
                    .find('img')
                        .each(function (i) {
                            // Put all images in the center default position
                            var newLeft, newTop;
                            if (orientation == "horizontal") {
                                newLeft = (data.containerWidth / 2) - ($(this).width() / 2);
                                newTop = options.centerOffset;
                            } else {
                                newLeft = options.centerOffset;
                                newTop = (data.containerHeight / 2) - ($(this).height() / 2);
                            }
                            $(this)
                            // Apply positioning and layering to the images
                                .css({
                                    left: newLeft,
                                    top: newTop,
                                    position: 'absolute',
                                    'z-index': options.flankingItems + 2,
                                    'opacity': 1
                                })
                            // Give each image a data object so it remembers specific data about
                            // it's original form
                                .data({
                                    currentPosition: 0,
                                    width: $(this).width(),
                                    owidth: $(this).width(),
                                    height: $(this).height(),
                                    oheight: $(this).height(),
                                    top: newTop,
                                    left: newLeft,
                                    opacity: 1,
                                    index: i
                                })
                            // The image has been setup... Now we can show it
                                .show();
                        });
            }

            /**
            * All the items to the left and right of the center item need to be
            * animated to their starting positions. This function will
            * figure out what items go where and will animate them there
            */
            function setupStarterRotation() {
                // Get the center item and collections of the items that will be
                // to the left and to the right
                var $centerItem = data.itemsContainer.children('img').eq(options.startingItem - 1);
                var $previousItems = $centerItem.prevAll();
                var $nextItems = $centerItem.nextAll();

                // We will be rotating the carousel, so we set the animation queue to one
                data.carouselRotationsLeft = 1;

                var counter;
                counter = 1;
                // Move all the left side items to their proper positions
                $previousItems.each(function () {
                    for (i = 0; i < counter; i++) {
                        moveItem($(this), false);
                    }
                    counter++;
                });

                counter = 1;
                // Move all the right side items to their proper positions
                $nextItems.each(function () {
                    for (i = 0; i < counter; i++) {
                        moveItem($(this), true);
                    }
                    counter++;
                });
            }

            function setupSlider() {
                $("#slider").slider({
                    min: 1,
                    max: data.items.length,
                    step: 1,
                    value: options.startingItem,
                    animate: true,
                    stop: function () {
                        stopAnimations();
                        data.currentlyMoving = false;
                        var oldValue = $('#slider').slider('value');
                        var positionOfItem = data.items[oldValue - 1].data().currentPosition;

                        if (positionOfItem < 0) {
                            rotateCarousel(true, Math.abs(positionOfItem));
                        } else if (positionOfItem > 0) {
                            rotateCarousel(false, positionOfItem);
                        }

                    }
                });

            }

            /**
            * Given the item and position, this function will calculate the new data
            * for the item. One the calculations are done, it will store that data in
            * the items data object
            */
            function performCalculations($item, newPosition) {

                // Distance to the center
                var oldPosition = $item.data().currentPosition;
                var newDistanceFromCenter = Math.abs(newPosition);

                /** CALCULATE THE NEW WIDTH AND HEIGHT OF THE ITEM **/
                var oldWidth = $item.data().width, newWidth = $item.data().owidth;
                var oldHeight = $item.data().height, newHeight = $item.data().oheight;
                for (var i = 0; i < newDistanceFromCenter; i++) {
                    newWidth = newWidth * options.itemDecreaseFactor;
                    newHeight = newHeight * options.itemDecreaseFactor;
                }
                var widthDifference = Math.abs(oldWidth - newWidth);
                var heightDifference = Math.abs(oldHeight - newHeight);

                /** CALCULATE THE NEW WAVE SEPARATION OF THE ITEM **/
                var waveSeparation = 0, centeringNumber
                // number to center item on horizon (vertical or horizontal)
                if (orientation == "horizontal")
                    centeringNumber = heightDifference / 2;
                else
                    centeringNumber = widthDifference / 2;
                // Item growing
                if ((newPosition > -1 && (newPosition < oldPosition)) || (newPosition < 1 && (newPosition > oldPosition))) {
                    // center item along the horizon
                    waveSeparation -= centeringNumber;
                    // now add the wave
                    waveSeparation += data.waveDistances[Math.abs(newPosition)];
                    // Item shrinking
                } else if ((newPosition > -1 && (newPosition > oldPosition)) || (newPosition < 1 && (newPosition < oldPosition))) {
                    // center item along the horizon
                    waveSeparation += centeringNumber;
                    // now subtract the wave
                    waveSeparation -= data.waveDistances[Math.abs(newPosition) - 1];
                }

                /** CALCULATE THE NEW ITEM SEPARATION OF THE ITEM **/
                var itemSeparation = 0;
                // if moving towards the center, the separation value will be different
                // than if it were moving away from the center
                if (Math.abs(newPosition) < Math.abs(oldPosition)) {
                    itemSeparation = data.itemDistances[Math.abs(newPosition)];
                    // if not moving towards center, just give it normal positioning
                } else {
                    itemSeparation = data.itemDistances[Math.abs(newPosition) - 1];
                }
                // Need to account for additional size separation only if the item is
                // on the right side or moving to the center from the right side
                if (newPosition > 0 || (newPosition == 0 && oldPosition == 1)) {
                    if (orientation == "horizontal")
                        itemSeparation += widthDifference;
                    else
                        itemSeparation += heightDifference;
                }
                // We want to separation to be negative if the image is going towards the left
                if (newPosition < oldPosition) {
                    itemSeparation = itemSeparation * -1;
                }

                /** CALCULATE NEW OPACITY OF THE ITEM **/
                var newOpacity;
                if (newPosition == 0) {
                    newOpacity = 1;
                } else {
                    newOpacity = data.itemOpacities[Math.abs(newPosition) - 1];
                }

                // Figure out the new top and left values based on the orientation
                var newTop = $item.data().top;
                var newLeft = $item.data().left;
                if (orientation == "horizontal") {
                    newTop = $item.data().top + waveSeparation;
                    newLeft = $item.data().left + itemSeparation;
                } else {
                    newTop = $item.data().top + itemSeparation;
                    newLeft = $item.data().left + waveSeparation;
                }

                // Depth will be reverse distance from center
                var newDepth = options.flankingItems + 2 - newDistanceFromCenter;
                // Set calculations
                $item.data('width', newWidth);
                $item.data('height', newHeight);
                $item.data('top', newTop);
                $item.data('left', newLeft);
                $item.data('currentPosition', newPosition);
                $item.data('depth', newDepth);
                $item.data('opacity', newOpacity);
            }

            /**
            * This function is called when moving an item in the given direction.
            * It will figure out the new position based on the direction of the carousel
            * and then obtain the new calcuations for the item and apply them
            */
            function moveItem($item, direction) {
                // Get old and new positions
                var oldPosition = $item.data('currentPosition'), newPosition;
                if (direction == false) {
                    newPosition = oldPosition - 1;
                } else {
                    newPosition = oldPosition + 1;
                }

                // Only want to physically move the item if it is within the boundaries
                // or in the first position just outside either boundary
                if (Math.abs(newPosition) <= options.flankingItems + 1) {
                    // increment number of items animating
                    data.itemsAnimating++;
                    // Obtain the updated data values for the item
                    performCalculations($item, newPosition);
                    // NOTE: After this method is called, the items data object has updated
                    // position values

                    // Change depth of item right away based on its new position
                    $item.css('z-index', $item.data().depth);
                    $item
                    // Animate the items to their new position values
                        .animate({
                            left: $item.data().left,
                            width: $item.data().width,
                            height: $item.data().height,
                            top: $item.data().top,
                            opacity: $item.data().opacity
                        }, data.currentSpeed, options.animationEasing, function () {
                            // Animation for the item has completed, call method
                            itemAnimationComplete($item, newPosition, direction);
                        });
                    // If the item is moving further outside of the boundary, don't move it,
                    // just increment its position
                } else if (Math.abs(newPosition) > options.flankingItems) {
                    $item.data('currentPosition', newPosition);
                }

            }

            /**
            * This function is called once an item has finished animating to its
            * given position. Several different statements are executed here, such as
            * dealing with the animation queue
            */
            function itemAnimationComplete($item, newPosition, direction) {
                // If the item moved to the center position, trigger the custom event
                if (newPosition == 0) {
                    options.movedToCenter($item);
                }

                // Decrement one from the amount of items that are animating
                data.itemsAnimating--;
                // If there are no more items left animating, that means that all the items within
                // the carousel have finished animating
                if (data.itemsAnimating == 0) {
                    // Decrement one from the amount of rotations the carousel has made
                    data.carouselRotationsLeft -= 1;
                    // The carousel has finished rotating and is no longer moving
                    data.currentlyMoving = false;
                    // If there are still rotations left in the queue, rotate the carousel again
                    if (data.carouselRotationsLeft > 0) {
                        rotateCarousel(direction, 0);
                        // Otherwise there are no more rotations and...
                    } else {
                        // Reset the speed of the carousel to original
                        data.currentSpeed = options.speed;

                $('.centreContentText').css('display', 'none');
$('#centreContentText' + StartID).fadeIn(2000);

                    }
                }
            }

            function stopAnimations() {
                for (var i = 0; i < data.items.length; i++) {
                    data.items[i].stop();
                }
            }

            // Short function to determine if a rotation is allowed or not
            function rotationAllowed(direction) {
                // Deny if currently moving already
                if (data.currentlyMoving == true)
                    return false;
                // Deny if trying to move to right and already at right-most item
                if (direction == true && data.items[0].data().currentPosition == 0)
                    return false;
                // Deny if trying to move to left and already at left-most item
                if (direction == false && data.items[data.totalItems - 1].data().currentPosition == 0)
                    return false;

                // Everything is OKAY
                return true;
            }

            /**
            * Function called to rotate the carousel the given number of rotations
            * in the given direciton. Will check to make sure the carousel should
            * be able to move, and then adjust speed and move items
            */
            function rotateCarousel(direction, rotations) {

                // Check to see that a rotation is allowed
                if (rotationAllowed(direction)) {
			if (direction == true) {
				StartID -= 1;
			} else {
				StartID += 1;
			}

                    // Carousel is now moving
                    data.currentlyMoving = true;
                    // Reset items animating to zero
                    data.itemsAnimating = 0;
                    // Add given rotations to queue
                    data.carouselRotationsLeft += rotations;

                    if (options.quickerForFurther == true) {
                        // Figure out how fast the carousel should rotate
                        if (rotations > 1) {
                            data.currentSpeed = options.speed / rotations;
                        }
                        // Assure the speed is above the minimum to avoid weird results
                        data.currentSpeed = (data.currentSpeed < 100) ? 100 : data.currentSpeed;
                    } else {
                        data.currentSpeed = options.speed;
                    }

                    // Iterate thru each item and move it
                    for (var i = 0; i < data.items.length; i++) {
                        var $item = $(data.items[i]);
                        var currrentPosition = $item.data().currentPosition;
                        // Only move items that are within the boundaries of the carousel
                        // (but also the first flanking hidden item on either side if there is one)
                        if (currrentPosition >= ((options.flankingItems * -1) - 1) && currrentPosition <= (options.flankingItems) + 1) {
                            moveItem($item, direction);
                            // If the item is not in the boundaries, then that means it is a hidden flank image
                            // we don't want to move it, but we want to increment it's position
                        } else {
                            if (direction == true) {
                                $item.data('currentPosition', currrentPosition + 1);
                            } else {
                                $item.data('currentPosition', currrentPosition - 1);
                            }
                        }
                    }
                }
            }

            /**
            * The event handler when an image within the carousel is clicked
            * This function will rotate the carousel the correct number of rotations
            * to get the clicked item to the center, or will fire the custom event
            * the user passed in if the center item is clicked
            */
            $('img', data.containerIDTag).live("click", function () {
                var itemPosition = $(this).data().currentPosition;

                var rotations = Math.abs(itemPosition);
                if (itemPosition < 0) {
                    rotateCarousel(true, rotations);
                } else if (itemPosition > 0) {
                    rotateCarousel(false, rotations);
                } else {
                    options.clickedCenter($(this));
                }
            });

            $('#left').click(function () {
                rotateCarousel(false, 1);
            });

            $('#right').click(function () {
                rotateCarousel(true, 1);
            });

        });

    };

    $.fn.waterwheelCarousel.defaults = {
        startingItem: 1,      // item to place in the center at the start
        startingItemSeparation: 150,    // the starting separation distance between each item
        itemSeparationFactor: .5,     // determines how drastically the item separation decreases
        startingWaveSeparation: 30,      // the starting separation distance for the wave
        waveSeparationFactor: .75,    // determines how drastically the wave separation decreases
        itemDecreaseFactor: .8,     // determines how drastically the item's width and height decrease
        opacityDecreaseFactor: .5,     // determines how drastically the item's opacity decreases
        centerOffset: 40,     // the number of pixels to offset the center item in the carousel
        flankingItems: 4,      // the number of items visible on either side of the center
        speed: 300,    // speed in milliseconds it will take to rotate from one to the next
        animationEasing: 'linear', // the animation easing when rotating each item
        quickerForFurther: true, // set to true to make animations faster when clicking an item that is far away from the center
        movedToCenter: $.noop, // custom function executed when an item moves to the center
        clickedCenter: $.noop  // custom function executed when the center item is clicked
    };

})(jQuery);
