/**
 * @ignore
 * custom event for dom.
 * @author yiminghe@gmail.com
 */
KISSY.add('event/dom/base/observable', function (S, Dom, Special, DomEventUtils, DomEventObserver, DomEventObject, BaseEvent) {

    // 记录手工 fire(domElement,type) 时的 type
    // 再在浏览器通知的系统 eventHandler 中检查
    // 如果相同,那么证明已经 fire 过了,不要再次触发了
    var BaseUtils = BaseEvent.Utils;
    var logger= S.getLogger('s/event');

    /**
     * custom event for dom
     * @param {Object} cfg
     * @private
     * @class KISSY.Event.DomEvent.Observable
     * @extends KISSY.Event.Observable
     */
    function DomEventObservable(cfg) {
        var self = this;
        S.mix(self, cfg);
        self.reset();
        /**
         * html node which binds current custom event
         * @cfg {HTMLElement} currentTarget
         */
    }

    S.extend(DomEventObservable, BaseEvent.Observable, {

        setup: function () {
            var self = this,
                type = self.type,
                s = Special[type] || {},
                currentTarget = self.currentTarget,
                eventDesc = DomEventUtils.data(currentTarget),
                handle = eventDesc.handle;
            // 第一次注册该事件,dom 节点才需要注册 dom 事件
            if (!s.setup || s.setup.call(currentTarget, type) === false) {
                DomEventUtils.simpleAdd(currentTarget, type, handle)
            }
        },

        constructor: DomEventObservable,

        reset: function () {
            var self = this;
            DomEventObservable.superclass.reset.call(self);
            self.delegateCount = 0;
            self.lastCount = 0;
        },

        /**
         * notify current event 's observers
         * @param {KISSY.Event.DomEvent.Object} event
         * @return {*} return false if one of custom event 's observers  else
         * return last value of custom event 's observers 's return value.
         */
        notify: function (event) {
            /*
             As some listeners may remove themselves from the
             event, the original array length is dynamic. So,
             let's make a copy of all listeners, so we are
             sure we'll call all of them.
             */
            /*
             Dom3 Events: EventListenerList objects in the Dom are live. ??
             */
            var target = event.target,
                eventType = event['type'],
                self = this,
                currentTarget = self.currentTarget,
                observers = self.observers,
                currentTarget0,
                allObservers = [],
                ret,
                gRet,
                observerObj,
                i,
                j,
                delegateCount = self.delegateCount || 0,
                len,
                currentTargetObservers,
                currentTargetObserver,
                observer;

            // collect delegated observers and corresponding element
            if (delegateCount && target.nodeType) {
                while (target != currentTarget) {
                    if (target.disabled !== true || eventType !== 'click') {
                        var cachedMatch = {},
                            matched, key, filter;
                        currentTargetObservers = [];
                        for (i = 0; i < delegateCount; i++) {
                            observer = observers[i];
                            filter = observer.filter;
                            key = filter + '';
                            matched = cachedMatch[key];
                            if (matched === undefined) {
                                matched = cachedMatch[key] = Dom.test(target, filter);
                            }
                            if (matched) {
                                currentTargetObservers.push(observer);
                            }
                        }
                        if (currentTargetObservers.length) {
                            allObservers.push({
                                currentTarget: target,
                                'currentTargetObservers': currentTargetObservers
                            });
                        }
                    }
                    target = target.parentNode || currentTarget;
                }
            }

            // root node's observers is placed at end position of add observers
            // in case child node stopPropagation of root node's observers
            if (delegateCount < observers.length) {
                allObservers.push({
                    currentTarget: currentTarget,
                    // http://www.w3.org/TR/dom/#dispatching-events
                    // Let listeners be a static list of the event listeners
                    // associated with the object for which these steps are run.
                    currentTargetObservers: observers.slice(delegateCount)
                });
            }

            //noinspection JSUnresolvedFunction
            for (i = 0, len = allObservers.length; !event.isPropagationStopped() && i < len; ++i) {

                observerObj = allObservers[i];
                currentTargetObservers = observerObj.currentTargetObservers;
                currentTarget0 = observerObj.currentTarget;
                event.currentTarget = currentTarget0;

                //noinspection JSUnresolvedFunction
                for (j = 0; !event.isImmediatePropagationStopped() && j < currentTargetObservers.length; j++) {

                    currentTargetObserver = currentTargetObservers[j];

                    ret = currentTargetObserver.notify(event, self);

                    // 和 jQuery 逻辑保持一致
                    // 有一个 false,最终结果就是 false
                    // 否则等于最后一个返回值
                    if (gRet !== false && ret !== undefined) {
                        gRet = ret;
                    }
                }
            }

            // fire 时判断如果 preventDefault,则返回 false 否则返回 true
            // 这里返回值意义不同
            return gRet;
        },

        /**
         * fire dom event from bottom to up , emulate dispatchEvent in Dom3 Events
         * @param {Object|KISSY.Event.DomEvent.Object} [event] additional event data
         * @param {Boolean} [onlyHandlers] for internal usage
         */
        fire: function (event, onlyHandlers/*internal usage*/) {
            event = event || {};

            var self = this,
                eventType = String(self.type),
                domEventObservable,
                eventData,
                specialEvent = Special[eventType] || {},
                bubbles = specialEvent.bubbles !== false,
                currentTarget = self.currentTarget;

            // special fire for click/focus/blur
            if (specialEvent.fire && specialEvent.fire.call(currentTarget, onlyHandlers) === false) {
                return;
            }

            if (!(event instanceof DomEventObject)) {
                eventData = event;
                event = new DomEventObject({
                    currentTarget: currentTarget,
                    type: eventType,
                    target: currentTarget
                });
                S.mix(event, eventData);
            }

            if (specialEvent.preFire && specialEvent.preFire.call(currentTarget, event, onlyHandlers) === false) {
                return;
            }

            // onlyHandlers is equal to event.halt()
            // but we can not call event.halt()
            // because handle will check event.isPropagationStopped
            var cur = currentTarget,
                win = Dom.getWindow(cur),
                curDocument = win.document,
                eventPath = [],
                gret,
                ret,
                ontype = 'on' + eventType,
                eventPathIndex = 0;

            // http://www.w3.org/TR/dom/#dispatching-events
            // let event path be a static ordered list of all its ancestors in tree order,
            // or let event path be the empty list otherwise.
            do {
                eventPath.push(cur);
                // Bubble up to document, then to window
                cur = cur.parentNode || cur.ownerDocument || (cur === curDocument) && win;
            } while (!onlyHandlers && cur && bubbles);

            cur = eventPath[eventPathIndex];

            // bubble up dom tree
            do {
                event['currentTarget'] = cur;
                domEventObservable = DomEventObservable.getDomEventObservable(cur, eventType);
                // default bubble for html node
                if (domEventObservable) {
                    ret = domEventObservable.notify(event);
                    if (ret !== undefined && gret !== false) {
                        gret = ret;
                    }
                }
                // Trigger an inline bound script
                if (cur[ ontype ] && cur[ ontype ].call(cur) === false) {
                    event.preventDefault();
                }
                cur = eventPath[++eventPathIndex];
            } while (!onlyHandlers && cur && !event.isPropagationStopped());

            if (!onlyHandlers && !event.isDefaultPrevented()) {
                // now all browser support click
                // https://developer.mozilla.org/en-US/docs/Dom/element.click
                try {
                    // execute default action on dom node
                    // exclude window
                    if (currentTarget[ eventType ] && !S.isWindow(currentTarget)) {
                        // 记录当前 trigger 触发
                        DomEventObservable.triggeredEvent = eventType;

                        // 只触发默认事件,而不要执行绑定的用户回调
                        // 同步触发
                        currentTarget[ eventType ]();
                    }
                } catch (eError) {
                    logger.debug('trigger action error: '+eError);
                }

                DomEventObservable.triggeredEvent = '';
            }

            return gret;
        },

        /**
         * add a observer to custom event's observers
         * @param {Object} cfg {@link KISSY.Event.DomEvent.Observer} 's config
         */
        on: function (cfg) {
            var self = this,
                observers = self.observers,
                s = Special[self.type] || {},
            // clone event
                observer = cfg instanceof DomEventObserver ? cfg : new DomEventObserver(cfg);

            if (S.Config.debug) {
                if (!observer.fn) {
                    S.error('lack event handler for ' + self.type);
                }
            }

            if (self.findObserver(/**@type KISSY.Event.DomEvent.Observer
             @ignore*/observer) == -1) {
                // 增加 listener
                if (observer.filter) {
                    observers.splice(self.delegateCount, 0, observer);
                    self.delegateCount++;
                } else {
                    if (observer.last) {
                        observers.push(observer);
                        self.lastCount++;
                    } else {
                        observers.splice(observers.length - self.lastCount, 0, observer);
                    }
                }

                if (s.add) {
                    s.add.call(self.currentTarget, observer);
                }
            }
        },

        /**
         * remove some observers from current event 's observers by observer config param
         * @param {Object} cfg {@link KISSY.Event.DomEvent.Observer} 's config
         */
        detach: function (cfg) {
            var groupsRe,
                self = this,
                s = Special[self.type] || {},
                hasFilter = 'filter' in cfg,
                filter = cfg.filter,
                context = cfg.context,
                fn = cfg.fn,
                currentTarget = self.currentTarget,
                observers = self.observers,
                groups = cfg.groups;

            if (!observers.length) {
                return;
            }

            if (groups) {
                groupsRe = BaseUtils.getGroupsRe(groups);
            }

            var i, j, t, observer, observerContext, len = observers.length;

            // 移除 fn
            if (fn || hasFilter || groupsRe) {
                context = context || currentTarget;

                for (i = 0, j = 0, t = []; i < len; ++i) {
                    observer = observers[i];
                    observerContext = observer.context || currentTarget;
                    if (
                        (context != observerContext) ||
                            // 指定了函数,函数不相等,保留
                            (fn && fn != observer.fn) ||
                            // 1.没指定函数
                            // 1.1 没有指定选择器,删掉 else2
                            // 1.2 指定选择器,字符串为空
                            // 1.2.1 指定选择器,字符串为空,待比较 observer 有选择器,删掉 else
                            // 1.2.2 指定选择器,字符串为空,待比较 observer 没有选择器,保留
                            // 1.3 指定选择器,字符串不为空,字符串相等,删掉 else
                            // 1.4 指定选择器,字符串不为空,字符串不相等,保留
                            // 2.指定了函数且函数相等
                            // 2.1 没有指定选择器,删掉 else
                            // 2.2 指定选择器,字符串为空
                            // 2.2.1 指定选择器,字符串为空,待比较 observer 有选择器,删掉 else
                            // 2.2.2 指定选择器,字符串为空,待比较 observer 没有选择器,保留
                            // 2.3 指定选择器,字符串不为空,字符串相等,删掉  else
                            // 2.4 指定选择器,字符串不为空,字符串不相等,保留
                            (
                                hasFilter &&
                                    (
                                        (filter && filter != observer.filter) ||
                                            (!filter && !observer.filter)
                                        )
                                ) ||

                            // 指定了删除的某些组,而该 observer 不属于这些组,保留,否则删除
                            (groupsRe && !observer.groups.match(groupsRe))
                        ) {
                        t[j++] = observer;
                    } else {
                        if (observer.filter && self.delegateCount) {
                            self.delegateCount--;
                        }
                        if (observer.last && self.lastCount) {
                            self.lastCount--;
                        }
                        if (s.remove) {
                            s.remove.call(currentTarget, observer);
                        }
                    }
                }

                self.observers = t;
            } else {
                // 全部删除
                self.reset();
            }

            self.checkMemory();
        },

        checkMemory: function () {
            var self = this,
                type = self.type,
                domEventObservables,
                handle,
                s = Special[type] || {},
                currentTarget = self.currentTarget,
                eventDesc = DomEventUtils.data(currentTarget);
            if (eventDesc) {
                domEventObservables = eventDesc.observables;
                if (!self.hasObserver()) {
                    handle = eventDesc.handle;
                    // remove(el, type) or fn 已移除光
                    // dom node need to detach handler from dom node
                    if ((!s['tearDown'] || s['tearDown'].call(currentTarget, type) === false)) {
                        DomEventUtils.simpleRemove(currentTarget, type, handle);
                    }
                    // remove currentTarget's single event description
                    delete domEventObservables[type];
                }

                // remove currentTarget's  all domEventObservables description
                if (S.isEmptyObject(domEventObservables)) {
                    eventDesc.handle = null;
                    DomEventUtils.removeData(currentTarget);
                }
            }
        }
    });

    DomEventObservable.triggeredEvent = '';

    /**
     * get custom event from html node by event type.
     * @param {HTMLElement} node
     * @param {String} type event type
     * @return {KISSY.Event.DomEvent.Observable}
     */
    DomEventObservable.getDomEventObservable = function (node, type) {

        var domEventObservablesHolder = DomEventUtils.data(node),
            domEventObservables;
        if (domEventObservablesHolder) {
            domEventObservables = domEventObservablesHolder.observables;
        }
        if (domEventObservables) {
            return domEventObservables[type];
        }

        return null;
    };


    DomEventObservable.getDomEventObservablesHolder = function (node, create) {
        var domEventObservables = DomEventUtils.data(node);
        if (!domEventObservables && create) {
            DomEventUtils.data(node, domEventObservables = {});
        }
        return domEventObservables;
    };

    return DomEventObservable;

}, {
    requires: ['dom', './special', './utils', './observer', './object', 'event/base']
});