/**
 * ExpandingImage is a simple animation for expanding a thumbnail into
 * a full size image.  It expects two elements, a div that will
 * be used to display the larger image (as a background) and the
 * container for the thumbnail.
 */
var ExpandingImage = Class.create();
ExpandingImage.prototype = {
  /**
   * Constructor
   *
   * @param div   The large image display element or id
   * @param tnail The thumbnail container element or id
   */
  initialize: function(div, tnail) {
    this.div   = $(div);
    this.tnail = $(tnail);
    this.frames = null;
    this.timerId = null;
    this.src = null;
    
    this.div.style.display = 'none';
    this.div.style.position = 'absolute';
    this.div.style.zIndex = '10';
    this.div.style.overflow = 'hidden';
    this.div.style.backgroundImage = 'none';
    this.div.style.backgroundRepeat = 'no-repeat';
  },
  
  /**
   * Expand and display an image
   *
   * @param {String} src    The image URL
   * @param {int}    width  The image width
   * @param {int}    height The image height
   */
  expand: function(src, width, height) {
    this.cancel();
    
    this.src = src;
    
    var initialBounds = this.getBounds(this.tnail);
    // determine the viewport boundaries
    var viewBounds = {top: document.body.scrollTop,
                      left: document.body.scrollLeft,
                      width: document.body.clientWidth,
                      height: document.body.clientHeight};
    
    // now determine the final size of the image
    var finalSize = {width: Math.min(width, viewBounds.width), height: Math.min(height, viewBounds.height)};
    // the final boundaries will be an image centered over the body
    var finalBounds = {top: Math.floor(viewBounds.top+(viewBounds.height-finalSize.height)/2),
                       left: Math.floor(viewBounds.left+(viewBounds.width-finalSize.width)/2),
                       width: finalSize.width,
                       height: finalSize.height};
    
    // we will create a sequence of five frames for the expansion
    // begin by calculating the stepping of each data point
    var stepping = {top: Math.floor((finalBounds.top-initialBounds.top)/4),
                    left: Math.floor((finalBounds.left-initialBounds.left)/4),
                    width: Math.floor((finalBounds.width-initialBounds.width)/4),
                    height: Math.floor((finalBounds.height-initialBounds.height)/4)};
    
    // assemble the sequence
    this.frames = [initialBounds,
                   {top: initialBounds.top+stepping.top, left: initialBounds.left+stepping.left, width: initialBounds.width+stepping.width, height: initialBounds.height+stepping.height},
                   {top: initialBounds.top+stepping.top*2, left: initialBounds.left+stepping.left*2, width: initialBounds.width+stepping.width*2, height: initialBounds.height+stepping.height*2},
                   {top: initialBounds.top+stepping.top*3, left: initialBounds.left+stepping.left*3, width: initialBounds.width+stepping.width*3, height: initialBounds.height+stepping.height*3},
                   finalBounds];
    
    // prepare the div
    this.div.style.display = 'block';
    
    // run the sequence
    this.animate();
  },
  
  /**
   * Cancel any animation in progress and remove the expanded image
   */
  cancel: function() {
    this.div.style.backgroundImage = 'none';
    this.div.style.display = 'none';
    if (this.frames) {
      window.clearTimeout(this.timerId);
      this.frames = null;
      this.timerId = null;
    }
  },
  
  /**
   * Determine the bounds of an element.  Returns an object containing
   * top, left, width, and height properties.
   *
   * @param {Element} elem  The element to find boundaries for
   */
  getBounds: function(elem) {
    // top
    var top = 0;
    var e = elem;
    while (e) { top += e.offsetTop; e = e.offsetParent; }
    
    // left
    var left = 0;
    e = elem;
    while (e) { left += e.offsetLeft; e = e.offsetParent; }
    
    // width & height
    var dimensions = Element.getDimensions(elem);
    
    return {top: top, left: left, width: dimensions.width, height: dimensions.height};
  },
  
  /**
   * Called when animating the expansion
   */
  animate: function() {
    // the final step is to display the image
    if (this.frames.length == 0) {
      this.div.style.backgroundImage = 'url('+this.src+')';
      this.timerId = null;
      this.frames = null;
    } else {
      var op = this.frames.shift();
      this.div.style.top = op.top+'px';
      this.div.style.left = op.left+'px';
      this.div.style.width = op.width+'px';
      this.div.style.height = op.height+'px';
      
      var obj = this;
      this.timerId = window.setTimeout(function() {obj.animate();}, 40);
    }
  }
};

/**
 * Slideshow handles the gallery/slideshow implementation on the
 * Examples (portfolio) page.
 */
