/**
 * Bravo Game Studios slideshow.
 */

if(!window.Bravo) var Bravo = {}; // Declares the Bravo namespace.

/**
 * @class Slideshow.
 *
 * @param {string}elementId  - The slideshow container identifier.
 * @param {object}parameters - List of parameters:
 *        duration      : per-preview duration, in milliseconds.
 *        autoStart     : true to autostart, false otherwise.
 *        thumbSize     : thumbnail size descriptor, having "width" for width, "height" for height.
 *        defLabelClass : label CSS class to use if the item hasn't one.
 *        defIconClass  : icon  CSS class to use if the item hasn't one.
 *        items         : list of items. To specify a label CSS class, use "labelClass". To specify an icon class,
 *                        use "iconClass". Label title is "caption" (automatically included on an H3), description is
 *                        "label" (automatically included on an H5).
 *                        The URL of the game page must be included in "infoLink". If game purchasing is available,
 *                        use "shopLink" to declare the URL (icons only appear on purchasable games).
 *                        Images must be "previewURL" for the regular one, "thumbURL" for the thumbnail.
 */
Bravo.Slideshow = function(elementId, parameters) {

  if(!parameters) parameters  = {}; // Sets defaults.
  for(var member in parameters) if('function' != typeof(parameters[member])) this[member] = parameters[member];

  if(!this.duration ) this.duration  = 2000 ;
  if(!this.thumbSize) this.thumbSize = { width: 200, height: 100 };
  if(!this.autoStart) this.autoStart = false;  

  this.el = document.getElementById(elementId); // Retrieves the slideshow container.
  this.itemsLength = parameters.items.length;

  if( this.autoStart) this.start(); // Starts automatically.

} /* Bravo.Slideshow class */

