/* MacStyleDock.js - a function for creating a Mac-OSX-style dock
 *
 * The author of this program, Safalra (Stephen Morley), irrevocably releases
 * all rights to this program, with the intention of it becoming part of the
 * public domain. Because this program is released into the public domain, it
 * comes with no warranty either expressed or implied, to the extent permitted
 * by law.
 *
 * For more public domain JavaScript code by the same author, visit:
 *
 * http://www.safalra.com/web-design/javascript/
 */

/* Creates a Carrousel. A Carrousel is a row of images that expand as the
 * mouse pointer moves over them, and move slowly to the left. Images that
 * disappear from the left side are re-added at the end (right side). The
 * images are created as children of the specified node with the specified
 * minimum and maximum sizes. Two other parameters specify the images to
 * be used and the range of expansion. The parameters are:
 *
 * node         - the node at which to create the Carrousel
 * imageDetails - an array each of whose elements are objects with three
 *                properties:
 *                - small     - The small image
 *                - large     - The large image
 *                - onclick   - the function to call when the image is clicked
 * minimumWidth, -Height   - the minimum size of icons in the dock
 * maximumWidth, -Height   - the maximum size of icons in the dock
 * range          - the range of expansion, in icons. This must be an integer.
 * spacing        - the space between the icons.
 */
