
/* Aria Singleton */
var Aria = {
	Trees: new Array(), // instances of Aria.Tree Class
	isEnabled: function(inNode){
		// todo: this may need to check isEnabled on all parentNodes, inheritence of aria-enabled is ambiguous
		if(inNode.getAttribute('aria-enabled') && inNode.getAttribute('aria-enabled').toLowerCase()=='false') return false;
		else return true;
	},
	isExpanded: function(inNode){
		if(inNode.getAttribute('aria-expanded') && inNode.getAttribute('aria-expanded').toLowerCase()=='false') return false;
		else return true;
	},
	isTreeItem: function(inNode){
		if(inNode.getAttribute('role') && inNode.getAttribute('role').toLowerCase()=='treeitem') return true;
		else return false;
	}
};

Aria.Tree = Class.create();
Aria.Tree.prototype = {
	initialize: function(inNode){
		if(!$(inNode) && console.error) console.error('Error from aria.js: Aria.Tree instance initialized with invalid element, '+ inNode);
		this.el = $(inNode);
		this.index = Aria.Trees.length; // each tree should know its index in the Aria singleton's list, in order to concatenate id strings
		this.strActiveDescendant = this.el.getAttribute('aria-activedescendant');
		this.strDefaultActiveDescendant = 'tree'+this.index+'_item0'; // default first item
		if(!$(this.strActiveDescendant)) this.strActiveDescendant = this.strDefaultActiveDescendant; // set to default if no existing activedescendant
		this.setActiveDescendant($(this.strActiveDescendant));
		
		// set up event delegation on the tree node
		Event.observe(this.el, 'click', this.handleClick.bindAsEventListener(this));
		Event.observe(this.el, 'keydown', this.handleKeyPress.bindAsEventListener(this)); //webkit doesn't send keypress events for arrow keys, so use keydown instead
		
	},
	getActiveDescendant: function(inNode){
		if(inNode){ // if inNode (from event target), sets the activedescendant to nearest ancestor treeitem
			var el = $(inNode);
			while(el != this.el){
				if(Aria.isTreeItem(el)) break; // exit the loop; we have the treeitem
				el = el.parentNode;
			}
			if(el == this.el) {
				this.setActiveDescendant(); // set to default activedescendant
			} else {
				this.setActiveDescendant(el);
				return el;
			}
		} else {
			return $(this.el.getAttribute('aria-activedescendant'));
		}
	},
	getNextTreeItem: function(inNode){
		var el = $(inNode);
		var originalElm = $(inNode);
		while(!Aria.isTreeItem(el) || el == originalElm){
			if(Aria.isExpanded(el) && el.down()){ // should be el.down('[role="treeitem"]');
				var elements = el.getElementsByTagName('*');
				for(var i=0, c=elements.length; i<c; i++){
					if(Aria.isTreeItem(elements[i])) return elements[i];
				}
			}
			if(el.next()){
				el = el.next();
			} else {
				while(!el.parentNode.next() && el.parentNode != this.el){
					el = el.parentNode;
				}
				if(el.parentNode == this.el) return originalElm; // if no next items in tree, return current treeitem
				else el = el.parentNode.next();
			}
		}
		return el;
	},
	getPreviousTreeItem: function(inNode){
		var el = $(inNode);
		var originalElm = $(inNode);
		while(!Aria.isTreeItem(el) || el == originalElm){
			if(el.previous()){
				el = el.previous();
				// recursively choose last child node of previous el, as long as it's not in an collapsed node
				if (el.down() && Aria.isExpanded(el)){
					el = el.down();
					while (el.next() || (el.down() && Aria.isExpanded(el))){
						if (el.next()) el = el.next();
						else el = el.down();
					}
				}
			} else {
				if(el.parentNode == this.el) return originalElm; // if no previous items in tree, return current treeitem
				el = el.parentNode;
			}
		}
		if(el == this.el) return originalElm; // if no previous items in tree, return current treeitem
		return el;
	},
	handleClick: function(inEvent){
		var target = inEvent.target; // get the click target
		var el = this.getActiveDescendant(target);
		if(target.className.indexOf('expander')>-1){ // if it's an expander widget
			this.toggleExpanded(el); // toggle the aria-expanded attribute on activedescendant
			Event.stop(inEvent); // and stop the event
		}
	},
	handleKeyPress: function(inEvent){
		switch(inEvent.keyCode){
			// case Event.KEY_PAGEUP:   break;
			// case Event.KEY_PAGEDOWN: break;
			// case Event.KEY_END:      break;
			// case Event.KEY_HOME:     break;
			case Event.KEY_LEFT:     this.keyLeft();  break;
			case Event.KEY_UP:       this.keyUp();    break;
			case Event.KEY_RIGHT:    this.keyRight(); break;
			case Event.KEY_DOWN:     this.keyDown();  break;
			default: 
				//console.log(inEvent.keyCode);
				return;
		}
		Event.stop(inEvent);
	},
	keyLeft: function(){
		var el = this.activeDescendant;
		if(Aria.isExpanded(el)){
			el.setAttribute('aria-expanded','false');
			this.setActiveDescendant(this.activeDescendant); // redundant, workaround for <rdar://problem/6100903>
		}
	},
	keyUp: function(){
		var el = this.activeDescendant;
		this.setActiveDescendant(this.getPreviousTreeItem(el));
	},
	keyRight: function(){
		var el = this.activeDescendant;
		if(!Aria.isExpanded(el)){
			el.setAttribute('aria-expanded','true');
			this.setActiveDescendant(this.activeDescendant); // redundant, workaround for <rdar://problem/6100903>
		}
	},
	keyDown: function(){
		var el = this.activeDescendant;
		this.setActiveDescendant(this.getNextTreeItem(el));
	},
	setActiveDescendant: function(inNode){
		Element.removeClassName(this.activeDescendant,'activedescendant')
		if($(inNode)) this.activeDescendant = $(inNode);
		else this.activeDescendant = $(this.strDefaultActiveDescendant);
		Element.addClassName(this.activeDescendant,'activedescendant')
		this.strActiveDescendant = this.activeDescendant.id;
		this.el.setAttribute('aria-activedescendant', this.activeDescendant.id);
	},
	toggleExpanded: function(inNode){
		var el = $(inNode);
		if(Aria.isExpanded(el)){
			el.setAttribute('aria-expanded','false');
		} else {
			el.setAttribute('aria-expanded','true');	
		}
		this.setActiveDescendant(el);
	}
};
