/**

 * 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,

                     width: false,

                     height: false,

                     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) {

            thumb_wrapper_width += 10;

            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).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')) {

            context.nextImage();

            context.slideshow.stop();
          } else {

            context.prevImage();

            context.slideshow.stop();

          };


          return false;

        }

      ).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);
