/**
 * A bar that features a number of specific items in a HTML page, scrolls them through 
 *  automatically and enables the visitor to speed up scrolling using a 'forward' button
 */
function PreviewBar() {

	/**
	 * The items that are to be shown in this preview bar
	 * @var PreviewItem[]
	 */
	var items;
	
	/**
	 * The current state of the controller of the previewbar
	 * @var String		Either 'initializing', 'waiting' or 'moving'
	 */
	var state;
	
	/**
	 * The amount of moved pixels (only important when the state is 'moving')
	 * @var float		A number within the range [0, 3 * (257 + 51)>
	 */
	var movePosition;
	
	/**
	 * The index of the item that is currently shown in this.itemContainer[0]
	 */
	var currentItemIndex;
	
	/**
	 * Timer that starts the next scrolling movement
	 * @var Timeout
	 */
	var nextScrollTimer;
	
	/**
	 * Timer that is used to cause a new timestep during scrolling
	 * @var Timeout
	 */
	var scrollStepTimer;
	
	/**
	 * The container that contains the preview bar
	 * @var HtmlElement
	 */
	var container;
	
	/**
	 * The containers of the 4 individual list items in the container (of which the 4th is either invisible or partly visible)
	 * @var HtmlElement
	 */
	var itemContainers;

	/**
	 * Returns the container unordered list element which contains the HTML code that represents this preview bar
	 * @return HtmlElement
	 * @note The container should not be modified by anything other than this preview bar
	 */
	this.getContainer = function() {
		return this.container;
	}
	
	/**
	 * Adds a preview item to the preview bar
	 * @param PreviewItem item
	 * @pre This preview bar is in 'initializing' state
	 * @note The order in which the items are shown is equal to the order in which they were added
	 */
	this.addItem = function(item) {
		if (this.state != 'initializing') alert("PreviewBar.addItem: state should be 'initializing' but is '" + this.state + "'");
		
		//Add item to the back of the list
		this.items.push(item);
	}
	
	/**
	 * Starts the preview bar
	 * @pre At least 1 preview item should be specified
	 */
	this.start = function() {
		if (this.items.length == 0) alert("PreviewBar.start: at least one preview item should be specified using the 'PreviewBar.addItem' method");

		//Initialize the currentItemIndex at the first item
		this.currentItemIndex = 0;
		
		//Set the current move position to 0 (first 3 items are shown)
		this.movePosition = 0;
		
		//Populate the list items with the correct containers
		for (var i = 0; i < 4; i++) {
			var item = this.getItemAtIndex(i);
			
			//Remove old contents
			this.clearItemContainer(this.itemContainers[i]);
			
			//Set new contents
			this.itemContainers[i].appendChild(item.createContainer());
		}
		
		//Update the positions of the item containers
		this.updateItemContainerPositions();
		
		//Shown preview items may no longer be changed
		this.state = 'waiting';
		
		//Start the timer that scrolls to the next item
		var obj = this;
		this.nextScrollTimer = setTimeout(function() {obj.tickNextScrollTimer()}, 7000);
	}
	
	/**
	 * Updates the positions of the item containers according to the current 'movePosition' setting
	 * @access private
	 */
	this.updateItemContainerPositions = function() {
		//The amount that the currently leftmost item is already moved
		var modulatedMovePosition = this.movePosition % (257 + 51);
		
		//Calculate new 'left' property for the first item
		var left = -modulatedMovePosition + 51;
		
		for (var i = 0; i < 4; i++) {
			this.itemContainers[i].style.left = left + 'px';

			//Calculate new 'left' property for the next item (add both item width and margin)
			left += 257 + 51;
		}
	}
	
	/**
	 * Remove all 'children' of the specified item container
	 * @param HtmlElement itemContainer
	 * @access private
	 */
	this.clearItemContainer = function(itemContainer) {
		var itemChildNodes = itemContainer.childNodes;
		var n = itemChildNodes.length;
		for (var i = 0; i < n; i++) {
			itemContainer.removeChild(itemChildNodes[i]);
		}
	}
	
	/**
	 * Returns the preview item at index i, in which roll-over is taken into account
	 * @return PreviewItem
	 * @access private
	 */
	this.getItemAtIndex = function(i) {
		if (this.items.length == 0) alert("PreviewBar.getItemAtIndex: no preview items specified");
	
		//After all items have passed, the first item will be shown again
		return this.items[i % this.items.length];
	}
	
	/**
	 * Fired when the visitor has clicked on the 'next' button
	 * @param Event e
	 */
	this.clickNext = function(e) {
		if (this.state == 'waiting') {
			//Set state to moving
			this.state = 'moving';
			
			//Stop the next scroll timer
			clearTimeout(this.nextScrollTimer);
			
			//Start scrolling timer
			var obj = this;
			this.scrollStepTimer = setTimeout(function() {obj.tickScrollStepTimer()}, 10);
		} else {
			//Preview bar is moving already; ignore click
		}
	}
	
	/**
	 * Scroll to the next item
	 * @pre Currently, the preview bar is in waiting state
	 */
	this.tickNextScrollTimer = function() {
		if (this.state != 'waiting') alert("PreviewBar.tickNextScrollTimer: state should be 'waiting' but is '" + this.state + "'");

		//Start moving
		this.state = 'moving';

		//Start scrolling timer
		var obj = this;
		this.scrollStepTimer = setTimeout(function() {obj.tickScrollStepTimer()}, 10);
	}
	
	/**
	 * Scroll the preview bar a tiny bit further until it can go back to the 'waiting' state
	 * @pre Currently, the preview bar is in moving state
	 */
	this.tickScrollStepTimer = function() {
		if (this.state != 'moving') alert("PreviewBar.tickScrollStepTimer: state should be 'moving' but is '" + this.state + "'");
		
		//Get the completion (value between 0 and 1)
		var completion = (((257 + 51) * 3) - this.movePosition) / ((257 + 51) * 3);
		
		//Calculate speed in pixels/timestamp, depending on completion
		var speed = completion * 15 + 5;

		//Calculate the number of items that were already moved
		var alreadyMoved = Math.floor(this.movePosition / (257 + 51));
		
		//Actually move
		this.movePosition += speed;
		
		//Check if another item is moved
		if (Math.floor(this.movePosition / (257 + 51)) > alreadyMoved) {
			//Move the contents of the preview items forward
			var tmp = this.itemContainers[0];
			for (var i = 1; i < 4; i++) {
				this.itemContainers[i - 1] = this.itemContainers[i];
			}
			this.itemContainers[3] = tmp;
			
			//Also move the order in the container
			var itemContainersParent = this.itemContainers[0].parentNode;
			itemContainersParent.removeChild(tmp);
			itemContainersParent.appendChild(tmp);
			
			//Set the next preview item in the list item
			this.currentItemIndex++;
			if (this.currentItemIndex == this.items.length) this.currentItemIndex = 0;	//Check for roll-over
			this.clearItemContainer(tmp);
			var nextItem = this.getItemAtIndex(this.currentItemIndex + 3);
			tmp.appendChild(nextItem.createContainer());
		}

		//Check if the moving is finished and the preview bar can go back to waiting state
		if (this.movePosition > ((257 + 51) * 3)) {
			//Set the move position to 0 
			this.movePosition = 0;
			
			//Restart the next scroll timer
			var obj = this;
			this.nextScrollTimer = setTimeout(function() {obj.tickNextScrollTimer()}, 7000);
			
			//Go back to waiting state
			this.state = 'waiting';
		} else {
			//Restart the timer
			var obj = this;
			this.scrollStepTimer = setTimeout(function() {obj.tickScrollStepTimer()}, 10);
		}
		
		//Update the positions of the item containers
		this.updateItemContainerPositions();
	}
	
	//Initialize list of items
	this.items = new Array();
	
	//Initialize state
	this.state = 'initializing';

	//Create unordered list container for the preview bar
	this.container = document.createElement('DIV');
	this.container.className = 'previewBar';

	var list = document.createElement('UL');
	this.container.appendChild(list);
	
	//Adds the item containers to the container
	this.itemContainers = new Array();
	for (var i = 0; i < 4; i++) {
		this.itemContainers[i] = document.createElement('LI');
		list.appendChild(this.itemContainers[i]);
	}
	
	//Add 'fade' image to the container
	var fade = document.createElement('IMG');
	fade.className = 'fade';
	fade.alt = '';
	fade.title = '';
	fade.src = '/file/picture/frame/fade.png';
	this.container.appendChild(fade);
	
	//Add 'next' link to the container
	var next = document.createElement('DIV');
	next.className = 'next';
	this.container.appendChild(next);
	

	
	//Enables event listener for the 'next' button
	PreviewBar.addEventListener(next, 'click', this.clickNext, this);
}

/**
 * Wraps the specified event handler in a closure in order to be able to maintain the specified 'this' scope
 *  When an event registered with the specified function is fired
 * @param HtmlElement element				Element to add the listener to
 * @param String eventName					The name of an existing event (without the 'on' part)
 * @param Object eventHandlerFunction		The event handling function
 * @param Object scope						The desired 'this' scope when calling the eventHandlerFunction
 * @return Reference to the event handler that was added
 * @access private
 * @static
 */
PreviewBar.addEventListener = function(element, eventName, eventHandlerFunction, scope) {
	var scopedEventHandler = function(e) { 
		try {
			//Call the specified event handler
			return eventHandlerFunction.apply(scope, [e]);
		} catch(error) {
			if (error.stack != null) alert(error.message + '\n\n' + error.stack); else alert(error.message);
		};
	};

	if (document.addEventListener) {
		element.addEventListener(eventName, scopedEventHandler, false);
	} else if (document.attachEvent) {
		element.attachEvent("on"+eventName, scopedEventHandler);
	}
	
	return scopedEventHandler;
}