function Carrousel(node, imageDetails, minimumWidth, minimumHeight, maximumWidth, maximumHeight, range, spacing){

  // create a container for the icons and add it to the dock container
  var iconsNode = node;

  // set the icon containers to centre its contents
  iconsNode.style.position          = 'relative';
  
  // initialise the scale factor to 0
  var scale  = 0;
  
  var maximumTotalWidth = 0;
  
  var carrouselPosition = 0;
  
  // initialise the time-outs and intervals to 0
  var closeTimeout     = null;
  var closeInterval    = null;
  var openInterval     = null;
  var mouseoverTimeout = null;
  
  // create an array to store the DOM nodes of the icons
  var iconNodesSmall = [];
  
  // create an array to store the DOM nodes of reflections of the icons
  var iconNodesLarge = [];
  
  // create an array to store the sizes of the icons
  var iconWidth = [];
  var iconHeight = [];
  
  var iconPosition = [];
  
  var iconMouseover = [];
  
  var activeIndex = false;
  var activeAcross = 0;
  
  // loop over the images
  for (var i = 0; i < imageDetails.length; i++){
  
    // create and store a node for the icon for this image
    iconNodesSmall[i] = document.createElement('div');
    iconNodesSmall[i].className = 'carrousel-small';
    iconNodesLarge[i] = document.createElement('div');
    iconNodesLarge[i].className = 'carrousel-large';
    
    // position the icon for this image relatively
    iconNodesSmall[i].style.position = 'absolute';
    iconNodesLarge[i].style.position = 'absolute';
    
    iconPosition[i] = (minimumWidth+spacing) * i;
    
    //insert the images
    var imageSmall = document.createElement('img');
    imageSmall.setAttribute('src', imageDetails[i].small);
    iconNodesSmall[i].appendChild(imageSmall);
    
    var imageLarge = document.createElement('img');
    imageLarge.setAttribute('src', imageDetails[i].large);
    iconNodesLarge[i].appendChild(imageLarge);
       
    iconWidth[i] = minimumWidth;
    iconHeight[i] = minimumHeight;
       
      // add the DOM node to the array of stored images
    // store the initial size of the icon for this image
    //iconSizes[i] = minimumSize;
    
    // create and store a node for the icon for this image
    
    // update the properties of the icon for this image
    updateIconProperties(i);
    
    // add the span for this image to the dock
    iconsNode.appendChild(iconNodesSmall[i]);
    iconsNode.appendChild(iconNodesLarge[i]);
    
    // add the span for this image to the dock
    
    // add the appropriate event listeners to the icon for this image
    /*if (iconNodesSmall[i].addEventListener){
      iconNodesSmall[i].addEventListener('mousemove', processMouseMove, false); 
      iconNodesSmall[i].addEventListener('mouseout', processMouseOut, false);
      iconNodesSmall[i].addEventListener('click', imageDetails[i].onclick, false);
    }else if (iconNodesSmall[i].attachEvent){
      iconNodesSmall[i].attachEvent('onmousemove', processMouseMove);
      iconNodesSmall[i].attachEvent('onmouseout', processMouseOut);
      iconNodesSmall[i].attachEvent('onclick', imageDetails[i].onclick);
    }*/
    if (iconNodesLarge[i].addEventListener){      
      iconNodesLarge[i].addEventListener('mouseover', processMouseOver, false); 
      iconNodesLarge[i].addEventListener('mousemove', processMouseMove, false); 
      iconNodesLarge[i].addEventListener('mouseout', processMouseOut, false);
      iconNodesLarge[i].addEventListener('click', imageDetails[i].onclick, false);
    }else if (iconNodesLarge[i].attachEvent){
      iconNodesLarge[i].attachEvent('onmouseover', processMouseOver);
      iconNodesLarge[i].attachEvent('onmousemove', processMouseMove);
      iconNodesLarge[i].attachEvent('onmouseout', processMouseOut);
      iconNodesLarge[i].attachEvent('onclick', imageDetails[i].onclick);
    }
    
    iconNodesLarge[i].style.cursor = 'pointer';
    
//    if (imageDetails[i].onmouseover) {
//      iconMouseover[i] = imageDetails[i].onmouseover;
//      iconNodesLarge[i].style.cursor = 'pointer';
//    }
    
  }
  moveCarrousel();


  /* Sets a toolbar image to the specified size. The parameter is:
   *
   * index - the 0-based index of the image to be sized
   */
  function updateIconProperties(index){
  
    // determine the size for the icon, taking into account the scale factor
    var width = minimumWidth + scale * (iconWidth[index] - minimumWidth);
    var height = Math.round(minimumHeight + scale * (iconHeight[index] - minimumHeight));
    
    var opacity = (width - minimumWidth) / (maximumWidth - minimumWidth);
    
    width = Math.round(width);

    // set the width and height of the image for the icon and its reflection
    iconNodesSmall[index].style.width = width+'px';
    iconNodesSmall[index].style.height = height+'px';
    iconNodesLarge[index].style.width = width+'px';
    iconNodesLarge[index].style.height = height+'px';
    
    iconNodesSmall[index].childNodes[0].setAttribute('width',  width);
    iconNodesSmall[index].childNodes[0].setAttribute('height', height);
    iconNodesLarge[index].childNodes[0].setAttribute('width',  width);
    iconNodesLarge[index].childNodes[0].setAttribute('height', height);
    
    iconNodesSmall[index].style.left = (index * 100)+'px';
    iconNodesLarge[index].style.left = (index * 100)+'px';
    
    iconNodesLarge[index].style.opacity = opacity;
    iconNodesLarge[index].style.filter = 'alpha(opacity = ' + Math.round(opacity * 100) + ')';
    
    iconNodesSmall[index].style.left = iconPosition[index]+'px';
    iconNodesLarge[index].style.left = iconPosition[index]+'px';
    iconNodesSmall[index].style.top = -1 * Math.round(scale * ((iconHeight[index]-minimumHeight)/2))+'px';
    iconNodesLarge[index].style.top = -1 * Math.round(scale * ((iconHeight[index]-minimumHeight)/2))+'px';
    
    // set the top margin of the image for the icon
    //iconNodes[index].style.marginTop = (maximumSize - size) + 'px';
    //reflectedIconNodes[index].style.marginBottom = (maximumSize - size) + 'px';
    
  }
  
  function processMouseOver(e){
    processMouseMove(e);
    
    if (!e) {
      e = window.event;
    }
    
    // find the DOM node on which the mouseover event occured
    var target = e.target || e.srcElement;
    
    // obtain the index of the icon on which the mouseover event occured
    var index = 0;
    while ((iconNodesLarge[index] != target) && (iconNodesLarge[index].childNodes[0] != target)) {
      index++;
    }
    
    if (iconMouseover[index]) {
      window.clearTimeout(mouseoverTimeout);
      mouseoverTimeout = window.setTimeout(function(){
        iconMouseover[index]();
      }, 250);
    }
  }

  /* Processes a mousemove event on an image in the 'dock'. The parameter is:
   *
   * e - the event object. window.event will be used if this is undefined.
   */
  function processMouseMove(e){
   
    // clear the closing interval and time-out
    window.clearTimeout(closeTimeout);
    closeTimeout = null;
    window.clearInterval(closeInterval);
    closeInterval = null;
    
    // check that the opening interval is required but does not yet exist
    if (scale != 1 && !openInterval){
    
      // create the opening interval
      openInterval = window.setInterval(
          function(){
            if (scale < 1) {
              scale += 0.125;
            }
            if (scale >= 1){
              scale = 1;
              window.clearInterval(openInterval);
              openInterval = null;
            }
            updateAllIconProperties();
          },
          20);
          
    }
    
    // set the event object if the browser does not supply it
    if (!e) {
      e = window.event;
    }
    
    // find the DOM node on which the mouseover event occured
    var target = e.target || e.srcElement;
    
    // obtain the index of the icon on which the mouseover event occured
    var index = 0;
    while ((iconNodesLarge[index] != target) && (iconNodesLarge[index].childNodes[0] != target)) index++;
    
    // obtain the fraction across the icon that the mouseover event occurred
    var across = (e.layerX || e.offsetX) / iconWidth[index];
    
    // check a distance across the icon was found (in some cases it will not be)
    if (across){
      calculateSize(index, across);
    }
  }
    
  function calculateSize(index, across)
  {
    activeIndex = index;
    activeAcross = across;
    // initialise the current width to 0
    var currentWidth = 0;
  
    // loop over the icons
    for (var i = 0; i < iconNodesLarge.length; i++){
    
      // check whether the icon is in the range to be resized
      if (i < index - range || i > index + range){
      
        // set the icon size to the minimum size
        iconWidth[i] = minimumWidth;
        iconHeight[i] = minimumHeight;
        
      }else if (i == index){
      
        // set the icon size to be the maximum size
        iconWidth[i] = maximumWidth;
        iconHeight[i] = maximumHeight;
      
      }else if (i < index){
      
        // set the icon size to the appropriate value
        iconWidth[i] =
            minimumWidth
            + Math.round(
                (maximumWidth - minimumWidth - 1)
                * (
                    Math.cos(
                        (i - index - across + 1) / range * Math.PI)
                    + 1)
                / 2);
        iconHeight[i] =
            minimumHeight
            + Math.round(
                (maximumHeight - minimumHeight - 1)
                * (
                    Math.cos(
                        (i - index - across + 1) / range * Math.PI)
                    + 1)
                / 2);
        
        // add the icon size to the current width
        currentWidth += iconWidth[i];
      
      }else{
      
        // set the icon size to the appropriate value
        iconWidth[i] =
            minimumWidth
            + Math.round(
                (maximumWidth - minimumWidth - 1)
                * (
                    Math.cos(
                        (i - index - across) / range * Math.PI)
                    + 1)
                / 2);
        iconHeight[i] =
            minimumHeight
            + Math.round(
                (maximumHeight - minimumHeight - 1)
                * (
                    Math.cos(
                        (i - index - across) / range * Math.PI)
                    + 1)
                / 2);
        
        // add the icon size to the current width
        currentWidth += iconWidth[i];
      
      }
      
     
    }
    
    // update the maximum width if necessary
    if (currentWidth > maximumTotalWidth) maximumTotalWidth = currentWidth;
    
    // detect if the total size should be corrected
    if (index >= range
        && index < iconWidth.length - range
        && currentWidth < maximumTotalWidth){
          
      // correct the size of the smallest magnified icons
      iconWidth[index - range] += Math.floor((maximumTotalWidth - currentWidth) / 2);
      iconWidth[index + range] += Math.ceil((maximumTotalWidth - currentWidth) / 2);
    
    }
    
    // update the sizes of the images
    updateAllIconProperties();
    
  }

  // Processes a mouseout event on an image in the dock.
  function processMouseOut(){
  
    // check that neither the closing interval nor time-out are set
    if (!closeTimeout && !closeInterval){
    
      // create the closing time-out
      closeTimeout = window.setTimeout(
          function(){
            closeTimeout = null;
            if (openInterval){
              window.clearInterval(openInterval);
              openInterval = null;
            }
            closeInterval = window.setInterval(
                function(){
                  if (scale > 0) scale -= 0.125;
                  if (scale <= 0){
                    scale = 0;
                    window.clearInterval(closeInterval);
                    closeInterval = null;
                  }
                  /*for (var i = 0; i < iconNodesLarge.length; i++){
                    updateIconProperties(i);
                  }*/
                  updateAllIconProperties()
                },
                20);
          },
          100);
          
    }
    activeIndex = false;
  }
  
  function updateAllIconProperties()
  {
    var width = 0;
    for (var i = 0; i < iconWidth.length; i++) {
      width += iconWidth[i]
    }
    var shift = Math.round(scale * (width - (iconWidth.length * minimumWidth)) / 2);
    var left = carrouselPosition - shift;
    for (var i = 0; i < iconNodesLarge.length; i++) {
      iconPosition[i] = left;
      updateIconProperties(i);
      left += Math.round(minimumWidth + scale*(iconWidth[i] - minimumWidth)) + spacing;
    }
  }

  function rotateArray(a)
  {
    var tmp = a[0];
    for (var i = 1; i < a.length; i++) {
      a[i-1] = a[i];
    }
    a[a.length-1] = tmp;
  }
  
  function moveCarrousel()
  {
    carrouselPosition--;
    if ((carrouselPosition * -1) > maximumWidth + spacing) {
      carrouselPosition += (minimumWidth + (scale * (iconWidth[0] - minimumWidth)) + spacing);
      rotateArray(iconNodesSmall);
      rotateArray(iconNodesLarge);
      rotateArray(iconWidth);
      rotateArray(iconHeight);
      rotateArray(iconMouseover);
      activeIndex--;
    }
    
    if (activeIndex) {
      var across = 0;
      across = activeAcross + (1/maximumWidth);
      calculateSize(activeIndex, across);
    }
    
    updateAllIconProperties();
    setTimeout(moveCarrousel, 50);
  }
}
