/**
 * @package JMF.Events
 * This file contains cross browser events implementation
 * along with custom events hadler. There are some extensions to events
 * handling such as DOMContentLoaded event and mouse wheel event.
 * For IE compatibility sake only bubbling phase is supported
 * SVN: $Id: events.js 6163 2008-08-14 14:36:31Z mjezierski $
 * @version 0.1b
 * TODO:
 * implement wheel event in add/removelistener
 * extend createEvent method to support native events
 * make custom events applicable to DOM elements
 * change event handling so that "this" should point to event source  
 */

JMF.registerLib('JMF.Events','$Id: events.js 6163 2008-08-14 14:36:31Z mjezierski $');

/**
 * Events pseudo singleton class
 */
JMF.Events = {
   /**
    * This object contains registered cutom events and is used to determine
    * if event should be handled as custom or as native event
    */
	customEvents:{},
	/**
	 * Global UID used to mark hadlers, so that they can be easily
	 * recognized in removeListener method
	 */
	GUID:0,
	handlers:{},
	
	/**
	 * Adds event listener to object. This method can be used only to add listeners for native events
	 * @param {Object} obj DOM node to add listener to
	 * @param {String} eventName Event to handle (name should not contain 'on' prefix)
	 * @param {Function} eventHandler Handler to call when event occurs  
	 */
	addListener:function(obj,eventName,eventHandler) {
      var  phsh = {},ea = JMF.Events.special.attach,en = eventName.toLowerCase();
      if(ea[en] && !(phsh = ea[en].apply(this,arguments))) {
      	return;
      } 
      
      obj            = phsh.obj || obj;
      eventName      = phsh.eventName || eventName;
      eventHandler   = phsh.eventHandler || eventHandler;

		obj = JMF.$H.find(obj);
      
      var guid = JMF.$EE.getGUID(eventHandler);
      var func = function(e){return eventHandler.call(obj,JMF.$E(e));};
      JMF.$EE.handlers[guid] = func;

		if(window.addEventListener) {
		   obj.addEventListener(eventName,func,false);
		} else {
			obj.attachEvent('on'+eventName,func);
		}
	},
   /**
    * Removes event listener from object. This method can be used only to remove native events listeners
    * @param {Object} obj DOM node to add listener to
    * @param {String} eventName Event to handle (name should not contain 'on' prefix)
    * @param {Function} eventHandler Handler to call when event occurs  
    */
	removeListener:function(obj,eventName,eventHandler) {
      var  phsh = {},ed = JMF.Events.special.detach,en = eventName.toLowerCase(); 
      if(ed[en] && !(phsh = ed[en].apply(this,arguments))) {
         return;
      }
      
      obj = phsh.obj || obj;
      eventName = phsh.eventName || eventName;
      eventHandler = phsh.eventHandler || eventHandler;


      if(!JMF.browser.Opera&&eventName.toLowerCase() == 'mousewheel') {
            eventName = 'DOMMouseScroll';
      }

      var guid = JMF.$EE.getGUID(eventHandler);
      var func = JMF.$EE.handlers[guid] || eventHandler; 

		if(window.addEventListener) {
			obj.removeEventListener(eventName,func,false);
		} else {
			obj.detachEvent('on'+eventName,func);
		}
	},
	/**
	 * Creates cutom event. This method cannot be used for native event creation.
	 * It is still in developement phase, but can be used. Now, it is used in JMF.Layout object trees.
	 * @param {String} evt Event type
	 * @param {Object} evtData Object with event data passed to listeners
	 * @return {Object} Event object that can be used in JMF.Layout dispatchEvent method.  
	 */
	create:function(evt,evtData) {
		if(!evtData) {
			evtData = {};
		}
		evtData.type = evt;
		evtData.stopPropagation = function(){this.$$stopPropagation = true;};
		
		return evtData;
	},
	/**
	 * Standarizes event to quasi W3C specification format
	 * @param {Event} DOM Event object or null in case of IE
	 * @return {Event} Standarized event object 
	 */
	event:function(evt) {
		evt = evt?evt:window.event;
   
	   if(evt.type == 'UserEvent' || evt.$$fixed) {
	      return evt;
	   }
	
	   evt.$$fixed = true;
	
	   //Stop propagation function 
	   if(!evt.stopPropagation) {
	      evt.stopPropagation = function() {
	         evt.cancelBubble = true;
	      };
	   }
	   
	   //Related target property
	   if(!evt.relatedTarget&&evt.toElement) {
	      evt.relatedTarget = evt.toElement;
	   }
	   
	   //Prevent default function
	   if(!evt.preventDefault) {
	      evt.preventDefault = function() {
	         evt.returnValue = false;
	      };
	   }
	   
	   //block event method
	   if(!evt.block) {
	   	evt.block = function() {
	   		evt.preventDefault();
	   		evt.stopPropagation();
	   		return false;
	   	};
	   }
	   
	   //target property
	   if(!evt.target&&evt.srcElement) {
	      evt.target = evt.srcElement;
	   }
	   
	   //currentTarget property
	   if(!evt.currentTarget&&evt.fromElement) {
	      evt.currentTaget = evt.fromElement;
	   }
	   
	   //wheel property 1 - mouse wheel up, -1 - mouse wheel down
	   if(evt.wheelDelta) {
	      evt.wheel = evt.wheelDelta/120;
	      if(window.opera) {
	         evt.wheel = - evt.wheel;
	      }
	   } 
	   else if(evt.type == 'DOMMouseScroll') {
	      evt.wheel = evt.detail>0?-1:1;
	   }

      /* IE seems not to attach scroll information to body, but to the topmost node */	   
	   //pageX
	   if(!evt.pageX&&evt.clientX) {
	      evt.pageX = evt.clientX + (document.body.parentNode&&!document.body.scrollLeft?document.body.parentNode.scrollLeft:document.body.scrollLeft); 
	   }
	   
	   //pageY
	   if(!evt.pageY&&evt.clientY) {
	      evt.pageY = evt.clientY + (document.body.parentNode&&!document.body.scrollTop?document.body.parentNode.scrollTop:document.body.scrollTop);
	   }
	   
	   if(evt.type == 'DOMMouseScroll') {
	      evt.name = 'mousewheel';    
	   } else {
	      evt.name = evt.type;
	   } 
	   return evt;
	},
   /**
    * Registers custom event, so that it will properlu recognized by addListener method in 
    * classes derived from JMF.Layout
    * @param {String} evtType Event type, wchich can be passed to addListener method 
    */
	registerCustomEvent:function(evtType) {
		this.customEvents[evtType] = true;
	},
	/**
	 * Returns true if given event type is registered as custom.
	 * This is internal method of JMF library
	 * @param {String} evtType Event type registered (or not) with registerCustomEvent method
	 * @return {Boolean} true if event is registered as custom
	 */
	isCustomEvent:function(evtType) {
      return !!this.customEvents[evtType];		
	},
   /**
    * This function can be used as a handler to block event in certain point of domm tree.
    * It should not be called solely.
    */
	blockEvent:function(evt) {
	  	return JMF.$E(evt).block();
	},
   /**
    * This is internal implementation of DOMContentLoaded event for IE
    */
	IEDOMContentLoadedHandler:function() {
		if(JMF.Events.DCLHandlers.run) {
			return;
		}

		JMF.Events.DCLHandlers.run= true;
		for(var i=0;i<JMF.Events.DCLHandlers.length;i++) {
         try {
				JMF.Events.DCLHandlers[i].call(window); 	
				//testing purposes
         } catch (e) {JMF._dbg.error(e);}
		}
	},
	/**
	 * This array contains handlers for DOMContentLoaded events in IE
	 */
	DCLHandlers:[],
	/**
	 * Gets sequential GUID for marking event handlers
	 * If handler has already guid
	 * @member JMF.Events
	 * @param {Function} hnd Optional handler to mark with GUID
	 * @return {Number} GUID
	 */
	getGUID:function(hnd) {
      if(!hnd) {
      	return JMF.Events.GUID++;
      }
      
      if(hnd.$$GUID === undefined) {
      	hnd.$$GUID = JMF.Events.GUID++;
      }
      
		return hnd.$$GUID;
	},
	
	special: {
      attach: {
        //pre-onload handler
        domcontentloaded : function(obj,eventName,eventHandler)	{
	         //assure that handlers will be called even when added after
	         //content loaded
	         if(JMF.domReady) {
	            setTimeout(eventHandler,0);
	            return null;
	         }
	         //jQuery solution for safari
	         if(JMF.browser.Safari || JMF.browser.IE) {
	            JMF.$EE.DCLHandlers.push(eventHandler);
	            var timer = setInterval(function(){
	               if(/loaded|complete/.test(document.readyState)) {
	                  clearInterval(timer);
	                  JMF.$EE.IEDOMContentLoadedHandler();
	               }
	            },10);
	            return null;
	         }
	         return {};
		  },
        mouseheel:function(obj,eventName,eventHandler) {
	       if(!JMF.browser.Opera&&eventName.toLowerCase() == 'mousewheel') {
	            return {eventName:'DOMMouseScroll'};
	       }
	       return {};
		  },
		  //reacts when user enters an area
		  //when used without mouseleave it will act as normal mouseover
        mouseenter:function(obj,eventName,eventHandler){
            var fh = function(e) {
               //store handler's result
               var ret = eventHandler.call(this,e);

               //attach timeout, so that mouselave will work even
               //if mouseout won't fire
               var fn = JMF.$EE.handlers[JMF.$EE.MLG]||function(){};
               JMF.$EE.MLT = setTimeout(function(){
                  fn.call(obj,e);
               },30);
               return ret;
            } ;

            //store real handler, so we can detach it later
            obj.$$mef = fh; 
            //store handler's GUID for detaching
            obj.$$meg = JMF.$EE.getGUID(eventHandler);  
            
            //return changed parameters to standard addListener
            return {
              eventHandler:fh,
              eventName:'mouseover'
            };
         },
         //reacts when user leaves area
         mouseleave:function(obj,eventName,eventHandler){
            //set guid for mouseenter ecent 
            JMF.$EE.handlers[JMF.$EE.MLG = JMF.$EE.getGUID()] = function(e){return eventHandler.call(this,e);};
            
            //store function references for detaching
            obj.$$mlov = function(){
               clearTimeout(JMF.$EE.MLT);
            };
            obj.$$mlou = function(e){
               if(!this.isIn(e.relatedTarget)) {
                  eventHandler.call(obj,e);
               }
            };
            obj.$$mlg = JMF.$EE.getGUID(eventHandler);
            
            //apply standard listeners
            JMF.$H(obj).addListener('mouseover',obj.$$mlov).
               addListener('mouseout',obj.$$mlou);
            return;
         }
      },
      detach:{
        mouseheel:function(obj,eventName,eventHandler) {
          if(!JMF.browser.Opera&&eventName.toLowerCase() == 'mousewheel') {
               return {eventName:'DOMMouseScroll'};
          }
          return {};
			},
			mouseenter:function(obj,eventName,eventHandler) {
            if(obj.$$meg === $JMF.$EE.getGUID(eventHandler)) {
               obj.removeListener('mouseover',$$mef);
               obj.$$meg = obj.$$mef = null;
            }
			},
			mouseleave:function(obj,eventName,eventHandler) {
            if(obj.$$mlg === JMF.$EE.getGUID(eventHandler)) {
            	obj.removeListener('mouseover',obj.$$mlov);
            	obj.removeListener('mouseout',obj.$$mlou);
            	obj.$$mlou = obj.$$mlov = obj.$$mlg = null; 
            }
        }
      }
	}
};





JMF.$E = JMF.Events.event;
JMF.$EE = JMF.Events;

JMF.Events.addListener(window,'DOMContentLoaded',function(){JMF.domReady = true;});