/**
 * @abstract
 * @class qrenderer.event.Eventful
 * 
 * Provide event system for the classes that do not support events, the implementation here
 * is similar to W3C DOM events, all the classes need event support can mixin the functions here.
 * 
 * 
 * 为不支持事件机制的类提供事件支持,基本机制类似 W3C DOM 事件,需要事件机制的类可以 mixin 此类中的工具函数。
 * 
 * @author @Kener-林峰 <kener.linfeng@gmail.com>
 * @docauthor 大漠穷秋 <damoqiongqiu@126.com>
 */

/**
 * @method constructor Eventful
 * @param {Object} [eventProcessor] 
 * The object eventProcessor is the scope when `eventProcessor.xxx` called. 
 * 
 * 
 * 事件处理者,也就是当前事件处理函数执行时的作用域。
 * @param {Function} [eventProcessor.normalizeQuery]
 *        param: {String|Object} Raw query.
 *        return: {String|Object} Normalized query.
 * @param {Function} [eventProcessor.filter] 
 * Event will be dispatched only if it returns `true`.
 * 
 * 
 * 当此方法返回 `true` 的时候事件才会派发。
 *        param: {String} eventType
 *        param: {String|Object} query
 *        return: {Boolean}
 * @param {Function} [eventProcessor.afterTrigger] 
 * Called after all handlers called.
 * 
 * 
 * 此方法会在事件处理函数被调用之后执行。
 *        param: {String} eventType
 * @param {Function} [eventProcessor.afterListenerChanged] 
 * Called when any listener added or removed.
 * 
 * 
 * 此方法在添加或者删除事件监听器的时候被调用。
 *        param: {String} eventType
 */
var Eventful = function Eventful(eventProcessor) {
  this._$handlers = {};
  this._$eventProcessor = eventProcessor;
  this._$suspends = new Set();
};

Eventful.prototype = {
  constructor: Eventful,
  clearAll: function clearAll() {
    this._$handlers = {};
    this._$eventProcessor = null;
    this._$suspends = new Set();
  },

  /**
   * @method
   * The handler can only be triggered once, then removed.
   *
   * 
   * 处理函数只会被调用一次,然后就会删除。
   * @param {String} event 
   * The event name.
   * 
   * 
   * 事件名。
   * @param {String|Object} [query] 
   * Condition used on event filter.
   * 
   * 
   * 事件过滤条件。
   * @param {Function} handler 
   * The event handler.
   * 
   * 
   * 事件处理函数。
   * @param {Object} context
   * The context in which the handler is executed.
   * 
   * 
   * 事件处理函数执行的上下文。
   */
  one: function one(event, query, handler, context) {
    return _on(this, event, query, handler, context, true);
  },

  /**
   * @method
   * Bind a event handler.
   *
   * 
   * 绑定事件处理函数。
   * 
   * @param {String} event 
   * The event name.
   * 
   * 
   * 事件名。
   * @param {String|Object} [query] 
   * Condition used on event filter.
   * 
   * 
   * 事件过滤条件。
   * @param {Function} handler 
   * The event handler.
   * 
   * 
   * 事件处理函数。
   * @param {Object} context
   * The context in which the handler is executed.
   * 
   * 
   * 事件处理函数执行的上下文。
   */
  on: function on(event, query, handler, context) {
    return _on(this, event, query, handler, context, false);
  },

  /**
   * @method
   * Whether any event handler has bound.
   *
   * 
   * 是否绑定了事件处理函数。
   * @param  {String}  event
   * @return {Boolean}
   */
  isSilent: function isSilent(event) {
    var _h = this._$handlers;
    return !_h[event] || !_h[event].length;
  },

  /**
   * @method
   * Unbind a event handler.
   *
   * 
   * 解除事件处理函数。
   * @param {String} [event] 
   * The event name. If this parameter is null, "off" all listeners.
   * 
   * 
   * 事件名,如果此参数为 null,所有事件监听器都会被删除。
   * @param {Function} [handler] 
   * The event handler. If this parameter is null, "off" all listeners.
   * 
   * 
   * 事件处理函数,如果此参数为 null,所有事件监听器都会被删除。
   */
  off: function off(event, handler, context) {
    var _h = this._$handlers;

    if (!event) {
      this._$handlers = {};
      return this;
    }

    if (handler) {
      if (_h[event]) {
        var newList = [];

        for (var i = 0, l = _h[event].length; i < l; i++) {
          if (_h[event][i].ctx !== context && _h[event][i].h !== handler) {
            newList.push(_h[event][i]);
          }
        }

        _h[event] = newList;
      }

      if (_h[event] && _h[event].length === 0) {
        delete _h[event];
      }
    } else {
      delete _h[event];
    }

    callListenerChanged(this, event);
    return this;
  },

  /**
   * @method
   * Trigger an event.
   *
   * 
   * 触发一个事件。
   * @param {String} eventName The event name.
   */
  trigger: function trigger(eventName) {
    if (this._$suspends.has(eventName)) {
      return;
    }

    var _h = this._$handlers[eventName];
    var eventProcessor = this._$eventProcessor;

    if (_h) {
      var args = arguments;
      var argLen = args.length;

      if (argLen > 3) {
        args = Array.prototype.slice.call(args, 1);
      }

      var len = _h.length;

      for (var i = 0; i < len;) {
        var hItem = _h[i];

        if (eventProcessor && eventProcessor.filter && hItem.query != null && !eventProcessor.filter(eventName, hItem.query)) {
          i++;
          continue;
        } // Optimize advise from backbone


        switch (argLen) {
          case 1:
            hItem.h.call(hItem.ctx);
            break;

          case 2:
            hItem.h.call(hItem.ctx, args[1]);
            break;

          case 3:
            hItem.h.call(hItem.ctx, args[1], args[2]);
            break;

          case 4:
            hItem.h.call(hItem.ctx, args[1], args[2], args[3]);
            break;

          case 5:
            hItem.h.call(hItem.ctx, args[1], args[2], args[3], args[4]);
            break;

          default:
            hItem.h.apply(hItem.ctx, args);
            break;
        }

        if (hItem.one) {
          _h.splice(i, 1);

          len--;
        } else {
          i++;
        }
      }
    }

    eventProcessor && eventProcessor.afterTrigger && eventProcessor.afterTrigger(eventName);
    return this;
  },

  /**
   * @method suspend
   * Suspend an event. The suspended event will not be triggered. During the touch and mouse interaction, we often need to 
   * suspend some events to provent them been triggered by accident.
   * 
   * 
   * 被挂起的事件不会触发。在鼠标和触摸屏交互的过程中,经常需要把某个事件临时挂起以避免误触。
   * @param {String} eventName 
   */
  suspend: function suspend(eventName) {
    this._$suspends.add(eventName);
  },

  /**
   * @method resume
   * Resume an event.
   * 
   * 
   * 恢复触发。
   * @param {String} eventName 
   */
  resume: function resume(eventName) {
    this._$suspends["delete"](eventName);
  },

  /**
   * @method triggerWithContext
   * Dispatch a event with context, which is specified sa the last parameter of the trigger() method.
   *
   * 
   * 在指定的上下文中触发事件,上下文可以在 trigger 函数的最后一个参数进行指定。
   * @param {String} eventName The event name.
   */
  triggerWithContext: function triggerWithContext(eventName) {
    var _h = this._$handlers[eventName];
    var eventProcessor = this._$eventProcessor;

    if (_h) {
      var args = arguments;
      var argLen = args.length;

      if (argLen > 4) {
        args = Array.prototype.slice.call(args, 1, args.length - 1);
      }

      var ctx = args[args.length - 1];
      var len = _h.length;

      for (var i = 0; i < len;) {
        var hItem = _h[i];

        if (eventProcessor && eventProcessor.filter && hItem.query != null && !eventProcessor.filter(eventName, hItem.query)) {
          i++;
          continue;
        } // Optimize advise from backbone


        switch (argLen) {
          case 1:
            hItem.h.call(ctx);
            break;

          case 2:
            hItem.h.call(ctx, args[1]);
            break;

          case 3:
            hItem.h.call(ctx, args[1], args[2]);
            break;

          case 4:
            hItem.h.call(ctx, args[1], args[2], args[3]);
            break;

          case 5:
            hItem.h.call(ctx, args[1], args[2], args[3], args[4]);
            break;

          default:
            hItem.h.apply(ctx, args);
            break;
        }

        if (hItem.one) {
          _h.splice(i, 1);

          len--;
        } else {
          i++;
        }
      }
    }

    eventProcessor && eventProcessor.afterTrigger && eventProcessor.afterTrigger(eventName);
    return this;
  }
};
/**
 * @private
 * @method callListenerChanged
 * @param {Element} eventful 
 * @param {String} eventType 
 */

