var inherits = require('util').inherits;
var events = require('events');
var tls = require('tls');
var net = require('net');

var DEFAULT_NETWORK_ERROR = 'Server unexpectedly closed network connection.';
var DEFAULT_TIMEOUT = 60000;

/**
 * Connection constructor
 *
 * @param  string host
 * @param  int port
 * @param  object options
 * @param  function callback
 */
var Connection = function(host, port, options, callback) {
  events.EventEmitter.call(this);

  this.stream = null;
  this.connected = false;
  this.buffer = [];
  this.requestBuffer = [];

  this.host = host;
  this.port = port;

  options = options || {};
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }
  this.options = options;

  if (callback) {
    this.connect(callback);
  }
};

inherits(Connection, events.EventEmitter);

/**
 * Initiate stream connection
 *
 * @param  function callback
 */
Connection.prototype.connect = function(callback) {
  var self = this;
  var proto = this.options.clear ? net : tls;

  this.stream = proto.connect({
    host: this.host,
    port: this.port,
    rejectUnauthorized: this.options.verifyCert === false ? false : true
  }, function() {
    self.connected = true;
    self.flushRequestBuffer();
    callback && callback(self);
    self.emit('connect');
  });
  this.stream.on('error', this.close.bind(this));
  this.stream.on('data', this.receive.bind(this));
  this.stream.on('close', this.close.bind(this));
  this.stream.on('timeout', this.timeout.bind(this));
  this.stream.setEncoding('utf8');
  this.stream.setTimeout(this.options.timeout || DEFAULT_TIMEOUT);
};

/**
 * Call remote server method
 *
 * @param  string method
 * ...
 * @param  function response (last argument)
 */
Connection.prototype.request = function() {
  // Copy args to avoid leaking
  var args = new Array(arguments.length);
  for (var i = 0; i < arguments.length; i++) {
    args[i] = arguments[i];
  }

  this.requestBuffer.push(args);

  if (!this.connected) {
    if (!this.stream) {
      this.connect();
    }
    return;
  }

  var request = JSON.stringify(args.slice(0, -1));
  this.stream.write(request + "\n");
};

/**
 * Receive data from the server
 *
 * @param  object buffer
 */
Connection.prototype.receive = function(buffer) {
  // Split buffer data on newline char
  for (var i = 0, j = 0; i < buffer.length; i++) {
    if (buffer[i] === '\n') {
      this.buffer.push(buffer.slice(j, i));

      var data = this.buffer.join('');

      this.buffer = [];
      this.receiveResponse(data);

      j = i + 1;
    }
  }
  if (j < buffer.length) {
    this.buffer.push(buffer.slice(j, buffer.length));
  }
};

/**
 * Handle server response data (JSON string)
 *
 * @param  string data
 */
Connection.prototype.receiveResponse = function(data) {
  var response;
  var request = this.requestBuffer.shift();
  var responder = request && request.pop();

  if (responder === undefined) {
    return;
  }

  try {
    response = JSON.parse(data);
  } catch (err) {
    response = 'Unable to parse response from server ('+data+')';
    this.emit('error.protocol', response);
    return responder({
      $status: 500,
      $error: response
    });
  }

  if (!response || typeof response !== 'object') {
    response = 'Invalid response from server ('+data+')';
    this.emit('error.protocol', response);
    return responder({
      $status: 500,
      $error: response
    });
  }

  if (response.$error) {
    this.emit('error.server', response.$error);
  }
  if (response.$end) {
    this.close();
  }

  // Note: response always returns in the same order as request
  if (typeof responder === 'function') {
    responder(response);
  }
};

/**
 * Close this connection
 */
Connection.prototype.close = function(error) {
  if (!this.connected) {
    return;
  }

  if (this.stream && this.stream.writable) {
    this.stream.end();
  }

  this.connected = false;
  this.stream = null;
  this.emit('close');

  var hasRequests = this.requestBuffer.length;
  if (error || hasRequests) {
    this.emit('error.network', error || DEFAULT_NETWORK_ERROR);
  }

  var responder;
  while (--hasRequests >= 0) {
    responder = this.requestBuffer.shift().pop();
    responder({
      $status: 500,
      $error: error || DEFAULT_NETWORK_ERROR
    });
  }
};

/**
 * Handle timeout by closing if no requests are pending
 */
Connection.prototype.timeout = function(error) {
  if (this.requestBuffer.length) {
    return;
  }
  this.close();
};

/**
 * Flush local request buffer when connected
 *
 * @return void
 */
Connection.prototype.flushRequestBuffer = function() {
  if (!this.connected) {
    return;
  }

  var hasRequests = this.requestBuffer.length;
  while (--hasRequests >= 0) {
    this.request.apply(this, this.requestBuffer.shift());
  }
};

// Exports
exports.Connection = Connection;

@@ -1328,37 +1328,37 @@
 n('error', this.
-close
+error
 .bind(this));%0A  
@@ -3674,24 +3674,155 @@
 e);%0A  %7D%0A%7D;%0A%0A
+/**%0A * Emit an error%0A *%0A * @param  Error error%0A */%0AConnection.prototype.error = function(error) %7B%0A  this.emit('error', error);%0A%7D;%0A%0A
 /**%0A * Close
@@ -3872,37 +3872,32 @@
 lose = function(
-error
 ) %7B%0A  if (!this.
@@ -4072,34 +4072,20 @@
 e');%0A%0A  
-var hasRequests = 
+if (
 this.req
@@ -4105,151 +4105,10 @@
 ngth
-;%0A  if (error %7C%7C hasRequests) %7B%0A    this.emit('error.network', error %7C%7C DEFAULT_NETWORK_ERROR);%0A  %7D%0A%0A  var responder;%0A  while (--hasRequests
  %3E
-=
  0) 
@@ -4117,140 +4117,21 @@
     
-responder = this.requestBuffer.shift().pop();%0A    responder(%7B%0A      $status: 500,%0A      $error: error %7C%7C DEFAULT_NETWORK_ERROR%0A    %7D
+this.connect(
 );%0A 

Make it reconnect if requests are pending