text: 'use strict';
  
  const co = require('co');
  const util = require('util');
  const is = require('is-type-of');
  const assert = require('assert');
  const awaitEvent = require('await-event');
  const awaitFirst = require('await-first');
  const EventEmitter = require('events').EventEmitter;
  const CLOSE_PROMISE = Symbol('base#closePromise');
  
  class Base extends EventEmitter {
    constructor(options) {
      super();
  
      if (options && options.initMethod) {
        assert(is.function(this[options.initMethod]),
          `[sdk-base] this.${options.initMethod} should be a function.`);
  
        process.nextTick(() => {
          if (is.generatorFunction(this[options.initMethod])) {
            this[options.initMethod] = co.wrap(this[options.initMethod]);
          }
          const ret = this[options.initMethod]();
          assert(is.promise(ret), `[sdk-base] this.${options.initMethod} should return either a promise or a generator`);
          ret.then(() => this.ready(true))
            .catch(err => this.ready(err));
        });
      }
      this.options = options || {};
      this._ready = false;
      this._readyError = null;
      this._readyCallbacks = [];
      this._closed = false;
  
      // support `yield this.await('event')`
      this.await = awaitEvent;
      this.awaitFirst = awaitFirst;
  
      this.on('error', err => { this._defaultErrorHandler(err); });
    }
  
    _wrapListener(eventName, listener) {
      if (is.generatorFunction(listener)) {
        assert(eventName !== 'error', '[sdk-base] `error` event should not have a generator listener.');
  
        const newListener = (...args) => {
          co(function* () {
            yield listener(...args);
          }).catch(err => {
            err.name = 'EventListenerProcessError';
            this.emit('error', err);
          });
        };
        newListener.original = listener;
        return newListener;
      }
      return listener;
    }
  
    addListener(eventName, listener) {
      return super.addListener(eventName, this._wrapListener(eventName, listener));
    }
  
    on(eventName, listener) {
      return super.on(eventName, this._wrapListener(eventName, listener));
    }
  
    once(eventName, listener) {
      return super.once(eventName, this._wrapListener(eventName, listener));
    }
  
    prependListener(eventName, listener) {
      return super.prependListener(eventName, this._wrapListener(eventName, listener));
    }
  
    prependOnceListener(eventName, listener) {
      return super.prependOnceListener(eventName, this._wrapListener(eventName, listener));
    }
  
    removeListener(eventName, listener) {
      let target = listener;
      if (is.generatorFunction(listener)) {
        const listeners = this.listeners(eventName);
        for (const fn of listeners) {
          if (fn.original === listener) {
            target = fn;
            break;
          }
        }
      }
      return super.removeListener(eventName, target);
    }
  
    /**
     * detect sdk start ready or not
     * @return {Boolean} ready status
     */
    get isReady() {
      return this._ready;
    }
  
    /**
     * set ready state or onready callback
     *
     * @param {Boolean|Error|Function} flagOrFunction - ready state or callback function
     * @return {void|Promise} ready promise
     */
    ready(flagOrFunction) {
      if (arguments.length === 0) {
        // return a promise
        // support `this.ready().then(onready);` and `yield this.ready()`;
        return new Promise((resolve, reject) => {
          if (this._ready) {
            return resolve();
          } else if (this._readyError) {
            return reject(this._readyError);
          }
          this._readyCallbacks.push(err => {
            if (err) {
              reject(err);
            } else {
              resolve();
            }
          });
        });
      } else if (is.function(flagOrFunction)) {
        this._readyCallbacks.push(flagOrFunction);
      } else if (flagOrFunction instanceof Error) {
        this._ready = false;
        this._readyError = flagOrFunction;
        if (!this._readyCallbacks.length) {
          this.emit('error', flagOrFunction);
        }
      } else {
        this._ready = flagOrFunction;
      }
  
      if (this._ready || this._readyError) {
        this._readyCallbacks.splice(0, Infinity).forEach(callback => {
          process.nextTick(() => {
            callback(this._readyError);
          });
        });
      }
    }
  
    _defaultErrorHandler(err) {
      if (this.listeners('error').length > 1) {
        // ignore defaultErrorHandler
        return;
      }
      console.error('\n[%s][pid: %s][%s] %s: %s \nError Stack:\n  %s',
        Date(), process.pid, this.constructor.name, err.name,
        err.message, err.stack);
  
      // try to show addition property on the error object
      // e.g.: `err.data = {url: '/foo'};`
      const additions = [];
      for (const key in err) {
        if (key === 'name' || key === 'message') {
          continue;
        }
  
        additions.push(util.format('  %s: %j', key, err[key]));
      }
      if (additions.length) {
        console.error('Error Additions:\n%s', additions.join('\n'));
      }
      console.error();
    }
  
    close() {
      if (this._closed) {
        return Promise.resolve();
      }
      if (this[CLOSE_PROMISE]) {
        return this[CLOSE_PROMISE];
      }
      if (!this._close) {
        this._closed = true;
        return Promise.resolve();
      }
      let closeFunc = this._close;
      if (is.generatorFunction(closeFunc)) {
        closeFunc = co.wrap(closeFunc);
      }
      this[CLOSE_PROMISE] = closeFunc.apply(this);
      assert(is.promise(this[CLOSE_PROMISE]), '[sdk-base] this._close should return either a promise or a generator');
      return this[CLOSE_PROMISE]
        .then(() => {
          this._closed = true;
        })
        .catch(err => {
          this._closed = true;
          this.emit('error', err);
        });
    }
  }
  
  module.exports = Base;
  // support es module
  module.exports.default = Base;
  