/**
	@fileOverview Contains the coweldco.county.imageRotator namespace.
	@author Philip Siedow-Thompson
	@version $Id$
*/

var coweldco;
	coweldco = (coweldco) ? coweldco : {};
	coweldco.county = (coweldco.county) ? coweldco.county : {};
	
(function() {
	
	/**
		@namespace Contains constants, classes and utility functions that make up the image rotator
		used on the county website.
	*/
	coweldco.county.imageRotator = (coweldco.county.imageRotator) ? coweldco.county.imageRotator : {};
	
	/**
		Deines the path to the xml definition file.
		@type String
		@default imagerotator.xml
	*/
	coweldco.county.imageRotator.IMAGEROTATOR_XML = 'imagerotator.xml';
	
	/**
		The duration of the image Fade transition in seconds.
		@type Number
		@default 1
	*/
	coweldco.county.imageRotator.IMAGEROTATOR_TRANSITION_DURATION = 1.5;
	
	/**
		The interpolation function to use in the image transition.
		@type Function
		@default coweldco.animate.exponentialInterpolation
	*/
	coweldco.county.imageRotator.IMAGEROTATOR_TRANSITION_INTERPOLATION 
		= (function() {return coweldco.animate.exponentialInterpolation;})(); //this is wrapped to help jsdoc-toolkit
	
	/**
		The framerate of the image transition in frames/sec
		@type Number
		@value 30
	*/
	coweldco.county.imageRotator.IMAGEROTATOR_TRANSITION_FRAMERATE = 30;
	
	/**
		Determines if the imageRotator should automatically iterate over its
		images
		@type Boolean
		@default true
	*/
	coweldco.county.imageRotator.IMAGEROTATOR_ROTATE = true;
	
	/**
		The rate of rotation in seconds
		@type Number
		@default 10
	*/
	coweldco.county.imageRotator.IMAGEROTATOR_ROTATE_RATE = 10;
	
	/**
		Constructs a new RotatorItem.
		
		@class Represents an Item in the image rotator. It Binds together the title, image and
		and the description nodes with an HTML list Item Node (the <code>node</code> property)
		that can be used, in conjunction with a callback, to select an image. The RotatorItem
		cannot be shared between instances because only one callback can be defined.
		
		@param {String} [title=""] the title of the RotatorItem which is displayed in the node.
		@param {String} [image=""] the path to the image to display.
		@param {String} [url=""] the url to link the Item to.
		@param {Node[]} [descriptionNodes="[]"] an array of DOM nodes that should be displayed as
		the description.
		
		@property {String} title The title of the RotatorItem.
		@property {String} image The path to the image.
		@property {String} url The url to link to.
		@property {Node[]} descriptionNodes The HTML DOM nodes that make up the description.
		@property {Function} selectedCallback The callback function. This function is called
		when the image is selected. To register as a selectedCallback a function should take
		two args, a reference to the RotatorItem and a boolean value indicating if the 
		selection was made by the timer (true) or by user input (false).
		@property {Boolean} isSelected READONLY!: True if the item is selected, otherwise false.
		@property {Node} node The LI HTML Element that is used to represent the RotatorItem in
		a display/library.
	*/
	coweldco.county.imageRotator.RotatorItem = function(title, image, url, descriptionNodes) {
		this.title = (title) ? title : '';
		this.image = (image) ? image : '';
		this.url = (url) ? url : '#';
		this.descriptionNodes = (descriptionNodes) ? descriptionNodes : [];
		this.selectedCallback = null;
		this.isSelected = false;
		
		this.node = this.buildNode();
	}
	
	/**
		Constucts the HTML DOM node that will represent the RotatorItem in the library.
		Override this method to create a different node.
		@memberOf coweldco.county.imageRotator.RotatorItem.prototype
		@returns {Node} An HTML node that is the visual representation of the
		RotatorItem
	*/
	coweldco.county.imageRotator.RotatorItem.prototype.buildNode = function() {
		var node = new Element('li', {'class':'coweldco-county-imagerotator-navigation-unselected'});
			node.appendChild(new Element('a', {'href':'#'})
				.update(this.title)
				.observe('click', this.onSelect.bind(this)));
		return node;
	}
	
	/**
		A Method suitable for event binding. Sets the current ImageRotator to selected.
		@memberOf coweldco.county.imageRotator.RotatorItem.prototype
		@param {Event} [event] the DOM Event.
		@param {Boolean} [autoInitiated="false"] True if the ImageRotator was selected by the 
		application or false if it was selected by user interaction.
	*/
	coweldco.county.imageRotator.RotatorItem.prototype.onSelect = function(event, autoInitiated) {
		if(!this.isSelected) {
			this.isSelected = true;
			this.node.removeClassName('coweldco-county-imagerotator-navigation-unselected');
			this.node.addClassName('coweldco-county-imagerotator-navigation-selected');
			
			if(this.selectedCallback) {
				this.selectedCallback(this, !!autoInitiated);
			}
		}
		
		//Halt further propagation/bubbling of the event and prevent the default opperation
		if(event)
			event.stop();
	}
	
	/**
		Selects the RotatorItem. This will always specify autoInitated when the
		callback is called.
		@memberOf coweldco.county.imageRotator.RotatorItem.prototype
	*/
	coweldco.county.imageRotator.RotatorItem.prototype.select = function() {
		this.onSelect(null, true);
	}
	
	/**
		Unselects the RotatorItem if it is selected.
		@memberOf coweldco.county.imageRotator.RotatorItem.prototype
	*/
	coweldco.county.imageRotator.RotatorItem.prototype.unselect = function() {
		this.isSelected = false;
		this.node.removeClassName('coweldco-county-imagerotator-navigation-selected');
		this.node.addClassName('coweldco-county-imagerotator-navigation-unselected');
	}
	
	/**
		Constucts an ImageRotator backed by an array of RotatorItems
		
		@class An ImageRotator manages the Display and rotation of RotatorItems.
		The ImageRotator is represented in the DOM by four nodes/elements:
		<ul>
			<li>
				displayNode - the loading class is toggled on this node and the url
				is set on this node. By default this node/element should express 
				the <code>.coweldco-county-imagerotator-display</code> class.
			</li>
			<li>
				navigationNode - rotatorItems are appended to this node, it 
				is usually a unordered list. By default this node/element 
				should express the <code>.coweldco-county-imagerotator-navigation</code> 
				class.
			</li>
			<li>
				<descriptionNode - the rotatorItems description nodes are 
				appended to this node. By default this node/element should express 
				the <code>.coweldco-county-imagerotator-description</code> class.
			</li>
			<li>
				imageNode - the img node to display the rotatorItem image. By
				default this node/element should express the
				<code>.coweldco-county-imagerotator-image</code> class and must
				be an 'img' node.
			</li>
		</ul>
		@param {Node} baseNode The base or root node where the image rotator should search
		for the classes that are associated with the four primary nodes described above.
		This allows multiple ImageRotators to be displayed on the same page.
		@param {coweldco.county.imageRotator.RotatorItem[]} rotatorItems an array
		of rotator items that are to be displayed.
		
		@property {Number} currentSelected the index of the currently selected 
		rotatorItem (-1 if none is selected)
		@property {Boolean} isLoaded true if the ImageRotator is loaded
		@property {Node} displayNode see class description.
		@property {Node} navigationNode see class description.
		@property {Node} descriptionNode see class description.
		@property {Node} imageNode see class description.
 	*/
	coweldco.county.imageRotator.ImageRotator = function(baseNode, rotatorItems) {
		this.currentSelected = -1;
		this.isLoaded = true;
		this.rotatorItems = rotatorItems;
		this.transition = null;
		this.timer = null;
		
		try {
			this.loadNodes(baseNode);
			this.loadItems();
			
		} catch(err) {
			this.isLoaded = false;
			alert(err);
		}
	}
	
	/**
		Finds and Loads the appropriate dom nodes setting the major node properties. Override
		this function if you want to change what nodes are used.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
		@param {Node} baseNode the root DOM node to search.
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.loadNodes = function(baseNode) {
		var tNode = $(baseNode)
		
		this.displayNode = tNode.getElementsBySelector('.coweldco-county-imagerotator-display')[0];
		this.navigationNode = tNode.getElementsBySelector('.coweldco-county-imagerotator-navigation')[0];
		this.descriptionNode = tNode.getElementsBySelector('.coweldco-county-imagerotator-description')[0];
		this.imageNode = tNode.getElementsBySelector('img.coweldco-county-imagerotator-image')[0];
	}
	
	/**
		Loads the rotatorItems setting their callbacks and adding their nodes to the navigationNode.
		You should only have to call this method if you need to reload the rotatorItems for any reason.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.loadItems = function() {
		if(this.isLoaded) {
			for(var i=0; i<this.rotatorItems.length; i++) {
				var rotatorItem = this.rotatorItems[i];
				rotatorItem.selectedCallback = this.onSelect.bind(this);
				
				this.addNavigationItem(rotatorItem.node);
			}
		}
	}
	
	/**
		Adds the given rotatorItems node to the Navigation Node. Override this method to change the 
		default behavior.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
		@param {Node} rotatorItemNode the node to add to the navigation node.
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.addNavigationItem = function(rotatorItemNode) {
		this.navigationNode.appendChild(rotatorItemNode);
	}
	
	/**
		Displays the rotatorItem at the given index.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
		@param {Number} index the zero based index of the rotatorItem in the rotatorItems array.
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.selectIndex = function(index) {
		if(index != this.currentSelected 
			&& index >= 0 
			&& index < this.rotatorItems.length) {
			this.rotatorItems[index].select();
		}
	}
	
	/**
		Selects the first item in in the rotatorItems array.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.selectFirst = function() {
		this.selectIndex(0);
	}
	
	/**
		Selects the next item in the rotatorItems array.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.selectNext = function() {
		var index = this.currentSelected + 1;
		
		if(index >= this.rotatorItems.length) 
			index = 0;
		
		this.selectIndex(index);
	}
	
	/**
		Selects the previous item in the rotatorItems array.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.selectPrevious = function() {
		var index = this.currentSelected - 1;
		
		if(index < 0) 
			index = this.rotatorItems.length -1;
		
		this.selectIndex(index);
	}
	
	/**
		Displays a given rotatorItem only if it already exists in the rotatorItems
		array. This method also allows for a call to be made that indicates it was
		autoinitiated. If autoinitiated is set ot false, then the doDelay method is
		not called and the ImageRotator will no longer rotate automatically.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
		@param {coweldco.county.imageRotator.RotatorItem} selectedItem the item to display
		@param {Boolean} autoInitiated true if this is auto initated, otherwise false.
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.onSelect = function(selectedItem, autoInitiated) {
		for(var i = 0; i < this.rotatorItems.length; i++) {
			if(this.rotatorItems[i] != selectedItem) {
				this.rotatorItems[i].unselect();
			}
		}
		
		/*
			This is seperated into its own loop due to an error in prototype
			that only affects some browsers...
		*/
		for(var i = 0; i < this.rotatorItems.length; i++) {
			if(this.rotatorItems[i] == selectedItem) {
				this.currentSelected = i;
				this.displayNode.addClassName('coweldco-county-imagerotator-display-loading');
				
				this.changeUrl(selectedItem.url);
				this.addDescriptionNodes(selectedItem.descriptionNodes);
				this.loadImage(selectedItem.image);
				
				if(autoInitiated) {
					this.doDelay();
				} else {
					this.clearDelay();
				}
				
				break; //only allow one item to be selected.
			}
		}
	}
	
	/**
		Changes the currently linked url. Override this method to change functionality.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
		@param {String} url the new url to link to.
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.changeUrl = function(url) {
		this.displayNode.writeAttribute({href:url});
	}
	
	/**
		Clears the descriptioNode and adds the new description children.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
		@param {Node[]} descriptionNodes an of Nodes to add to the description.
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.addDescriptionNodes = function(descriptionNodes) {
		while(this.descriptionNode.lastChild) {
			this.descriptionNode.removeChild(this.descriptionNode.lastChild);
		}
		
		for(var i=0; i<descriptionNodes.length; i++) {
			this.descriptionNode.appendChild(descriptionNodes[i]);
		}
	}
	
	/**
		Starts the rotator delay.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.doDelay = function() {
		this.clearDelay();
		
		if(coweldco.county.imageRotator.IMAGEROTATOR_ROTATE) {
			this.timer = window.setTimeout(this.selectNext.bind(this), 
				coweldco.county.imageRotator.IMAGEROTATOR_ROTATE_RATE * 1000);
		}
	}
	
	/**
		Clears the rotator delay.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.clearDelay = function() {
		if(this.timer!=null) {
			window.clearTimeout(this.timer);
			this.timer = null;
		}
	}
	
	/**
		Loads an image asynchronously displaying it and calling doTransition when complete.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
		@param {String} url the url of the image to load.
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.loadImage = function(url) {
		this.imageNode.hide();
		
		var asyncImage = new Element('img');
		asyncImage.observe('load', (function(event) {
			asyncImage.stopObserving('load');
			
			this.displayNode.removeClassName('coweldco-county-imagerotator-display-loading');
			
			this.imageNode.writeAttribute({src: url});
			this.imageNode.show();
			
			this.doTransition();
			
		}).bind(this));
		asyncImage.src = url;
	}
	
	/**
		Runs the image load transition.
		@memberOf coweldco.county.imageRotator.ImageRotator.prototype
	*/
	coweldco.county.imageRotator.ImageRotator.prototype.doTransition = function() {
		if(this.transition) {
			this.transition.stop();
			this.transition = null;
		}
	
		this.transition = new coweldco.animate.Animator(this.imageNode, 
			coweldco.county.imageRotator.IMAGEROTATOR_TRANSITION_INTERPOLATION, 
			coweldco.county.imageRotator.IMAGEROTATOR_TRANSITION_FRAMERATE);
			
		this.transition.animate({opacity:0}, 
			{opacity:1}, 
			coweldco.county.imageRotator.IMAGEROTATOR_TRANSITION_DURATION);
	}
	
	var convertXMLToDOMNode = function(xmlNode) {
		var domNode = null;
		
		if(xmlNode) {
			if(xmlNode.nodeType == 3) {
				//text node
				domNode = $(document.createTextNode(xmlNode.nodeValue));
				
			} else if (xmlNode.nodeType == 1) {
				//element node
				domNode = $(new Element(xmlNode.nodeName));
				
				for(var i=0; i<xmlNode.attributes.length; i++) {
					var attr = xmlNode.attributes[i];
					domNode.writeAttribute(attr.name, attr.value);
				}
				
				for(var j=0; j<xmlNode.childNodes.length; j++) {
					var childDomNode = convertXMLToDOMNode(xmlNode.childNodes[j]);
					
					if(childDomNode) {
						domNode.appendChild(childDomNode);
					}
				}
			}
		}
		
		return domNode;
	}
	
	/**
		Gets the node value from the first returned xml node.
		@private
	*/
	var getFirstXmlNodeValue = function(xmlNode, tagName) {
		var nodeValue = null;
		var nodeTargets = xmlNode.getElementsByTagName(tagName);
		
		if(nodeTargets != null 
			&& nodeTargets.length
			&& nodeTargets[0].firstChild) {
			nodeValue = nodeTargets[0].firstChild.nodeValue;
		}
		
		return nodeValue;
	}
	
	/**
		Converts xml nodes into dom nodes
		@private
	*/
	var getDOMNodes = function(xmlNode, tagName) {
		var domNodes = [];
		var nodeTargets = xmlNode.getElementsByTagName(tagName);
		
		if(nodeTargets != null 
			&& nodeTargets.length) {
			for(var i=0; i<nodeTargets[0].childNodes.length; i++) {
				var tNode = convertXMLToDOMNode(nodeTargets[0].childNodes[i]);
				
				if(tNode) {
					domNodes.push(tNode);
				}
			}
		}
		
		return domNodes;
	}
	
	/**
		Creates an ImageRotator given a base node and a url to an xml config file.
		@param {Node} node the base node (or containing node) of the html imageRotator
		@param {String} [url="coweldco.county.imageRotator.IMAGEROTATOR_XML"] the 
		url to an xml config file specifying the RotatorItems
	*/
	coweldco.county.imageRotator.createImageRotator = function(node, url) {
		new Ajax.Request( ((url) ? url : coweldco.county.imageRotator.IMAGEROTATOR_XML), {
			method: 'GET',
			onSuccess: function(response) {
				var rotatorItems = [];
				
				if(response.responseXML) {
					var xmlDocument = response.responseXML;
					var xmlItems = xmlDocument.getElementsByTagName('item');
					
					for(var i=0; i < xmlItems.length; i++) {
						var xmlItem = xmlItems[i];
						
						rotatorItems.push( new coweldco.county.imageRotator.RotatorItem(
							getFirstXmlNodeValue(xmlItem, 'title'),
							getFirstXmlNodeValue(xmlItem, 'image'), 
							getFirstXmlNodeValue(xmlItem, 'url'),
							getDOMNodes(xmlItem, 'description')
							)
						);
					}
					
				}
				
				//init
				var imageRotator = new coweldco.county.imageRotator.ImageRotator($(node), rotatorItems);
				imageRotator.selectFirst();
			}
		});
	}
	
})();
