/*
    This file is part of ClassBehaviours release 20100930.

    ClassBehaviours is a javascript framework based on class-name parsing.
    Copyright (C) 2010  Maurice van Creij (http://www.classbehaviours.com)

    ClassBehaviours is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ClassBehaviours is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ClassBehaviours. If not, see http://www.gnu.org/licenses/gpl.html.
*/

	// CLASSBEHAVIOURS CLASS
	// create the root classbehaviours object if it doesn't already exist
	if(typeof(classBehaviours)=='undefined') classBehaviours = function(){};

		// COMMON FADER FUNCTIONS
		// create the utilities child object if it doesn't already exist
		if(typeof(classBehaviours.fader)=='undefined') classBehaviours.fader = function(){}

			// returns the size of an object
			classBehaviours.fader.getSize = function(node){
				// measure the height of the container
				var nodeWidth = node.offsetWidth;
				var nodeHeight = node.offsetHeight;
				// measure the height of all the childnodes of the container
				var totalWidth = 0;
				var totalHeight = 0;
				var contents = node.childNodes;
				for(var a=0; a<contents.length; a++){
					totalWidth += (contents[a].offsetWidth) ? contents[a].offsetWidth : 0 ;
					totalHeight += (contents[a].offsetHeight) ? contents[a].offsetHeight : 0 ;
				}
				// pass back the largest number
				return new Array(nodeWidth, nodeHeight, totalWidth, totalHeight);
			}

			// sets the size of an object
			classBehaviours.fader.setSize = function(node, xAmount, yAmount){
				if(xAmount!=null) node.style.width = xAmount + 'px';
				if(yAmount!=null) node.style.height = yAmount + 'px';
			}

			// increases or decreases a given objects size in steps and returns a pseudo even handler afterwards
			classBehaviours.fader.size = function(id, start, end, step, delay, acceleration, evalOnEnd){
				var cf = classBehaviours.fader;
				// get the target node
				target = document.getElementById(id);
				// get the start value if missing
				if(start==null) 		start = cf.getSize(target)[1];
				if(end==null) 			end = cf.getSize(target)[3];
				if(step==null) 			step = 10;
				if(delay==null) 		delay = 10;
				if(acceleration==null) 	acceleration = 10;
				if(evalOnEnd==null) 	evalOnEnd = '';
				// calculate the new value
				if(start<end)		{value = (start+step>end) ? end : start+step ;}
				else if(start>end)	{value = (start-step<end) ? end : start-step ;}
				// set the fade
				cf.setSize(target, null, value);
				// order the next step
				if(value!=end) 		{setTimeout("classBehaviours.fader.size('"+id+"',"+value+","+end+","+(step+acceleration)+","+delay+","+acceleration+",'"+evalOnEnd+"')", delay);}
				else 				{eval(evalOnEnd);}
			}

		// COMMON UTILITY FUNCTIONS
		// create the utilities child object if it doesn't already exist
		if(typeof(classBehaviours.utilities)=='undefined') classBehaviours.utilities = function(){}

			// get a string parameter from a class name
			classBehaviours.utilities.getClassParameter = function(targetNode, paramName, defaultValue){
				if(targetNode!=null){
					var parameterValue = (targetNode.className.indexOf(' ' + paramName + '_')>-1) ?
						targetNode.className.split(' ' + paramName + '_')[1].split(' ')[0] :
						defaultValue ;
					return (isNaN(defaultValue) || defaultValue==null || defaultValue=='') ?
						parameterValue :
						parseFloat(parameterValue.replace('D','.'));
				}else{
					return defaultValue;
				}
			}

			// find the node that follows the given node
			classBehaviours.utilities.nextNode = function(targetNode, parentCount){
				var testNode = targetNode;
				if(parentCount==null) parentCount = 1;
				for(var a=0; a<parentCount; a++){
					do {
						testNode = (testNode.nextSibling!=null) ? testNode.nextSibling : targetNode ;
					}while(testNode.nodeName.indexOf('#text')>-1);
				}
				return testNode;
			}

			// returns all nodes of the same class
			classBehaviours.utilities.getElementsByClassName = function(className, targetNode){
				// use the whole body if no target was provided
				targetNode = (targetNode!=null) ? targetNode : document ;
				// make an empty array for the results
				var foundNodes = new Array();
				// for all elements in the parent node
				var allNodes = (targetNode.all) ? targetNode.all : targetNode.getElementsByTagName("*");
				for(var a=0; a<allNodes.length; a++){
					// if the item has a className
					if(allNodes[a].className){
						// process the classname
						nodeClass = allNodes[a].className + ' ';
						// add it to the results if the classname was found
						if(nodeClass.indexOf(className+' ')>-1) foundNodes[foundNodes.length] = allNodes[a];
					}
				}
				// return the list
				return foundNodes;
			}

			// adds an event-handler the proper way
			classBehaviours.utilities.addEvent = function(eventNode, eventName, eventHandler){
				if(eventNode.addEventListener) 	eventNode.addEventListener(eventName, eventHandler, false)
				else if(eventNode.attachEvent) 	eventNode.attachEvent("on"+eventName, function(event){eventHandler(event)})
				else 							eventNode["on"+eventName] = eventHandler;
				return true;
			}

		// CLASSNAME BEHAVIOUR FUNCTIONS
		// create the handlers child object if it doesn't already exist
		if(typeof(classBehaviours.handlers)=='undefined') classBehaviours.handlers = function(){}

			// open or close a container object
			document.writeln("<style>.toggleNextNode{cursor:pointer;}</style>");
			classBehaviours.handlers.toggleNextNode = {
				// properties
				name: 'toggleNextNode',
				step: 0,
				acceleration: 100,
				extra: 0,
				delay: 10,
				index: 0,
				// methods
				start: function(node){
					var cu = classBehaviours.utilities;
					// set the event handlers for the source node
					if(node.nodeName=='INPUT' || node.nodeName=='TEXTAREA' || node.nodeName=='SELECT'){
						cu.addEvent(node, 'focus', this.toggle);
						cu.addEvent(node, 'blur', this.toggle);
					}else{
						// cu.addEvent(node, 'click', this.toggle);
						node.onclick = this.toggle;
					}
					// give this node an id if it doesn't have one yet
					node.id = (node.id) ? node.id : this.name + this.index++ ;
					// is this node marked as "auto", order it to open on a timeout
					autoDelay = cu.getClassParameter(node, 'auto', null);
					if(autoDelay){
						this.index++
						node.id = (node.id) ? node.id : this.name + this.index ;
						setTimeout('classBehaviours.handlers.toggleNextNode.toggle(document.getElementById("'+node.id+'"))', autoDelay);
					}
				},
				findContainer: function(targetLabel, targetRecursion){
					var t2n = classBehaviours.handlers.toggleNextNode;
					var cu = classBehaviours.utilities;
					// if there was a target recursion, recurse parent nodes
					if(targetRecursion) for(var a=0; a<parseInt(targetRecursion); a++) targetLabel = targetLabel.parentNode;
					// get the next sibling of the label, which should be the container
					targetObject = cu.nextNode(targetLabel);
					// give it an ID for reference
					targetObject.id = (targetObject.id) ? targetObject.id : t2n.name + 'Target' + t2n.index++;
					// pass it back
					return targetObject;
				},
				positionContainer: function(targetContainer, screenXpos, screenYpos){
					if(targetContainer.firstChild!=null){
						// if the position is too close to the edge
						targetContainerWidth = targetContainer.firstChild.offsetWidth;
						screenWidth = (window.innerWidth) ? window.innerWidth : document.documentElement.clientWidth ;
						scrolledWidth = (typeof(document.documentElement.scrollLeft)!='undefined') ? document.documentElement.scrollLeft : window.pageXOffset ;
						if(screenXpos+targetContainerWidth > screenWidth+window.pageXOffset) screenXpos -= targetContainerWidth;
						// if the position is too close to the bottom
						targetContainerHeight = targetContainer.firstChild.offsetHeight;
						screenHeight = (window.innerHeight) ? window.innerHeight : document.documentElement.clientHeight ;
						scrolledHeight = (typeof(document.documentElement.scrollTop)!='undefined') ? document.documentElement.scrollTop : window.pageYOffset ;
						if(screenYpos+targetContainerHeight+10 > screenHeight+scrolledHeight) screenYpos -= targetContainerHeight;
						// set the position
						targetContainer.style.left = screenXpos + 'px';
						targetContainer.style.top = screenYpos + 'px';
					}
				},
				// events
				toggle: function(that){
					var objNode = (typeof(this.nodeName)=='undefined') ? that : this ;
					var t2n = classBehaviours.handlers.toggleNextNode;
					var cu = classBehaviours.utilities;
					// get all information on this node
					targetLabel = objNode;
					targetAnchor = (targetLabel.getAttribute('href')!=null) ? (targetLabel.getAttribute('href').indexOf('#')>-1) ? targetLabel.getAttribute('href').split('#')[1] : null : null ;
					targetContainerId = cu.getClassParameter(targetLabel, 'id', targetAnchor);
					targetRecursion = cu.getClassParameter(targetLabel, 'parent', null);
					targetClosable = (cu.getClassParameter(targetLabel, 'closable', 'yes') == 'yes');
					targetAtMouse = (cu.getClassParameter(targetLabel, 'atmouse', 'no') == 'yes');
					targetContainer = (targetContainerId!=null) ? document.getElementById(targetContainerId) : t2n.findContainer(targetLabel, targetRecursion) ;
					// if the target container is not marked for hiding or showing, mark it now
					if(targetContainer.className.indexOf('hideThisNode')<0 && targetContainer.className.indexOf('showThisNode')<0) targetContainer.className += ' showThisNode' ;
					// call for the node to grow or shrink
					targetGrows = (targetContainer.className.indexOf('hideThisNode')>-1);
					if(!targetGrows && targetClosable || targetGrows) t2n.update(targetLabel.id, targetGrows);
					// position the node at the mouse' position if needed
					if(targetAtMouse && targetGrows){
						// get the position
						screenXpos = (typeof(event)!='undefined' && document.all) ? event.x : that.layerX ;
						screenYpos = (typeof(event)!='undefined' && document.all) ? event.y : that.layerY ;
						// set the position
						t2n.positionContainer(targetContainer, screenXpos, screenYpos);
					}
					// cancel the click
					return false;
				},
				update: function(id, grows){
					var t2n = classBehaviours.handlers.toggleNextNode;
					var cu = classBehaviours.utilities;
					// get all information on this node
					targetLabel = document.getElementById(id);
					targetAnchor = (targetLabel.getAttribute('href')!=null) ? (targetLabel.getAttribute('href').indexOf('#')>-1) ? targetLabel.getAttribute('href').split('#')[1] : null : null ;
					targetContainerId = cu.getClassParameter(targetLabel, 'id', targetAnchor);
					targetFamily = cu.getClassParameter(targetLabel, 'family', null);
					targetRecursion = cu.getClassParameter(targetLabel, 'parent', null);
					targetContainer = (targetContainerId!=null) ? document.getElementById(targetContainerId) : t2n.findContainer(targetLabel, targetRecursion) ;
					// get all nodes of this class
					allNodes = cu.getElementsByClassName(t2n.name);
					// for every node in the node-list
					for(var a=0; a<allNodes.length; a++){
						// get its properties
						peerLabel = allNodes[a];
						peerAnchor = (peerLabel.getAttribute('href')!=null) ? (peerLabel.getAttribute('href').indexOf('#')>-1) ? peerLabel.getAttribute('href').split('#')[1] : null : null ;
						peerContainerId = cu.getClassParameter(peerLabel, 'id', peerAnchor);
						peerFamily = cu.getClassParameter(peerLabel, 'family', null);
						peerRecursion = cu.getClassParameter(peerLabel, 'parent', null);
						peerContainer = (peerContainerId!=null) ? document.getElementById(peerContainerId) : t2n.findContainer(peerLabel, peerRecursion) ;
						// TARGET: if this node has the same id and is open
						if(peerLabel.id==targetLabel.id){
							// prepare the container
							peerContainer.style.overflow = 'hidden';
							peerContainer.style.visibility = 'visible';
							// open or close the container
							if(!grows)	classBehaviours.fader.size(peerContainer.id, null, 1, t2n.step, t2n.delay, t2n.acceleration, 'classBehaviours.handlers.hideThisNode.start(document.getElementById("'+peerContainer.id+'"))');
							else		classBehaviours.fader.size(peerContainer.id, 1, null, t2n.step, t2n.delay, t2n.acceleration, 'classBehaviours.handlers.showThisNode.start(document.getElementById("'+peerContainer.id+'"))');
							// activate the link
							peerLabel.className = (!grows) ? peerLabel.className.replace('active', 'link') : peerLabel.className.replace('link', 'active');
						}
						// PEERS: if this node belongs to the same family and is open but has a different id
						if(peerFamily==targetFamily && peerFamily!=null && peerContainer.className.indexOf('hideThisNode')<0 && peerContainer!=targetContainer){
							// prepare the container
							peerContainer.style.overflow = 'hidden';
							peerContainer.style.visibility = 'visible';
							// close the container
							classBehaviours.fader.size(peerContainer.id, null, 1, t2n.step ,t2n.delay, t2n.acceleration, 'classBehaviours.handlers.hideThisNode.start(document.getElementById("'+peerContainer.id+'"))');
							// de-activate the link
							peerLabel.className = peerLabel.className.replace('active', 'link');
						}
					}
				}
			}

			// hide the "hideThisNode" class behaviour by default
			document.writeln("<style>.hideThisNode{overflow:hidden; visibility:hidden; height:1px;}</style>");
			classBehaviours.handlers.hideThisNode = {
				// properties
				name: 'hideThisNode',
				// methods
				start: function(node){
					node.style.overflow = 'hidden';
					node.style.visibility = 'hidden';
					node.style.height = '1px';
					node.className = node.className.replace('showThisNode', 'hideThisNode');
				}
			}

			// explicit opposite of a hidden node
			document.writeln("<style>.ShowThisNode{overflow:visible; visibility:visible; height:auto;}</style>");
			classBehaviours.handlers.showThisNode = {
				// properties
				name: 'showThisNode',
				// methods
				start: function(node){
					node.style.overflow = 'visible';
					node.style.visibility = 'visible';
					node.style.height = 'auto';
					node.className = node.className.replace('hideThisNode', 'showThisNode');
				}
			}

	// JQUERY WRAPPER
	if(typeof(jQuery)!='undefined'){
		(function($){
			var methods = {
				init : function(options) {
					return this.each(function(){
						classBehaviours.handlers.toggleNextNode.start($(this).context);
					});
				},
				hideThisNode : function(){
					return this.each(function(){
						classBehaviours.handlers.hideThisNode.start($(this).context);
					});
				},
				showThisNode : function(){
					return this.each(function(){
						classBehaviours.handlers.showThisNode.start($(this).context);
					});
				}
			};
			$.fn.toggleNextNode = function(method){
				if(methods[method]) {
					return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
				}else if(typeof method === 'object' || !method){
					return methods.init.apply(this, arguments);
				}else{
					$.error('Method ' +  method + ' does not exist on jQuery.togglenextnode');
				}
			};
		})(jQuery);

		// JQUERY EVENTS
		$(document).ready(function() {
			$(".toggleNextNode").toggleNextNode();
			$(".hideThisNode").toggleNextNode('hideThisNode');
			$(".showThisNode").toggleNextNode('showThisNode');
		});
	}

