function Bus() {
  this._events = Object.create(null);
}

function toArray(list, start) {
  start = start || 0;
  var i = list.length - start;
  var ret = new Array(i);

  while (i--) {
    ret[i] = list[i + start];
  }

  return ret;
}

var hookRE = /^hook:/;

Bus.prototype.$on = function (event, fn) {
  var vm = this;

  if (Array.isArray(event)) {
    for (var i = 0, l = event.length; i < l; i++) {
      vm.$on(event[i], fn);
    }
  } else {
    (vm._events[event] || (vm._events[event] = [])).push(fn); // optimize hook:event cost by using a boolean flag marked at registration
    // instead of a hash lookup

    if (hookRE.test(event)) {
      vm._hasHookEvent = true;
    }
  }

  return vm;
};

Bus.prototype.$once = function (event, fn) {
  var vm = this;

  function on() {
    vm.$off(event, on);
    fn.apply(vm, arguments);
  }

  on.fn = fn;
  vm.$on(event, on);
  return vm;
};

Bus.prototype.$off = function (event, fn) {
  var vm = this; // all

  if (!arguments.length) {
    vm._events = Object.create(null);
    return vm;
  } // array of events


  if (Array.isArray(event)) {
    for (var i = 0, l = event.length; i < l; i++) {
      vm.$off(event[i], fn);
    }

    return vm;
  } // specific event


  var cbs = vm._events[event];

  if (!cbs) {
    return vm;
  }

  if (!fn) {
    vm._events[event] = null;
    return vm;
  }

  if (fn) {
    // specific handler
    var cb;
    var i$1 = cbs.length;

    while (i$1--) {
      cb = cbs[i$1];

      if (cb === fn || cb.fn === fn) {
        cbs.splice(i$1, 1);
        break;
      }
    }
  }

  return vm;
};

Bus.prototype.$emit = function (event) {
  var vm = this;
  var cbs = vm._events[event];

  if (cbs) {
    cbs = cbs.length > 1 ? toArray(cbs) : cbs;
    var args = toArray(arguments, 1);

    for (var i = 0, l = cbs.length; i < l; i++) {
      try {
        cbs[i].apply(vm, args);
      } catch (e) {
        console.error(e, vm, 'event handler for "' + event + '"');
      }
    }
  }

  return vm;
};

export default new Bus();