
YE = YAHOO.util.Event;
YD = YAHOO.util.Dom;
YL = YAHOO.util.Lang;
YUA = YAHOO.env.ua;

CWS = {
  debug: false,
  log: function()
  {
    if(CWS.debug && typeof console === 'object' && typeof console.log !== 'undefined')
    {
      if(YUA.gecko > 0)
        console.log.apply(this, arguments);
      else if (YUA.ie > 0 || YUA.webkit)
        console.log(CWS.sprintf.apply(this, arguments));
    }
  },
  hasAllProperties: function(o, properties)
  {
    var hasAll = true;

    for(var i = 0; i < properties.length; i++)
    {
      if(!(properties[i] in o))
      {
        CWS.log('Missing property: %s', properties[i]);
        hasAll = false;
      }
    }

    return hasAll;
  },
  sprintf: function() //from http://code.google.com/p/sprintf/
  {
    var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
    while (f) {
      if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
      else if (m = /^\x25{2}/.exec(f)) o.push('%');
      else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
        if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
        if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
				{
					a = parseInt(a);
				}
        switch (m[7]) {
          case 'b': a = a.toString(2); break;
          case 'c': a = String.fromCharCode(a); break;
          case 'd': a = parseInt(a); break;
          case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
          case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
          case 'o': a = a.toString(8); break;
          case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
          case 'u': a = Math.abs(a); break;
          case 'x': a = a.toString(16); break;
          case 'X': a = a.toString(16).toUpperCase(); break;
        }
        a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
        c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
        x = m[5] - String(a).length;
        p = m[5] ? CWS.str_repeat(c, x) : '';
        o.push(m[4] ? a + p : p + a);
      }
      else throw ("Huh ?!");
      f = f.substring(m[0].length);
    }
    return o.join('');
  },
  str_repeat: function(i, m)
  {
    for (var o = []; m > 0; o[--m] = i); return(o.join(''));
  }
};


/**
 * CWS.Carousel fades images in and out and can display arbitrary html over
 * each image at arbitrary positions.
 *
 * You should only need to call the init function. init() will add
 * a listener for the canvas id being added to the dom so you can call
 * init() whether the canvas div is present in the dom or not. The same is
 * true of the nav unordered list.
 *
 * Example:
 *
 * <!-- Required styles -->
 *
 * <style>
 *
 *   #carousel-canvas {
 *     position: relative;
 *   }
 *
 *   .carousel-image,
 *   .carousel-overlay {
 *     position: absolute;
 *   }
 *
 * </style>
 *
 *
 * <!-- Required js. The order of the script tags matters. -->
 *
 * <script type="text/javascript" src="http://yui.yahooapis.com/combo?2.8.0r4/build/yahoo-dom-event/yahoo-dom-event.js&2.8.0r4/build/animation/animation-min.js"></script>
 * <script type="text/javascript" src="path/to/cws/carousel.js"></script>
 *
 *
 * <!-- The div, ul and script below can appear anywhere in the dom and in any order. -->
 *
 * <div id="carousel-canvas"><!-- nothing here --></div>
 *
 * <ul id="carousel-nav"><!-- nothing here --></ul>
 *
 * <script type="text/javascript">
 *
 *   var config = {
 *     imagePath: 'path/to/images',
 *     width: '600px',
 *     height: '400px',
 *     canvas: 'carousel-canvas', // id of canvas div
 *     nav: 'carousel-nav', // id of navigation ul
 *     navTemplate: '<a href="#" id="{id}">{content}</a>',
 *     items: [
 *       {
 *         image: 'image1.jpg',
 *         overlay: '<h1>This is some arbitraty html shown over the image.</h1>',
 *         overlayPosition: ['30px', '60px'],
 *         navContent: 'Navigation Text',
 *         transitionDuration: 1000, //ms
 *         displayDuration: 1000, // ms
 *         fadeType: 'easeNone', // see http://developer.yahoo.com/yui/docs/YAHOO.util.Easing.html for possible options
 *         url: null
 *       },
 *       {
 *         image: 'image2.jpg',
 *         overlay: '<h1>Lorem ipsum....</h1>',
 *         overlayPosition: ['70px', '12px'],
 *         navContent: 'More Navigation Text',
 *         transitionDuration: 1000, //ms
 *         displayDuration: 1000, // ms
 *         fadeType: 'easeNone',
 *         url: 'http://www.google.com'
 *       },
 *       // more images
 *     ]
 *   };
 *
 *   CWS.Carousel.init(config);
 *
 * </script>
 */