Bravo.Slideshow.prototype = {

  duration    : null, /// Per-thumbnail duration, in milliseconds.
  autoStart   : null, /// True to start automatically after loading the images.
  thumbSize   : null, /// Size of the thumb (hashmap containing "width" and "height" pixel sizes).
  items       : null, /// Item container.
  itemsLength : null, /// Number of items in the list.
  interval    : null, /// Main loop interval.
  el          : null, /// DOM element containing the slideshow.
  thumbsEl    : null, /// DOM element containing the thumbs.
  currentItem : null, /// Item being currently shown.
  ulMarginTop : null, /// Thumbs list default margin top.
  isIE6       : false /*@cc_on || @_jscript_version <= 5.7 @*/,
  running     : null, /// True if loop is running; false otherwise.

  /**
   * Runs on the slideshow.
   */
  start: function() {
  
    var This = this;
    if(this.interval) return; // Already running.
    if(this.thumbsEl) this.onStart(this); else this.onInitialize(this.onStart, this);
  
  },
  
  /**
   * Stops running the slideshow.
   */
  stop: function() {

    if(this.interval) clearInterval(this.interval);
    this.interval = null;

  },

  /**
   * (Internal)Processes the next frame.
   */
  nextFrame: function() {

    if(this.running) return; // Nothing to do.

    var This = this, sinVal, angle = 270, foregroundEl = this.getForegroundPreviewEl(), interval,
        backgroundEl = this.getBackgroundPreviewEl(), halfHeight = this.thumbSize.height >> 1;

    this.running = true;

    this.applyPreview(this.items[this.currentItem], backgroundEl);
    this.setAlpha(backgroundEl, 1); // Restores background opacity.
    interval = setInterval(function() {

      sinVal = Math.sin(0.017453293 * angle );
      This.thumbsEl.style.marginTop = Math.round(sinVal * halfHeight + This.ulMarginTop + halfHeight) + 'px';
      This.setAlpha(foregroundEl, 1 - sinVal);

      angle+= 2;
      if(angle >= 450) This.stopNextFrame(interval);
    
    }, 25);

  },

  /**
   * (Internal)Stops the frame iteration.
   *
   * @param  {object}interval - Interval to stop.
   */  
  stopNextFrame: function(interval) {

    clearInterval(interval);
    this.running = false;
    this.nextStackFrame();
    this.swapPreviewEls();

    if(++this.currentItem == this.items.length) this.currentItem = 0;

  },
  
  /**
   * (Internal)Moves the stack pointer one element ahead.
   */
  nextStackFrame: function() {

    var thumbsEl = this.thumbsEl, el = thumbsEl.childNodes[thumbsEl.childNodes.length - 1];
    
    thumbsEl.removeChild (el);
    thumbsEl.insertBefore(el, thumbsEl.firstChild);
    thumbsEl.style.marginTop = this.ulMarginTop + 'px';

  },
  
  /**
   * (Internal)Swaps both preview elements.
   */
  swapPreviewEls: function() {
    
    var backgroundEl = this.getBackgroundPreviewEl(), foregroundEl = this.getForegroundPreviewEl();
    
    this.el.removeChild (foregroundEl);
    this.el.insertBefore(foregroundEl, backgroundEl);  
  
  },
  
  /**
   * (Internal)Applies the given item preview block to the specified DOM element.
   *
   * @param  {object}item - The item descriptor.
   * @param  {object}el - The DOM element to apply item data to.
   */
  applyPreview: function(item, el) {

    this.updateLabel(item, el);
    this.updatePreviewImage(item, el);
    if(item.shopLink) this.enableIcon(item, el); else this.disableIcon(item, el);

  },

  /**
   * (Internal)Updates a preview label.
   *
   * @param  {object}item - The item descriptor.
   * @param  {object}el - The DOM element to apply item data to.
   */
  updateLabel: function(item, el) {

    var subEl = el.labelEl, infoLink = item.infoLink;
    subEl.className = item.labelClass? item.labelClass : this.defLabelClass? this.defLabelClass : '';
    subEl.innerHTML = '<H3>' + item.caption + '</H3>' + '<H5>' + item.label + '</H5>';
    el.style.cursor = 'pointer';
    el.onclick = function() { window.location.href = infoLink; }

  },
 
  /**
   * (Internal)Updates preview image on the given element.
   *
   * @param {object}item - Item containing the image.
   * @param {object}el - Target element.
   */
  updatePreviewImage: function(item, el) {

    if(el.getElementsByTagName('IMG').length) el.removeChild(el.getElementsByTagName('IMG')[0]);
    el.insertBefore(item.previewImage, el.firstChild);
  
  },

  /**
   * (Internal)Enables shopping icon for the preview.
   *
   * @param  {object}item - The item descriptor.
   * @param  {object}el - The DOM element to apply to..
   */
  enableIcon: function(item, el) {

    var subEl = el.iconEl ;
    subEl.className = item.iconClass? item.iconClass : this.defIconClass? this.defIconClass : '';
    subEl.style.display = 'block';
    subEl.style.cursor  = 'pointer';
    subEl.onclick = function() { el.onclick = null; window.location.href = item.shopLink; }

  },
  
  /**
   * (Internal)Disables shopping icon for the preview.
   *
   * @param  {object}item - The item descriptor.
   * @param  {object}el - The DOM element to apply to.
   */
  disableIcon: function(item, el) {

    el.iconEl.style.display = 'none';

  },  

  /**
   * (Internal)Fades in an image.
   *
   * @param {object}image - Image to fade in.
   */
  fadeIn: function(image) {

    var This = this, opacity = 0, interval = setInterval(function() {

      This.setAlpha(image, opacity);
      opacity+= 0.1;
      if(1 == opacity) clearInterval(interval);

    }, 50);

  },

  /**
   * (Internal)Loads all involved images.
   *
   * @param {object}el - Thumbnail container.
   * @param {function}onComplete - Function to launch on end.
   * @param {object}This - Slideshow instance.
   */
  loadImages: function(el, onComplete, This) {

    this.loadThumbs(el, onComplete, This);

  },

  /**
   * (Internal)Loads all thumbnails images.
   *
   * @param {object}el - Thumbnail container.
   * @param {function}onComplete - Function to launch on end.
   * @param {object}This - Slideshow instance.
   */
  loadThumbs: function(el, onComplete, This) {

    var remaining = This.itemsLength, i = 0, l = This.itemsLength, image;

    for(; i < l; i++) {
    
      image = new Image();
      This.setAlpha(image,  0);
      image.thumbEl  = el.childNodes[l - i - 1];
      image.infoLink = This.items[i].infoLink;
      image.style.verticalAlign = 'bottom'; // The IE LI gap fix.

      // After loading the image, launches the preview loader.
      image.onload   = function() {
        this.thumbEl.appendChild(this);
        This.fadeIn(this);
        if(!(--remaining)) This.loadPreviews(onComplete, This);
      }
      
      // Click event, launches informational page.      
      image.onclick = function() {
        window.location.href = this.infoLink;
      }
      image.src = This.items[i].thumbURL;
    
    }

  },

  /**
   * (Internal)Loads all regular images.
   *
   * @param {function}onComplete - Function to launch on end.
   * @param {object}This - Slideshow instance.
   */
  loadPreviews: function(onComplete, This) {

    var remaining = This.itemsLength, i = 0, l = This.itemsLength, image;

    for(; i < l; i++) {
      image = new Image();

      // When image gets loaded, launches the completion callback.
      image.onload = function() {
        if(!(--remaining)) onComplete(This);
      }
      This.items[i].previewImage  =  image;
      image.src = This.items[i].previewURL;
      image.style.cssFloat = 'left';
      image.style.left = '0px';
      image.style.top  = '0px';
    }

  },

  /**
   * (Internal)Creates a DOM element then applies a set of style properties.
   *
   * @param  {string}tag - Tag of the DOM element to create.
   * @param  {object}parameters - Named list containing style properties as keys, style values as values.
   * @return {object}The resulting DOM element.
   */
  createEl: function(tag, parameters) {
  
    if(!parameters) parameters = {}; // Sets defaults.
    
    var retval = document.createElement(tag), member;
    for(member in parameters) if('function' != typeof(parameters[member])) retval.style[member] = parameters[member];
    return retval;    
  
  },  

  /**
   * (Internal)Creates a DOM element representing a thumbnail.
   *
   * @return {object}DOM element that should represent a thumbnail.
   */
  createThumbnailEl: function() {

    var retval  = this.createEl('LI' , {
        width   : this.thumbSize.width ,
        height  : this.thumbSize.height + 'px',
        cursor  : 'pointer',
        margin  : '0px',
        padding : '0px'
    });
    return retval;      

  },

  /**
   * (Internal)Creates a DOM element representing a preview.
   *
   * @return {object}DOM element that should represent a preview.
   */
  createPreviewEl: function() {

    var retval    = this.createEl('DIV'  , {
        width     : this.el.offsetWidth - this.thumbSize.width,
        height    : this.el.offsetHeight,
        cssFloat  : 'left',
        position  : 'absolute',
        overflow  : 'hidden',
        top       : '0px'      
    }), labelEl   = this.createEl('SPAN' , {
        display   : 'block'
    }), iconEl    = this.createEl('SPAN' , {
        display   : 'block'
    });
    
    retval.labelEl = labelEl;     
    retval.appendChild(labelEl); // Adds then registers the label.
    retval.iconEl  = iconEl;      
    retval.appendChild(iconEl ); // Adds then registers the icon.

    return retval;      

  },

  /**
   * (Internal)Start function, run by start() or onInitialize() whenever all required elements get loaded.
   *
   * @param {object}This - The slideshow instance.
   */
  onStart: function(This) {

    This.currentItem = 0;
    This.nextFrame(); // Everything is done, so launch first frame.
    
    This.interval = setInterval(function() {
    
      This.nextFrame();
      
    }, This.duration * 0.75 >> 0); // Frame iterator.

  },
  
  /**
   * (Internal)Initialization callback.
   *
   * @param {function}onComplete - The function to run whenever all data gets loaded.
   * @param {object}This - The slideshow instance.
   */  
  onInitialize: function(onComplete, This) {
  
    this.ulMarginTop = this.el.offsetHeight - (this.itemsLength * this.thumbSize.height);
    var i, l, el  = this.createEl('UL', {
        listStyle : 'none',
        margin    : '0px',
        padding   : '0px',
        cssFloat  : 'left',
        marginLeft: this.el.offsetWidth - this.thumbSize.width + 'px',
        marginTop : this.ulMarginTop + 'px'
    }), image, remaining, thisItem, This = this;
    this.thumbsEl = el;
    this.el.appendChild(el);
    
    for(i = 0, l = this.itemsLength; i < l; i++) el.appendChild(this.createThumbnailEl());
    this.el.appendChild(this.createPreviewEl());
    this.el.appendChild(this.createPreviewEl());
    
    this.el.style.visibility = 'visible'; // Makes the container visible.
    this.onInitialize = function() { /* Intentionally blank */ };

    this.loadImages(el, this.onStart, this);
  
  },

  /**
   * (Internal)Sets the opacity level of an element.
   *
   * @param {object}Element to apply the opacity on.
   * @param {number}Normalized opacity, from 0.0 (fully transparent) to 1.0 (fully opaque)
   */  
  setAlpha: function(el, opacity) {

    if(opacity < 0) opacity = 0; else if(opacity > 1) opacity = 1;
    
    if(this.isIE6) el.style.filter  = 'alpha(opacity=' + (opacity * 100) + ')'; else el.style.opacity = opacity;


  },
  
  /**
   * (Internal)Retrieves the DOM element representing the preview in the background.
   *
   * @return {object}Background preview DOM element.
   */  
  getBackgroundPreviewEl: function() {

    return this.el.getElementsByTagName('DIV')[0];

  },

  /**
   * (Internal)Retrieves the DOM element representing the preview in the foreground.
   *
   * @return {object}Foreground preview DOM element.
   */
  getForegroundPreviewEl: function() {

    return this.el.getElementsByTagName('DIV')[1];

  },

  /**
   * Retrieves left position for the current element.
   *
   * @param  {object}el - Element to recover left position from.
   * @return {number}The left position for the element.
   */
  getLeft: function(el) {
  
    var currentLeft = 0;

    if(el.offsetParent) while(1) {
      currentLeft+= el.offsetLeft;
      if(!el.offsetParent) break;
      el = el.offsetParent;
    } else if(el.x) currentLeft+= el.x;

    return currentLeft;
  
  },

  /**
   * Retrieves top  position for the current element.
   *
   * @param  {object}el - Element to recover top  position from.
   * @return {number}The top  position for the element.
   */
  getTop : function(el) {

    var currentTop  = 0;
    if(el.offsetParent) while(1) {
      currentTop += el.offsetTop ;
      if(!el.offsetParent) break;
      el = el.offsetParent;
    } else if(el.y) currentTop += el.y;
    
    return currentTop ;

  }

} /* Bravo.Slideshow class prototype */

// EOF
    
