/*
File: carousel.js

About: Version
    1.0

Description:
    Provides behavioral functionality for carousels. This is a shared module available for any feature carousel modules.

Requires:
    - jQuery 1.3.2 <http://jquery.com>
    - jQuery Flash (Schematic customized)
    - jQuery Formulate (Schematic customized)
    - jQuery jCarouselLite (Schematic customized)
    - jQuery Easing

Requires:
    - <bootstrap.js>
    - <namespace.js>
    - <global.js>
    
*/

/*
Class: CLEAR
    Scoped to the CLEAR Global Namespace
*/
var CLEAR = window.CLEAR || {};

/*
Namespace: CLEAR.com
    Under the CLEAR.com Local Namespace
*/
CLEAR.com = CLEAR.com || {};

/*
Namespace: CLEAR.com.Carousel
    Under the Carousel Subnamespace
*/
CLEAR.com.Carousel = CLEAR.com.Carousel || {};

// When the DOM is ready.
(function () {
    
    var $space = CLEAR.com,
        $self = this;
    
    /*
    Namespace: CLEAR.com.Carousel.vars
        Shared local variables
    */
    $self.vars = {
        
        /*
        variable: movies
            Stores a cache of Flash movies to generate
        */
        movies : {},
        
        /*
        variable: timer
            A general speed to use for animations
        */
        timer : 800,
        
        /*
        variable: autoTimer
            The amount to wait in between slide animation
        */
        autoTimer : 6000,
        
        /*
        variable: arrows
            Returns left/right navigation arrows HTML
        */
        arrows : (function() {
            var div = function(val) {
                return '<div class="arrows ' + val.toLowerCase() + '-slide"><a href="#">' + val + '</a></div>';
            };
            
            return div("Previous") + div("Next");
        })(),
        
        /*
        variable: mask
            Returns top/right/bottom/left HTML masks
        */
        mask : (function(pos) {
            var span = function(pos) {
                return '<span class="feature-mask ' + pos + '"></span>';
            };
            
            return span("top") + span("right") + span("bottom") + span("left");
        })(),
        
        /*
        variable: icon
            Returns an empty div for use with preloading
        */
        icon : '<div class="feature-carousel-preloader-icon"></div>',
        
        /*
        variable: border
            Returns an empty div for use with drawing thumbnail borders
        */
        border : '<div class="feature-carousel-border"></div>'
    };
    
    /*
    Namespace: CLEAR.com.Carousel.utils
        Shared local utilities
    */
    $self.utils = {
        
        /*
        sub: addMovie
            Adds a movie reference to a Flash queue
            
        parameters:
            movie - The movie reference to queue. This reference must include:
                - id: The ID of the parent list item
                - url: The Flash wrapper path
                - flashvars: An object containing the FLV URL
        */
        addMovie : function(movie) {
            $self.vars.movies[movie.id] = movie;
        },
        
        /*
        sub: removeMovie
            Removes a Flash object from the DOM
            
        parameters:
            movie - The movie object to remove from the Document
        */
        removeMovie : function(movie) {
            $("#" + movie.id + "-flash").parent().remove();
            movie.loaded = false;
        },
        
        /*
        sub: pauseMovie
            Sends a Flash event to pause the target Flash video

        parameters:
            movie - The movie element to send the request
        */
        pauseMovie : function(movie) {
            return movie.pauseVideo();
        },
        
        /*
        sub: playMovie
            Sends a Flash event to play the target Flash video

        parameters:
            movie - The movie element to send the request
        */
        playMovie : function(movie) {
            return movie.playVideo();
        },
        
        /*
        sub: fade
            A generic fade utility for use in the Carousel namespace
        
        parameters:
            el - The element to fade
            value - The value to fade to
            easing - (optional) The easing to use while fading
            callback - (optional) Calls a function after fade is complete
        */
        fade : function(el, value, easing, callback) {
            el.stop().animate({
                opacity : value
            }, {
                duration : $self.vars.timer / 1.5,
                easing : easing,
                complete : callback
            });
        },
        
        /*
        sub: loadMovieSlide
            Handles the Flash movie injection and related events (such as listening for the Flash movie to end)

        parameters:
            slide - The parent list item to append the generated movie
            movie - The movie reference in the Flash queue. See <addMovie>
        */
        loadMovieSlide : function(slide, movie) {
            var dummy = $('<span></span>').appendTo(slide),
            
                // Get Movie URL
                url = movie.url;
            
            // Defaults
            var flashobject = {
                "name" : movie.id + "-flash",
                "id" : movie.id + "-flash",
                "class" : "flash-content",
                "src" : url,
                "width" : 640,
                "height" : 480,
                "allowfullscreen" : true,
                "allowscriptaccess" : "always",
                "flashvars" : movie.flashvars,
                "wmode" : "opaque"
            };
            
            // Flash!
            dummy.flash(flashobject);
            
            // Bind event listener to detect when movie ends
            $(document).one("FlashMovieEnd", function(e) {
                $self.utils.unbindMovieControllerEvents(slide);
                $self.utils.removeMovie(movie);
            });
            
            // Flag movie as loaded so we don't keep reloading it
            movie.loaded = true;
            
            $self.utils.bindMovieControllerEvents(slide);
        },
        
        /*
        sub: bindMovieControllerEvents
            Binds the controller show/hide events. These are toggled when a Flash movie is active.
        
        parameters:
            slide - The Flash movie container to target
        */
        bindMovieControllerEvents : function(slide) {
            var controller = $self.vars.controller,
                _timer = null;
            
            $self.utils.minimizeController();
            
            var mousemove = function(e) {
                if (_timer) {
                    window.clearTimeout(_timer);
                }
                
                if (!controller.data("maximizing")) {
                    $self.utils.maximizeController();
                }
                
                _timer = window.setTimeout($self.utils.minimizeController, 2000);
            };
            
            slide.bind("mousemove", mousemove);
            
            controller.bind("mouseenter", function() {
                slide.unbind("mousemove");
                
                if (_timer) {
                    window.clearTimeout(_timer);
                }
                
                $self.utils.maximizeController();
            });
            
            controller.bind("mouseleave", function() {
                $self.utils.minimizeController();
                slide.bind("mousemove", mousemove);
            });
        },
        
        /*
        sub: unbindMovieControllerEvents
            Removes the mouse event handlers once the Flash movie slide is inactive
            
        parameters:
            slide - The Flash movie container to target
        */
        unbindMovieControllerEvents : function(slide) {
            var controller = $self.vars.controller;
            
            // Unbind Slider
            $self.vars.slides.unbind("mousemove");
            
            // Unbind Controller
            controller.trigger("mouseenter");
            controller.unbind("mouseenter");
            controller.unbind("mouseleave");
        },
        
        /*
        sub: minimizeController
            Slides the controller down when activated
        */
        minimizeController : function() {
            var controller = $self.vars.controller;
            controller.removeData("maximizing");
            
            controller.stop().animate({
                top : 130
            }, {
                duration : 500
            });
        },
        
        /*
        sub: maximizeController
            Returns the controller to its original position when activated
        */
        maximizeController : function() {
            var controller = $self.vars.controller;
            controller.data("maximizing", true);
            
            controller.stop().animate({
                top : 0
            }, {
                duration : 500,
                complete : function() {
                    controller.removeData("maximizing");
                }
            });
        },
        
        /*
        sub: beforeSlide
            Prepares the carousel slides and controller for the slide animation. It positions elements absolutely to prevent glitching during animation.
            
        parameters:
            el - The destination controller element. Reference is passed in to calculate its offset value.
            active - The current controller element. Reference is passed in to calculate the current index.
        */
        beforeSlide : function(el, active) {
            var items = $(".feature-carousel-controller li"),
                index = Math.max(items.index(el), items.index(active)) + 1,
                rightOfActiveSlide = items.splice(index, items.length);
            
            $.each(rightOfActiveSlide, function(i, item) {
                var el = $(item),
                    pos = el.position();
                
                el.css({
                    left : pos.left,
                    top : pos.top
                });
            });
            
            $(rightOfActiveSlide).addClass("absolute-slide");
            
            $self.utils.unbindMovieControllerEvents();
        },
        
        /*
        sub: slide
            Handles the carousel slide animation.
            
        parameters:
            el - The element to animate.
            value - The destination width.
            callback - (optional) Fires a function once the slide animation completes.
        */
        slide : function(el, value, callback) {
            el.stop(true, true).animate({
                width : value
            }, {
                duration : $self.vars.timer,
                complete : function() {
                    
                    if (!$self.vars.totalWidth) {
                        $self.utils.setTotalListWidth();
                    }
                    
                    if (callback) {
                        callback();
                    }
                }
            });
        },
        
        /*
        sub: afterSlide
            Resets positioning on elements with .absolute-slide after slide animation ends.
        */
        afterSlide : function() {
            $(".absolute-slide").css({
                left : "",
                top : ""
            }).removeClass("absolute-slide");
        },
        
        /*
        sub: setTotalListWidth
            Calculates the total list width of the carousel. Value is stored in CLEAR.com.Carousel.vars.totalWidth.
        */
        setTotalListWidth : function() {
            var triggers = $self.vars.triggers;
            
            var totalWidth = (function() {
                var width = 0;
                
                triggers.each(function() {
                    width += $(this).outerWidth(true);
                });

                return width;
            })();

            triggers.parent().width(totalWidth);
            $self.vars.totalWidth = totalWidth;
        },
        
        /*
        sub: getTotalWidth
            Returns an element's total width (including padding and borders).
            
        parameters:
            el - The element to target
        */
        getTotalWidth : function(el) {
            return el.outerWidth(true);
        },
        
        /*
        sub: getRelatedController
            Returns a target slide's matching controller element (based on the element's index)
            
        parameters:
            slide - The slide to target.
        */
        getRelatedController : function(slide) {
            var index = $self.vars.slides.index(slide);
            return $self.vars.triggers.eq(index);
        },
        
        /*
        sub: triggerPreview
            Handles the controller element's click event. This function triggers the slide animation, and marks its controller element as active.
        
        parameters:
            e - Captures the target's event
        */
        triggerPreview : function(e) {
            if (e) {
                e.preventDefault();
            }

            var el = $(this),
                triggers = $self.vars.triggers,
                wrap = triggers.parent().parent(),
                active, slideParent;

            if (el.hasClass("loading")) {
                return false;
            }

            active = triggers.filter(".active");
            
            slideParent = function(controller, value) {
                controller.parent().animate({
                    left : value
                }, {
                    duration : $self.vars.timer
                });
            };
            
            if (!el.hasClass("active")) {
                
                if ($self.vars.home) {
                        
                    var offset = (el.outerWidth(true) * triggers.index(el));
                    offset = Math.min(offset, 150 * (triggers.length - 1) + 350 - wrap.width());
                    offset = Math.max(0, offset);
                    slideParent(el, -offset);
                    
                    $self.utils.fade(active.find("img"), 0.4);
                    $self.utils.fade(active.find(".feature-carousel-border"), 1);

                    $self.utils.fade(el.find("img"), 1);
                    $self.utils.fade(el.find(".feature-carousel-border"), 0);

                    if (active.get(0)) {
                        $self.utils.beforeSlide(el, active);
                        $self.utils.slide(active, 150);
                    }

                    $self.utils.slide(el, 350, $self.utils.afterSlide);
                }

                active.removeClass("active");
                el.addClass("active");
            }

        }
    };
    
    /*
    Namespace: CLEAR.com.Carousel
        Under the Carousel Subnamespace
    */
    
    /*
    Function: settings
        Caches a series of settings to reference within Clear.com.Carousel
    */
    $self.settings = function() {
        var carousel = $("#feature"),
            slider = carousel.find(".feature-carousel-slides"),
            slides = slider.find("li"),
            content = slides.find(".feature-carousel-content").not(".fallback-content"),
            controller = carousel.find(".feature-carousel-controller"),
            triggers = controller.find("li");

        $self.vars.Carousel = carousel;
        $self.vars.slider = slider;
        $self.vars.slides = slides;
        $self.vars.content = content;
        $self.vars.controller = controller;
        $self.vars.triggers = triggers;
        $self.vars.auto = true;
    };
    
    /*
    Function: preload
        Preloads carousel thumbnails. 
        Each image is located and called via Ajax. 
        Once complete, a flag is set and the Carousel continues initializing.
    */
    $self.preload = function() {
        
        var controller = $self.vars.controller,
            content = $self.vars.content,
            images = controller.find("img"),
            i = 0, j = 0, thumbsAreDone, assetsAreDone;
        
        var assets = [
            "/images/home/carousel/ajax-loader.gif",
            "/images/home/carousel/ajax-logo-preloader.png",
            "/images/home/carousel/sprite-carousel.png"
        ];
        
        images.each(function() {
            assets.push($(this).attr("src"));
        });
        
        // Remove display none
        $("body").addClass("feature-carousel-initialized");
        
        var finished = function() {
            if (/*thumbsAreDone && */assetsAreDone) {
                // Show controller
                $self.utils.fade(controller, 1, null, function() {
                    controller.css("filter", "");
                });
                
                // Initialize!
                $space.utils.init($self.functions);
            }
        };
        
        if (assets[3]) {
            
            // Hide our controller until assets have loaded
            controller.css("opacity", 0);
            content.css("opacity", 0);

            $.each(assets, function(i, asset) {

                $.ajax({
                    type : "GET",
                    url : asset,
                    cache : true,
                    dataType : "image",
                    complete : function(e) {
                        if (++i === assets.length) {
                            // Flag assets as done
                            assetsAreDone = true;
                            // finish
                            finished();
                        }
                    }
                });
            });
        } else {
            assetsAreDone = true;
            finished();
        }
    };
    
    /*
    Namespace: CLEAR.com.Carousel.functions
        These functions are namespaced in order to prevent automatically firing on DOM Ready. 
        They instead need to be fired once the carousel thumbnails are done preloading.
    */
    $self.functions = (function() {
        return {
            
            /*
            sub: carouselContent
                Binds event handlers based on carousel contents. 
                Slides with movies need to have its click event generate a Flash movie, 
                while all others need to redirect to a URL based on a link inside the carousel content parent
            */
            carouselContent : function() {
                var content = $self.vars.slides.find(".feature-carousel-content");
                
                content.each(function() {
                    var el = $(this),
                        link = el.find("a").not(".overlay-link"),
                        parent = el.parent(),
                        movie = $self.vars.movies[parent.attr("id")];
                    
                    parent.bind("click", function(e) {
                        e.preventDefault();
                        
                        if (movie && !movie.loaded) {
                            
                            // Click the controller to stop auto-rotation
                            var controller = $self.utils.getRelatedController(parent);
                            controller.trigger("click");
                            
                            // Load the movie
                            $self.utils.loadMovieSlide(parent, movie);
                        } else if (link.attr("href")) {
                            location.href = link.attr("href");
                        }
                    });
                    
                });
            },

            /*
            sub: loadCarouselSlides
                Preloads carousel slides, locating background images within each slide and queueing them up. 
                The images are then called with Ajax. Once the final image is loaded, a flag is set and the carousel continues its load methods.
            */
            loadCarouselSlides : function() {
                var carousel = $self.vars.Carousel,
                    items = $self.vars.slides,
                    triggers = $self.vars.triggers,
                    item, src, url;
                
                // Attach ajax preloaders to our thumbnails
                triggers.addClass("loading");
                triggers.append($self.vars.icon);
                triggers.find(".preview").append($self.vars.border);

                // Prep image-based content
                var i = 0;

                var iterate = function() {
                    i++;

                    if (items.get(i)) {
                        // loop if necessary
                        preload();
                    }

                };

                var preload = function() {

                    item = items.eq(i);
                    url = item.find("img").attr("src");

                    if (url) {
                        
                        var onComplete = function() {
                            var controller = $self.utils.getRelatedController(item);

                            controller.removeClass("loading");
                            controller.find(".feature-carousel-preloader-icon").fadeOut(250, function(i) {
                                $(this).remove();

                                // First one is done loading. Make thumbnail active.
                                if ((i === 0) && (!controller.hasClass("active"))) {
                                    $self.utils.triggerPreview.call(controller);
                                }
                            }(i));
                            
                            var img = item.find("img");
                            
                            if (img.get(0)) {
                                item.css("background", "url(" + url + ") no-repeat 0 0");
                                img.remove();
                            }

                            iterate();
                        };

                        $.ajax({
                            type : "GET",
                            url : url,
                            cache : true,
                            dataType : "image",
                            complete : onComplete
                        });

                    } else {
                        iterate();
                    }
                };

                preload();
            },

            /*
            sub: previewControl
                Binds event handlers to each controller trigger. 
                - Adds hover faders to each controller
                - Adds click events that triggers <CLEAR.com.Carousel.utils.triggerPreview>
                - Stops propagation on links inside .more-information (so they behave as normal)
                - Triggers mouseleave event, in case the controller is collapsed due to Flash functionality
            */
            previewControl : function() {

                var carousel = $self.vars.Carousel,
                    triggers = $self.vars.triggers;

                // Prep triggers
                $($self.vars.arrows).appendTo(triggers.parent().parent());

                $(".arrows").click(function(e) {
                    e.preventDefault();
                    
                    if (!e.originalEvent || (e.originalEvent && !e.originalEvent.preventStop)) {
                        window.clearTimeout($self.vars.auto);
                        $self.vars.auto = false;
                    }
                });
                
                if ($self.vars.slides.length < 2) {
                    $(".arrows").hide();
                }
                
                triggers.find("img").css("opacity", 0.4);
                
                triggers.click(function(e) {
                    if ($self.vars.isAnimating) {
                        return;
                    }
                    
                    if (!e.originalEvent || (e.originalEvent && !e.originalEvent.preventStop)) {
                        window.clearTimeout($self.vars.auto);
                        $self.vars.auto = false;
                    }
                    
                    $self.utils.triggerPreview.call(this, e);
                });
                
                triggers.find(".more-information a").click(function(e) {
                    e.stopPropagation();
                });

                triggers.hover(function() {
                    var el = $(this);

                    if (!el.hasClass("loading") && !el.hasClass("active")) {
                        $self.utils.fade(el.find("img"), 1);
                    }
                }, function() {
                    var el = $(this);

                    if (!el.hasClass("loading") && !el.hasClass("active")) {
                        $self.utils.fade(el.find("img"), 0.4);
                    }
                });

                triggers.trigger("mouseleave");

                // Inject rounded corner mask
                $($self.vars.mask).appendTo(carousel);
            },

            /*
            sub: carouselProperties
                Applies jCarouselLite to the carousel. 
                This is the heart of the carousel functionality. 
                Definitions for each jCarouselLite setting can be found in the jquery.jcarousellite.js
            */
            carouselProperties : function(selector) {
                var triggers = $self.vars.triggers;

                // Call jCarouselLite
                $("#feature .feature-carousel-slides").jCarouselLite({
                    clickDuringAnim : false,
                    btnNext : ".feature-carousel-controller .next-slide",
                    btnPrev : ".feature-carousel-controller .previous-slide",
                    btnGo : (function() {
                        var array = [];

                        $.each(triggers, function(i, preview) {
                            array.push(this);
                        });

                        return array;
                    })(),
                    speed : $self.vars.timer,
                    visible : 1,
                    auto : $self.vars.autoTimer,
                    circular : false,
                    onStart : function(slide) {
                        $self.vars.isAnimating = true;
                        
                        // Do stuff to our new slide
                        controller = $self.utils.getRelatedController(slide);
                        
                        var selfMovie = $self.vars.movies[slide.attr("id")];

                        if (selfMovie && !selfMovie.loaded) {
                            controller.find(".more-information a").click(function(e) {
                                e.preventDefault();
                                
                                if (!slide.find(".flash-content").get(0)) {
                                    $(this).closest("li").trigger("click");
                                    $self.utils.loadMovieSlide(slide, selfMovie);
                                }
                            });
                        }
                        
                        var movies = slide.siblings().find(".flash-content");
                        if (movies.get(0)) {
                            movies.each(function(i, movie) {
                                $self.utils.pauseMovie(movie);
                            });
                        }
                        
                        if (controller.hasClass("active")) {
                            return;
                        }
                        
                        $self.utils.triggerPreview.call(controller);
                    },
                    afterEnd : function(slide) {

                        // Hide content on all other slides
                        var siblings = slide.siblings(),
                            others = siblings.find(".feature-carousel-content");

                        others.each(function(i, item) {
                            var el = $(this),
                                parent = el.closest("li");

                            var movie = $self.vars.movies[parent.attr("id")];

                            if (movie && movie.loaded) {
                                $self.utils.removeMovie(movie);
                            }
                        });
                        
                        var last = triggers.eq(triggers.length - 1);
                        
                        // Cycle if true
                        if (last.hasClass("active") && $self.vars.auto) {
                            $self.vars.auto = window.setTimeout(function() {
                                var evt = $.Event({
                                    type : "click",
                                    preventStop : true
                                });
                                
                                triggers.eq(0).trigger(evt);
                            }, $self.vars.autoTimer);
                        }
                        
                        $self.vars.isAnimating = false;
                    }
                });
            }
        };
    })();
    
    /*
    Callback: init
        Sends local functions to a global queuer for initialization See: <CLEAR.utils.init>
    */
    $self.init = function() {
        
        // Let's set a flag for IE 6
        $.extend($.browser, {
            ie6 : function () {
                return !!($.browser.msie && $.browser.version == 6);
            }()
        });
        
        // Yay! No carousel for IE 6.
        if (!$.browser.ie6) {
            
            // Set a flag for home
            $self.vars.home = $("body").hasClass("home");
            
            // Pass namespace to initialization function
            $space.utils.init($self);
        }
        
    }();

}).call(CLEAR.com.Carousel);