/**
 * Initialize a new `Emitter`.
 *
 * @api public
 */

function Emitter (obj) {
  if (obj) return mixin(obj)
};

/**
 * Mixin the emitter properties.
 *
 * @param {Object} obj
 * @return {Object}
 * @api private
 */

function mixin (obj) {
  for (var key in Emitter.prototype) {
    obj[key] = Emitter.prototype[key]
  }
  return obj
}

/**
 * Listen on the given `event` with `fn`.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.on =
Emitter.prototype.addEventListener = function (event, fn) {
  this._callbacks = this._callbacks || {};
  (this._callbacks['$' + event] = this._callbacks['$' + event] || [])
    .push(fn)
  return this
}

/**
 * Adds an `event` listener that will be invoked a single
 * time then automatically removed.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.once = function (event, fn) {
  function on () {
    this.off(event, on)
    fn.apply(this, arguments)
  }

  on.fn = fn
  this.on(event, on)
  return this
}

/**
 * Remove the given callback for `event` or all
 * registered callbacks.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {Emitter}
 * @api public
 */

Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function (event, fn) {
  this._callbacks = this._callbacks || {}

  // all
  if (arguments.length === 0) {
    this._callbacks = {}
    return this
  }

  // specific event
  var callbacks = this._callbacks['$' + event]
  if (!callbacks) return this

  // remove all handlers
  if (arguments.length === 1) {
    delete this._callbacks['$' + event]
    return this
  }

  // remove specific handler
  var cb
  for (var i = 0; i < callbacks.length; i++) {
    cb = callbacks[i]
    if (cb === fn || cb.fn === fn) {
      callbacks.splice(i, 1)
      break
    }
  }
  return this
}

/**
 * Emit `event` with the given args.
 *
 * @param {String} event
 * @param {Mixed} ...
 * @return {Emitter}
 */

Emitter.prototype.emit = function (event) {
  this._callbacks = this._callbacks || {}
  var args = [].slice.call(arguments, 1)

  var callbacks = this._callbacks['$' + event]

  if (callbacks) {
    callbacks = callbacks.slice(0)
    for (var i = 0, len = callbacks.length; i < len; ++i) {
      callbacks[i].apply(this, args)
    }
  }

  return this
}

/**
 * Return array of callbacks for `event`.
 *
 * @param {String} event
 * @return {Array}
 * @api public
 */

Emitter.prototype.listeners = function (event) {
  this._callbacks = this._callbacks || {}
  return this._callbacks['$' + event] || []
}

/**
 * Check if this emitter has `event` handlers.
 *
 * @param {String} event
 * @return {Boolean}
 * @api public
 */

Emitter.prototype.hasListeners = function (event) {
  return !!this.listeners(event).length
}

// function bind (obj, fn) {
//   if (typeof fn === 'string') fn = obj[fn]
//   if (typeof fn !== 'function') throw new Error('bind() requires a function')
//   var args = [].slice.call(arguments, 2)
//   return function () {
//     return fn.apply(obj, args.concat([].slice.call(arguments)))
//   }
// };

/**
 * postMessager管理器
 * @param {String} target 父级源地址
 * @param {Object} opts postMessage配置
 *
 */
function PostMessager (target = '*', opts) {
  if (!(this instanceof PostMessager)) {
    return new PostMessager(target, opts)
  }

  if (target && (typeof target === 'object')) {
    opts = target
    target = undefined
  }

  Emitter(this)

  opts = opts || {}

  opts.path = opts.path || '/'

  this.opts = opts
  this.target = target
  this.requestQueue = {} // 请求队列

  window.addEventListener('message', (event) => {
    this.msgHandler(event)
  }, false)
}

PostMessager.prototype.send = function (request, callback) {
  request.reqId = new Date().getTime()

  window.parent.postMessage(request, this.target)

  return new Promise((resolve, reject) => {
    this.on(`res_${request.reqId}`, (obj) => {
      if (obj && obj.data && obj.data.response) {
        resolve(obj.data.response)
      } else {
        reject(obj.data.error)
      }

      callback && callback(obj)
    })
  })
}

PostMessager.prototype.msgHandler = function (event) {
  let resId = event.data.resId
  if (resId) {
    this.emit(`res_${resId}`, event)
  } else {
    this.opts['callback'] && this.opts['callback'](event.data)
  }
}

export default PostMessager
