/**
 * Script: Slideshow.js Slideshow - A javascript class for Mootools to stream
 * and animate the presentation of images on your website.
 * 
 * License: MIT-style license.
 * 
 * Copyright: Copyright (c) 2008 [Aeron
 * Glemann](http://www.electricprism.com/aeron/).
 * 
 * Dependencies: Mootools 1.2 Core: Fx.Morph, Fx.Tween, Selectors,
 * Element.Dimensions. Mootools 1.2 More: Assets.
 */

Slideshow = new Class(
    {
    Implements: [Chain, Events, Options],
    options:
        { /*
           * onComplete: $empty, onEnd: $empty, onStart: $empty,
           */
        captions: false,
        center: true,
        classes: [],
        controller: false,
        delay: 2000,
        duration: 750,
        fast: false,
        height: false,
        href: '',
        hu: '',
        linked: false,
        loader:
            {
            'animate': ['css/loader-#.png', 12]
            },
        loop: true,
        match: /\?slide=(\d+)$/,
        overlap: true,
        paused: false,
        properties: ['href', 'rel', 'rev', 'title'],
        random: false,
        replace: [/(\.[^\.]+)$/, 't$1'],
        resize: 'width',
        slide: 0,
        thumbnails: false,
        titles: true,
        transition: function(p)
        {
          return -(Math.cos(Math.PI * p) - 1) / 2;
        },
        width: false
        },

    /**
     * Constructor: initialize Creates an instance of the Slideshow
     * class.
     * 
     * Arguments: element - (element) The wrapper element. data - (array
     * or object) The images and optional thumbnails, captions and links
     * for the show. options - (object) The options below.
     * 
     * Syntax: var myShow = new Slideshow(element, data, options);
     */

    initialize: function(el, data, options)
    {
      this.setOptions(options);
      this.slideshow = $(el);

      if (!this.slideshow)
        return;
      this.slideshow.set('styles',
          {
          'display': 'block',
          'position': 'relative',
          'z-index': 0
          });

      var match = window.location.href.match(this.options.match);
      this.slide = (this.options.match && match) ? match[1].toInt() : this.options.slide;
      this.counter = this.delay = this.transition = 0;
      this.direction = 'left';
      this.paused = false;

      if (!this.options.overlap)
        this.options.duration *= 2;
      var anchor = this.slideshow.getElement('a') || new Element('a');

      if (!this.options.href)
        this.options.href = anchor.get('href') || '';

      if (this.options.hu.length && !this.options.hu.test(/\/$/))
        this.options.hu += '/';

      if (this.options.fast === true)
        this.options.fast = 2;

      // styles

      var keys = ['slideshow', 'first', 'prev', 'play', 'pause', 'next', 'last', 'images', 'captions', 'controller',
          'thumbnails', 'hidden', 'visible', 'inactive', 'active', 'loader'];

      var values = keys.map(function(key, i)
      {
        return this.options.classes[i] || key;
      }, this);
      this.classes = values.associate(keys);
      this.classes.get = function()
      {
        var str = '.' + this.slideshow;

        for (var i = 0, l = arguments.length; i < l; i++)
            str += ('-' + this[arguments[i]]);
        return str;
      }.bind(this.classes);

      // data

      if (!data)
      {
        this.options.hu = '';
        data = {};
        var thumbnails = this.slideshow.getElements(this.classes.get('thumbnails') + ' img');
        this.slideshow.getElements(this.classes.get('images') + ' img').each(function(img, i)
        {
          var src = img.get('src');
          var caption = $pick(img.get('alt'), img.get('title'), '');
          var parent = img.getParent();
          var properties = (parent.get('tag') == 'a') ? parent.getProperties : {};
          var href = img.getParent().get('href') || '';
          var thumbnail = (thumbnails[i]) ? thumbnails[i].get('src') : '';
          data[src] =
              {
              'caption': caption,
              'href': href,
              'thumbnail': thumbnail
              };
        });
      }

      var loaded = this.load(data);

      if (!loaded)
        return;

      // events

      this.events = $H(
          {
          'keydown': [],
          'keyup': [],
          'mousemove': []
          });

      var keyup = function(e)
      {
        switch (e.key)
        {
          case 'left':
              this.prev(e.shift);
              break;

          case 'right':
              this.next(e.shift);
              break;

          case 'p':
              this.pause();
              break;
        }
      }.bind(this);
      this.events.keyup.push(keyup);
      document.addEvent('keyup', keyup);

      // required elements

      var el = this.slideshow.getElement(this.classes.get('images'));
      var images = (el) ? el.empty() : new Element('div',
          {
          'class': this.classes.get('images').substr(1)
          }).inject(this.slideshow);

      var div = images.getSize();
      this.height = this.options.height || div.y;
      this.width = this.options.width || div.x;
      images.set(
          {
          'styles':
              {
              'display': 'block',
              'height': this.height,
              'overflow': 'hidden',
              'position': 'relative',
              'width': this.width
              }
          });

      this.slideshow.store('images', images);
      this.a = this.image = this.slideshow.getElement('img') || new Element('img');

      if (Browser.Engine.trident && Browser.Engine.version > 4)
        this.a.style.msInterpolationMode = 'bicubic';
      this.a.set('styles',
          {
          'display': 'none',
          'position': 'absolute',
          'zIndex': 1
          });

      this.b = this.a.clone();
      [
      this.a,
      this.b
      ].each(function(img)
      {
        anchor.clone().cloneEvents(anchor).grab(img).inject(images);
      });

      // optional elements

      if (this.options.captions)
        this._captions();

      if (this.options.controller)
        this._controller();

      if (this.options.loader)
        this._loader();

      if (this.options.thumbnails)
        this._thumbnails();

      // begin show

      this._preload();
    },

    /**
     * Public method: go Jump directly to a slide in the show.
     * 
     * Arguments: n - (integer) The index number of the image to jump
     * to, 0 being the first image in the show.
     * 
     * Syntax: myShow.go(n);
     */

    go: function(n, direction)
    {
      var x = (this.slide - 1 + this.data.images.length);
      var y = this.data.images.length;
      var xy = x % y;
      var t = $time();

      if ((this.slide - 1 + this.data.images.length) % this.data.images.length == n)
        return;
      // if ( $time() < this.transition)
      // return;
      $clear(this.timer);
      this.delay = 0;
      this.direction = (direction) ? direction : ((n < this.slide) ? 'right' : 'left');
      this.slide = n;

      if (this.preloader)
        this.preloader = this.preloader.destroy();
      this._preload(this.options.fast == 2 || (this.options.fast == 1 && this.paused));
    },

    /**
     * Public method: first Goes to the first image in the show.
     * 
     * Syntax: myShow.first();
     */

    first: function()
    {
      this.prev(true);
    },

    /**
     * Public method: prev Goes to the previous image in the show.
     * 
     * Syntax: myShow.prev();
     */

    prev: function(first)
    {
      var n = 0;

      if (!first)
      {
        if (this.options.random)
        {

          // if it's a random show get the previous slide from the
          // showed array

          if (this.showed.i < 2)
            return;

          this.showed.i -= 2;
          n = this.showed.array[this.showed.i];
        }

        else
          n = (this.slide - 2 + this.data.images.length) % this.data.images.length;
      }
      this.go(n, 'right');
    },

    /**
     * Public method: pause Toggles play / pause state of the show.
     * 
     * Arguments: p - (undefined, 1 or 0) Call pause with no arguments
     * to toggle the pause state. Call pause(1) to force pause, or
     * pause(0) to force play.
     * 
     * Syntax: myShow.pause(p);
     */

    pause: function(p)
    {
      if ($chk(p))
        this.paused = (p) ? false : true;

      if (this.paused)
      {
        this.paused = false;
        this.delay = this.transition = 0;
        this.timer = this._preload.delay(100, this);
        [
        this.a,
        this.b
        ].each(function(img)
        {
          [
          'morph',
          'tween'
          ].each(function(p)
          {
            if (this.retrieve(p))
              this.get(p).resume();
          }, img);
        });

        if (this.options.controller)
          this.slideshow.getElement('.' + this.classes.pause).removeClass(this.classes.play);
      }

      else
      {
        this.paused = true;
        this.delay = Number.MAX_VALUE;
        this.transition = 0;
        $clear(this.timer);
        [
        this.a,
        this.b
        ].each(function(img)
        {
          [
          'morph',
          'tween'
          ].each(function(p)
          {
            if (this.retrieve(p))
              this.get(p).pause();
          }, img);
        });

        if (this.options.controller)
          this.slideshow.getElement('.' + this.classes.pause).addClass(this.classes.play);
      }
    },

    /**
     * Public method: next Goes to the next image in the show.
     * 
     * Syntax: myShow.next();
     */

    next: function(last)
    {
      var n = (last) ? this.data.images.length - 1 : this.slide;
      this.go(n, 'left');
    },

    /**
     * Public method: last Goes to the last image in the show.
     * 
     * Syntax: myShow.last();
     */

    last: function()
    {
      this.next(true);
    },

    /**
     * Public method: load Loads a new data set into the show: will stop
     * the current show, rewind and rebuild thumbnails if applicable.
     * 
     * Arguments: data - (array or object) The images and optional
     * thumbnails, captions and links for the show.
     * 
     * Syntax: myShow.load(data);
     */

    load: function(data)
    {
      this.firstrun = true;
      this.showed =
          {
          'array': [],
          'i': 0
          };

      if ($type(data) == 'array')
      {
        this.options.captions = false;
        data = new Array(data.length).associate(data.map(function(
        image, i)
        {
          return image + '?' + i
        }));
      }
      this.data =
          {
          'images': [],
          'captions': [],
          'hrefs': [],
          'thumbnails': []
          };

      for (var image in data)
      {
        var obj = data[image] || {};
        var caption = (obj.caption) ? obj.caption.trim() : '';
        var href = (obj.href) ? obj.href.trim() : ((this.options.linked) ? this.options.hu + image : this.options.href);
        var thumbnail = (obj.thumbnail)
            ? obj.thumbnail.trim() : image.replace(this.options.replace[0], this.options.replace[1]);
        this.data.images.push(image);
        this.data.captions.push(caption);
        this.data.hrefs.push(href);
        this.data.thumbnails.push(thumbnail);
      }

      if (this.options.random)
        this.slide = $random(0, this.data.images.length - 1);

      // only run when data is loaded dynamically into an existing
      // slideshow instance

      if (this.options.thumbnails && this.slideshow.retrieve('thumbnails'))
        this._thumbnails();

      if (this.slideshow.retrieve('images'))
      {
        [
        this.a,
        this.b
        ].each(function(img)
        {
          [
          'morph',
          'tween'
          ].each(function(p)
          {
            if (this.retrieve(p))
              this.get(p).cancel();
          }, img);
        });
        this.slide = this.transition = 0;
        this.go(0);
      }
      return this.data.images.length;
    },

    /**
     * Public method: destroy Destroys a Slideshow instance.
     * 
     * Arguments: p - (string) The images and optional thumbnails,
     * captions and links for the show.
     * 
     * Syntax: myShow.destroy(p);
     */

    destroy: function(p)
    {
      this.events.each(function(array, e)
      {
        array.each(function(fn)
        {
          document.removeEvent(e, fn);
        });
      });
      this.pause(1);

      if (this.options.loader)
        $clear(this.slideshow.retrieve('loader').retrieve('timer'));

      if (this.options.thumbnails)
        $clear(this.slideshow.retrieve('thumbnails').retrieve('timer'));
      this.slideshow.uid = Native.UID++;

      if (p)
        this.slideshow[p]();
    },

    /**
     * Private method: preload Preloads the next slide in the show, once
     * loaded triggers the show, updates captions, thumbnails, etc.
     */

    _preload: function(fast)
    {
      if (!this.preloader)
      {
        var x =
            {
            'onload': function()
            {
              this.store('loaded', true)
            }
            };
        this.preloader = new Asset.image(this.options.hu + this.data.images[this.slide], x);
      }

      if (this.preloader.retrieve('loaded') && $time() > this.delay && $time() > this.transition)
      {
        if (this.stopped)
        {
          if (this.options.captions)
            this.slideshow.retrieve('captions').get('morph').cancel().start(this.classes.get('captions', 'hidden'));
          this.pause(1);

          if (this.end)
            this.fireEvent('end');
          this.stopped = this.end = false;
          return;
        }
        this.image = (this.counter % 2) ? this.b : this.a;
        this.image.set('styles',
            {
            'display': 'block',
            'height': 'auto',
            'visibility': 'hidden',
            'width': 'auto',
            'zIndex': this.counter
            });

        [
        'src',
        'height',
        'width'
        ].each(function(prop)
        {
          this.image.set(prop, this.preloader.get(prop));
        }, this);
        this._resize(this.image);
        this._center(this.image);
        var anchor = this.image.getParent();

        if (this.data.hrefs[this.slide])
          anchor.set('href', this.data.hrefs[this.slide]);

        else
          anchor.erase('href');
        var text = (this.data.captions[this.slide])
            ? this.data.captions[this.slide].replace(/<.+?>/gm, '').replace(/</g, '&lt;').replace(/>/g,
                '&gt;').replace(/"/g, "'") : '';
        this.image.set('alt', text);

        if (this.options.titles)
          anchor.set('title', text);

        if (this.options.loader)
          this.slideshow.retrieve('loader').fireEvent('hide');

        if (this.options.captions)
          this.slideshow.retrieve('captions').fireEvent('update', fast);

        if (this.options.thumbnails)
          this.slideshow.retrieve('thumbnails').fireEvent('update', fast);
        this._show(fast);
        this._loaded();
      }

      else
      {
        if ($time() > this.delay && this.options.loader)
          this.slideshow.retrieve('loader').fireEvent('show');
        this.timer = (this.paused && this.preloader.retrieve('loaded')) ? null : this._preload.delay(100, this, fast);
      }
    },

    /**
     * Private method: show Does the slideshow effect.
     */

    _show: function(fast)
    {
      if (!this.image.retrieve('morph'))
      {
        var options = (this.options.overlap) ?
            {
            'duration': this.options.duration,
            'link': 'cancel'
            } :
            {
            'duration': this.options.duration / 2,
            'link': 'chain'
            };

        $$(this.a, this.b).set('morph', $merge(options,
            {
            'onStart': this._start.bind(this),
            'onComplete': this._complete.bind(this),
            'transition': this.options.transition
            }));
      }

      var hidden = this.classes.get('images', ((this.direction == 'left') ? 'next' : 'prev'));
      var visible = this.classes.get('images', 'visible');
      var img = (this.counter % 2) ? this.a : this.b;

      if (fast)
      {
        img.get('morph').cancel().set(hidden);
        this.image.get('morph').cancel().set(visible);
      }

      else
      {
        if (this.options.overlap)
        {
          img.get('morph').set(visible);
          this.image.get('morph').set(hidden).start(visible);
        }

        else
        {
          var fn = function(hidden, visible)
          {
            this.image.get('morph').set(hidden).start(visible);
          }.pass([hidden, visible], this);
          hidden = this.classes.get('images', ((this.direction == 'left') ? 'prev' : 'next'));
          img.get('morph').set(visible).start(hidden).chain(fn);
        }
      }
    },

    /**
     * Private method: loaded Run after the current image has been
     * loaded, sets up the next image to be shown.
     */

    _loaded: function()
    {
      this.counter++;
      this.delay = (this.paused) ? Number.MAX_VALUE : $time() + this.options.duration + this.options.delay;
      this.direction = 'left';
      this.transition = (this.options.fast == 2 || (this.options.fast == 1 && this.paused))
          ? 0 : $time() + this.options.duration;

      if (this.slide + 1 == this.data.images.length && !this.options.loop && !this.options.random)
        this.stopped = this.end = true;

      if (this.options.random)
      {
        this.showed.i++;

        if (this.showed.i >= this.showed.array.length)
        {
          var n = this.slide;

          if (this.showed.array.getLast() != n)
            this.showed.array.push(n);

          while (this.slide == n)
              this.slide = $random(0, this.data.images.length - 1);
        }

        else
          this.slide = this.showed.array[this.showed.i];
      }

      else
        this.slide = (this.slide + 1) % this.data.images.length;

      if (this.image.getStyle('visibility') != 'visible')
        (function()
        {
          this.image.setStyle('visibility', 'visible');
        }).delay(1, this);

      if (this.preloader)
        this.preloader = this.preloader.destroy();
      this._preload();
    },

    /**
     * Private method: center Center an image.
     */

    _center: function(img)
    {
      if (this.options.center)
      {
        var size = img.getSize();
        img.set('styles',
            {
            'left': (size.x - this.width) / -2,
            'top': (size.y - this.height) / -2
            });
      }
    },

    /**
     * Private method: resize Resizes an image.
     */

    _resize: function(img)
    {
      if (this.options.resize)
      {
        var h = this.preloader.get('height'), w = this.preloader.get('width');
        var dh = this.height / h, dw = this.width / w, d;

        if (this.options.resize == 'length')
          d = (dh > dw) ? dw : dh;

        else
          d = (dh > dw) ? dh : dw;
        img.set('styles',
            {
            height: Math.ceil(h * d),
            width: Math.ceil(w * d)
            });
      }
    },

    /**
     * Private method: start Callback on start of slide change.
     */

    _start: function()
    {
      this.fireEvent('start');
    },

    /**
     * Private method: complete Callback on start of slide change.
     */

    _complete: function()
    {
      if (this.firstrun && this.options.paused)
      {
        this.firstrun = false;
        this.pause(1);
      }
      this.fireEvent('complete');
    },

    /**
     * Private method: captions Builds the optional caption element,
     * adds interactivity. This method can safely be removed if the
     * captions option is not enabled.
     */

    _captions: function()
    {
      if (this.options.captions === true)
        this.options.captions = {};
      var el = this.slideshow.getElement(this.classes.get('captions'));
      var captions = (el) ? el.empty() : new Element('div',
          {
          'class': this.classes.get('captions').substr(1)
          }).inject(this.slideshow);

      captions.set(
          {
          'events':
              {
              'update': function(fast)
              {
                var captions = this.slideshow.retrieve('captions');
                var empty = (this.data.captions[this.slide] === '');

                if (fast)
                {
                  var p = (empty) ? 'hidden' : 'visible';
                  captions.set('html',
                      this.data.captions[this.slide]).get('morph').cancel().set(this.classes.get('captions', p));
                }

                else
                {
                  var fn = (empty) ? $empty : function(n)
                  {
                    this.slideshow.retrieve('captions').set('html',
                        this.data.captions[n]).morph(this.classes.get('captions', 'visible'))
                  }.pass(this.slide, this);
                  captions.get('morph').cancel().start(this.classes.get('captions', 'hidden')).chain(fn);
                }
              }.bind(this)
              },
          'morph': $merge(this.options.captions,
              {
              'link': 'chain'
              })
          });
      this.slideshow.store('captions', captions);
    },

    /**
     * Private method: controller Builds the optional controller
     * element, adds interactivity. This method can safely be removed if
     * the controller option is not enabled.
     */

    _controller: function()
    {
      if (this.options.controller === true)
        this.options.controller = {};
      var el = this.slideshow.getElement(this.classes.get('controller'));
      var controller = (el) ? el.empty() : new Element('div',
          {
          'class': this.classes.get('controller').substr(1)
          }).inject(this.slideshow);

      var ul = new Element('ul').inject(controller);
      $H(
          {
          'first': 'Shift + Leftwards Arrow',
          'prev': 'Leftwards Arrow',
          'pause': 'P',
          'next': 'Rightwards Arrow',
          'last': 'Shift + Rightwards Arrow'
          }).each(function(accesskey, action)
      {
        var li = new Element('li',
            {
            'class': (action == 'pause' && this.options.paused) ? this.classes.play + ' '
            + this.classes[action] : this.classes[action]
            }).inject(ul);

        var a = this.slideshow.retrieve(action, new Element('a',
            {
            'title': ((action == 'pause') ? this.classes.play.capitalize() + ' / ' : '')
            + this.classes[action].capitalize() + ' ['
            + accesskey + ']'
            }).inject(li));

        a.set('events',
            {
            'click': function(action)
            {
              this[action]();
            }.pass(action, this),
            'mouseenter': function(active)
            {
              this.addClass(active);
            }.pass(this.classes.active, a),
            'mouseleave': function(active)
            {
              this.removeClass(active);
            }.pass(this.classes.active, a)
            });
      }, this);
      controller.set(
          {
          'events':
              {
              'hide': function(hidden)
              {
                if (!this.retrieve('hidden'))
                  this.store('hidden', true).morph(hidden);
              }.pass(this.classes.get('controller', 'hidden'), controller),
              'show': function(visible)
              {
                if (this.retrieve('hidden'))
                  this.store('hidden', false).morph(visible);
              }.pass(this.classes.get('controller', 'visible'), controller)
              },
          'morph': $merge(this.options.controller,
              {
              'link': 'cancel'
              })
          }).store('hidden', false);
      var keydown = function(e)
      {
        if (
            [
            'left',
            'right',
            'p'
            ].contains(e.key))
        {
          var controller = this.slideshow.retrieve('controller');

          if (controller.retrieve('hidden'))
            controller.get('morph').set(this.classes.get('controller', 'visible'));

          switch (e.key)
          {
            case 'left':
                this.slideshow.retrieve((e.shift) ? 'first' : 'prev').fireEvent('mouseenter');
                break;

            case 'right':
                this.slideshow.retrieve((e.shift) ? 'last' : 'next').fireEvent('mouseenter');
                break;

            default:
                this.slideshow.retrieve('pause').fireEvent('mouseenter');
                break;
          }
        }
      }.bind(this);
      this.events.keydown.push(keydown);
      var keyup = function(e)
      {
        if (
            [
            'left',
            'right',
            'p'
            ].contains(e.key))
        {
          var controller = this.slideshow.retrieve('controller');

          if (controller.retrieve('hidden'))
            controller.store('hidden', false).fireEvent('hide');

          switch (e.key)
          {
            case 'left':
                this.slideshow.retrieve((e.shift) ? 'first' : 'prev').fireEvent('mouseleave');
                break;

            case 'right':
                this.slideshow.retrieve((e.shift) ? 'last' : 'next').fireEvent('mouseleave');
                break;

            default:
                this.slideshow.retrieve('pause').fireEvent('mouseleave');
                break;
          }
        }
      }.bind(this);
      this.events.keyup.push(keyup);
      var mousemove = function(e)
      {
        var images = this.slideshow.retrieve('images').getCoordinates();

        if (e.page.x > images.left && e.page.x < images.right && e.page.y > images.top && e.page.y < images.bottom)
          this.slideshow.retrieve('controller').fireEvent('show');

        else
          this.slideshow.retrieve('controller').fireEvent('hide');
      }.bind(this);
      this.events.mousemove.push(mousemove);
      document.addEvents(
          {
          'keydown': keydown,
          'keyup': keyup,
          'mousemove': mousemove
          });

      this.slideshow.retrieve('controller', controller).fireEvent('hide');
    },

    /**
     * Private method: loader Builds the optional loader element, adds
     * interactivity. This method can safely be removed if the loader
     * option is not enabled.
     */

    _loader: function()
    {
      if (this.options.loader === true)
        this.options.loader = {};
      var loader = new Element('div',
          {
          'class': this.classes.get('loader').substr(1),
          'morph': $merge(this.options.loader,
              {
              'link': 'cancel'
              })
          }).store('hidden', false).store('i', 1).inject(this.slideshow.retrieve('images'));

      if (this.options.loader.animate)
      {
        for (var i = 0; i < this.options.loader.animate[1]; i++)
            img = new Asset.image(this.options.loader.animate[0].replace(/#/, i));

        if (Browser.Engine.trident4 && this.options.loader.animate[0].contains('png'))
          loader.setStyle('backgroundImage', 'none');
      }
      loader.set('events',
          {
          'animate': function()
          {
            var loader = this.slideshow.retrieve('loader');
            var i = (loader.retrieve('i').toInt() + 1) % this.options.loader.animate[1];
            loader.store('i', i);
            var img = this.options.loader.animate[0].replace(/#/, i);

            if (Browser.Engine.trident4 && this.options.loader.animate[0].contains('png'))
              loader.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'
                  + img + '", sizingMethod="scale")';

            else
              loader.setStyle('backgroundImage', 'url(' + img + ')');
          }.bind(this),
          'hide': function()
          {
            var loader = this.slideshow.retrieve('loader');

            if (!loader.retrieve('hidden'))
            {
              loader.store('hidden', true).morph(this.classes.get('loader', 'hidden'));

              if (this.options.loader.animate)
                $clear(loader.retrieve('timer'));
            }
          }.bind(this),
          'show': function()
          {
            var loader = this.slideshow.retrieve('loader');

            if (loader.retrieve('hidden'))
            {
              loader.store('hidden', false).morph(this.classes.get('loader', 'visible'));

              if (this.options.loader.animate)
                loader.store('timer', function()
                {
                  this.fireEvent('animate');
                }.periodical(50, loader));
            }
          }.bind(this)
          });
      var x = this.slideshow.retrieve('loader', loader);
      x.fireEvent('hide');
    },

    /**
     * Private method: thumbnails Builds the optional thumbnails
     * element, adds interactivity. This method can safely be removed if
     * the thumbnails option is not enabled.
     */

    _thumbnails: function()
    {
      if (this.options.thumbnails === true)
        this.options.thumbnails = {};
      var el = this.slideshow.getElement(this.classes.get('thumbnails'));
      var thumbnails = (el) ? el.empty() : new Element('div',
          {
          'class': this.classes.get('thumbnails').substr(1)
          }).inject(this.slideshow);

      thumbnails.setStyle('overflow', 'hidden');
      var ul = new Element('ul',
          {
          'tween':
              {
              'link': 'cancel'
              }
          }).inject(thumbnails);

      this.data.thumbnails.each(function(thumbnail, i)
      {
        var li = new Element('li').inject(ul);
        var a = new Element('a',
            {
            'events':
                {
                'click': function(i)
                {
                  this.go(i);
                  return false;
                }.pass(i, this),
                'loaded': function()
                {
                  this.data.thumbnails.pop();

                  if (!this.data.thumbnails.length)
                  {
                    var div = thumbnails.getCoordinates();
                    var props = thumbnails.retrieve('props');
                    var limit = 0, pos = props[1], size = props[2];
                    thumbnails.getElements('li').each(function(
                    li)
                    {
                      var li = li.getCoordinates();

                      if (li[pos] > limit)
                        limit = li[pos];
                    }, this);
                    thumbnails.store('limit', div[size] + div[props[0]] - limit);
                  }
                }.bind(this)
                },
            'href': this.options.hu + this.data.images[i],
            'morph': $merge(this.options.thumbnails,
                {
                'link': 'cancel'
                })
            }).inject(li);

        if (this.data.captions[i] && this.options.titles)
          a.set('title',
              this.data.captions[i].replace(/<.+?>/gm, '').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g,
                  "'"));
        var img = new Asset.image(this.options.hu + thumbnail,
            {
            'onload': function()
            {
              this.fireEvent('loaded');
            }.bind(a)
            }).inject(a);
      }, this);
      thumbnails.set('events',
          {
          'scroll': function(n, fast)
          {
            var div = this.getCoordinates();
            var ul = this.getElement('ul').getPosition();
            var props = this.retrieve('props');
            var axis = props[3], delta, pos = props[0], size = props[2], value;
            var tween = this.getElement('ul').get('tween',
                {
                'property': pos
                });

            if ($chk(n))
            {
              var li = this.getElements('li')[n].getCoordinates();
              delta = div[pos] + (div[size] / 2) - (li[size] / 2) - li[pos]
              value = (ul[axis] - div[pos] + delta).limit(this.retrieve('limit'), 0);

              if (fast)
                tween.set(value);

              else
                tween.start(value);
            }

            else
            {
              var area = div[props[2]] / 3, page = this.retrieve('page'), velocity = -0.2;

              if (page[axis] < (div[pos] + area))
                delta = (page[axis] - div[pos] - area) * velocity;

              else if (page[axis] > (div[pos] + div[size] - area))
                delta = (page[axis] - div[pos] - div[size] + area) * velocity;

              if (delta)
              {
                value = (ul[axis] - div[pos] + delta).limit(this.retrieve('limit'), 0);
                tween.set(value);
              }
            }
          }.bind(thumbnails),
          'update': function(fast)
          {
            var thumbnails = this.slideshow.retrieve('thumbnails');
            thumbnails.getElements('li').each(function(a, i)
            {
              if (i == this.slide)
              {
                if (!a.retrieve('active', false))
                {
                  a.store('active', true);
                  var active = this.classes.get('thumbnails', 'active');

                  if (fast)
                    a.get('morph').set(active);

                  else
                    a.morph(active);
                }
              }

              else
              {
                if (a.retrieve('active', true))
                {
                  a.store('active', false);
                  var inactive = this.classes.get('thumbnails', 'inactive');

                  if (fast)
                    a.get('morph').set(inactive);

                  else
                    a.morph(inactive);
                }
              }
            }, this);

            if (!thumbnails.retrieve('mouseover'))
              thumbnails.fireEvent('scroll', [this.slide, fast]);
          }.bind(this)
          })

      var div = thumbnails.getCoordinates();
      thumbnails.store('props', (div.height > div.width)
          ? ['top', 'bottom', 'height', 'y'] : ['left', 'right', 'width', 'x']);

      var mousemove = function(e)
      {
        var div = this.getCoordinates();

        if (e.page.x > div.left && e.page.x < div.right && e.page.y > div.top && e.page.y < div.bottom)
        {
          this.store('page', e.page);

          if (!this.retrieve('mouseover'))
          {
            this.store('mouseover', true);
            this.store('timer', function()
            {
              this.fireEvent('scroll');
            }.periodical(50, this));
          }
        }

        else
        {
          if (this.retrieve('mouseover'))
          {
            this.store('mouseover', false);
            $clear(this.retrieve('timer'));
          }
        }
      }.bind(thumbnails);
      this.events.mousemove.push(mousemove);
      document.addEvent('mousemove', mousemove);
      this.slideshow.store('thumbnails', thumbnails);
    }
    });
