/**
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,*/
    accesskeys: { 'first': 'shift + left', 'prev': 'left', 'pause': 'p', 'next': 'right', 'last': 'shift + right' },
    captions: false,
    center: true,
    classes: [],
    controller: true,
    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,
    preload: 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 = document.id(el);
    if (!this.slideshow)
      return;
    this.slideshow.set('styles', {'display': 'block', 'position': 'relative', 'z-index': 0});
    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 = 'http://bea.st/css/fakeThumb.jpg';
        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();
     
    // setup first slide  
     
    this.slide = this.options.slide;
    var match = window.location.href.match(this.options.match);
    if (this.options.match && match){
      if (this.data.images.contains(match[1]))
        this.slide = this.data.images.indexOf(match[1]);
      else if ($type(match[1].toInt()) == 'number')
        this.slide = match[1] % this.data.images.length;
    }

    // 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){
    if ((this.slide - 1 + this.data.images.length) % this.data.images.length == n || $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': [], 'targets': []};
    for (var image in data){
      var obj = data[image] || {};
      var caption = obj.caption ? obj.caption.trim() : '';
      var href = obj.caption ? obj.caption : image.replace("pics", "pics/full");
      var target = obj.caption ? "" : "_blank";
      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.targets.push(target);
      this.data.thumbnails.push(thumbnail);
    }
    if (this.options.random)
      this.slide = $random(0, this.data.images.length - 1);
    if (this.options.preload){
      this.data.images.each(function(image){
        new Asset.image(this.options.hu + image);
      }, this);
    }
   
    // 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)
       this.preloader = new Asset.image(this.options.hu + this.data.images[this.slide], {
        'onerror': function(){
          ['images', 'captions', 'hrefs'].each(function(key){
            this.data[key].splice(this.slide, 1);
          }, this);
          if (this.options.thumbnails && this.slideshow.retrieve('thumbnails')){
            this.slideshow.retrieve('thumbnails').getElements('li')[this.slide].destroy();
            this.data.thumbnails.pop();
            if (!this.data.thumbnails.length)
              this.slideshow.retrieve('thumbnails').fireEvent('resize');
          }
          this.preloader = this.preloader.destroy();
          this._preload();
        }.bind(this),
        'onload': function(){
          this.store('loaded', true);
        }
      });  
    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');
        
      if (this.data.targets[this.slide])
        anchor.set('target', this.data.targets[this.slide]);
      else
        anchor.erase('target');
      
      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'), action = 'pause';
        if (controller.retrieve('hidden'))
          controller.get('morph').set(this.classes.get('controller', 'visible'));
        if (e.key == 'left')
          action = e.shift ? 'first' : 'prev';
        if (e.key == 'right')
          action = e.shift ? 'last' : 'next';
        this.slideshow.retrieve(action).fireEvent('mouseenter');
      }
    }.bind(this);
    this.events.keydown.push(keydown);
    var keyup = function(e){
      if (['left', 'right', 'p'].contains(e.key)){
        var controller = this.slideshow.retrieve('controller'), action = 'pause';
        if (controller.retrieve('hidden'))
          controller.store('hidden', false).fireEvent('hide');
        if (e.key == 'left')
          action = e.shift ? 'first' : 'prev';
        if (e.key == 'right')
          action = e.shift ? 'last' : 'next';
        this.slideshow.retrieve(action).fireEvent('mouseleave');
      }
    }.bind(this);
    this.events.keyup.push(keyup);
    var mousemove = function(e){
      var images = this.slideshow.retrieve('images').getCoordinates();
      var action = (e.page.x > images.left && e.page.x < images.right && e.page.y > images.top && e.page.y < images.bottom) ? 'show' : 'hide';
      this.slideshow.retrieve('controller').fireEvent(action);
    }.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)
    });
    this.slideshow.retrieve('loader', loader).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);
    var uuid = thumbnails.setStyle('overflow', 'hidden').retrieve('uuid', this.classes['thumbnails'] + '-' + $time());
    var ul = new Element('ul', {'styles': {'left': 0, 'position': 'absolute', 'top': 0, 'width' : this.width}, 'tween': {'link': 'cancel'}}).inject(thumbnails);
    this.data.thumbnails.each(function(thumbnail, i){
      var li = new Element('li', {'id': uuid + i}).inject(ul);
      var a = new Element('a', {
        'class': this.classes.get('thumbnails', 'hidden').substr(1),
        'events': {
          'click': function(i){
            this.go(i);
            return false;
          }.pass(i, this)
        },
        'href': this.options.hu + this.data.images[i],
        'morph': $merge(this.options.thumbnails, {'link': 'cancel'}),
        'title': (this.data.captions[i] && this.options.titles) ? this.data.captions[i].replace(/<.+?>/gm, '').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, "'") : ''
      }).store('uuid', i).inject(li);
      new Asset.image('http://bea.st/css/fakeThumb.jpg', {
        'onload': function(i){
          var thumbnails = this.slideshow.retrieve('thumbnails');
          var a = thumbnails.getElements('a')[i]
          if (a){
            (function(a){              
              a.store('loaded', true).get('morph').set(this.classes.get('thumbnails', 'hidden')).start(this.classes.get('thumbnails', 'inactive'));  
            }).delay(50 * i, this, a);
          }          
          if (thumbnails.retrieve('limit'))
            return;
          var props = thumbnails.retrieve('props');
          var pos = props[1], length = props[2], width = props[4];
          var div = thumbnails.getCoordinates();
          var li = thumbnails.getElement('li:nth-child(' + (i + 1) + ')').getCoordinates();
          var n = Math.floor(div[width] / li[width]); // number of rows or columns
          var x = Math.ceil(this.data.images.length / n); // number of images per row or column
          var len = x * li[length]; // length of a single row or column
          var ul = thumbnails.getElement('ul');//.setStyle(length, len); //LAUREN
          var lis = ul.getElements('li').setStyles({'height': li.height, 'width': li.width});
          if (this.options.thumbnails.scroll == 'y'){
            ul.innerHTML = '';
            for (var i = 0; i < x; i++){
              for (var j = 0; j < n; j++){
                var li = lis[i + (x * j)];
                if (li) li.inject(ul);
              }
            }
          }
          thumbnails.store('limit', div[length] - len);
        }.pass(i, this)
      }).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 uuid = this.retrieve('uuid');
          var li = $(uuid + 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');
        var uuid = thumbnails.retrieve('uuid');
        thumbnails.getElements('a').each(function(a, i){
          if (a.retrieve('loaded')){
            if (a.retrieve('uuid') == 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();
    if (!this.options.thumbnails.scroll)
      this.options.thumbnails.scroll = (div.height > div.width) ? 'y' : 'x';
    var props = (this.options.thumbnails.scroll == 'y') ? ['top', 'bottom', 'height', 'y', 'width']
      : ['left', 'right', 'width', 'x', 'height'];
    thumbnails.store('props', props);
    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);  
  }
});

