// $Id: picture_album_slideshow.js 273 2007-07-21 09:15:15Z naquah $
// Copyright © 2006-2007 Dennis Stevense (dennis@naquah.net), all rights reserved.

$(document).ready(function() {
  if ($('body').is('.pa-slideshow')) {
    // Initialize a slideshow object.
    new paSlideshow();
  }
});

/**
 * Main slideshow class.
 */
function paSlideshow() {
  var me = this;

  // Parse the data given in hidden form elements.
  this.home = eval('(' + $('#home').val() + ');');
  this.album = eval('(' + $('#album').val() + ');');
  this.pictures = eval('(' + $('#pictures').val() + ');');
  this.pictures_data = eval('(' + $('#pictures-data').val() + ');');
  this.icons = eval('(' + $('#icons').val() + ');');

  // Is all the data provided?
  var valid = (this.album && this.pictures.length && this.pictures_data) ? true : false;

  // If we are valid, do a more thorough check, while initializing preloader objects.
  if (valid) {
    var nid;
    for (var p = 0; p < this.pictures.length; p++) {
      nid = this.pictures[p];
      if (this.pictures_data[nid] && this.pictures_data[nid].src) {
        this.pictures_data[nid].preload = new paPreloader(this.pictures_data[nid].src);
      }
      else {
        valid = false;
        break;
      }
    }
  }

  // Print an error if data is not valid, otherwise remove the warning.
  if (!valid) {
    $('#warning').html('').append(document.createTextNode('Error: no or invalid data.'));
    return;
  }
  else {
    $('#warning').remove();
  }

  // Build DOM elements.
  this.build();

  // Go to the first picture.
  var hash;
  if ((hash = this.getHash()) && this.indexOf(hash) != -1)
    this.go(hash);
  else
    this.go(this.pictures[0]);

  setInterval(function() {
    var hash;
    if ((hash = me.getHash()) && hash != me.state.current && me.indexOf(hash) != -1) {
      me.go(hash);
    }
  }, 400);

  $(document).keypress(function(e) {
    var key = e.keyCode ? e.keyCode : e.which;
    switch (key) {
      case 44:
        me.goPrevious();
        break;
      case 46:
      case 32:
        me.goNext();
        break;
    }
  });
}

/**
 * Build all DOM elements and other objects required for the slideshow.
 */
paSlideshow.prototype.build = function() {
  var me = this;
  var content = $('#content');

  // This is where we'll keep references to the elements.
  this.controls = new Object();

  // The container that is being resized animatedly.
  this.controls.container = $(document.createElement('div')).id('container').width('100px').height('100px').appendTo(content);
  this.controls.wrapper = $(document.createElement('div')).id('wrapper').appendTo(this.controls.container);

  // The img tag, or for IE, another container that will contain an img tag.
  if ($.browser.msie)
    this.controls.image = $(document.createElement('div')).appendTo(this.controls.wrapper);
  else
    this.controls.image = $(document.createElement('img')).appendTo(this.controls.wrapper);
  this.controls.image.css('opacity', 0).mouseup(function() { me.goNext(); });

  var body = $('body');

  // Previous and next buttons.
  this.controls.previous = $(document.createElement('div')).id('previous').css('opacity', 0).appendTo(body).mouseup(function() { me.goPrevious(); });
  this.controls.next = $(document.createElement('div')).id('next').css('opacity', 0).appendTo(body).mouseup(function() { me.goNext(); });

  // Info bar.
  this.controls.info = $(document.createElement('div')).id('info').css('opacity', 0).appendTo(body).hover(function() {
    $(this).fadeTo('fast', 0.9);
  }, function() {
    $(this).fadeTo('fast', 0.6);
  });
  if (this.album.obfuscate) {
    this.controls.info.addClass('obfuscate');
  }
  if (this.home) {
    this.controls.homeTitle = document.createTextNode(this.home.title);
    this.controls.homeLink = $(document.createElement('a')).id('info-home').href(this.home.link).appendTo(this.controls.info).append(this.controls.homeTitle).prepend(document.createTextNode(' ')).prepend($(this.icons.home));
  }
  this.controls.albumTitle = document.createTextNode(this.album.title);
  this.controls.albumLink = $(document.createElement('a')).id('info-album').href(this.album.link).appendTo(this.controls.info).append(this.controls.albumTitle).prepend(document.createTextNode(' ')).prepend($(this.icons.album));
  this.controls.pictureTitle = document.createTextNode(' ');
  this.controls.pictureLink = $(document.createElement('a')).id('info-picture').appendTo(this.controls.info).append(this.controls.pictureTitle).prepend(document.createTextNode(' ')).prepend($(this.icons.picture));
  this.controls.infoSeq = document.createTextNode(' ');
  $(document.createElement('span')).id('info-seq').appendTo(this.controls.info).append(this.controls.infoSeq);
}

/**
 * Get the current hash excluding the #.
 */