CWS.Carousel = function() 
{
  // private properties of CWS.Carousel

  var that = null,
    imageInstances = [],
    readyImages = [],
    items = [],
    imagePath = null,
    width = null,
    height = null,
    canvas = null,
    currentIndex = null,
    fadeinImage = null,
    fadeinOverlay = null,
    navTemplate = null,
    nav = null,
    stop = true,
    a = null,
    b = null,
    current = null,
    notCurrent = null,
    showNextDelayTimeout = null;

  /**
   * Called once the animation displaying a new image is finished.
   */
  var delayShowNext = function()
  {
    CWS.log('Showing: index=%d, duration=%dms', currentIndex, items[currentIndex].displayDuration);

    YD.setStyle(notCurrent.image, 'opacity', 0);
    YD.setStyle(notCurrent.overlay, 'opacity', 0);

    showNextDelayTimeout = setTimeout(showNext, items[currentIndex].displayDuration);
  };

  /**
   * Called once displayDuration has passed.
   */
  var showNext = function()
  {    
    if(stop)
    {
      CWS.log('Aborting show next');
      return;
    }
    
    var nextI = nextIndex(); 
    
    if(readyImages[nextI] !== true)
    {
      CWS.log('Delaying showNext until image %d loaded.', nextI);
      
      YE.on(imageInstances[nextI], 'load', function()
      {
        readyImages[nextI] = true
        showNext();
      });
      
      return;
    }

    var firstShowNext = (currentIndex == -1);

    increment();
    swapCurrent();

    current.image.innerHTML = getImageTag(currentIndex);
    current.overlay.innerHTML = items[currentIndex].overlay;

		setOverlayBackground();

    positionOverlay();
    preloadImage(nextIndex());

    if(firstShowNext)
    {
      delayShowNext();
      return;
    }

    var animationType = 'YAHOO.util.Easing.'+items[currentIndex].fadeType;
    var animDuration = items[currentIndex].transitionDuration;

    CWS.log('Fading in: index=%d, src=%s, duration=%dms, type=%s', currentIndex, getImageSrc(currentIndex), animDuration, animationType);

    animDuration = animDuration/1000;

    animationType = window[animationType];

    fadeinImage = new YAHOO.util.Anim(
      current.image,
      {opacity: {from: 0, to: 1}},
      animDuration,
      animationType
    );

    fadeinImage.onComplete.subscribe(delayShowNext);

    fadeinOverlay = new YAHOO.util.Anim(
      current.overlay,
      {opacity: {from: 0, to: 1}},
      animDuration,
      animationType
    );

    fadeinImage.animate();
    fadeinOverlay.animate();
    
    setTimeout(setActiveNav, items[currentIndex].transitionDuration/2);
  };

  /**
   * Called when a nav item is clicked.
   */
  var showIndex = function(i)
  {
    CWS.log('Show index (nav clicked): index=%d', i);

    currentIndex = i;

    preloadImage(i);
    
    swapCurrent();

    current.image.innerHTML = getImageTag(i);
    current.overlay.innerHTML = items[i].overlay;

		setOverlayBackground();

    positionOverlay();

    YD.setStyle(current.image, 'opacity', 1);
    YD.setStyle(notCurrent.image, 'opacity', 0);
    YD.setStyle(current.overlay, 'opacity', 1);
    YD.setStyle(notCurrent.overlay, 'opacity', 0);

    setActiveNav();
    
    preloadImage(nextIndex());
  };

	var setOverlayBackground = function()
	{
		if (YUA.ie > 0) { // internet explorer, text gets ugly if there's no background
      YD.setStyle(current.overlay, 'background-image', 'url('+getImageSrc(currentIndex)+')');
	YD.setStyle(current.overlay, 'background-color', '#fff');
    }
	};
  
  var setActiveNav = function()
  {
    //currentIndex will be -1 if initNav happens before initCanvas
    var i = (currentIndex == -1) ? 0 : currentIndex;
    
    that.onChange.fire(items[i]);
    
    if(typeof items[0].nav === 'undefined')
      return;
    
    for(var j = 0; j < items.length; j++)
    {
      YD.removeClass(items[j].nav, 'carousel-nav-active');
    }

    YD.addClass(items[i].nav, 'carousel-nav-active');
  }

  var swapCurrent = function()
  {
    YE.removeListener(current.image, 'click');
    YD.setStyle(current.image, 'cursor', 'default');
    
    var t = current;
    current = notCurrent;
    notCurrent = t;

    if(YL.isString(items[currentIndex].url) && items[currentIndex].url.length > 0)
    {
      YE.on(current.image, 'click', function(){document.location = items[currentIndex].url});
      YD.setStyle(current.image, 'cursor', 'pointer');
    }
    
    YD.setStyle(current.image, 'z-index', 22);
    YD.setStyle(current.overlay, 'z-index', 24);
    YD.setStyle(notCurrent.image, 'z-index', 12);
    YD.setStyle(notCurrent.overlay, 'z-index', 14);
  };

  var positionOverlay = function()
  {
    YD.setStyle(current.overlay, 'left', items[currentIndex].overlayPosition[0]);
    YD.setStyle(current.overlay, 'top', items[currentIndex].overlayPosition[1]);

    if (YUA.ie > 0) { // internet explorer

			/*var x = items[currentIndex].overlayPosition[0].replace('px',''),
				y = items[currentIndex].overlayPosition[1].replace('px','');

				x = parseInt(x)+2;
				y = parseInt(y)+2;

      YD.setStyle(current.overlay, 'background-position', '-'+x + 'px -' + y + 'px');*/

			YD.setStyle(current.overlay, 'background-position', '-'+items[currentIndex].overlayPosition[0] + ' -' + items[currentIndex].overlayPosition[1]);
    }
  };

  var preloadImage = function(i)
  {
    if(YD.get(getImageId(i)))
    {
      CWS.log('Image id %s present already?', getImageId(i));
    }    
    
    if(YL.isObject(imageInstances[i]))
    {
      CWS.log('Image already preloaded: index=%d', i);
      return;
    }

    CWS.log('Preloading image: index=%d', i);

    imageInstances[i] = new Image();
    
    readyImages[i] = false;
    
    YE.on(imageInstances[i], 'load', function()
    {
      CWS.log('Image %d ready', i);
      readyImages[i] = true
    });
    
    imageInstances[i].src = getImageSrc(i);
  };

  var increment = function()
  {
    currentIndex = nextIndex();
  };

  var nextIndex = function()
  {
    return (currentIndex+1) % items.length;
  }

  var getImageId = function(i)
  {
    return 'carousel-image-'+i;
  };
  
  var getImageTag = function(i)
  {
    return '<img '+
      'id="'+getImageId(i)+'" '+
      'src="'+getImageSrc(i)+'" '+
      'height="'+height+'" '+
      'width="'+width+'"/>';
  };

  var getImageSrc = function(i)
  {
    var img = items[i].image;
    return imagePath+'/' + img;
  };

  var initCanvas = function()
  {
    canvas = YD.get(canvas);
    
    canvas.innerHTML = ''+
      '<div id="carousel-image-a" class="carousel-image"></div>'+
      '<div id="carousel-overlay-a" class="carousel-overlay"></div>'+
      '<div id="carousel-image-b" class="carousel-image"></div>'+
      '<div id="carousel-overlay-b" class="carousel-overlay"></div>';

    notCurrent = a = {
      image: YD.get('carousel-image-a'),
      overlay: YD.get('carousel-overlay-a')
    };

    current = b = {
      image: YD.get('carousel-image-b'),
      overlay: YD.get('carousel-overlay-b')
    };

    var containers = [a.image, b.image, canvas];

    YD.setStyle(containers, 'width', width);
    YD.setStyle(containers, 'height', height);

    preloadImage(0);
    
    that.play();
  };

  var initNav = function()
  {
    nav = YD.get(nav);

    var navHTML = '';

    for(var i = 0; i < items.length; i++)
    {
      var id = 'carousel-nav-item-'+i;
      items[i].nav = id;
      navHTML += '<li>'+navTemplate.replace('{content}', items[i].navContent).replace('{id}', id)+'</li>';
    }

    nav.innerHTML = navHTML;

    for(i = 0; i < items.length; i++)
    {
      items[i].nav = YD.get(items[i].nav);
      YE.on(items[i].nav, 'click', setIndex);
      if(i === items.length-1)
        YD.addClass(items[i].nav, 'last');
    }

    setActiveNav(0);
  };

  var setIndex = function(e)
  {
    YE.preventDefault(e);

    for(var i = 0; i < items.length; i++)
    {
      if(items[i].nav==this)
      {
        that.stopAndShow(i);
      }
    }

    return false;
  };

  return {

    // public properties of CWS.Carousel

		stop: function()
    {
			that.stopAndShow(that.currentIndex);
    },

    stopAndShow: function(i)
    {
      stop = true; //prevents further changes to canvas
      
      if(YL.isObject(fadeinOverlay) && fadeinOverlay.isAnimated())
      {
        CWS.log('Stopping animation');
        fadeinOverlay.stop();
        fadeinImage.stop();
      }
      
      that.onStop.fire();
      
      showIndex(i);
    },
    
    play: function()
    {
      if(stop)
      {
        stop = false;
        that.onPlay.fire();
        clearTimeout(showNextDelayTimeout);        
        showNext();
      }
    },
    
    onChange: null,
    
    onPlay: null,
    
    onStop: null,
    
    init: function(config)
    {
      that = this;
      
      this.onChange = new YAHOO.util.CustomEvent('onChange');
      this.onPlay = new YAHOO.util.CustomEvent('onPlay');
      this.onStop = new YAHOO.util.CustomEvent('onStop');
      
      var configProperties = [
        'width', 'height', 'items', 'imagePath',
        'canvas', 'navTemplate', 'nav'
      ];

      if(!CWS.hasAllProperties(config, configProperties))
      {
        CWS.log('Invalid configuration, stopping.');
        return;
      }

      width = config.width;
      height = config.height;
      items = config.items;
      imagePath = config.imagePath;
      canvas = config.canvas;
      navTemplate = config.navTemplate;
      nav = config.nav;

      if(items.length < 1)
      {
        CWS.log('Empty carousel, will not attempt to load carousel.');
        return;
      }

      var itemProperties = [
        'image', 'overlay', 'overlayPosition', 'navContent',
        'transitionDuration', 'displayDuration', 'fadeType',
        'url'
      ];

      for(var i = 0; i < items.length; i++)
      {
        if(!CWS.hasAllProperties(items[i], itemProperties))
        {
          CWS.log('Invalid item at index %d, stopping.', i);
          return;
        }
      }

      currentIndex = -1;

      YE.onContentReady(canvas, initCanvas);
      YE.onContentReady(nav, initNav);
    }
  };
}();


