Ext.lib.Event = function() {
    var loadComplete = false,
        listeners = [],
        unloadListeners = [],
        retryCount = 0,
        onAvailStack = [],
        _interval,
        locked = false,
        win = window,
        doc = document,
        
        // constants            
        POLL_RETRYS = 200,
        POLL_INTERVAL = 20,
        EL = 0,
        TYPE = 1,
        FN = 2,
        WFN = 3,
        OBJ = 3,
        ADJ_SCOPE = 4,   
        SCROLLLEFT = 'scrollLeft',
        SCROLLTOP = 'scrollTop',
        UNLOAD = 'unload',
        MOUSEOVER = 'mouseover',
        MOUSEOUT = 'mouseout',
        // private
        doAdd = function() {
            var ret;
            if (win.addEventListener) {
                ret = function(el, eventName, fn, capture) {
                    if (eventName == 'mouseenter') {
                        fn = fn.createInterceptor(checkRelatedTarget);
                        el.addEventListener(MOUSEOVER, fn, (capture));
                    } else if (eventName == 'mouseleave') {
                        fn = fn.createInterceptor(checkRelatedTarget);
                        el.addEventListener(MOUSEOUT, fn, (capture));
                    } else {
                        el.addEventListener(eventName, fn, (capture));
                    }
                    return fn;
                };
            } else if (win.attachEvent) {
                ret = function(el, eventName, fn, capture) {
                    el.attachEvent("on" + eventName, fn);
                    return fn;
                };
            } else {
                ret = function(){};
            }
            return ret;
        }(),    
        // private
        doRemove = function(){
            var ret;
            if (win.removeEventListener) {
                ret = function (el, eventName, fn, capture) {
                    if (eventName == 'mouseenter') {
                        eventName = MOUSEOVER;
                    } else if (eventName == 'mouseleave') {
                        eventName = MOUSEOUT;
                    }                        
                    el.removeEventListener(eventName, fn, (capture));
                };
            } else if (win.detachEvent) {
                ret = function (el, eventName, fn) {
                    el.detachEvent("on" + eventName, fn);
                };
            } else {
                ret = function(){};
            }
            return ret;
        }();        

    var isXUL = Ext.isGecko ? function(node){ 
        return Object.prototype.toString.call(node) == '[object XULElement]';
    } : function(){};
        
    var isTextNode = Ext.isGecko ? function(node){
        try{
            return node.nodeType == 3;
        }catch(e) {
            return false;
        }

    } : function(node){
        return node.nodeType == 3;
    };
        
    function checkRelatedTarget(e) {
        var related = pub.getRelatedTarget(e);
        return !(isXUL(related) || elContains(e.currentTarget,related));
    }

    function elContains(parent, child) {
       if(parent && parent.firstChild){  
         while(child) {
            if(child === parent) {
                return true;
            }
            try {
                child = child.parentNode;
            } catch(e) {
                // In FF if you mouseout an text input element
                // thats inside a div sometimes it randomly throws
                // Permission denied to get property HTMLDivElement.parentNode
                // See https://bugzilla.mozilla.org/show_bug.cgi?id=208427
                
                return false;
            }                
            if(child && (child.nodeType != 1)) {
                child = null;
            }
          }
        }
        return false;
    }

        
    // private  
    function _getCacheIndex(el, eventName, fn) {
        var index = -1;
        Ext.each(listeners, function (v,i) {
            if(v && v[FN] == fn && v[EL] == el && v[TYPE] == eventName) {
                index = i;
            }
        });
        return index;
    }
                    
    // private
    function _tryPreloadAttach() {
        var ret = false,                
            notAvail = [],
            element,
            tryAgain = !loadComplete || (retryCount > 0);                       
        
        if (!locked) {
            locked = true;
            
            Ext.each(onAvailStack, function (v,i,a){
                if(v && (element = doc.getElementById(v.id))){
                    if(!v.checkReady || loadComplete || element.nextSibling || (doc && doc.body)) {
                        element = v.override ? (v.override === true ? v.obj : v.override) : element;
                        v.fn.call(element, v.obj);
                        onAvailStack[i] = null;
                    } else {
                        notAvail.push(v);
                    }
                }   
            });

            retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;

            if (tryAgain) { 
                startInterval();
            } else {
                clearInterval(_interval);
                _interval = null;
            }

            ret = !(locked = false);
        }
        return ret;
    }
    
    // private              
    function startInterval() {            
        if(!_interval){                    
            var callback = function() {
                _tryPreloadAttach();
            };
            _interval = setInterval(callback, POLL_INTERVAL);
        }
    }
    
    // private 
    function getScroll() {
        var dd = doc.documentElement, 
            db = doc.body;
        if(dd && (dd[SCROLLTOP] || dd[SCROLLLEFT])){
            return [dd[SCROLLLEFT], dd[SCROLLTOP]];
        }else if(db){
            return [db[SCROLLLEFT], db[SCROLLTOP]];
        }else{
            return [0, 0];
        }
    }
        
    // private
    function getPageCoord (ev, xy) {
        ev = ev.browserEvent || ev;
        var coord  = ev['page' + xy];
        if (!coord && coord !== 0) {
            coord = ev['client' + xy] || 0;

            if (Ext.isIE) {
                coord += getScroll()[xy == "X" ? 0 : 1];
            }
        }

        return coord;
    }

    var pub =  {
        onAvailable : function(p_id, p_fn, p_obj, p_override) {             
            onAvailStack.push({ 
                id:         p_id,
                fn:         p_fn,
                obj:        p_obj,
                override:   p_override,
                checkReady: false });

            retryCount = POLL_RETRYS;
            startInterval();
        },


        addListener: function(el, eventName, fn) {
            var ret;                
            el = Ext.getDom(el);                
            if (el && fn) {
                if (UNLOAD == eventName) {
                    ret = !!(unloadListeners[unloadListeners.length] = [el, eventName, fn]);                    
                } else {
                    listeners.push([el, eventName, fn, ret = doAdd(el, eventName, fn, false)]);
                }
            }
            return !!ret;
        },

        removeListener: function(el, eventName, fn) {
            var ret = false,
                index, 
                cacheItem;

            el = Ext.getDom(el);

            if(!fn) {                   
                ret = this.purgeElement(el, false, eventName);
            } else if (UNLOAD == eventName) {   
                Ext.each(unloadListeners, function(v, i, a) {
                    if( v && v[0] == el && v[1] == eventName && v[2] == fn) {
                        unloadListeners.splice(i, 1);
                        ret = true;
                    }
                });
            } else {    
                index = arguments[3] || _getCacheIndex(el, eventName, fn);
                cacheItem = listeners[index];
                
                if (el && cacheItem) {
                    doRemove(el, eventName, cacheItem[WFN], false);     
                    cacheItem[WFN] = cacheItem[FN] = null;                       
                    listeners.splice(index, 1);     
                    ret = true;
                }
            }
            return ret;
        },

        getTarget : function(ev) {
            ev = ev.browserEvent || ev;                
            return this.resolveTextNode(ev.target || ev.srcElement);
        },

        resolveTextNode : function(node) {
            return node && !isXUL(node) && isTextNode(node) ? node.parentNode : node;
        },

        getRelatedTarget : function(ev) {
            ev = ev.browserEvent || ev;
            return this.resolveTextNode(ev.relatedTarget || 
                    (ev.type == MOUSEOUT ? ev.toElement :
                     ev.type == MOUSEOVER ? ev.fromElement : null));
        },
        
        getPageX : function(ev) {
            return getPageCoord(ev, "X");
        },

        getPageY : function(ev) {
            return getPageCoord(ev, "Y");
        },


        getXY : function(ev) {                             
            return [this.getPageX(ev), this.getPageY(ev)];
        },

// Is this useful?  Removing to save space unless use case exists.
//             getTime: function(ev) {
//                 ev = ev.browserEvent || ev;
//                 if (!ev.time) {
//                     var t = new Date().getTime();
//                     try {
//                         ev.time = t;
//                     } catch(ex) {
//                         return t;
//                     }
//                 }

//                 return ev.time;
//             },

        stopEvent : function(ev) {                            
            this.stopPropagation(ev);
            this.preventDefault(ev);
        },

        stopPropagation : function(ev) {
            ev = ev.browserEvent || ev;
            if (ev.stopPropagation) {
                ev.stopPropagation();
            } else {
                ev.cancelBubble = true;
            }
        },

        preventDefault : function(ev) {
            ev = ev.browserEvent || ev;
            if (ev.preventDefault) {
                ev.preventDefault();
            } else {
                ev.returnValue = false;
            }
        },
        
        getEvent : function(e) {
            e = e || win.event;
            if (!e) {
                var c = this.getEvent.caller;
                while (c) {
                    e = c.arguments[0];
                    if (e && Event == e.constructor) {
                        break;
                    }
                    c = c.caller;
                }
            }
            return e;
        },

        getCharCode : function(ev) {
            ev = ev.browserEvent || ev;
            return ev.charCode || ev.keyCode || 0;
        },

        //clearCache: function() {},

        _load : function(e) {
            loadComplete = true;
            var EU = Ext.lib.Event;    
            if (Ext.isIE && e !== true) {
        // IE8 complains that _load is null or not an object
        // so lets remove self via arguments.callee
                doRemove(win, "load", arguments.callee);
            }
        },            
        
        purgeElement : function(el, recurse, eventName) {
            var me = this;
            Ext.each( me.getListeners(el, eventName), function(v){
                if(v){
                    me.removeListener(el, v.type, v.fn);
                }
            });

            if (recurse && el && el.childNodes) {
                Ext.each(el.childNodes, function(v){
                    me.purgeElement(v, recurse, eventName);
                });
            }
        },

        getListeners : function(el, eventName) {
            var me = this,
                results = [], 
                searchLists;

            if (eventName){  
                searchLists = eventName == UNLOAD ? unloadListeners : listeners;
            }else{
                searchLists = listeners.concat(unloadListeners);
            }

            Ext.each(searchLists, function(v, i){
                if (v && v[EL] == el && (!eventName || eventName == v[TYPE])) {
                    results.push({
                                type:   v[TYPE],
                                fn:     v[FN],
                                obj:    v[OBJ],
                                adjust: v[ADJ_SCOPE],
                                index:  i
                            });
                }   
            });                

            return results.length ? results : null;
        },

        _unload : function(e) {
             var EU = Ext.lib.Event, 
                i, 
                j, 
                l, 
                len, 
                index,
                scope;
                

            Ext.each(unloadListeners, function(v) {
                if (v) {
                    try{
                        scope =  v[ADJ_SCOPE] ? (v[ADJ_SCOPE] === true ? v[OBJ] : v[ADJ_SCOPE]) :  win; 
                        v[FN].call(scope, EU.getEvent(e), v[OBJ]);
                    }catch(ex){}
                }   
            });     

            unloadListeners = null;

            if(listeners && (j = listeners.length)){                    
                while(j){                        
                    if((l = listeners[index = --j])){
                        EU.removeListener(l[EL], l[TYPE], l[FN], index);
                    }                        
                }
                //EU.clearCache();
            }

            doRemove(win, UNLOAD, EU._unload);
        }            
    };        
    
    // Initialize stuff.
    pub.on = pub.addListener;
    pub.un = pub.removeListener;
    if (doc && doc.body) {
        pub._load(true);
    } else {
        doAdd(win, "load", pub._load);
    }
    doAdd(win, UNLOAD, pub._unload);    
    _tryPreloadAttach();
    
    return pub;
}();