/**
* Copyright (c) 2009 Anders Ekdahl (http://coffeescripter.com/)
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Version: 1.2.2
*
* Demo and documentation: http://coffeescripter.com/code/ad-gallery/
*/
(function($) {
    $.fn.adGallery = function(options) {
        var defaults = { loader_image: 'loader.gif',
            start_at_index: 0,
            thumb_opacity: 0.7,
            animate_first_image: false,
            animation_speed: 400,
            display_next_and_prev: true,
            display_back_and_forward: true,
            scroll_jump: 0, // If 0, it jumps the width of the container
            slideshow: {
                enable: true,
                autostart: false,
                speed: 5000,
                start_label: 'Start',
                stop_label: 'Stop',
                stop_on_scroll: true,
                countdown_prefix: '(',
                countdown_sufix: ')',
                onStart: false,
                onStop: false
            },
            effect: 'slide-hori', // or 'slide-vert', 'fade', or 'resize', 'none'
            enable_keyboard_move: true,
            cycle: true,
            callbacks: {
                init: false,
                afterImageVisible: false,
                beforeImageVisible: false
            }
        };
        var settings = $.extend(false, defaults, options);
        if (options && options.slideshow) {
            settings.slideshow = $.extend(false, defaults.slideshow, options.slideshow);
        };
        if (!settings.slideshow.enable) {
            settings.slideshow.autostart = false;
        };
        var galleries = [];
        $(this).each(function() {
            var gallery = new AdGallery(this, settings);
            galleries[galleries.length] = gallery;
        });
        // Sorry, breaking the jQuery chain because the gallery instances
        // are returned so you can fiddle with them
        return galleries;
    };

    function VerticalSlideAnimation(img_container, direction, desc) {
        var current_top = parseInt(img_container.css('top'), 10);
        if (direction == 'left') {
            var old_image_top = '-' + this.image_wrapper_height + 'px';
            img_container.css('top', this.image_wrapper_height + 'px');
        } else {
            var old_image_top = this.image_wrapper_height + 'px';
            img_container.css('top', '-' + this.image_wrapper_height + 'px');
        };
        if (desc) {
            desc.css('bottom', '-' + desc[0].offsetHeight + 'px');
            desc.animate({ bottom: 0 }, this.settings.animation_speed * 2);
        };
        return { old_image: { top: old_image_top },
            new_image: { top: current_top}
        };
    };

    function HorizontalSlideAnimation(img_container, direction, desc) {
        var current_left = parseInt(img_container.css('left'), 10);
        if (direction == 'left') {
            var old_image_left = '-' + this.image_wrapper_width + 'px';
            img_container.css('left', this.image_wrapper_width + 'px');
        } else {
            var old_image_left = this.image_wrapper_width + 'px';
            img_container.css('left', '-' + this.image_wrapper_width + 'px');
        };
        if (desc) {
            desc.css('bottom', '-' + desc[0].offsetHeight + 'px');
            desc.animate({ bottom: 0 }, this.settings.animation_speed * 2);
        };
        return { old_image: { left: old_image_left },
            new_image: { left: current_left}
        };
    };

    function ResizeAnimation(img_container, direction, desc) {
        var image_width = img_container.width();
        var image_height = img_container.height();
        var current_left = parseInt(img_container.css('left'), 10);
        var current_top = parseInt(img_container.css('top'), 10);
        img_container.css({ width: 0, height: 0, top: this.image_wrapper_height / 2, left: this.image_wrapper_width / 2 });
        return { old_image: { width: 0,
            height: 0,
            top: this.image_wrapper_height / 2,
            left: this.image_wrapper_width / 2
        },
            new_image: { width: image_width,
                height: image_height,
                top: current_top,
                left: current_left}
            };
        };

        function FadeAnimation(img_container, direction, desc) {
            img_container.css('opacity', 0);
            return { old_image: { opacity: 0 },
                new_image: { opacity: 1}
            };
        };

        // Sort of a hack, will clean this up... eventually
        function NoneAnimation(img_container, direction, desc) {
            img_container.css('opacity', 0);
            return { old_image: { opacity: 0 },
                new_image: { opacity: 1 },
                speed: 0
            };
        };

        function AdGallery(wrapper, settings) {
            this.init(wrapper, settings);
        };
        AdGallery.prototype = {
            // Elements
            wrapper: false,
            image_wrapper: false,
            gallery_info: false,
            nav: false,
            loader: false,
            preloads: false,
            thumbs_wrapper: false,
            scroll_back: false,
            scroll_forward: false,
            next_link: false,
            prev_link: false,

            slideshow: false,
            image_wrapper_width: 0,
            image_wrapper_height: 0,
            current_index: 0,
            current_image: false,
            nav_display_width: 0,
            settings: false,
            images: false,
            in_transition: false,
            animations: false,
            init: function(wrapper, settings) {
                var context = this;
                this.wrapper = $(wrapper);
                this.settings = settings;
                this.setupElements();
                this.setupAnimations();
                if (this.settings.width) {
                    this.image_wrapper_width = this.settings.width;
                    this.image_wrapper.width(this.settings.width);
                    this.wrapper.width(this.settings.width);
                } else {
                    this.image_wrapper_width = this.image_wrapper.width();
                };
                if (this.settings.height) {
                    this.image_wrapper_height = this.settings.height;
                    this.image_wrapper.height(this.settings.height);
                } else {
                    this.image_wrapper_height = this.image_wrapper.height();
                };
                this.nav_display_width = this.nav.width();
                this.current_index = 0;
                this.current_image = false;
                this.in_transition = false;
                this.findImages();
                if (this.settings.display_next_and_prev) {
                    this.initNextAndPrev();
                };
                // The slideshow needs a callback to trigger the next image to be shown
                // but we don't want to give it access to the whole gallery instance
                var nextimage_callback = function(callback) {
                    return context.nextImage(callback);
                };
                this.slideshow = new AdGallerySlideshow(nextimage_callback, this.settings.slideshow);
                this.controls.append(this.slideshow.create());
                if (this.settings.slideshow.enable) {
                    this.slideshow.enable();
                } else {
                    this.slideshow.disable();
                };
                if (this.settings.display_back_and_forward) {
                    this.initBackAndForward();
                };
                if (this.settings.enable_keyboard_move) {
                    this.initKeyEvents();
                };
                var start_at = this.settings.start_at_index;
                if (window.location.hash && window.location.hash.indexOf('#ad-image') === 0) {
                    start_at = window.location.hash.replace(/[^0-9]+/g, '');
                    // Check if it's a number
                    if ((start_at * 1) != start_at) {
                        start_at = this.settings.start_at_index;
                    };
                };

                this.loading(true);
                this.showImage(start_at,
        function() {
            // We don't want to start the slideshow before the image has been
            // displayed
            if (context.settings.slideshow.autostart) {
                context.preloadImage(start_at + 1);
                context.slideshow.start();
            };
        }
      );
                this.fireCallback(this.settings.callbacks.init);
            },
            setupAnimations: function() {
                this.animations = {
                    'slide-vert': VerticalSlideAnimation,
                    'slide-hori': HorizontalSlideAnimation,
                    'resize': ResizeAnimation,
                    'fade': FadeAnimation,
                    'none': NoneAnimation
                };
            },
            setupElements: function() {
                this.controls = this.wrapper.find('.ad-controls');
                this.gallery_info = $('<p class="ad-info"></p>');
                this.controls.append(this.gallery_info);
                this.image_wrapper = this.wrapper.find('.ad-image-wrapper');
                this.image_wrapper.empty();
                this.nav = this.wrapper.find('.ad-nav');
                this.thumbs_wrapper = this.nav.find('.ad-thumbs');
                this.preloads = $('<div class="ad-preloads"></div>');
                this.loader = $('<img class="ad-loader" src="' + this.settings.loader_image + '">');
                this.image_wrapper.append(this.loader);
                this.loader.hide();
                $(document.body).append(this.preloads);
            },
            loading: function(bool) {
                if (bool) {
                    this.loader.show();
                } else {
                    this.loader.hide();
                };
            },
            addAnimation: function(name, fn) {
                if ($.isFunction(fn)) {
                    this.animations[name] = fn;
                };
            },
            findImages: function() {
                var context = this;
                this.images = [];
                var thumb_wrapper_width = 0;
                var thumbs_loaded = 0;
                var thumbs = this.thumbs_wrapper.find('a');
                var thumb_count = thumbs.length;
                if (this.settings.thumb_opacity < 1) {
                    thumbs.find('img').css('opacity', this.settings.thumb_opacity);
                };
                thumbs.each(
        function(i) {
            var link = $(this);
            var image_src = link.attr('href');
            var thumb = link.find('img');
            // Check if the thumb has already loaded
            if (!context.isImageLoaded(thumb[0])) {
                thumb.load(
              function() {
                  thumb_wrapper_width += this.parentNode.parentNode.offsetWidth;
                  thumbs_loaded++;
              }
            );
            } else {
                thumb_wrapper_width += thumb[0].parentNode.parentNode.offsetWidth;
                thumbs_loaded++;
            };
            link.addClass('ad-thumb' + i);
            link.click(
            function() {
                context.showImage(i);
                context.slideshow.stop();
                return false;
            }
          ).hover(
            function() {
                if (!$(this).is('.ad-active') && context.settings.thumb_opacity < 1) {
                    $(this).find('img').fadeTo(300, 1);
                };
                context.preloadImage(i);
            },
            function() {
                if (!$(this).is('.ad-active') && context.settings.thumb_opacity < 1) {
                    $(this).find('img').fadeTo(300, context.settings.thumb_opacity);
                };
            }
          );
            var desc = false;
            if (thumb.data('ad-desc')) {
                desc = thumb.data('ad-desc');
            } else if (thumb.attr('longdesc') && thumb.attr('longdesc').length) {
                desc = thumb.attr('longdesc');
            };
            var title = false;
            if (thumb.data('ad-title')) {
                title = thumb.data('ad-title');
            } else if (thumb.attr('title') && thumb.attr('title').length) {
                title = thumb.attr('title');
            };
            context.images[i] = { thumb: thumb.attr('src'), image: image_src, error: false,
                preloaded: false, desc: desc, title: title, size: false
            };
        }
      );
                // Wait until all thumbs are loaded, and then set the width of the ul
                var inter = setInterval(
        function() {
            if (thumb_count == thumbs_loaded) {
                context.nav.find('.ad-thumb-list').css('width', thumb_wrapper_width + 'px');
                clearInterval(inter);
            };
        },
        100
      );
            },
            initKeyEvents: function() {
                var context = this;
                $(document).keydown(
        function(e) {
            if (e.keyCode == 39) {
                // right arrow
                context.nextImage();
                context.slideshow.stop();
            } else if (e.keyCode == 37) {
                // left arrow
                context.prevImage();
                context.slideshow.stop();
            };
        }
      );
            },
            initNextAndPrev: function() {
                this.next_link = $('<div class="ad-next"><div class="ad-next-image"></div></div>');
                this.prev_link = $('<div class="ad-prev"><div class="ad-prev-image"></div></div>');
                this.image_wrapper.append(this.next_link);
                this.image_wrapper.append(this.prev_link);
                var context = this;
                this.prev_link.add(this.next_link).mouseover(
        function(e) {
            // IE 6 hides the wrapper div, so we have to set it's width
            $(this).css('height', context.image_wrapper_height);
            $(this).find('div').show();
        }
      ).mouseout(
        function(e) {
            $(this).find('div').hide();
        }
      ).click(
        function() {
            if ($(this).is('.ad-next')) {
                context.nextImage();
                context.slideshow.stop();
            } else {
                context.prevImage();
                context.slideshow.stop();
            };
        }
      ).find('div').css('opacity', 0.7);
            },
            initBackAndForward: function() {
                var context = this;
                this.scroll_forward = $('<div class="ad-forward"></div>');
                this.scroll_back = $('<div class="ad-back"></div>');
                this.nav.append(this.scroll_forward);
                this.nav.prepend(this.scroll_back);
                var has_scrolled = 0;
                var thumbs_scroll_interval = false;
                $(this.scroll_back).add(this.scroll_forward).click(
        function() {
            // We don't want to jump the whole width, since an image
            // might be cut at the edge
            var width = context.nav_display_width - 50;
            if (context.settings.scroll_jump > 0) {
                var width = context.settings.scroll_jump;
            };
            if ($(this).is('.ad-forward')) {
                var left = context.thumbs_wrapper.scrollLeft() + width;
            } else {
                var left = context.thumbs_wrapper.scrollLeft() - width;
            };
            if (context.settings.slideshow.stop_on_scroll) {
                context.slideshow.stop();
            };
            context.thumbs_wrapper.animate({ scrollLeft: left + 'px' });
            return false;
        }
      ).css('opacity', 0.6).hover(
        function() {
            var direction = 'left';
            if ($(this).is('.ad-forward')) {
                direction = 'right';
            };
            thumbs_scroll_interval = setInterval(
            function() {
                has_scrolled++;
                // Don't want to stop the slideshow just because we scrolled a pixel or two
                if (has_scrolled > 30 && context.settings.slideshow.stop_on_scroll) {
                    context.slideshow.stop();
                };
                var left = context.thumbs_wrapper.scrollLeft() + 1;
                if (direction == 'left') {
                    left = context.thumbs_wrapper.scrollLeft() - 1;
                };
                context.thumbs_wrapper.scrollLeft(left);
            },
            10
          );
            $(this).css('opacity', 1);
        },
        function() {
            has_scrolled = 0;
            clearInterval(thumbs_scroll_interval);
            $(this).css('opacity', 0.6);
        }
      );
            },
            _afterShow: function() {
                this.gallery_info.html((this.current_index + 1) + ' / ' + this.images.length);
                if (!this.settings.cycle) {
                    // Needed for IE
                    this.prev_link.show().css('height', this.image_wrapper_height);
                    this.next_link.show().css('height', this.image_wrapper_height);
                    if (this.current_index == (this.images.length - 1)) {
                        this.next_link.hide();
                    };
                    if (this.current_index == 0) {
                        this.prev_link.hide();
                    };
                };
                this.fireCallback(this.settings.callbacks.afterImageVisible);
            },
            /**
            * Checks if the image is small enough to fit inside the container
            * If it's not, shrink it proportionally
            */
            _getContainedImageSize: function(image_width, image_height) {
                if (image_height > this.image_wrapper_height) {
                    var ratio = image_width / image_height;
                    image_height = this.image_wrapper_height;
                    image_width = this.image_wrapper_height * ratio;
                };
                if (image_width > this.image_wrapper_width) {
                    var ratio = image_height / image_width;
                    image_width = this.image_wrapper_width;
                    image_height = this.image_wrapper_width * ratio;
                };
                return { width: image_width, height: image_height };
            },
            /**
            * If the image dimensions are smaller than the wrapper, we position
            * it in the middle anyway
            */
            _centerImage: function(img_container, image_width, image_height) {
                img_container.css('top', '0px');
                if (image_height < this.image_wrapper_height) {
                    var dif = this.image_wrapper_height - image_height;
                    img_container.css('top', (dif / 2) + 'px');
                };
                img_container.css('left', '0px');
                if (image_width < this.image_wrapper_width) {
                    var dif = this.image_wrapper_width - image_width;
                    img_container.css('left', (dif / 2) + 'px');
                };
            },
            _getDescription: function(image) {
                var desc = false;
                if (image.desc.length || image.title.length) {
                    var title = '';
                    if (image.title.length) {
                        title = '<strong class="ad-description-title">' + image.title + '</strong>';
                    };
                    var desc = '';
                    if (image.desc.length) {
                        desc = '<span>' + image.desc + '</span>';
                    };
                    desc = $('<p class="ad-image-description">' + title + desc + '</p>');
                };
                return desc;
            },
            /**
            * @param function callback Gets fired when the image has loaded, is displaying
            *                          and it's animation has finished
            */
            showImage: function(index, callback) {
                if (this.images[index] && !this.in_transition) {
                    var context = this;
                    var image = this.images[index];
                    this.in_transition = true;
                    if (!image.preloaded) {
                        this.loading(true);
                        this.preloadImage(index, function() {
                            context.loading(false);
                            context._showWhenLoaded(index, callback);
                        });
                    } else {
                        this._showWhenLoaded(index, callback);
                    };
                };
            },
            /**
            * @param function callback Gets fired when the image has loaded, is displaying
            *                          and it's animation has finished
            */
            _showWhenLoaded: function(index, callback) {
                if (this.images[index]) {
                    var context = this;
                    var image = this.images[index];
                    var img_container = $(document.createElement('div')).addClass('ad-image');
                    var img = $(new Image()).attr('src', image.image);
                    img_container.append(img);
                    this.image_wrapper.prepend(img_container);
                    var size = this._getContainedImageSize(image.size.width, image.size.height);
                    img.attr('width', size.width);
                    img.attr('height', size.height);
                    img_container.css({ width: size.width + 'px', height: size.height + 'px' });
                    this._centerImage(img_container, size.width, size.height);
                    var desc = this._getDescription(image, img_container);
                    if (desc) {
                        img_container.append(desc);
                        var width = size.width - parseInt(desc.css('padding-left'), 10) - parseInt(desc.css('padding-right'), 10);
                        desc.css('width', width + 'px');
                    };
                    this.highLightThumb(this.nav.find('.ad-thumb' + index));

                    var direction = 'right';
                    if (this.current_index < index) {
                        direction = 'left';
                    };
                    this.fireCallback(this.settings.callbacks.beforeImageVisible);
                    if (this.current_image || this.settings.animate_first_image) {
                        var animation_speed = this.settings.animation_speed;
                        var easing = 'swing';
                        var animation = this.animations[this.settings.effect].call(this, img_container, direction, desc);
                        if (typeof animation.speed != 'undefined') {
                            animation_speed = animation.speed;
                        };
                        if (typeof animation.easing != 'undefined') {
                            easing = animation.easing;
                        };
                        if (this.current_image) {
                            var old_image = this.current_image;
                            old_image.animate(animation.old_image, animation_speed, easing,
              function() {
                  old_image.remove();
              }
            );
                        };
                        img_container.animate(animation.new_image, animation_speed, easing,
            function() {
                context.current_index = index;
                context.current_image = img_container;
                context.in_transition = false;
                context._afterShow();
                context.fireCallback(callback);
            }
          );
                    } else {
                        this.current_index = index;
                        this.current_image = img_container;
                        this.in_transition = false;
                        context._afterShow();
                        this.fireCallback(callback);
                    };
                };
            },
            nextIndex: function() {
                if (this.current_index == (this.images.length - 1)) {
                    if (!this.settings.cycle) {
                        return false;
                    };
                    var next = 0;
                } else {
                    var next = this.current_index + 1;
                };
                return next;
            },
            nextImage: function(callback) {
                var next = this.nextIndex();
                if (next === false) return false;
                this.preloadImage(next + 1);
                this.showImage(next, callback);
                return true;
            },
            prevIndex: function() {
                if (this.current_index == 0) {
                    if (!this.settings.cycle) {
                        return false;
                    };
                    var prev = this.images.length - 1;
                } else {
                    var prev = this.current_index - 1;
                };
                return prev;
            },
            prevImage: function(callback) {
                var prev = this.prevIndex();
                if (prev === false) return false;
                this.preloadImage(prev - 1);
                this.showImage(prev, callback);
                return true;
            },
            preloadAll: function() {
                var context = this;
                var i = 0;
                function preloadNext() {
                    if (i < context.images.length) {
                        i++;
                        context.preloadImage(i, preloadNext);
                    };
                };
                context.preloadImage(i, preloadNext);
            },
            preloadImage: function(index, callback) {
                if (this.images[index]) {
                    var image = this.images[index];
                    if (!this.images[index].preloaded) {
                        var img = $(new Image());
                        img.attr('src', image.image);
                        if (!this.isImageLoaded(img[0])) {
                            this.preloads.append(img);
                            var context = this;
                            img.load(
              function() {
                  image.preloaded = true;
                  image.size = { width: this.width, height: this.height };
                  context.fireCallback(callback);
              }
            ).error(
              function() {
                  image.error = true;
                  image.preloaded = false;
                  image.size = false;
              }
            );
                        } else {
                            image.preloaded = true;
                            image.size = { width: img[0].width, height: img[0].height };
                            this.fireCallback(callback);
                        };
                    } else {
                        this.fireCallback(callback);
                    };
                };
            },
            isImageLoaded: function(img) {
                if (typeof img.complete != 'undefined' && !img.complete) {
                    return false;
                };
                if (typeof img.naturalWidth != 'undefined' && img.naturalWidth == 0) {
                    return false;
                };
                return true;
            },
            highLightThumb: function(thumb) {
                this.thumbs_wrapper.find('.ad-active').removeClass('ad-active');
                thumb.addClass('ad-active');
                if (this.settings.thumb_opacity < 1) {
                    this.thumbs_wrapper.find('a:not(.ad-active) img').fadeTo(300, this.settings.thumb_opacity);
                    thumb.find('img').fadeTo(300, 1);
                };
                var left = thumb[0].parentNode.offsetLeft;
                left -= (this.nav_display_width / 2) - (thumb[0].offsetWidth / 2);
                this.thumbs_wrapper.animate({ scrollLeft: left + 'px' });
            },
            fireCallback: function(fn) {
                if ($.isFunction(fn)) {
                    fn.call(this);
                };
            }
        };

        function AdGallerySlideshow(nextimage_callback, settings) {
            this.init(nextimage_callback, settings);
        };
        AdGallerySlideshow.prototype = {
            start_link: false,
            stop_link: false,
            countdown: false,
            controls: false,

            settings: false,
            nextimage_callback: false,
            enabled: false,
            running: false,
            countdown_interval: false,
            init: function(nextimage_callback, settings) {
                var context = this;
                this.nextimage_callback = nextimage_callback;
                this.settings = settings;
            },
            create: function() {
                this.start_link = $('<span class="ad-slideshow-start">' + this.settings.start_label + '</span>');
                this.stop_link = $('<span class="ad-slideshow-stop">' + this.settings.stop_label + '</span>');
                this.countdown = $('<span class="ad-slideshow-countdown"></span>');
                this.controls = $('<div class="ad-slideshow-controls"></div>');
                this.controls.append(this.start_link).append(this.stop_link).append(this.countdown);
                this.countdown.hide();

                var context = this;
                this.start_link.click(
        function() {
            context.start();
        }
      );
                this.stop_link.click(
        function() {
            context.stop();
        }
      );
                $(document).keydown(
        function(e) {
            if (e.keyCode == 83) {
                // 's'
                if (context.running) {
                    context.stop();
                } else {
                    context.start();
                };
            };
        }
      );
                return this.controls;
            },
            disable: function() {
                this.enabled = false;
                this.stop();
                this.controls.hide();
            },
            enable: function() {
                this.enabled = true;
                this.controls.show();
            },
            toggle: function() {
                if (this.enabled) {
                    this.disable();
                } else {
                    this.enable();
                };
            },
            start: function() {
                if (this.running || !this.enabled) return false;
                var context = this;
                this.running = true;
                this.controls.addClass('ad-slideshow-running');
                this._next();
                this.fireCallback(this.settings.onStart);
                return true;
            },
            stop: function() {
                if (!this.running) return false;
                this.running = false;
                this.countdown.hide();
                this.controls.removeClass('ad-slideshow-running');
                clearInterval(this.countdown_interval);
                this.fireCallback(this.settings.onStop);
                return true;
            },
            _next: function() {
                var context = this;
                var pre = this.settings.countdown_prefix;
                var su = this.settings.countdown_sufix;
                clearInterval(context.countdown_interval);
                this.countdown.show().html(pre + (this.settings.speed / 1000) + su);
                var slide_timer = 0;
                this.countdown_interval = setInterval(
        function() {
            slide_timer += 1000;
            if (slide_timer >= context.settings.speed) {
                var whenNextIsShown = function() {
                    // A check so the user hasn't stoped the slideshow during the
                    // animation
                    if (context.running) {
                        context._next();
                    };
                    slide_timer = 0;
                };
                if (!context.nextimage_callback(whenNextIsShown)) {
                    context.stop();
                };
                slide_timer = 0;
            };
            var sec = parseInt(context.countdown.text().replace(/[^0-9]/g, ''), 10);
            sec--;
            if (sec > 0) {
                context.countdown.html(pre + sec + su);
            };
        },
        1000
      );
            },
            fireCallback: function(fn) {
                if ($.isFunction(fn)) {
                    fn.call(this);
                };
            }
        };
    })(jQuery);