paSlideshow.prototype.getHash = function() {
  return window.location.hash.replace(/#/, '');
}

/**
 * Set the hash.
 */
paSlideshow.prototype.setHash = function(hash) {
  window.location.hash = hash;
}

/**
 * Go to the specified picture.
 */
paSlideshow.prototype.go = function(nid) {
  if (!this.state) {
    this.state = new Object();
    this.state.busy = false;
    this.state.visible = false;
    this.state.virgin = true;
  }

  var index = this.indexOf(nid);

  // Abort if we are busy, picture is not found, or picture is already visible.
  if (this.state.busy || this.state.current == nid || index == -1) return;

  // Set state variables.
  this.state.busy = true;
  this.state.current = nid;
  this.state.previous = this.pictures[index - 1];
  this.state.next = this.pictures[index + 1];
  this.state.next2 = this.pictures[index + 2];

  // Already start loading picture.
  this.pictures_data[this.state.current].preload.load();

  // Will do animation and viewing stuff.
  this.refresh();
}

/**
 * Go to the previous picture.
 */
paSlideshow.prototype.goPrevious = function() {
  if (this.state.previous) {
    this.go(this.state.previous);
  }
}

/**
 * Go to the next picture.
 */
paSlideshow.prototype.goNext = function() {
  if (this.state.next) {
    this.go(this.state.next);
  }
}

/**
 * Gather the index of the given picture.
 */
paSlideshow.prototype.indexOf = function(nid) {
  // Non-gecko browsers do not support indexOf function for arrays. Emulate it if necessary.
  if (this.pictures.indexOf == undefined) {
    for (var p = 0; p < this.pictures.length; p++) {
      if (this.pictures[p] == nid) {
        return p;
      }
    }
    return -1;
  }

  return this.pictures.indexOf(nid);
}

/**
 * Prepare to show a new picture.
 */
paSlideshow.prototype.refresh = function() {
  var me = this;

  // Fade out and clear the current visible picture, if any.
  if (this.state.visible) {
    this.controls.image.fadeTo('normal', 0, function() {
      if ($.browser.msie)
        $(me.controls.image).children().remove();
      else
        me.controls.image.removeAttr('src');

      me.state.visible = false;
      me.refresh(); // Call again.
    });
    return;
  }

  // Show busy indicator.
  if (!this.pictures_data[this.state.current].preload.loaded) {
    this.controls.wrapper.addClass('busy');
  }

  // Set location hash.
  this.setHash(this.state.current);

  // Show the picture as soon as it's been loaded.
  this.pictures_data[this.state.current].preload.load(function() {
    me.controls.wrapper.removeClass('busy');
    me.show();
    me.state.visible = true;
  });
}

/**
 * Show a picture that has just been loaded.
 */
paSlideshow.prototype.show = function() {
  var me = this;

  // Show the preloaded image.
  // IE needs the actual image object for preloading to be beneficial, but this doesn't work in some other browsers like Safari.
  var image = this.pictures_data[this.state.current].preload.image;
  if ($.browser.msie)
    $(this.controls.image).append(image);
  else
    this.controls.image.src(image.src);

  // Animate the container and show the image. First time is different.
  if (this.state.virgin) {
    this.controls.container.width(image.width + 'px').height(image.height + 'px');
    this.controls.image.fadeTo('normal', 1, function() {
      me.controls.container.background('black');
    });
    this.controls.info.fadeTo('normal', 0.6);
    this.state.busy = false;
    this.state.virgin = false;
  }
  else {
    this.controls.container.animate({width: image.width, height: image.height}, 'fast', function() {
      me.controls.image.fadeTo('normal', 1);
      me.state.busy = false;
    });
  }

  // Fade previous and next buttons accordingly.
  if (this.state.previous)
    this.controls.previous.show().fadeTo(350, 0.6);
  else
    this.controls.previous.fadeTo(350, 0, function() { $(this).hide(); });
  if (this.state.next)
    this.controls.next.show().fadeTo(350, 0.6);
  else
    this.controls.next.fadeTo(350, 0, function() { $(this).hide(); });

  // Set picture title, link and sequence info.
  this.controls.pictureTitle.data = this.pictures_data[this.state.current].title;
  this.controls.pictureLink.href(this.pictures_data[this.state.current].link);
  this.controls.infoSeq.data = (this.indexOf(this.state.current) + 1) + "/" + this.pictures.length;

  // Preload after a short delay. Prevents browsers from staying in a loading state.
  setTimeout(function() {
    me.preload();
  }, 400);
}

/**
 * Preload two upcoming and one previous picture.
 */
paSlideshow.prototype.preload = function() {
  if (this.state.previous)
    this.pictures_data[this.state.previous].preload.load();
  if (this.state.next) {
    this.pictures_data[this.state.next].preload.load();
    if (this.state.next2)
      this.pictures_data[this.state.next2].preload.load();
  }
}

function paPreloader(src) {
  this.src = src;
  this.subscribers = [];
}

paPreloader.prototype.load = function(subscriber) {
  if (subscriber) {
    this.subscribers.push(subscriber);
  }

  if (this.image) {
    if (this.loaded) {
      paLog("paPreloader: no need to load  " + this.src);
      this.notify();
    }
    else {
      paLog("paPreloader: still busy loading " + this.src);
    }
  }
  else {
    var me = this;

    paLog("paPreloader: starting loading " + this.src);
    this.image = new Image();
    this.image.src = this.src;
    this.image.onload = function() {
      paLog("paPreloader: done loading " + this.src);
      me.loaded = true;
      me.notify();
    };

    this.loaded = false;
  }
}

paPreloader.prototype.notify = function() {
  var subscriber;
  while (subscriber = this.subscribers.pop()) {
    subscriber.call();
  }
}

var debug = false;
function paLog(message) {
  if (debug && window.console) {
    window.console.log(message);
  }
}
