text: var _ = require('lodash');
  var JSONStream = require('JSONStream');
  var JSONstringify = require('json-stringify-safe');
  var uuid = require('uuid/v4');
  var generateRequest = require('./generateRequest');
  
  /** * @namespace */
  var Utils = module.exports;
  
  // same reference as other files use, for tidyness
  var utils = Utils;
  
  Utils.request = generateRequest;
  
  /**
   *  Generates a JSON-RPC 1.0 or 2.0 response
   *  @param {Object} error Error member
   *  @param {Object} result Result member
   *  @param {String|Number|null} id Id of request
   *  @param {Number} version JSON-RPC version to use
   *  @return {Object} A JSON-RPC 1.0 or 2.0 response
   */
  Utils.response = function(error, result, id, version) {
    id = typeof(id) === 'undefined' || id === null ? null : id;
    error = typeof(error) === 'undefined' || error === null ? null : error;
    version = typeof(version) === 'undefined' || version === null ? 2 : version;
    var response = (version === 2) ? { jsonrpc: "2.0", id: id } : { id: id };
  
    // errors are always included in version 1
    if(version === 1) {
      response.error = error;
    }
  
    // one or the other with precedence for errors
    if(error) {
      response.error = error;
    } else {
      response.result = result;
    }
    return response;
  };
  
  /**
   *  Generates a random UUID
   *  @return {String}
   */
  Utils.generateId = function() {
    return uuid();
  };
  
  /**
   *  Merges properties of object b into object a
   *  @param {...Object} Objects to be merged
   *  @return {Object}
   *  @private
   */
  Utils.merge = function() {
    return _.extend.apply(null, arguments);
  };
  
  /**
   * Parses an incoming stream for requests using JSONStream
   * @param {Stream} stream
   * @param {Object} options
   * @param {Function} onRequest - Called once for stream errors and an unlimited amount of times for valid requests
   */
  Utils.parseStream = function(stream, options, onRequest) {
  
    var onError = _.once(onRequest);
    var onSuccess = _.partial(onRequest, null);
  
    var result = JSONStream.parse();
  
    result.on('data', function(data) {
  
      // apply reviver walk function to prevent stringify/parse again
      if(_.isFunction(options.reviver)) {
        data = Utils.walk({'': data}, '', options.reviver);
      }
  
      onSuccess(data);
    });
  
    result.on('error', onError);
    stream.on('error', onError);
  
    stream.pipe(result);
  
  };
  
  /**
   *  Helper to parse a stream and interpret it as JSON
   *  @param {Stream} stream Stream instance
   *  @param {Function} [reviver] Optional reviver for JSON.parse
   *  @param {Function} callback
   */
  Utils.parseBody = function(stream, options, callback) {
  
    callback = _.once(callback);
    var data = '';
  
    stream.setEncoding('utf8');
  
    stream.on('data', function(str) {
      data += str;
    });
  
    stream.on('error', function(err) {
      callback(err);
    });
  
    stream.on('end', function() {
      utils.JSON.parse(data, options, function(err, request) {
        if(err) {
          return callback(err);
        }
        callback(null, request);
      });
    });
  
  };
  
  /**
   *  Returns a HTTP request listener bound to the server in the argument.
   *  @param {http.Server} self Instance of a HTTP server
   *  @param {JaysonServer} server Instance of JaysonServer (typically jayson.Server)
   *  @return {Function}
   *  @private
   */
  Utils.getHttpListener = function(self, server) {
    return function(req, res) {
      var options = self.options || {};
  
      server.emit('http request', req);
  
      //  405 method not allowed if not POST
      if(!Utils.isMethod(req, 'POST')) {
        return respond('Method Not Allowed', 405, {'allow': 'POST'});
      }
  
      // 415 unsupported media type if Content-Type is not correct
      if(!Utils.isContentType(req, 'application/json')) {
        return respond('Unsupported Media Type', 415);
      }
  
      Utils.parseBody(req, options, function(err, request) {
        if(err) {
          return respond(err, 400);
        }
  
        server.call(request, function(error, success) {
          var response = error || success;
          if(!response) {
            // no response received at all, must be a notification
            return respond('', 204);
          }
  
          utils.JSON.stringify(response, options, function(err, body) {
            if(err) {
              return respond(err, 500);
            }
  
            var headers = {
              'content-length': Buffer.byteLength(body, options.encoding),
              'content-type': 'application/json; charset=utf-8'
            };
  
            respond(body, 200, headers);
          });
  
        });
  
      });
  
      function respond(response, code, headers) {
        var body = response instanceof Error ? response.toString() : response;
        server.emit('http response', res, req);
        res.writeHead(code, headers || {});
        res.end(body);
      }
  
    };
  };
  
  /**
   *  Determines if a HTTP Request comes with a specific Content-Type
   *  @param {ServerRequest} request
   *  @param {String} type
   *  @return {Boolean}
   *  @private
   */
  Utils.isContentType = function(request, type) {
    request = request || {headers: {}};
    var contentType = request.headers['content-type'] || '';
    return RegExp(type, 'i').test(contentType);
  };
  
  /**
   *  Determines if a HTTP Request is of a specific method
   *  @param {ServerRequest} request
   *  @param {String} method
   *  @return {Boolean}
   *  @private
   */
  Utils.isMethod = function(request, method) {
    method = (method || '').toUpperCase();
    return (request.method || '') === method;
  };
  
  /**
   *  Determines the parameter names of a function
   *  @param {Function} func
   *  @return {Array}
   *  @private
   */
  Utils.getParameterNames = function(func) {
    if(typeof(func) !== 'function') {
      return [];
    }
  
    var body = func.toString().replace(/[\n\r]/g, " ");
    var args = /^(?:function )*.*?\((.+?)\)/.exec(body);
    if(!args) {
      return [];
    }
  
    var list = (args.pop() || '').split(',');
    return list.map(function(arg) { return arg.trim(); });
  };
  
  /** * @namespace */
  Utils.JSON = {};
  
  /**
   * Parses a JSON string and then invokes the given callback
   * @param {String} str The string to parse
   * @param {Object} options Object with options, possibly holding a "reviver" function
   */
  Utils.JSON.parse = function(str, options, callback) {
    var reviver = null;
    var obj = null;
    options = options || {};
  
    if(_.isFunction(options.reviver)) {
      reviver = options.reviver;
    }
  
    try {
      obj = JSON.parse.apply(JSON, _.compact([str, reviver]));
    } catch(err) {
      return callback(err);
    }
  
    callback(null, obj);
  };
  
  /**
   * Stringifies JSON and then invokes the given callback
   * @param {Object} obj The object to stringify
   * @param {Object} options Object with options, possibly holding a "replacer" function
   */
  Utils.JSON.stringify = function(obj, options, callback) {
    var replacer = null;
    var str = null;
    options = options || {};
  
    if(_.isFunction(options.replacer)) {
      replacer = options.replacer;
    }
  
    try {
      str = JSONstringify.apply(JSON, _.compact([obj, replacer]));
    } catch(err) {
      return callback(err);
    }
  
    callback(null, str);
  };
  
  /**
   * Recursively walk an object and apply a function on its members
   * @param {Object} holder The object to walk
   * @param {String} key The key to look at
   * @param {Function} fn The function to apply to members
   * @return {Object}
   */
  Utils.walk = function(holder, key, fn) {
    var k, v, value = holder[key];
    if (value && typeof value === 'object') {
      for (k in value) {
        if (Object.prototype.hasOwnProperty.call(value, k)) {
          v = Utils.walk(value, k, fn);
          if (v !== undefined) {
            value[k] = v;
          } else {
            delete value[k];
          }
        }
      }
    }
    return fn.call(holder, key, value);
  };
  
  /** * @namespace */
  Utils.Request = {};
  
  /**
   * Determines if the passed request is a batch request
   * @param {Object} request The request
   * @return {Boolean}
   */
  Utils.Request.isBatch = function(request) {
    return Array.isArray(request);
  };
  
  /**
   * Determines if the passed request is a notification request
   * @param {Object} request The request
   * @return {Boolean}
   */
  Utils.Request.isNotification = function(request) {
    return Boolean(
      request
      && !Utils.Request.isBatch(request)
      && (typeof(request.id) === 'undefined'
           || request.id === null)
    );
  };
  
  /**
   * Determines if the passed request is a valid JSON-RPC 2.0 Request
   * @param {Object} request The request
   * @return {Boolean}
   */
  Utils.Request.isValidVersionTwoRequest = function(request) {
    return Boolean(
      request
      && typeof(request) === 'object'
      && request.jsonrpc === '2.0'
      && typeof(request.method) === 'string'
      && (
        typeof(request.params) === 'undefined'
        || Array.isArray(request.params)
        || (request.params && typeof(request.params) === 'object')
      )
      && (
        typeof(request.id) === 'undefined'
        || typeof(request.id) === 'string'
        || typeof(request.id) === 'number'
        || request.id === null
      )
    );
  };
  
  /**
   * Determines if the passed request is a valid JSON-RPC 1.0 Request
   * @param {Object} request The request
   * @return {Boolean}
   */
  Utils.Request.isValidVersionOneRequest = function(request) {
    return Boolean(
      request
      && typeof(request) === 'object'
      && typeof(request.method) === 'string'
      && Array.isArray(request.params)
      && typeof(request.id) !== 'undefined'
    );
  };
  
  /**
   * Determines if the passed request is a valid JSON-RPC Request
   * @param {Object} request The request
   * @param {Number} version JSON-RPC version 1 or 2
   * @return {Boolean}
   */
  Utils.Request.isValidRequest = function(request, version) {
    version = version === 1 ? 1 : 2;
    return Boolean(
      request
      && (
        (version === 1 && Utils.Request.isValidVersionOneRequest(request)) ||
        (version === 2 && Utils.Request.isValidVersionTwoRequest(request))
      )
    );
  };
  
  /** * @namespace */
  Utils.Response = {};
  
  /**
   * Determines if the passed error is a valid JSON-RPC error response
   * @param {Object} error The error
   * @param {Number} [version=2] JSON-RPC version 1 or 2
   * @return {Boolean}
   */
  Utils.Response.isValidError = function(error, version) {
    version = version === 1 ? 1 : 2;
    return Boolean(
      version === 1 && (
        typeof(error) !== 'undefined'
        && error !== null
      )
      || version === 2 && (
        error
        && typeof(error.code) === 'number'
        && parseInt(error.code, 10) === error.code
        && typeof(error.message) === 'string'
      )
    );
  };
  
  /**
   * Determines if the passed object is a valid JSON-RPC response
   * @param {Object} response The response
   * @param {Number} [version=2] JSON-RPC version 1 or 2
   * @return {Boolean}
   */
  Utils.Response.isValidResponse = function(response, version) {
    version = version === 1 ? 1 : 2;
    return Boolean(
      response !== null
      && typeof response === 'object'
      && (version === 2 && (
        // check version
        response.jsonrpc === '2.0'
        && (
          // check id
          response.id === null
          || typeof response.id === 'string'
          || typeof response.id === 'number'
        )
        && (
          // result and error do not exist at the same time
          (typeof response.result === 'undefined' && typeof response.error !== 'undefined')
          || (typeof response.result !== 'undefined' && typeof response.error === 'undefined')
        )
        && (
          // check result
          (typeof response.result !== 'undefined')
          // check error object
          || (
            response.error !== null
            && typeof response.error === 'object'
            && typeof response.error.code === 'number'
            // check error.code is integer
            && ((response.error.code | 0) === response.error.code)
            && typeof response.error.message === 'string'
          )
        )
      )
        || version === 1 && (
          typeof response.id !== 'undefined'
          && (
            // result and error relation (the other null if one is not)
            (typeof response.result !== 'undefined' && response.error === null)
            || (typeof response.error !== 'undefined' && response.result === null)
          )
        ))
    );
  };
  