function callListenerChanged(eventful, eventType) {
  var eventProcessor = eventful._$eventProcessor;

  if (eventProcessor && eventProcessor.afterListenerChanged) {
    eventProcessor.afterListenerChanged(eventType);
  }
}
/**
 * @private
 * @method normalizeQuery
 * @param {*} host 
 * @param {*} query 
 */


function normalizeQuery(host, query) {
  var eventProcessor = host._$eventProcessor;

  if (query != null && eventProcessor && eventProcessor.normalizeQuery) {
    query = eventProcessor.normalizeQuery(query);
  }

  return query;
}
/**
 * @private
 * @method on
 * @param {Element} eventful 
 * @param {Event} event 
 * @param {*} query 
 * @param {Function} handler 
 * @param {Object} context 
 * @param {Boolean} isOnce 
 */


function _on(eventful, event, query, handler, context, isOnce) {
  var _h = eventful._$handlers;

  if (typeof query === 'function') {
    context = handler;
    handler = query;
    query = null;
  }

  if (!handler || !event) {
    return eventful;
  }

  query = normalizeQuery(eventful, query);

  if (!_h[event]) {
    _h[event] = [];
  }

  for (var i = 0; i < _h[event].length; i++) {
    if (_h[event][i].ctx === context && _h[event][i].h === handler) {
      return eventful;
    }
  }

  var wrap = {
    h: handler,
    one: isOnce,
    query: query,
    ctx: context || eventful,
    // FIXME
    // Do not publish this feature util it is proved that it makes sense.
    callAtLast: handler.qrEventfulCallAtLast
  };
  var lastIndex = _h[event].length - 1;
  var lastWrap = _h[event][lastIndex];
  lastWrap && lastWrap.callAtLast ? _h[event].splice(lastIndex, 0, wrap) : _h[event].push(wrap);
  callListenerChanged(eventful, event);
  return eventful;
}

var _default = Eventful;
module.exports = _default;