/*
	Copyright (c) 2004-2008, The Dojo Foundation
	All Rights Reserved.

	Licensed under the Academic Free License version 2.1 or above OR the
	modified BSD license. For more information on Dojo licensing, see:

		http://dojotoolkit.org/book/dojo-book-0-9/introduction/licensing
*/


if(!dojo._hasResource["dojo.behavior"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.behavior"] = true;
dojo.provide("dojo.behavior");

dojo.behavior = new function(){
	function arrIn(obj, name){
		if(!obj[name]){ obj[name] = []; }
		return obj[name];
	}

	var _inc = 0;

	function forIn(obj, scope, func){
		var tmpObj = {};
		for(var x in obj){
			if(typeof tmpObj[x] == "undefined"){
				if(!func){
					scope(obj[x], x);
				}else{
					func.call(scope, obj[x], x);
				}
			}
		}
	}

	// FIXME: need a better test so we don't exclude nightly Safari's!
	this._behaviors = {};
	this.add = function(behaviorObj){
		//	summary:
		//		add the specified behavior to the list of behaviors which will
		//		be applied the next time apply() is called. Calls to add() for
		//		an already existing behavior do not replace the previous rules,
		//		but are instead additive. New nodes which match the rule will
		//		have all add()-ed behaviors applied to them when matched.
		//
		//	description:
		//		behavior objects are specified in the following format(s):
		//		
		//			{ 
		//			 	"#id": {
		//					"found": function(element){
		//						// ...
		//					},
		//			
		//					"onblah": {targetObj: foo, targetFunc: "bar"},
		//			
		//					"onblarg": "/foo/bar/baz/blarg",
		//			
		//					"onevent": function(evt){
		//					},
		//			
		//					"onotherevent: function(evt){
		//						// ...
		//					}
		//				},
		//			
		//				"#id2": {
		//					// ...
		//				},
		//			
		//				"#id3": function(element){
		//					// ...
		//				},
		//			
		//				// publish the match on a topic
		//				"#id4": "/found/topic/name",
		//			
		//				// match all direct descendants
		//				"#id4 > *": function(element){
		//					// ...
		//				},
		//			
		//				// match the first child node that's an element
		//				"#id4 > :first-child": { ... },
		//			
		//				// match the last child node that's an element
		//				"#id4 > :last-child":  { ... },
		//			
		//				// all elements of type tagname
		//				"tagname": {
		//					// ...
		//				},
		//			
		//				"tagname1 tagname2 tagname3": {
		//					// ...
		//				},
		//			
		//				".classname": {
		//					// ...
		//				},
		//			
		//				"tagname.classname": {
		//					// ...
		//				},
		//			}
		//		
		//		The "found" method is a generalized handler that's called as soon
		//		as the node matches the selector. Rules for values that follow also
		//		apply to the "found" key.
		//		
		//		The "on*" handlers are attached with dojo.connect(). 
		//		
		//		If the value corresponding to the ID key is a function and not a
		//		list, it's treated as though it was the value of "found".

		var tmpObj = {};
		forIn(behaviorObj, this, function(behavior, name){
			var tBehavior = arrIn(this._behaviors, name);
			if(typeof tBehavior["id"] != "number"){
				tBehavior.id = _inc++;
			}
			var cversion = [];
			tBehavior.push(cversion);
			if((dojo.isString(behavior))||(dojo.isFunction(behavior))){
				behavior = { found: behavior };
			}
			forIn(behavior, function(rule, ruleName){
				arrIn(cversion, ruleName).push(rule);
			});
		});
	}

	var _applyToNode = function(node, action, ruleSetName){
		if(dojo.isString(action)){
			if(ruleSetName == "found"){
				dojo.publish(action, [ node ]);
			}else{
				dojo.connect(node, ruleSetName, function(){
					dojo.publish(action, arguments);
				});
			}
		}else if(dojo.isFunction(action)){
			if(ruleSetName == "found"){
				action(node);
			}else{
				dojo.connect(node, ruleSetName, action);
			}
		}
	}

	this.apply = function(){
		// summary:
		//		applies all currently registered behaviors to the document,
		//		taking care to ensure that only incremental updates are made
		//		since the last time add() or apply() were called. If new
		//		matching nodes have been added, all rules in a behavior will be
		//		applied to that node. For previously matched nodes, only
		//		behaviors which have been added since the last call to apply()
		//		will be added to the nodes.
		forIn(this._behaviors, function(tBehavior, id){
			dojo.query(id).forEach( 
				function(elem){
					var runFrom = 0;
					var bid = "_dj_behavior_"+tBehavior.id;
					if(typeof elem[bid] == "number"){
						runFrom = elem[bid];
						// console.debug(bid, runFrom);
						if(runFrom == (tBehavior.length)){
							return;
						}
					}
					// run through the versions, applying newer rules at each step

					for(var x=runFrom, tver; tver = tBehavior[x]; x++){
						// console.debug(tver);
						forIn(tver, function(ruleSet, ruleSetName){
							if(dojo.isArray(ruleSet)){
								dojo.forEach(ruleSet, function(action){
									_applyToNode(elem, action, ruleSetName);
								});
							}
						});
					}

					// ensure that re-application only adds new rules to the node
					elem[bid] = tBehavior.length;
				}
			);
		});
	}
}

dojo.addOnLoad(dojo.behavior, "apply");

}
