/**
 * PLUGIN CREATED BY BRIAN OSBORNE
 * PLEASE DO NOT USE ON A COMMERICIAL WEBSITE WITHOUT PERMISSION FROM THE OWNER
 *
 * HTTP://WWW.BKOSOLUTIONS.COM
 */
(function($) {

    $.fn.featureCarousel = function (options) {

        // override the default options with user defined options
        options = $.extend({}, $.fn.featureCarousel.defaults, options || {});

        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 pluginData = {
                centerFeatureWidth:     (options.containerWidth * options.centerFeatureFactor),
                smallFeatureWidth:      ((options.containerWidth * options.centerFeatureFactor) * options.smallFeatureFactor),
                currentCenterNum:       options.startingFeature,
                totalFeatureCount:      $(this).children("div").length,
                currentlyMoving:        false,
                featuresContainer:      $(this),
                containerIDTag:         "#"+$(this).attr("id"),
                timeoutVar:             null,
                animationQueue:         0,
                itemsToAnimate:         0
            }

            preload(function () {
                setupCarousel();
                setupFeaturePositions();
                // setup blips if user wants them
                if (options.useBlips == true) {
                    setupBlips();
                }
                // rotate the carousel once to get the ball rolling
                initiateMove(true,1);
            });

            function preload(callback) {
                // user may not want to preload images
                if (options.preload == true) {
                    // set position of loader and show it
                    $(".carouselLoader").css({
                        left: (options.containerWidth / 2) - ($(".carouselLoader").width() / 2)
                    }).show();

                    // NOTE THIS DOESNT WORK IF AN IMAGE IS BROKEN!
                    var $imageElements = pluginData.featuresContainer.find("img:not(.carouselLoader)");
                    var images = [];
                    $imageElements.each(function () {
                        if ($.inArray($(this).attr('src'),images) == -1) {
                            images.push($(this).attr('src'));
                        }
                    });
                    var loadedImages = 0;
                    var totalImages = images.length;
                    $imageElements.each(function () {
                        if ($.inArray($(this).attr('src'),images) > -1) {
                            $(this).load(function () {
                                loadedImages++;
                                if (loadedImages == totalImages) {
                                    $(".carouselLoader").fadeOut();
                                    callback();

                                }
                            });
                        }
                    });
                } else {
                    // if user doesn't want preloader, then just go right to callback
                    callback();
                }
            }

            // Gets the feature container based on the number
            function getContainer(featureNum) {
                return pluginData.featuresContainer.children("div").eq(featureNum - 1);
            }

            // get a feature given it's set position (the position that doesn't change)
            function getBySetPos(position) {
                pluginData.featuresContainer.children("div").each(function () {
                    if ($(this).data().setPosition == position)
                        return $(this);
                });
            }

            // get previous feature number
            function getPreviousNum(num) {
                if ((num - 1) == 0) {
                    return pluginData.totalFeatureCount;
                } else {
                    return num - 1;
                }
            }

            // get next feature number
            function getNextNum(num) {
                if ((num + 1) > pluginData.totalFeatureCount) {
                    return 1;
                } else {
                    return num + 1;
                }
            }

            // takes care of presetting a bunch of different things for the carousel
            function setupCarousel() {

                pluginData.featuresContainer
                    // Have to make the container relative positioning
                    .css('position','relative')
                    .children("div").each(function () {
                        // Center all the features in the middle and hide them
                        $(this).css({
                            'left': (options.containerWidth / 2) - (pluginData.smallFeatureWidth / 2),
                            'width': pluginData.smallFeatureWidth,
                            'top': options.topPadding,
                            'position': 'absolute',
                            'padding': options.featurePadding / 2,
                            'opacity': 0
                        });
                    })
                    // Set all the images to small feature size
                    .find(".featureImage").css({
                        'width': pluginData.smallFeatureWidth - (options.featurePadding)
                    });

                // figure out number of items that will rotate each time
                if (pluginData.totalFeatureCount < 4) {
                    pluginData.itemsToAnimate = pluginData.totalFeatureCount;
                } else {
                    pluginData.itemsToAnimate = 4;
                }

                // hide feature information if option set
                if (options.hideFeatureInfo == true) {
                    pluginData.featuresContainer.find(".storyinfo")
                        .hide();
                // or keep it visible and instead hide all other images and set fonts to small
                } else {
                    // if so, we want to set the font sizes and also hide any images within it
                    pluginData.featuresContainer
                        .find("img:not(.featureImage):not(.carouselLoader)")
                            .hide()
                        .end()
                        .find(".description")
                            .css("fontSize", options.smallDescriptionSize)
                        .end()
                        .find(".header")
                            .css("fontSize", options.smallHeaderSize);
                }

            }

            function setupFeaturePositions() {
                // Go back one - This is done because we call the move function right away, which
                // shifts everything to the right. So we set the current center back one, so that
                // it displays in the center when that happens
                var oneBeforeStarting = getPreviousNum(options.startingFeature);
                pluginData.currentCenterNum = oneBeforeStarting;

                // Center feature will be position 1
                var $centerFeature = pluginData.featuresContainer.children("div").eq(oneBeforeStarting-1);
                $centerFeature.data('position',1);

                // Everything before that center feature...
                var $prevFeatures = $centerFeature.prevAll();
                $prevFeatures.each(function (i) {
                    $(this).data('position',(pluginData.totalFeatureCount - i));
                });

                // And everything after that center feature...
                var $nextFeatures = $centerFeature.nextAll();
                $nextFeatures.each(function (i) {
                    $(this).data('position',(i + 2));
                });

                // give all features a set number that won't change so they remember their
                // original order
                pluginData.featuresContainer.children("div").each(function (i) {
                    $(this).data('setPosition',i+1);
                });
            }

            // These blips are used to show how many total features there are
            function setupBlips()
            {
                // construct the blip list
                var $list = $("<ul></ul>");
                $list.addClass("blipsContainer");
                for (var i = 0; i < pluginData.totalFeatureCount; i++) {
                    var $blip = $("<div></div>");
                    $blip.addClass("blip");
                    $blip.css("cursor","pointer");
                    $blip.attr("id","blip_"+(i+1));
                    var $listEntry = $("<li></li>");
                    $listEntry.append($blip);
                    $listEntry.css("float","left");
                    $listEntry.css("list-style-type","none");
                    $list.append($listEntry);
                }
                // add the blip list and then make sure it's visible
                $(pluginData.containerIDTag).append($list);
                $list.hide().show();
            }

            // displays what feature is in the center by changing the blips
            function changeBlip(oldCenter, newCenter)
            {
                // get selectors for the two blips
                var $blipsContainer = pluginData.featuresContainer.find(".blipsContainer");
                var $oldCenter = $blipsContainer.find("#blip_"+oldCenter);
                var $newCenter = $blipsContainer.find("#blip_"+newCenter);

                // change classes
                $oldCenter.removeClass("blipSelected");
                $newCenter.addClass("blipSelected");
            }

            // will set an interval to rotate carousel if option for autoplay
            function autoPlay() {
                // clear the timeout var if it exists
                if (pluginData.timeoutVar != null) {
                    pluginData.timeoutVar = clearTimeout(pluginData.timeoutVar);
                }

                // set interval for moving if autoplay is set
                if (options.autoPlay != 0) {
                    var autoTime = (Math.abs(options.autoPlay) < options.carouselSpeed) ? options.carouselSpeed : Math.abs(options.autoPlay);
                    pluginData.timeoutVar = setTimeout(function () {
                        if (options.autoPlay > 0)
                            initiateMove(true,1);
                        else if (options.autoPlay < 0)
                            initiateMove(false,1);
                    }, autoTime);
                }
            }

            // this function will rotate the positions of all the features
            function rotatePositions(direction) {
                pluginData.featuresContainer.children("div").each(function () {
                    var newPos;
                    if (direction == false) {
                        newPos = getNextNum($(this).data().position);
                    } else {
                        newPos = getPreviousNum($(this).data().position);
                    }
                    $(this).data('position',newPos);
                });
            }

            // This function is used to animate the given feature to the given
            // location. Valid locations are "left", "right", "center", "hidden"
            function animateFeature($feature, direction)
            {
                var new_width, new_top, new_left, new_zindex, new_headerfont, new_descriptionfont, new_padding, new_fade;

                // Determine the old and new positions of the feature
                var oldPosition = $feature.data('position');
                var newPosition;
                if (direction == true)
                    newPosition = getPreviousNum(oldPosition);
                else
                    newPosition = getNextNum(oldPosition);

                // Caculate new new css values depending on where the feature will be located
                if (newPosition == 1) {
                    new_headerfont = options.featureHeaderSize;
                    new_descriptionfont = options.featureDescriptionSize;
                    new_width = pluginData.centerFeatureWidth - (options.featurePadding * 2);
                    new_top = 0;
                    new_zindex = $feature.css("z-index");
                    new_left = (options.containerWidth / 2) - (pluginData.centerFeatureWidth / 2);
                    new_padding = options.featurePadding;
                    new_fade = 1.0;
                } else {
                    new_headerfont = options.smallHeaderSize;
                    new_descriptionfont = options.smallDescriptionSize;
                    new_width = pluginData.smallFeatureWidth - (options.featurePadding);
                    new_top = options.topPadding;
                    new_zindex = 1;
                    new_padding = options.featurePadding / 2;
                    new_fade = 0.4;
                    // some info is different for the left, right, and hidden positions
                    if (newPosition == pluginData.totalFeatureCount) {
                        new_left = options.sidePadding;
                    } else if (newPosition == 2) {
                        new_left = options.containerWidth - pluginData.smallFeatureWidth - options.sidePadding;
                    } else {
                        new_left = (options.containerWidth / 2) - (pluginData.smallFeatureWidth / 2);
                        new_fade = 0;
                    }
                }

                // This code block takes care of hiding the feature information if the feature is
                // NO LONGER going to be in the center (and the option is selected to do so)
                if (newPosition != 1 && options.hideFeatureInfo == true) {
                    // Slide up the story information
                    $feature.find(".storyinfo")
                        .slideUp("fast");
                }

                // Animate the feature div to its new location
                $feature
                    .animate(
                        {
                            width: new_width,
                            top: new_top,
                            left: new_left,
                            padding: new_padding,
                            opacity: new_fade
                        },
                        options.carouselSpeed,
                        'linear',
                        function () {
                            // if hiding option set and in center, take feature info out of hiding
                            if (newPosition == 1 && options.hideFeatureInfo == true) {
                                // slide down the story information
                                $feature.find(".storyinfo")
                                    .show();
                            }
                            // decrement the animation queue
                            pluginData.animationQueue = pluginData.animationQueue - 1;
                            // have to change the z-index after the animation is done
                            $feature.css("z-index", new_zindex);
                            // change blips if using them
                            if (options.useBlips == true) {
                                if (newPosition == 1) {
                                    // figure out what item was just in the center, and what item is now in the center
                                    var newCenterItemNum = pluginData.featuresContainer.children("div").index($feature) + 1;
                                    var oldCenterItemNum;
                                    if (direction == false)
                                        oldCenterItemNum = getNextNum(newCenterItemNum);
                                    else
                                        oldCenterItemNum = getPreviousNum(newCenterItemNum);
                                    // now change the active blip
                                    changeBlip(oldCenterItemNum, newCenterItemNum);
                                }
                            }

                            // did all the the animations finish yet?
                            var divide = pluginData.animationQueue / pluginData.itemsToAnimate;
                            if (divide % 1 == 0) {
                                // if so, set moving to false...
                                pluginData.currentlyMoving = false;
                                // change positions for all items...
                                rotatePositions(direction);
                                // and move carousel again if queue is not empty
                                if (pluginData.animationQueue > 0)
                                    move(direction);
                            }

                            // call autoplay again
                            autoPlay();
                        }
                    )
                    // select the image within the feature
                    .find(".featureImage")
                        // animate its size down
                        .animate({
                            width: new_width
                        },
                        options.carouselSpeed,
                        'linear')
                    .end();

                // No need to animate the text if we are hiding the feature info during animation
                // Only do so if we aren't hiding it
                if (options.hideFeatureInfo == false) {
                    $feature
                        .find(".header")
                            .animate({
                                fontSize: new_headerfont
                            },
                            options.carouselSpeed,
                            'linear')
                        .end()
                        .find(".description")
                            .animate({
                                fontSize: new_descriptionfont
                            },
                            options.carouselSpeed,
                            'linear')
                        .end();

                    // If in center, fade in other images within the feature
                    if (newPosition == 1) {
                        if ($.browser.msie == false) {
                            $feature
                                .find("img:not(.featureImage)")
                                    .fadeIn();
                        } else {
                            $feature
                                .find("img:not(.featureImage)")
                                    .show();
                        }
                    // If not in center, fade OUT other images within the feature
                    // This way don't have to worry about resizing them for animation
                    } else {
                        if ($.browser.msie == false) {
                            $feature
                                .find("img:not(.featureImage)")
                                    .fadeOut();
                        } else {
                            $feature
                                .find("img:not(.featureImage)")
                                    .hide();
                        }
                    }
                }
            }

            // move the carousel to the left or to the right
            // Rotate to the RIGHT when direction is TRUE and
            // rotate to the LEFT when direction is FALSE
            function move(direction)
            {
                // Set the carousel to currently moving
                pluginData.currentlyMoving = true;

                // Obtain the new feature positions based on the direction that the carousel is moving
                var $newCenter, $newLeft, $newRight, $newHidden;
                if (direction == true) {
                    // Shift features to the left
                    $newCenter = getContainer(getNextNum(pluginData.currentCenterNum));
                    $newLeft = getContainer(pluginData.currentCenterNum);
                    $newRight = getContainer(getNextNum(getNextNum(pluginData.currentCenterNum)));
                    $newHidden = getContainer(getPreviousNum(pluginData.currentCenterNum));
                    pluginData.currentCenterNum = getNextNum(pluginData.currentCenterNum);
                } else {
                    $newCenter = getContainer(getPreviousNum(pluginData.currentCenterNum));
                    $newLeft = getContainer(getPreviousNum(getPreviousNum(pluginData.currentCenterNum)));
                    $newRight = getContainer(pluginData.currentCenterNum);
                    $newHidden = getContainer(getNextNum(pluginData.currentCenterNum));
                    pluginData.currentCenterNum = getPreviousNum(pluginData.currentCenterNum);
                }

                // The z-index must be set before animations take place for certain movements
                // this makes the animations look nicer
                if (direction) {
                    $newLeft.css("z-index", 3);
                } else {
                    $newRight.css("z-index", 3);
                }
                $newCenter.css("z-index", 4);

                // Animate the features into their new positions
                animateFeature($newLeft, direction);
                animateFeature($newCenter, direction);
                animateFeature($newRight, direction);
                // Only want to animate the "hidden" feature if there are more than three
                if (pluginData.totalFeatureCount > 3) {
                    animateFeature($newHidden, direction);
                }
            }

            // This is used to relegate carousel movement throughout the plugin
            // It will only initiate a move if the carousel isn't currently moving
            // It will set the animation queue to the number of rotations given
            function initiateMove(direction, rotations) {
                if (pluginData.currentlyMoving == false) {
                    var queue = rotations * pluginData.itemsToAnimate;
                    pluginData.animationQueue = queue;
                    move(direction);
                }
            }

            // This will find the shortest distance to travel the carousel from
            // one position to another position. It will return the shortest distance
            // in number form, and will be positive to go to the right and negative for left
            function findShortestDistance(from, to) {

                var goingToLeft = 1, goingToRight = 1, tracker;
                tracker = from;
                // see how long it takes to go to the left
                while ((tracker = getPreviousNum(tracker)) != to) {
                    goingToLeft++;
                }

                tracker = from;
                // see how long it takes to to to the right
                while ((tracker = getNextNum(tracker)) != to) {
                    goingToRight++;
                }

                // whichever is shorter
                return (goingToLeft < goingToRight) ? goingToLeft*-1 : goingToRight;
            }

            $(".leftButton").click(function () {
                initiateMove(false,1);
            });

            $(".rightButton").click(function () {
                initiateMove(true,1);
            });

            pluginData.featuresContainer.children("div")
                .click(function () {
                    var position = $(this).data('position');
                    if (position == 2) {
                        initiateMove(true,1);
                    } else if (position == pluginData.totalFeatureCount) {
                        initiateMove(false,1);
                    }
                })
                .mouseover(function () {
                    if (pluginData.currentlyMoving == false) {
                        var position = $(this).data('position');
                        if (position == 2 || position == pluginData.totalFeatureCount) {
                            $(this).css("opacity",0.8);
                        }
                    }
                })
                .mouseout(function () {
                    if (pluginData.currentlyMoving == false) {
                        var position = $(this).data('position');
                        if (position == 2 || position == pluginData.totalFeatureCount) {
                            $(this).css("opacity",0.4);
                        }
                    }
                });

            // Add event listener to all clicks within the features container
            // This is done to disable any links that aren't within the center feature
            $("a", pluginData.containerIDTag).live("click", function (event) {
                var $parents = $(this).parentsUntil(pluginData.containerIDTag);
                $parents.each(function () {
                    var position = $(this).data('position');
                    if (position != undefined) {
                        if (position != 1) {
                            if (position == pluginData.totalFeatureCount) {
                                initiateMove(false,1);
                            } else if (position == 2) {
                                initiateMove(true,1);
                            }
                            event.preventDefault();
                            return false;
                        } else {
                            // do nothing... just let the link pass thru
                        }
                    }
                })
            });

            $(".blip").live("click",function () {
                var goTo = $(this).attr("id").substring(5);
                var whereIsIt = pluginData.featuresContainer.children("div").eq(goTo-1);
                whereIsIt = whereIsIt.data('position');
                var currentlyAt = pluginData.currentCenterNum;
                if (goTo != currentlyAt) {
                    var shortest = findShortestDistance(1, whereIsIt);
                    if (shortest < 0) {
                        initiateMove(false,(shortest*-1));
                    } else {
                        initiateMove(true,shortest);
                    }
                }

            })

            /* These are a bit sketchy in the way they mess with the carousel.
             * they work if each clicked once, but if any of the keys are held down
             * they will eventually screw up the carousel
            $(window).keydown(function(event){
                switch (event.keyCode) {
                    case 13: //enter
                        initiateMove(true,1);
                        break;
                    case 32: //space
                        initiateMove(true,1);
                        break;
                    case 37: //left arrow
                        initiateMove(false,1);
                        break;
                    case 39: //right arrow
                        initiateMove(true,1);
                        break;
                }
            });
            */
        });
    }

    $.fn.featureCarousel.defaults = {
        // all calcuations are based on this value. Carousel wont extend past it
        containerWidth:         800,
        // multiplied by the container width to get the width of the center feature
        // typically from .5 to .7
        centerFeatureFactor:    .65,
        // mulitplied by the center feature width to get the side feature width
        // typically from .3 to .6
        smallFeatureFactor:     .5,
        // spacing on top of the two side features (pixels)
        topPadding:             20,
        // spacing between the sides of the container (pixels)
        sidePadding:            30,
        // indicates which feature to start the carousel at
        startingFeature:        1,
        // speed in milliseconds it takes to rotate the carousel
        carouselSpeed:          500,
        // the padding within the features <div> (good if adding a border)
        featurePadding:         5,
        // font size for the header text of the side features
        smallHeaderSize:        9,
        // font size for the description text of the side features
        smallDescriptionSize:   6,
        // font size for the header text of the center feature
        featureHeaderSize:      14,
        // font size for the description text of the center feature
        featureDescriptionSize: 12,
        // time in milliseconds to set interval to autorotate the carousel
        // set to zero to disable it, negative to go left
        autoPlay:               0,
        // set to true to only show the feature description text and whatnot
        // when it is in the center. Otherwise it will animate
        hideFeatureInfo:        false,
        // set to true to enable the creation of blips to indicate how many
        // features there are
        useBlips:               true,
        // true to preload all images in the carousel before displaying anything
        preload:                false
    };

})(jQuery);