// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// VERSION 0.26

var Carousel = Class.create();
Carousel.prototype = {
	// Constructor
	initialize: function(carouselElemID) {
		this.carouselOriginal = $(carouselElemID);
		this.options = Object.extend({
			numVisible:           4,
			scrollInc:            3,
			animParameters:      {},
			buttonStateHandler:  null,
			animHandler:         null,
			ajaxHandler:         null,
			initDoneHandler:     null,
			queue:               "carousel",
			size:                0,
			prevElementID:       "prev-arrow",
			nextElementID:       "next-arrow",
			ajaxParameters:      null,
			url:                 null,
			
			buttonContainer:	 null,
			prevLabel:			 "Previous",
			nextLabel:			 "Next"
		}, arguments[1] || {});

		// Create clip region
		this.carouselClip = document.createElement('DIV');
		Element.makePositioned(this.carouselClip);
		Element.setStyle(this.carouselClip, {
			clear: 			Element.getStyle(this.carouselOriginal, 'clear'),
			width: 			Element.getStyle(this.carouselOriginal, 'width'),
			marginTop: 		Element.getStyle(this.carouselOriginal, 'margin-top'),
			marginRight: 	Element.getStyle(this.carouselOriginal, 'margin-right'),
			marginBottom: 	Element.getStyle(this.carouselOriginal, 'margin-bottom'),
			marginLeft: 	Element.getStyle(this.carouselOriginal, 'margin-left'),
			overflow: 		'hidden' 
		});
		
		this.carouselList = document.createElement('UL');
		Element.makePositioned(this.carouselList);
		Element.setStyle(this.carouselList, {
			width: 			'100000px'
		});
		
		for (var i = j = 0; i < this.carouselOriginal.childNodes.length; i++) {
			if (this.carouselOriginal.childNodes[i].tagName == 'LI') {
				var clone = this.carouselOriginal.childNodes[i].cloneNode(true);
				Element.makePositioned(clone);
				Element.setStyle(clone, {
					position: j > 0 ? 'absolute' : 'absolute',
					width:	Element.getStyle(this.carouselOriginal, 'width'),
					left:	parseFloat(Element.getStyle(this.carouselOriginal, 'width')) * j + 'px',
					top:	0
				});
				
				
				this.carouselList.appendChild(clone);
				
				j++;
			}
		}

		this.carouselClip.appendChild(this.carouselList);
		this.carouselOriginal.parentNode.replaceChild(this.carouselClip, this.carouselOriginal);
		this.carouselClip.id = 'carousel';

		if (this.options.buttonContainer) {
			this.options.buttonContainer = $(this.options.buttonContainer);
			
			this.prevElement = document.createElement('BUTTON');
			this.prevElement.id = this.options.prevElementID;
			this.prevElement.className = 'previous';
			this.prevElement.appendChild(document.createTextNode(this.options.prevLabel));
			this.options.buttonContainer.appendChild(this.prevElement);
			
			this.nextElement = document.createElement('BUTTON');
			this.nextElement.id = this.options.nextElementID;
			this.nextElement.className = 'next';
			this.nextElement.appendChild(document.createTextNode(this.options.nextLabel));
			this.options.buttonContainer.appendChild(this.nextElement);
		} 
		
		this.prevScroll = this._prevScroll.bindAsEventListener(this);
		this.nextScroll = this._nextScroll.bindAsEventListener(this);
		Event.observe(this.options.prevElementID, "click", this.prevScroll);
		Event.observe(this.options.nextElementID, "click", this.nextScroll);

		this.carouselElemID = this.carouselClip;
		this.initDone = false;
		this.animRunning = "none";
    	this.requestIsRunning = false;

		// add afterFinish options to animParameters (store old function)
		this.animAfterFinish = this.options.animParameters.afterFinish;
		Object.extend(this.options.animParameters, {afterFinish:  this._animDone.bind(this), queue: { position:'end', scope: this.options.queue }});
	  
		// Event bindings
		this.onComplete = this._onComplete.bindAsEventListener(this);
		this.onFailure  = this._onFailure.bindAsEventListener(this);

		// Init data
		this._init();
	},
  
  	// Destructor
 	destroy: function() {
		Event.stopObserving(this.options.prevElementID, "click", this.prevScroll);
		Event.stopObserving(this.options.nextElementID, "click", this.nextScroll);
	},
	
	scrollTo: function(newStart) {
		var old_inc = this.options.scrollInc;
		this.ignoreNoMoreImages = true;
		if(newStart > this.currentIndex) {
			this.options.scrollInc = newStart - this.currentIndex;
			this._nextScroll(this);
		} else {
			this.options.scrollInc = this.currentIndex - newStart;
			this._prevScroll(this);
		}
	    this.options.scrollInc = old_inc;
	},
	
	/* "Private" functions */
	_init: function() {
	    this.currentIndex = 0;
      
	    // Ajax content
	    if (this.options.url)
  			this._request(this.currentIndex, this.options.numVisible);
	  	// Static content
  		else {
  	  		this._getLiElementSize();
	    	this.options.size = this.carouselList.getElementsByTagName("li").length;
    		this._updateButtonStateHandler(this.options.prevElementID, false);
	  		this._updateButtonStateHandler(this.options.nextElementID, this.options.size > this.options.numVisible);
	  	}
  	},
  
  	_prevScroll: function(event) {
		if (event.target)
			event.target.blur();

		Event.stop(event);
    	if (this.animRunning != "none" || this.currentIndex == 0)
      		return;

	    var inc = this.options.scrollInc;

	    if (this.currentIndex - inc < 0)
			inc = this.currentIndex;

    	this._scroll(inc)		  
	  	return false;
  	},
  
  	_nextScroll: function(event) {    
		if (event.target)
			event.target.blur();
	
		Event.stop(event);
    	if (this.animRunning != "none")
			return false;
            
    	// Check if there are enough elements in cache
    	if (this.currentIndex + this.options.numVisible + this.options.scrollInc <= this.options.size) 
      		this._scroll(-this.options.scrollInc);
    	else {
      		// Compute how many are in the cache
      		this.nbInCache = this.options.size - (this.currentIndex + this.options.numVisible);
      		if (this.options.url && this.noMoreImages == false) 
		    	this._request(this.currentIndex + this.options.numVisible + this.nbInCache, this.options.scrollInc - this.nbInCache);
	    	else  {
	      		if (this.nbInCache > 0)
          			this._scroll(-this.nbInCache);
        	}
	  	}
	  	return false;
  	},
  
  	_request: function(start, nb) {
    	if (this.options.url && ! this.requestIsRunning) {
      		this.requestIsRunning = true;
      
      		if (this.options.ajaxHandler)
        		this.options.ajaxHandler(this, "before");
      
      		var params = "start=" + start + "&nb=" + nb;
      		if (this.options.ajaxParameters != null)
        		params += "&" + this.options.ajaxParameters
      
  			new Ajax.Request(this.options.url, {parameters: params, onComplete: this.onComplete, onFailure: this.onFailure});
		}
  	},
  
  	_onComplete: function(originalRequest) {
    	this.requestIsRunning = false;
    	this.carouselList.innerHTML += originalRequest.responseText;
		// Compute how many new elements we have
    	var size = this.options.size;
    	this.options.size = this.carouselList.getElementsByTagName("li").length;
    	var inc = this.options.size - size;
    
		// First run, compute li size
		if (this.initDone == false) {
  			this._getLiElementSize()
  			this.currentIndex = 0;
  			this.initDone = true;
      		if (this.options.initDoneHandler) 
        		this.options.initDoneHandler(this);

  			// Update button states
		  	this._updateButtonStateHandler(this.options.prevElementID, false);
		  	this._updateButtonStateHandler(this.options.nextElementID, this.options.size == this.options.numVisible);
		  	this.noMoreImages = this.options.size < this.options.numVisible
		}
		// Add images
		else {
			if (!this.ignoreNoMoreImages)
		    	this.noMoreImages = inc != this.options.scrollInc;
		  	else
		    	this.ignoreNoMoreImages = false;
		  	// Add images
		  	if (inc > 0) {
        		this._scroll(-inc, this.noMoreImages)
      		}
      		// No more images, disable next button
		  	else {
		    	if (this.nbInCache >0)
          			this._scroll(-this.nbInCache, true);
		    
		    	this._updateButtonStateHandler(this.options.nextElementID, false);
	    	}
		}
		if (this.options.ajaxHandler)
      		this.options.ajaxHandler(this, "after");
  	},
  
  	_onFailure: function(originalRequest){    
    	this.requestIsRunning = false;
  	},

  	_animDone: function(event){   
    	if (this.options.animHandler)
      		this.options.animHandler(this.carouselElemID, "after", this.animRunning);
     
    	this.animRunning = "none";
    	// Call animAfterFinish if exists
    	if (this.animAfterFinish)
      		this.animAfterFinish(event);
  	},
  
  	_updateButtonStateHandler: function(button, state) {
		if (this.options.buttonStateHandler) 
			this.options.buttonStateHandler(button, state)
   	},
  
  	_scroll: function(delta, forceDisableNext) {
    	// this.animRunning = delta > 0 ? "prev" : "next";
    
    	if (this.options.animHandler)
      		this.options.animHandler(this.carouselElemID, "before", this.animRunning);

		new Effect.Move(this.carouselList, Object.extend({
			x: delta * this.elementSize
    	}, this.options.animParameters || {})); 

		this.currentIndex -= delta;
    	this._updateButtonStateHandler(this.options.prevElementID, this.currentIndex != 0);
    
    	if (this.options.url && this.noMoreImages == false)
      		enable = true;
    	else
      		enable = (this.currentIndex + this.options.numVisible < this.options.size);
    	this._updateButtonStateHandler(this.options.nextElementID, (forceDisableNext ? false : enable));
  	},
  
  	_getLiElementSize: function() {
    	var li = $(this.carouselList.getElementsByTagName("li")[0]);
		this.elementSize = li.getDimensions().width + parseFloat(li.getStyle("margin-left")) + parseFloat(li.getStyle("margin-right"));
  	}
}