text: 'use strict'
  
  var net = require('net')
    , tls = require('tls')
    , http = require('http')
    , https = require('https')
    , events = require('events')
    , assert = require('assert')
    , util = require('util')
    , Buffer = require('safe-buffer').Buffer
    ;
  
  exports.httpOverHttp = httpOverHttp
  exports.httpsOverHttp = httpsOverHttp
  exports.httpOverHttps = httpOverHttps
  exports.httpsOverHttps = httpsOverHttps
  
  
  function httpOverHttp(options) {
    var agent = new TunnelingAgent(options)
    agent.request = http.request
    return agent
  }
  
  function httpsOverHttp(options) {
    var agent = new TunnelingAgent(options)
    agent.request = http.request
    agent.createSocket = createSecureSocket
    agent.defaultPort = 443
    return agent
  }
  
  function httpOverHttps(options) {
    var agent = new TunnelingAgent(options)
    agent.request = https.request
    return agent
  }
  
  function httpsOverHttps(options) {
    var agent = new TunnelingAgent(options)
    agent.request = https.request
    agent.createSocket = createSecureSocket
    agent.defaultPort = 443
    return agent
  }
  
  
  function TunnelingAgent(options) {
    var self = this
    self.options = options || {}
    self.proxyOptions = self.options.proxy || {}
    self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets
    self.requests = []
    self.sockets = []
  
    self.on('free', function onFree(socket, host, port) {
      for (var i = 0, len = self.requests.length; i < len; ++i) {
        var pending = self.requests[i]
        if (pending.host === host && pending.port === port) {
          // Detect the request to connect same origin server,
          // reuse the connection.
          self.requests.splice(i, 1)
          pending.request.onSocket(socket)
          return
        }
      }
      socket.destroy()
      self.removeSocket(socket)
    })
  }
  util.inherits(TunnelingAgent, events.EventEmitter)
  
  TunnelingAgent.prototype.addRequest = function addRequest(req, options) {
    var self = this
  
     // Legacy API: addRequest(req, host, port, path)
    if (typeof options === 'string') {
      options = {
        host: options,
        port: arguments[2],
        path: arguments[3]
      };
    }
  
    if (self.sockets.length >= this.maxSockets) {
      // We are over limit so we'll add it to the queue.
      self.requests.push({host: options.host, port: options.port, request: req})
      return
    }
  
    // If we are under maxSockets create a new one.
    self.createConnection({host: options.host, port: options.port, request: req})
  }
  
  TunnelingAgent.prototype.createConnection = function createConnection(pending) {
    var self = this
  
    self.createSocket(pending, function(socket) {
      socket.on('free', onFree)
      socket.on('close', onCloseOrRemove)
      socket.on('agentRemove', onCloseOrRemove)
      pending.request.onSocket(socket)
  
      function onFree() {
        self.emit('free', socket, pending.host, pending.port)
      }
  
      function onCloseOrRemove(err) {
        self.removeSocket(socket)
        socket.removeListener('free', onFree)
        socket.removeListener('close', onCloseOrRemove)
        socket.removeListener('agentRemove', onCloseOrRemove)
      }
    })
  }
  
  TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
    var self = this
    var placeholder = {}
    self.sockets.push(placeholder)
  
    var connectOptions = mergeOptions({}, self.proxyOptions,
      { method: 'CONNECT'
      , path: options.host + ':' + options.port
      , agent: false
      }
    )
    if (connectOptions.proxyAuth) {
      connectOptions.headers = connectOptions.headers || {}
      connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
          Buffer.from(connectOptions.proxyAuth).toString('base64')
    }
  
    debug('making CONNECT request')
    var connectReq = self.request(connectOptions)
    connectReq.useChunkedEncodingByDefault = false // for v0.6
    connectReq.once('response', onResponse) // for v0.6
    connectReq.once('upgrade', onUpgrade)   // for v0.6
    connectReq.once('connect', onConnect)   // for v0.7 or later
    connectReq.once('error', onError)
    connectReq.end()
  
    function onResponse(res) {
      // Very hacky. This is necessary to avoid http-parser leaks.
      res.upgrade = true
    }
  
    function onUpgrade(res, socket, head) {
      // Hacky.
      process.nextTick(function() {
        onConnect(res, socket, head)
      })
    }
  
    function onConnect(res, socket, head) {
      connectReq.removeAllListeners()
      socket.removeAllListeners()
  
      if (res.statusCode === 200) {
        assert.equal(head.length, 0)
        debug('tunneling connection has established')
        self.sockets[self.sockets.indexOf(placeholder)] = socket
        cb(socket)
      } else {
        debug('tunneling socket could not be established, statusCode=%d', res.statusCode)
        var error = new Error('tunneling socket could not be established, ' + 'statusCode=' + res.statusCode)
        error.code = 'ECONNRESET'
        options.request.emit('error', error)
        socket.destroy();
        self.removeSocket(placeholder)
      }
    }
  
    function onError(cause) {
      connectReq.removeAllListeners()
  
      debug('tunneling socket could not be established, cause=%s\n', cause.message, cause.stack)
      var error = new Error('tunneling socket could not be established, ' + 'cause=' + cause.message)
      error.code = 'ECONNRESET'
      options.request.emit('error', error)
      self.removeSocket(placeholder)
    }
  }
  
  TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
    var pos = this.sockets.indexOf(socket)
    if (pos === -1) return
  
    this.sockets.splice(pos, 1)
  
    var pending = this.requests.shift()
    if (pending) {
      // If we have pending requests and a socket gets closed a new one
      // needs to be created to take over in the pool for the one that closed.
      this.createConnection(pending)
    }
  }
  
  function createSecureSocket(options, cb) {
    var self = this
    TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
      // 0 is dummy port for v0.6
      var secureSocket = tls.connect(0, mergeOptions({}, self.options,
        { servername: options.host
        , socket: socket
        }
      ))
      self.sockets[self.sockets.indexOf(socket)] = secureSocket
      cb(secureSocket)
    })
  }
  
  
  function mergeOptions(target) {
    for (var i = 1, len = arguments.length; i < len; ++i) {
      var overrides = arguments[i]
      if (typeof overrides === 'object') {
        var keys = Object.keys(overrides)
        for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
          var k = keys[j]
          if (overrides[k] !== undefined) {
            target[k] = overrides[k]
          }
        }
      }
    }
    return target
  }
  
  
  var debug
  if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
    debug = function() {
      var args = Array.prototype.slice.call(arguments)
      if (typeof args[0] === 'string') {
        args[0] = 'TUNNEL: ' + args[0]
      } else {
        args.unshift('TUNNEL:')
      }
      console.error.apply(console, args)
    }
  } else {
    debug = function() {}
  }
  exports.debug = debug // for test
  