text: 'use strict';
  
  const core = require('./index.js');
  const EventEmitter = require('events').EventEmitter;
  
  function makeDoneCb(resolve, reject, localErr) {
    return function(err, rows, fields) {
      if (err) {
        localErr.message = err.message;
        localErr.code = err.code;
        localErr.errno = err.errno;
        localErr.sqlState = err.sqlState;
        localErr.sqlMessage = err.sqlMessage;
        reject(localErr);
      } else {
        resolve([rows, fields]);
      }
    };
  }
  
  function inheritEvents(source, target, events) {
    const listeners = {};
    target
      .on('newListener', eventName => {
        if (events.indexOf(eventName) >= 0 && !target.listenerCount(eventName)) {
          source.on(
            eventName,
            (listeners[eventName] = function() {
              const args = [].slice.call(arguments);
              args.unshift(eventName);
  
              target.emit.apply(target, args);
            })
          );
        }
      })
      .on('removeListener', eventName => {
        if (events.indexOf(eventName) >= 0 && !target.listenerCount(eventName)) {
          source.removeListener(eventName, listeners[eventName]);
          delete listeners[eventName];
        }
      });
  }
  
  class PromisePreparedStatementInfo {
    constructor(statement, promiseImpl) {
      this.statement = statement;
      this.Promise = promiseImpl;
    }
  
    execute(parameters) {
      const s = this.statement;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        if (parameters) {
          s.execute(parameters, done);
        } else {
          s.execute(done);
        }
      });
    }
  
    close() {
      return new this.Promise(resolve => {
        this.statement.close();
        resolve();
      });
    }
  }
  
  class PromiseConnection extends EventEmitter {
    constructor(connection, promiseImpl) {
      super();
      this.connection = connection;
      this.Promise = promiseImpl || global.Promise;
      inheritEvents(connection, this, [
        'error',
        'drain',
        'connect',
        'end',
        'enqueue'
      ]);
    }
  
    release() {
      this.connection.release();
    }
  
    query(query, params) {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        if (params) {
          c.query(query, params, done);
        } else {
          c.query(query, done);
        }
      });
    }
  
    execute(query, params) {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        if (params) {
          c.execute(query, params, done);
        } else {
          c.execute(query, done);
        }
      });
    }
  
    end() {
      return new this.Promise(resolve => {
        this.connection.end(resolve);
      });
    }
  
    beginTransaction() {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        c.beginTransaction(done);
      });
    }
  
    commit() {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        c.commit(done);
      });
    }
  
    rollback() {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        c.rollback(done);
      });
    }
  
    ping() {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        c.ping(done);
      });
    }
  
    connect() {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        c.connect((err, param) => {
          if (err) {
            localErr.message = err.message;
            localErr.code = err.code;
            localErr.errno = err.errno;
            localErr.sqlState = err.sqlState;
            localErr.sqlMessage = err.sqlMessage;
            reject(localErr);
          } else {
            resolve(param);
          }
        });
      });
    }
  
    prepare(options) {
      const c = this.connection;
      const promiseImpl = this.Promise;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        c.prepare(options, (err, statement) => {
          if (err) {
            localErr.message = err.message;
            localErr.code = err.code;
            localErr.errno = err.errno;
            localErr.sqlState = err.sqlState;
            localErr.sqlMessage = err.sqlMessage;
            reject(localErr);
          } else {
            const wrappedStatement = new PromisePreparedStatementInfo(
              statement,
              promiseImpl
            );
            resolve(wrappedStatement);
          }
        });
      });
    }
  
    changeUser(options) {
      const c = this.connection;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        c.changeUser(options, err => {
          if (err) {
            localErr.message = err.message;
            localErr.code = err.code;
            localErr.errno = err.errno;
            localErr.sqlState = err.sqlState;
            localErr.sqlMessage = err.sqlMessage;
            reject(localErr);
          } else {
            resolve();
          }
        });
      });
    }
  
    get config() {
      return this.connection.config;
    }
  
    get threadId() {
      return this.connection.threadId;
    }
  }
  
  function createConnection(opts) {
    const coreConnection = core.createConnection(opts);
    const createConnectionErr = new Error();
    const Promise = opts.Promise || global.Promise;
    if (!Promise) {
      throw new Error(
        'no Promise implementation available.' +
          'Use promise-enabled node version or pass userland Promise' +
          " implementation as parameter, for example: { Promise: require('bluebird') }"
      );
    }
    return new PromiseConnection(coreConnection, Promise);
  }
  
  // note: the callback of "changeUser" is not called on success
  // hence there is no possibility to call "resolve"
  
  // patching PromiseConnection
  // create facade functions for prototype functions on "Connection" that are not yet
  // implemented with PromiseConnection
  
  // proxy synchronous functions only
  (function(functionsToWrap) {
    for (let i = 0; functionsToWrap && i < functionsToWrap.length; i++) {
      const func = functionsToWrap[i];
  
      if (
        typeof core.Connection.prototype[func] === 'function' &&
        PromiseConnection.prototype[func] === undefined
      ) {
        PromiseConnection.prototype[func] = (function factory(funcName) {
          return function() {
            return core.Connection.prototype[funcName].apply(
              this.connection,
              arguments
            );
          };
        })(func);
      }
    }
  })([
    // synchronous functions
    'close',
    'createBinlogStream',
    'destroy',
    'escape',
    'escapeId',
    'format',
    'pause',
    'pipe',
    'resume',
    'unprepare'
  ]);
  
  class PromisePoolConnection extends PromiseConnection {
    constructor(connection, promiseImpl) {
      super(connection, promiseImpl);
    }
  
    destroy() {
      return core.PoolConnection.prototype.destroy.apply(
        this.connection,
        arguments
      );
    }
  }
  
  class PromisePool extends EventEmitter {
    constructor(pool, Promise) {
      super();
      this.pool = pool;
      this.Promise = Promise || global.Promise;
      inheritEvents(pool, this, ['acquire', 'connection', 'enqueue', 'release']);
    }
  
    getConnection() {
      const corePool = this.pool;
      return new this.Promise((resolve, reject) => {
        corePool.getConnection((err, coreConnection) => {
          if (err) {
            reject(err);
          } else {
            resolve(new PromisePoolConnection(coreConnection, this.Promise));
          }
        });
      });
    }
  
    query(sql, args) {
      const corePool = this.pool;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        const done = makeDoneCb(resolve, reject, localErr);
        if (args) {
          corePool.query(sql, args, done);
        } else {
          corePool.query(sql, done);
        }
      });
    }
  
    execute(sql, values) {
      const corePool = this.pool;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        corePool.execute(sql, values, makeDoneCb(resolve, reject, localErr));
      });
    }
  
    end() {
      const corePool = this.pool;
      const localErr = new Error();
      return new this.Promise((resolve, reject) => {
        corePool.end(err => {
          if (err) {
            localErr.message = err.message;
            localErr.code = err.code;
            localErr.errno = err.errno;
            localErr.sqlState = err.sqlState;
            localErr.sqlMessage = err.sqlMessage;
            reject(localErr);
          } else {
            resolve();
          }
        });
      });
    }
  }
  
  function createPool(opts) {
    const corePool = core.createPool(opts);
    const Promise = opts.Promise || global.Promise;
    if (!Promise) {
      throw new Error(
        'no Promise implementation available.' +
          'Use promise-enabled node version or pass userland Promise' +
          " implementation as parameter, for example: { Promise: require('bluebird') }"
      );
    }
  
    return new PromisePool(corePool, Promise);
  }
  
  (function(functionsToWrap) {
    for (let i = 0; functionsToWrap && i < functionsToWrap.length; i++) {
      const func = functionsToWrap[i];
  
      if (
        typeof core.Pool.prototype[func] === 'function' &&
        PromisePool.prototype[func] === undefined
      ) {
        PromisePool.prototype[func] = (function factory(funcName) {
          return function() {
            return core.Pool.prototype[funcName].apply(this.pool, arguments);
          };
        })(func);
      }
    }
  })([
    // synchronous functions
    'escape',
    'escapeId',
    'format'
  ]);
  
  exports.createConnection = createConnection;
  exports.createPool = createPool;
  exports.escape = core.escape;
  exports.escapeId = core.escapeId;
  exports.format = core.format;
  exports.raw = core.raw;
  exports.PromisePool = PromisePool;
  exports.PromiseConnection = PromiseConnection;
  exports.PromisePoolConnection = PromisePoolConnection;
  