/*
 * Copyright (C) 2007 Diego Perini
 * All rights reserved.
 *
 * nwevents.js - Javascript Event Manager
 *
 * Author: Diego Perini <diego.perini at gmail com>
 * Version: 1.05
 * Release: 20071218
 *
 * License:
 *   http://javascript.nwbox.com/NWEvents/MIT-LICENSE
 * Download:
 *   http://javascript.nwbox.com/NWEvents/nwevents.js
 */

window.NW=window.NW||{};

NW.Event=function(){

	var version='1.05',

	// phases constants, CAPTURING_PHASE not usable in IE
	CAPTURING_PHASE=1,AT_TARGET=2,BUBBLING_PHASE=3,

	// handlers collection
	Handlers={},

	// listeners collection
	Listeners={},

	// delegates collection
	Delegates={},

	// get element context window
	getContext=
		function(o){
			return (o.ownerDocument||o.document||o).parentWindow||window;
		},

	// fix IE event properties to
	// best fit with w3c standards
	fixEvent=
		function(o,e){
			e=e||getContext(o).event;
			// IE: emulate focus/blur bubbling
			if(/^focus(in|out)/i.test(e.type)){
				e=getContext(o).document.createEventObject(e);
				// replace focus/blur with focusin/focusout
				e.type=(e.type=='focusin')?'focus':'blur';
			}
			if(!e.target){
				e.target=e.srcElement;
			}
			if(!e.currentTarget){
				e.currentTarget=o;
			}
			if(!e.preventDefault){
				e.preventDefault=preventDefault;
			}
			if(!e.stopPropagation){
				e.stopPropagation=stopPropagation;
			}
			if(e.target&&(/3|4/).test(e.target.nodeType)){
				e.target=e.target.parentNode;
			}
			if(!e.eventPhase){
				e.eventPhase=e.target==o?AT_TARGET:BUBBLING_PHASE;
			}
			if(!e.relatedTarget&&e.toElement&&e.fromElement){
				if(e.type=='mouseout'){e.relatedTarget=e.toElement;}
				if(e.type=='mouseover'){e.relatedTarget=e.fromElement;}
			}
			if(!e.which&&(e.charCode||e.keyCode)){
				e.which=e.charCode||e.keyCode;
			}
			return e;
		},

	// prevent default action
	preventDefault=
		function(){
			this.returnValue=false;
		},

	// stop event propagation
	stopPropagation=
		function(){
			this.cancelBubble=true;
		},

	// search handlers collection for
	// (o)bject, (t)ype, (h)andler,
	// return index or false
	isHandler=
		function(o,t,h){
			var i,found=false;
			if(Handlers[t]&&Handlers[t].el){
				for(i=0;Handlers[t].el.length>i;i++){
					if(Handlers[t].el[i]===o&&Handlers[t].fn[i]===h){
						found=i;
						break;
					}
				}
			}
			return found;
		},

	// search listeners collection for
	// (o)bject, (t)ype, (h)andler,
	// return index or false
	isListener=
		function(o,t,h){
			var i,found=false;
			if(Listeners[t]&&Listeners[t].el){
				for(i=0;Listeners[t].el.length>i;i++){
					if(Listeners[t].el[i]===o&&Listeners[t].fn[i]===h){
						found=i;
						break;
					}
				}
			}
			return found;
		},

	// search delegates collection for
	// (o)bject, (h)andler matching
	// delegates for (t)ype events
	isDelegate=
		function(o,t,h,d){
			var i,n,found=false;
			for(i=0;Delegates[t].fn.length>i;i++){
				n=0;
				if(Delegates[t].fn[i]===h){
					if(typeof o=='string'){
						// a selector string
						if(Delegates[t].el[i]===o&&Delegates[t].de[i]===d){
							found=i;break;
						}
					}else{
						// an object reference
						if(Delegates[t].el[i].id)
						{n++;if(o.id.toLowerCase()==
							Delegates[t].el[i].id.
							toLowerCase()){n--;}}
						else if(Delegates[t].el[i].nodeName)
						{n++;if(o.nodeName.toLowerCase()==
							Delegates[t].el[i].nodeName.
							toLowerCase()){n--;}}
						else if(Delegates[t].el[i].className)
						{n++;if((' '+o.className+' ').
							replace(/\s+/g,' ').indexOf(' '+
							Delegates[t].el[i].className+' ')){n--;}}
						if(n===0){found=i;break;}
					}
				}
			}
			return found;
		},

	// handle listeners chain for (e)vent type
	handleListeners=
		function(e){
			var i,f,t=e.type;
			if(Listeners[t]){
				// make a copy of the Listeners[type] array
				// since it can be modified run time by the
				// events deleting themselves or adding new
				f=Listeners[t].fn.slice();
				for(i=0;f.length>i;i++){
					// element match current target ?
					if(Listeners[t].el[i]===this){
						if(typeof f[i]=='function'){
//							f[i].call(this,e);
							if(f[i].call(this,e)===false){//||(e.returnValue&&e.returnValue===false)){
								break;
							}
						}
						//else{
						//	throw new Error('NW.Event ('+t+'): handler for '+e.target+' is not a function.');
						//}
					}
				}
			}
		},

	// handle delegates chain for (e)vent type
	handleDelegates=
		function(e){
			var i,j,o,d,f,m=true,t=e.type;
			if(Delegates[t]){
				d=Delegates[t].de.slice();
				o=Delegates[t].el.slice();
				f=Delegates[t].fn.slice();
				// process chain in fifo order
				for(i=0;f.length>i;i++){
					if(typeof o[i]=='string'){
						// fix nasty load order bug
						// test nwdomlib has loaded
						if(window.NW&&window.NW.Dom){
							// selector string "matcher"
							if(window.NW.Dom.match(e.target,o[i])){
								f[i].call(e.target,e);
							}
						}
					}else{
						// real DOM element reference
						if(o[i]===e.target){
							f[i].call(e.target,e);
						}else{
							// selector object prop/values
							for(j in o[i]){
								if(o[i][j]!==e.target[j]){
									m=false;
									break;
								}
							}
							if(m){
								f[i].call(e.target,e);
							}
						}
					}
				}
			}
		},

	// search listeners collection for
	// (o)bject, (t)ype, (h)andler,
	// return array
	getListeners=
		function(o,t,h){
			var i,r=[];
			if(Listeners[t]&&Listeners[t].el){
				for(i=0;Listeners[t].el.length>i;i++){
					if(Listeners[t].el[i]===o&&Listeners[t].fn[i]===h){
						r.push(Listeners[t].fn[i]);
					}
				}
			}
			return r;
		},

	// return delegate useCount to
	// ensure that only one handler
	// is set for each type delegate
	getDelegates=
		function(t,d){
			for(var n=0,i=0;Delegates[t].de.length>i;i++){
				if(Delegates[t].de[i]===d){
					n++;
				}
			}
			return n;
		};

	// ********** begin public methods **********
	return {

		// use event registration (DOM2) or inline events (DOM0)
		EVENTS_W3C:true,

		// block any further event processing
		stop:
			function(e){
				if(e.preventDefault){
					e.preventDefault();
				}else{
					e.returnValue=false;
				}
				if(e.stopPropagation){
					e.stopPropagation();
				}else{
					e.cancelBubble=true;
				}
				return false;
			},

		// append event handler
		// (o)bject, (t)ype, (h)andler, (c)apture
		appendHandler:
			function(o,t,h,c){
				var key;
				//if(!checkArgs(arguments)){
				//	throw new Error('NW.Event ('+t+'): handler for '+h+' is not a function.');
				//	return false;
				//}
				// passed handler not in the chain
				if((key=isHandler(o,t,h))===false){
					// create handlers storage for (t)ype
					Handlers[t]=Handlers[t]||{el:[],fn:[],wr:[]};
					if(o.addEventListener&&NW.Event.EVENTS_W3C){
						o.addEventListener(t,h,c||false);
					}else if(o.attachEvent&&NW.Event.EVENTS_W3C){
						// only for IE add to wrappers array
						key=Handlers[t].wr.push(
							function(e){
								return h.call(o,fixEvent(o,e));
							}
						);
						o.attachEvent('on'+t,Handlers[t].wr[key-1]);
					}else{
						// first handler for (t)ype
						if(Handlers[t].el.length===0){
							// save old handler if any
							if(typeof o['on'+t]=='function'){
								// add old object to the elements array
								Handlers[t].el.push(o);
								// add old handler to the function array
								Handlers[t].fn.push(o['on'+t]);
							}
							o['on'+t]=
								function(e){
									return h.call(this,fixEvent(this,e));
								};
						}
					}
					// add new object to the elements array
					Handlers[t].el.push(o);
					// add new handler to the function array
					Handlers[t].fn.push(h);
					return true;
				}
				return false;
			},

		// remove event handler
		// (o)bject, (t)ype, (h)andler, (c)apture
		removeHandler:
			function(o,t,h,c){
				var key;
				if((key=isHandler(o,t,h))!==false){
					// remove event handler/listener from chain
					if(o.removeEventListener&&NW.Event.EVENTS_W3C){
						o.removeEventListener(t,h,c||false);
					}else if(o.detachEvent&&NW.Event.EVENTS_W3C){
						o.detachEvent('on'+t,Handlers[t].wr[key]);
						// remove wrapper from chain
						Handlers[t].wr.splice(key,1);
					}else{
						if(Handlers[t].el.length==1){
							o['on'+t]=Handlers[t].el[0];
						}
					}
					// remove element from chain
					Handlers[t].el.splice(key,1);
					// remove function from chain
					Handlers[t].fn.splice(key,1);
					// no more handlers for this type
					if(Handlers[t].el.length==1){
						// remove element from chain
						Handlers[t].el.splice(0,1);
						// remove function from chain
						Handlers[t].fn.splice(0,1);
						// delete handlers chain type
						delete Handlers[t];
					}
					return true;
				}
				return false;
			},

		// append listener to chain
		// (o)bject, (t)ype, (h)andler, (c)apture
		appendListener:
			function(o,t,h,c){
				var key;
				//if(!checkArgs(arguments)){
				//	throw new Error('NW.Event ('+t+'): handler for '+h+' is not a function.');
				//	return false;
				//}
				if((key=isListener(o,t,h))===false){
					// create listeners storage for event type
					Listeners[t]=Listeners[t]||{el:[],fn:[]};
					if(getListeners(o,t,h).length===0){
						// first listener, append the handler
						NW.Event.appendHandler(o,t,handleListeners,c);
					}
					// add listener to the chain
					Listeners[t].el.push(o);
					Listeners[t].fn.push(h);
					return true;
				}
				return false;
			},

		// remove listener from chain
		// (o)bject, (t)ype, (h)andler, (c)apture
		removeListener:
			function(o,t,h,c){
				var key;
				if((key=isListener(o,t,h))!==false){
					// remove listener from the chain
					Listeners[t].fn.splice(key,1);
					Listeners[t].el.splice(key,1);
					// no more listeners for this type
					if(Listeners[t].el.length===0){
						// remove the real handler for this chain
						NW.Event.removeHandler(o,t,handleListeners,c);
						// delete listeners chain type
						delete Listeners[t];
					}
					return true;
				}
				return false;
			},

		// append delegate to chain
		// (o)bject, (t)ype, (h)andler, (d)elegate
		appendDelegate:
			function(o,t,h,d){
				//if(!checkArgs(arguments)){
				//	throw new Error('NW.Event ('+t+'): handler for '+h+' is not a function.');
				//	return false;
				//}
				d=d||document.documentElement;
				Delegates[t]=Delegates[t]||{el:[],fn:[],de:[]};
				if(isDelegate(o,t,h,d)===false){
					// add delegate to the chain
					Delegates[t].el.push(o);
					Delegates[t].fn.push(h);
					Delegates[t].de.push(d);
					// first delegate for this event type
					if(getDelegates(t,d)==1){
						// IE: use bubbling clones of UI activation events (focus/blur)
						if(typeof document.fileSize!='undefined'&&/focus|blur/.test(t)){
							t='focus'+((t=='focus')?'in':'out');
						}
						NW.Event.appendHandler(d,t,handleDelegates,
							/focus|blur/i.test(t)?true:false);
					}
					return true;
				}
				return false;
			},

		// remove delegate from chain
		// (o)bject, (t)ype, (h)andler, (d)elegate
		removeDelegate:
			function(o,t,h,d){
				var i,key;
				if(Delegates[t]){
					d=d||document.documentElement;
					key=isDelegate(o,t,h,d);
					if(key!==false){
						// remove delegate listener from the chain
						Delegates[t].el.splice(key,1);
						Delegates[t].fn.splice(key,1);
						Delegates[t].de.splice(key,1);
						if(Delegates[t].fn.length===0){
							// IE: use bubbling clones of UI activation events (focus/blur)
							if(typeof document.fileSize!='undefined'&&/focus|blur/.test(t)){
								t='focus'+((t=='focus')?'in':'out');
							}
							// remove the real event handler for this chain
							NW.Event.removeHandler(d,t,handleDelegates,
								/focus|blur/i.test(t)?true:false);
							// remove chain for this type
							Delegates[t]=null;
							// check if some chain is still active
							for(i in Delegates){return true;}
							// if not cleanup delegate list
							Delegates=null;
						}
						return true;
					}
				}
				return false;
			},

		// key event extension
		keyEvent:function(){
			var o=this;
			o.mods=[];
			o.keys=[];
			o.funcs=[];
			o.toggle=[];
			o.keydown=function(e){
				if(e.altKey||e.ctrlKey||e.shiftKey){
					for(var m in {'altKey':0,'ctrlKey':0,'shiftKey':0}){
						for(var i=0;o.keys.length>i;i++){
							// in opera the ALT+key combination is not detected (use ALT+SHIFT+key)
							if((o.mods[i]==m.charAt(0)||NW.browser.opera)&&e[m]&&e.keyCode==o.keys[i]){
								o.funcs[i](o.toggle[i]);
								o.toggle[i]=1-o.toggle[i];
								NW.Event.stop(e);
							}
						}
					}
				}
			};
			o.addKey=function(k){
				o.mods[o.mods.length]=k.mod;
				o.keys[o.keys.length]=k.key;
				o.funcs[o.funcs.length]=k.onkey;
				o.toggle[o.toggle.length]=0;
			};
			o.delKey=function(k){
				for(var i in o.keys){
					if(k.key==o.keys[i].key){
						o.mods.splice(i,1);
						o.keys.splice(i,1);
						o.funcs.splice(i,1);
						o.toggle.splice(i,1);
					}
				}
			};
			o.start=function(){NW.Event.appendListener(NW.document,'keydown',o.keydown,false);};
			o.stop=function(){NW.Event.removeListener(NW.document,'keydown',o.keydown,false);};
			return o;
		},

		// capture global mouse events in
		// multi-document environments (iframes)
		// @t array of chain types to capture
		mouseCapture:function(t){
			var i,j,f=NW.window.frames;
			for(i=0;f.length>i;i++){
				for(j in t){try{NW.Event.appendListener(f[i].document,j,t[j],false);}catch(e){NW.window.status=e;}}
			}
		},

		// release global mouse events
		// multi-document environments (iframes)
		// @t array of chain types to release
		mouseRelease:function(t){
			var i,j,f=NW.window.frames;
			for(i=0;f.length>i;i++){
				for(j in t){try{NW.Event.removeListener(f[i].document,j,t[j],false);}catch(e){NW.window.status=e;}}
			}
		},

		// disable text selection on element
		// @e element reference or id string
		// @s state true=enable false=disable
		setSelection:function(e,s){
			if(NW.browser.ie){
				if(s){
					e.ondragstart=null;
					e.onselectstart=null;
					e.setAttribute('unselectable','off');
				}else{
					e.ondragstart=function(){return false;}
					e.onselectstart=function(){return false;}
					e.setAttribute('unselectable','on');
				}
			}else{
				if(e.nodeType==1){
					e.style.userSelect=s?'auto':'none';
					e.style.MozUserSelect=s?'auto':'none';
					e.style.KhtmlUserSelect=s?'auto':'none';
				}else{
					e.onmousedown=function(){return false;}
				}
			}
			if(e.nodeType==1){
				e.style.cursor=s?'auto':'default';
			}
		}

	}

}();