var Slideshow = Class.create();
Slideshow.prototype = {
  /**
   * Constructor
   *
   * The constructor accepts a single prefix to use in identifying
   * the elements used by the gallery.  It assumes elements will
   * have the following ids:
   *
   *   - {prefix}_caption   The element that will contain the
   *                        caption.
   *   - {prefix}_container The surrounding element that will
   *                        contain the image (a new image tag
   *                        will be created inside).
   *   - {prefix}_txt       The element that will contain
   *                        descriptive text.
   *   - {prefix}_expanded  A div for displaying expanded images.
   *   - {prefix}           The id that will be assigned to the
   *                        image tag created inside {prefix}_container.
   *
   * This class requires all thumbnail images to be displayed at the
   * same size for compatibility with IE.
   *
   * @param {String} prefix  The id prefix to use
   * @param {int}    width   Thumbnail width
   * @param {int}    height  Thumbnail height
   */
  initialize: function(prefix, width, height) {
    this.prefix    = prefix;
    this.caption   = $(prefix+'_caption');
    this.container = $(prefix+'_container');
    this.txt       = $(prefix+'_txt');
    this.pos       = null;
    this.images    = [];
    this.fadeIn    = null;
    this.tnail     = null;
    this.width     = width;
    this.height    = height;
    this.expander  = new ExpandingImage(prefix+'_expanded', this.container);
  },
  
  /**
   * Add a new image to the gallery.  Accepts an object with
   * the following properties:
   *
   *  - id           Image id
   *  - tnail_src    URL of the thumbnail
   *  - tnail_width  Width of the thumbnail
   *  - tnail_height Height of the thumbnail
   *  - img_src      URL of the fill-size image
   *  - img_width    Width of the full-size image
   *  - img_height   Height of the full-size image
   *  - title        Caption
   *  - description  Descriptive text
   *
   * @param properties  The object with the attributes shown above.
   */
  addImage: function(properties) {
    properties.tnail = this.createImage(properties.tnail_src);
    this.images.push(properties);
  },
  
  /**
   * Called internally to instantiate an Image object from a URL.
   * Used for preloading images.
   *
   * @param {String} src  The URL of the image to load
   */
  createImage: function(src) {
    var img = new Image();
    img.src = src;
    return img;
  },
  
  /**
   * Load the first image
   */
  start: function() {
    this.pos = 0;
    var img = this.images[this.pos];
    
    this.caption.innerHTML = img.title;
    this.txt.innerHTML = img.description;
    this.container.innerHTML = '<img name="'+this.prefix+'" id="'+this.prefix+'" width="'+this.width+'" height="'+this.height+'" alt="" src="'+img.tnail_src+'" style="visibility: hidden; width: '+this.width+'px; height: '+this.height+'px;">';
    this.tnail = $(this.prefix);
    
    if ((this.tnail.filters) && (!/Safari/.test(navigator.userAgent))) {
      this.tnail.style.filter = 'progid:DXImageTransform.Microsoft.Fade(duration=0.5)';
      this.tnail.filters[0].Apply();
      this.tnail.style.visibility = 'visible';
      this.tnail.filters[0].Play();
    } else {
      this.tnail.setOpacity(0);
      this.tnail.style.visibility = 'visible';
      this.fadeIn = new Effect.Opacity(this.tnail, {from: 0.0, to: 1.0, duration: 0.5});
    }
    
    // all thumbnails are pre-loaded, but large images are only loaded one the thumnail is visited
    if (!img.img) img.img = this.createImage(img.img_src);
  },
  
  /**
   * Advance to the next image
   */
  next: function() {
    var lastImg = this.images[this.pos];
    this.pos++;
    if (this.pos >= this.images.length) this.pos = 0;
    var img = this.images[this.pos];
    
    this.caption.innerHTML = img.title;
    this.txt.innerHTML = img.description;
    if (this.fadeIn) {
      this.container.style.backgroundImage = 'url('+lastImg.tnail_src+')';
      this.tnail.style.visibility = 'hidden';
      this.tnail.src = img.tnail_src;
      this.tnail.setOpacity(0);
      this.tnail.style.visibility = 'visible';
      this.fadeIn.start();
    } else {
      this.tnail.filters[0].Apply();
      this.tnail.src = img.tnail_src;
      this.tnail.filters[0].Play();
    }
    
    // all thumbnails are pre-loaded, but large images are only loaded one the thumnail is visited
    if (!img.img) img.img = this.createImage(img.img_src);
  },
  
  /**
   * Return to the previous image
   */
  prev: function() {
    var lastImg = this.images[this.pos];
    this.pos--;
    if (this.pos < 0) this.pos = this.images.length - 1;
    var img = this.images[this.pos];
    
    this.caption.innerHTML = img.title;
    this.txt.innerHTML = img.description;
    if (this.fadeIn) {
      this.container.style.backgroundImage = 'url('+lastImg.tnail_src+')';
      this.tnail.style.visibility = 'hidden';
      this.tnail.src = img.tnail_src;
      this.tnail.setOpacity(0);
      this.tnail.style.visibility = 'visible';
      this.fadeIn.start();
    } else {
      this.tnail.filters[0].Apply();
      this.tnail.src = img.tnail_src;
      this.tnail.filters[0].Play();
    }
    
    // all thumbnails are pre-loaded, but large images are only loaded one the thumnail is visited
    if (!img.img) img.img = this.createImage(img.img_src);
  },
  
  /**
   * Expand the currently displayed image
   */
  expandImage: function() {
    var img = this.images[this.pos];
    this.expander.expand(img.img_src, img.img_width, img.img_height);
  },
  
  /**
   * Remove the expanded image
   */
  removeExpandedImage: function() {
    this.expander.cancel();
  }
};
