/**
 * @ignore
 * setup event/dom api module
 * @author yiminghe@gmail.com
 */
KISSY.add('event/dom/base/dom-event', function (S, BaseEvent, DomEventUtils, Dom, Special, DomEventObservable, DomEventObject) {
    var BaseUtils = BaseEvent.Utils;

    var undefined = undefined;

    function fixType(cfg, type) {
        var s = Special[type] || {},
            typeFix;

        // in case overwritten by typeFix in special events
        // (mouseenter/leave,focusin/out)
        if (!cfg.originalType && (typeFix = s.typeFix)) {
            // when on mouseenter, it's actually on mouseover,
            // and observers is saved with mouseover!
            cfg.originalType = type;
            type = typeFix;
        }

        return type;
    }

    function addInternal(currentTarget, type, cfg) {
        var domEventObservablesHolder,
            domEventObservable,
            domEventObservables,
            handle;

        cfg = S.merge(cfg);
        type = fixType(cfg, type);

        // 获取事件描述
        domEventObservablesHolder = DomEventObservable.getDomEventObservablesHolder(currentTarget, 1);

        if (!(handle = domEventObservablesHolder.handle)) {
            handle = domEventObservablesHolder.handle = function (event) {
                // 是经过 fire 手动调用而浏览器同步触发导致的,就不要再次触发了,
                // 已经在 fire 中 bubble 过一次了
                // in case after page has unloaded
                var type = event.type,
                    domEventObservable,
                    currentTarget = handle.currentTarget;
                if (DomEventObservable.triggeredEvent == type ||
                    typeof KISSY == 'undefined') {
                    return undefined;
                }
                domEventObservable = DomEventObservable.getDomEventObservable(currentTarget, type);
                if (domEventObservable) {
                    event.currentTarget = currentTarget;
                    event = new DomEventObject(event);
                    return domEventObservable.notify(event);
                }
                return undefined;
            };
            handle.currentTarget = currentTarget;
        }

        if (!(domEventObservables = domEventObservablesHolder.observables)) {
            domEventObservables = domEventObservablesHolder.observables = {};
        }

        //事件 listeners , similar to eventListeners in Dom3 Events
        domEventObservable = domEventObservables[type];

        if (!domEventObservable) {
            domEventObservable = domEventObservables[type] = new DomEventObservable({
                type: type,
                currentTarget: currentTarget
            });

            domEventObservable.setup();
        }

        domEventObservable.on(cfg);

        currentTarget = null;
    }

    function removeInternal(currentTarget, type, cfg) {
        // copy
        cfg = S.merge(cfg);

        var customEvent;

        type = fixType(cfg, type);

        var domEventObservablesHolder = DomEventObservable.getDomEventObservablesHolder(currentTarget),
            domEventObservables = (domEventObservablesHolder || {}).observables;

        if (!domEventObservablesHolder || !domEventObservables) {
            return;
        }

        // remove all types of event
        if (!type) {
            for (type in domEventObservables) {
                domEventObservables[type].detach(cfg);
            }
            return;
        }

        customEvent = domEventObservables[type];

        if (customEvent) {
            customEvent.detach(cfg);
        }
    }

    /**
     * Dom Event Management
     * @private
     * @class KISSY.Event.DomEvent
     */
    var DomEvent={
        /**
         * Adds an event listener.similar to addEventListener in Dom3 Events
         * @param targets KISSY selector
         * @param type {String} The type of event to append.
         * use space to separate multiple event types.
         * @param fn {Function|Object} The event listener or event description object.
         * @param {Function} fn.fn The event listener
         * @param {Function} fn.context The context (this reference) in which the handler function is executed.
         * @param {String|Function} fn.filter filter selector string or function to find right element
         * @param {Boolean} fn.once whether fn will be removed once after it is executed.
         * @param {Object} [context] The context (this reference) in which the handler function is executed.
         */
        on: function (targets, type, fn, context) {
            // data : 附加在回调后面的数据,delegate 检查使用
            // remove 时 data 相等(指向同一对象或者定义了 equals 比较函数)
            targets = Dom.query(targets);

            BaseUtils.batchForType(function (targets, type, fn, context) {
                var cfg = BaseUtils.normalizeParam(type, fn, context), i, t;
                type = cfg.type;
                for (i = targets.length - 1; i >= 0; i--) {
                    t = targets[i];
                    addInternal(t, type, cfg);
                }
            }, 1, targets, type, fn, context);

            return targets;
        },

        /**
         * Detach an event or set of events from an element. similar to removeEventListener in Dom3 Events
         * @param targets KISSY selector
         * @param {String|Boolean} [type] The type of event to remove.
         * use space to separate multiple event types.
         * or
         * whether to remove all events from descendants nodes.
         * @param [fn] {Function|Object} The event listener or event description object.
         * @param {Function} fn.fn The event listener
         * @param {Function} [fn.context] The context (this reference) in which the handler function is executed.
         * @param {String|Function} [fn.filter] filter selector string or function to find right element
         * @param {Boolean} [fn.once] whether fn will be removed once after it is executed.
         * @param {Object} [context] The context (this reference) in which the handler function is executed.
         */
        detach: function (targets, type, fn, context) {

            targets = Dom.query(targets);

            BaseUtils.batchForType(function (targets, singleType, fn, context) {

                var cfg = BaseUtils.normalizeParam(singleType, fn, context),
                    i,
                    j,
                    elChildren,
                    t;

                singleType = cfg.type;

                for (i = targets.length - 1; i >= 0; i--) {
                    t = targets[i];
                    removeInternal(t, singleType, cfg);
                    // deep remove
                    if (cfg.deep && t.getElementsByTagName) {
                        elChildren = t.getElementsByTagName('*');
                        for (j = elChildren.length - 1; j >= 0; j--) {
                            removeInternal(elChildren[j], singleType, cfg);
                        }
                    }
                }

            }, 1, targets, type, fn, context);

            return targets;

        },

        /**
         * Delegate event.
         * @param targets KISSY selector
         * @param {String|Function} filter filter selector string or function to find right element
         * @param {String} [eventType] The type of event to delegate.
         * use space to separate multiple event types.
         * @param {Function} [fn] The event listener.
         * @param {Object} [context] The context (this reference) in which the handler function is executed.
         * 
         */
        delegate: function (targets, eventType, filter, fn, context) {
            return DomEvent.on(targets, eventType, {
                fn: fn,
                context: context,
                filter: filter
            });
        },
        /**
         * undelegate event.
         * @param targets KISSY selector
         * @param {String} [eventType] The type of event to undelegate.
         * use space to separate multiple event types.
         * @param {String|Function} [filter] filter selector string or function to find right element
         * @param {Function} [fn] The event listener.
         * @param {Object} [context] The context (this reference) in which the handler function is executed.
         * 
         */
        undelegate: function (targets, eventType, filter, fn, context) {
            return DomEvent.detach(targets, eventType, {
                fn: fn,
                context: context,
                filter: filter
            });
        },

        /**
         * fire event,simulate bubble in browser. similar to dispatchEvent in Dom3 Events
         * @param targets html nodes
         * 
         * @param {String} eventType event type
         * @param [eventData] additional event data
         * @param {Boolean} [onlyHandlers] for internal usage
         * @return {*} return false if one of custom event 's observers (include bubbled) else
         * return last value of custom event 's observers (include bubbled) 's return value.
         */
        fire: function (targets, eventType, eventData, onlyHandlers/*internal usage*/) {
            var ret = undefined;
            // custom event firing moved to target.js
            eventData = eventData || {};

            /**
             * identify event as fired manually
             * @ignore
             */
            eventData.synthetic = 1;

            BaseUtils.splitAndRun(eventType, function (eventType) {

                var r,
                    i,
                    target,
                    domEventObservable;

                BaseUtils.fillGroupsForEvent(eventType, eventData);

                // mouseenter
                eventType = eventData.type;
                var s = Special[eventType];

                var originalType = eventType;

                // where observers lie
                // mouseenter observer lies on mouseover
                if (s && s.typeFix) {
                    // mousemove
                    originalType = s.typeFix;
                }

                targets = Dom.query(targets);

                for (i = targets.length - 1; i >= 0; i--) {
                    target = targets[i];
                    domEventObservable = DomEventObservable.getDomEventObservable(target, originalType);
                    // bubbling
                    // html dom event defaults to bubble
                    if (!onlyHandlers && !domEventObservable) {
                        domEventObservable = new DomEventObservable({
                            type: originalType,
                            currentTarget: target
                        });
                    }
                    if (domEventObservable) {
                        r = domEventObservable.fire(eventData, onlyHandlers);
                        if (ret !== false && r !== undefined) {
                            ret = r;
                        }
                    }
                }
            });

            return ret;
        },

        /**
         * same with fire but:
         * - does not cause default behavior to occur.
         * - does not bubble up the Dom hierarchy.
         * @param targets html nodes
         * 
         * @param {String} eventType event type
         * @param [eventData] additional event data
         * @return {*} return false if one of custom event 's observers (include bubbled) else
         * return last value of custom event 's observers (include bubbled) 's return value.
         */
        fireHandler: function (targets, eventType, eventData) {
            return DomEvent.fire(targets, eventType, eventData, 1);
        },


        /**
         * copy event from src to dest
         * 
         * @param {HTMLElement} src srcElement
         * @param {HTMLElement} dest destElement
         * @private
         */
        clone: function (src, dest) {
            var domEventObservablesHolder,
                domEventObservables;
            if (!(domEventObservablesHolder = DomEventObservable.getDomEventObservablesHolder(src))) {
                return;
            }
            var srcData = DomEventUtils.data(src);
            if (srcData && srcData === DomEventUtils.data(dest)) {
                // remove event data (but without dom attached listener)
                // which is copied from above Dom.data
                DomEventUtils.removeData(dest);
            }
            domEventObservables = domEventObservablesHolder.observables;
            S.each(domEventObservables, function (customEvent, type) {
                S.each(customEvent.observers, function (observer) {
                    // context undefined
                    // 不能 this 写死在 handlers 中
                    // 否则不能保证 clone 时的 this
                    addInternal(dest, type, observer);
                });
            });
        }
    };

    return DomEvent;
}, {
    requires: ['event/base',
        './utils',
        'dom', './special', './observable', './object']
});
/*
 2012-02-12 yiminghe@gmail.com note:
 - 普通 remove() 不管 filter 都会查,如果 fn context 相等就移除
 - undelegate() filter 为 '',那么去除所有委托绑定的 handler
 */