text: /**
   * @license
   * Copyright 2015 gRPC authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   */
  
  'use strict';
  
  var _ = require('lodash');
  
  var grpc = require('./grpc_extension');
  
  var common = require('./common');
  
  var Metadata = require('./metadata');
  
  var constants = require('./constants');
  
  var stream = require('stream');
  
  var Readable = stream.Readable;
  var Writable = stream.Writable;
  var Duplex = stream.Duplex;
  var util = require('util');
  
  var EventEmitter = require('events').EventEmitter;
  
  /**
   * Handle an error on a call by sending it as a status
   * @private
   * @param {grpc.internal~Call} call The call to send the error on
   * @param {(Object|Error)} error The error object
   */
  function handleError(call, error) {
    var statusMetadata = new Metadata();
    var status = {
      code: constants.status.UNKNOWN,
      details: 'Unknown Error'
    };
    if (error.hasOwnProperty('message')) {
      status.details = error.message;
    }
    if (error.hasOwnProperty('code') && Number.isInteger(error.code)) {
      status.code = error.code;
      if (error.hasOwnProperty('details')) {
        status.details = error.details;
      }
    }
    if (error.hasOwnProperty('metadata')) {
      statusMetadata = error.metadata;
    }
    status.metadata = statusMetadata._getCoreRepresentation();
    var error_batch = {};
    if (!call.metadataSent) {
      error_batch[grpc.opType.SEND_INITIAL_METADATA] =
          (new Metadata())._getCoreRepresentation();
    }
    error_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status;
    call.startBatch(error_batch, function(){});
  }
  
  /**
   * Send a response to a unary or client streaming call.
   * @private
   * @param {grpc.Call} call The call to respond on
   * @param {*} value The value to respond with
   * @param {grpc~serialize} serialize Serialization function for the
   *     response
   * @param {grpc.Metadata=} metadata Optional trailing metadata to send with
   *     status
   * @param {number=} [flags=0] Flags for modifying how the message is sent.
   */
  function sendUnaryResponse(call, value, serialize, metadata, flags) {
    var end_batch = {};
    var statusMetadata = new Metadata();
    var status = {
      code: constants.status.OK,
      details: 'OK'
    };
    if (metadata) {
      statusMetadata = metadata;
    }
    var message;
    try {
      message = serialize(value);
    } catch (e) {
      e.code = constants.status.INTERNAL;
      handleError(call, e);
      return;
    }
    status.metadata = statusMetadata._getCoreRepresentation();
    if (!call.metadataSent) {
      end_batch[grpc.opType.SEND_INITIAL_METADATA] =
          (new Metadata())._getCoreRepresentation();
      call.metadataSent = true;
    }
    message.grpcWriteFlags = flags;
    end_batch[grpc.opType.SEND_MESSAGE] = message;
    end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status;
    call.startBatch(end_batch, function (){});
  }
  
  /**
   * Initialize a writable stream. This is used for both the writable and duplex
   * stream constructors.
   * @private
   * @param {Writable} stream The stream to set up
   * @param {function(*):Buffer=} Serialization function for responses
   */
  function setUpWritable(stream, serialize) {
    stream.finished = false;
    stream.status = {
      code : constants.status.OK,
      details : 'OK',
      metadata : new Metadata()
    };
    stream.serialize = common.wrapIgnoreNull(serialize);
    function sendStatus() {
      var batch = {};
      if (!stream.call.metadataSent) {
        stream.call.metadataSent = true;
        batch[grpc.opType.SEND_INITIAL_METADATA] =
            (new Metadata())._getCoreRepresentation();
      }
  
      if (stream.status.metadata) {
        stream.status.metadata = stream.status.metadata._getCoreRepresentation();
      }
      batch[grpc.opType.SEND_STATUS_FROM_SERVER] = stream.status;
      stream.call.startBatch(batch, function(){});
    }
    stream.on('finish', sendStatus);
    /**
     * Set the pending status to a given error status. If the error does not have
     * code or details properties, the code will be set to grpc.status.UNKNOWN
     * and the details will be set to 'Unknown Error'.
     * @param {Error} err The error object
     */
    function setStatus(err) {
      var code = constants.status.UNKNOWN;
      var details = 'Unknown Error';
      var metadata = new Metadata();
      if (err.hasOwnProperty('message')) {
        details = err.message;
      }
      if (err.hasOwnProperty('code')) {
        code = err.code;
        if (err.hasOwnProperty('details')) {
          details = err.details;
        }
      }
      if (err.hasOwnProperty('metadata')) {
        metadata = err.metadata;
      }
      stream.status = {code: code, details: details, metadata: metadata};
    }
    /**
     * Terminate the call. This includes indicating that reads are done, draining
     * all pending writes, and sending the given error as a status
     * @param {Error} err The error object
     * @this GrpcServerStream
     */
    function terminateCall(err) {
      // Drain readable data
      setStatus(err);
      stream.end();
    }
    stream.on('error', terminateCall);
    /**
     * Override of Writable#end method that allows for sending metadata with a
     * success status.
     * @param {Metadata=} metadata Metadata to send with the status
     */
    stream.end = function(metadata) {
      if (metadata) {
        stream.status.metadata = metadata;
      }
      Writable.prototype.end.call(this);
    };
  }
  
  /**
   * Initialize a readable stream. This is used for both the readable and duplex
   * stream constructors.
   * @private
   * @param {Readable} stream The stream to initialize
   * @param {grpc~deserialize} deserialize Deserialization function for
   *     incoming data.
   */
  function setUpReadable(stream, deserialize) {
    stream.deserialize = common.wrapIgnoreNull(deserialize);
    stream.finished = false;
    stream.reading = false;
  
    stream.terminate = function() {
      stream.finished = true;
      stream.on('data', function() {});
    };
  
    stream.on('cancelled', function() {
      stream.terminate();
    });
  }
  
  /**
   * Emitted when the call has been cancelled. After this has been emitted, the
   * call's `cancelled` property will be set to `true`.
   * @event grpc~ServerUnaryCall~cancelled
   */
  
  util.inherits(ServerUnaryCall, EventEmitter);
  
  /**
   * An EventEmitter. Used for unary calls.
   * @constructor grpc~ServerUnaryCall
   * @extends external:EventEmitter
   * @param {grpc.internal~Call} call The call object associated with the request
   * @param {grpc.Metadata} metadata The request metadata from the client
   */
  function ServerUnaryCall(call, metadata) {
    EventEmitter.call(this);
    this.call = call;
    /**
     * Indicates if the call has been cancelled
     * @member {boolean} grpc~ServerUnaryCall#cancelled
     */
    this.cancelled = false;
    /**
     * The request metadata from the client
     * @member {grpc.Metadata} grpc~ServerUnaryCall#metadata
     */
    this.metadata = metadata;
    /**
     * The request message from the client
     * @member {*} grpc~ServerUnaryCall#request
     */
    this.request = undefined;
  }
  
  /**
   * Emitted when the call has been cancelled. After this has been emitted, the
   * call's `cancelled` property will be set to `true`.
   * @event grpc~ServerWritableStream~cancelled
   */
  
  util.inherits(ServerWritableStream, Writable);
  
  /**
   * A stream that the server can write to. Used for calls that are streaming from
   * the server side.
   * @constructor grpc~ServerWritableStream
   * @extends external:Writable
   * @borrows grpc~ServerUnaryCall#sendMetadata as
   *     grpc~ServerWritableStream#sendMetadata
   * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerWritableStream#getPeer
   * @param {grpc.internal~Call} call The call object to send data with
   * @param {grpc.Metadata} metadata The request metadata from the client
   * @param {grpc~serialize} serialize Serialization function for writes
   */
  function ServerWritableStream(call, metadata, serialize) {
    Writable.call(this, {objectMode: true});
    this.call = call;
  
    this.finished = false;
    setUpWritable(this, serialize);
    /**
     * Indicates if the call has been cancelled
     * @member {boolean} grpc~ServerWritableStream#cancelled
     */
    this.cancelled = false;
    /**
     * The request metadata from the client
     * @member {grpc.Metadata} grpc~ServerWritableStream#metadata
     */
    this.metadata = metadata;
    /**
     * The request message from the client
     * @member {*} grpc~ServerWritableStream#request
     */
    this.request = undefined;
  }
  
  /**
   * Start writing a chunk of data. This is an implementation of a method required
   * for implementing stream.Writable.
   * @private
   * @param {Buffer} chunk The chunk of data to write
   * @param {string} encoding Used to pass write flags
   * @param {function(Error=)} callback Callback to indicate that the write is
   *     complete
   */
  function _write(chunk, encoding, callback) {
    /* jshint validthis: true */
    var batch = {};
    var self = this;
    var message;
    try {
      message = this.serialize(chunk);
    } catch (e) {
      e.code = constants.status.INTERNAL;
      callback(e);
      return;
    }
    if (!this.call.metadataSent) {
      batch[grpc.opType.SEND_INITIAL_METADATA] =
          (new Metadata())._getCoreRepresentation();
      this.call.metadataSent = true;
    }
    if (Number.isFinite(encoding)) {
      /* Attach the encoding if it is a finite number. This is the closest we
       * can get to checking that it is valid flags */
      message.grpcWriteFlags = encoding;
    }
    batch[grpc.opType.SEND_MESSAGE] = message;
    this.call.startBatch(batch, function(err, value) {
      if (err) {
        self.emit('error', err);
        return;
      }
      callback();
    });
  }
  
  ServerWritableStream.prototype._write = _write;
  
  /**
   * Emitted when the call has been cancelled. After this has been emitted, the
   * call's `cancelled` property will be set to `true`.
   * @event grpc~ServerReadableStream~cancelled
   */
  
  util.inherits(ServerReadableStream, Readable);
  
  /**
   * A stream that the server can read from. Used for calls that are streaming
   * from the client side.
   * @constructor grpc~ServerReadableStream
   * @extends external:Readable
   * @borrows grpc~ServerUnaryCall#sendMetadata as
   *     grpc~ServerReadableStream#sendMetadata
   * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerReadableStream#getPeer
   * @param {grpc.internal~Call} call The call object to read data with
   * @param {grpc.Metadata} metadata The request metadata from the client
   * @param {grpc~deserialize} deserialize Deserialization function for reads
   */
  function ServerReadableStream(call, metadata, deserialize) {
    Readable.call(this, {objectMode: true});
    this.call = call;
    setUpReadable(this, deserialize);
    /**
     * Indicates if the call has been cancelled
     * @member {boolean} grpc~ServerReadableStream#cancelled
     */
    this.cancelled = false;
    /**
     * The request metadata from the client
     * @member {grpc.Metadata} grpc~ServerReadableStream#metadata
     */
    this.metadata = metadata;
  }
  
  /**
   * Start reading from the gRPC data source. This is an implementation of a
   * method required for implementing stream.Readable
   * @access private
   * @param {number} size Ignored
   */
  function _read(size) {
    /* jshint validthis: true */
    var self = this;
    /**
     * Callback to be called when a READ event is received. Pushes the data onto
     * the read queue and starts reading again if applicable
     * @param {grpc.Event} event READ event object
     */
    function readCallback(err, event) {
      if (err) {
        self.terminate();
        return;
      }
      if (self.finished) {
        self.push(null);
        return;
      }
      var data = event.read;
      var deserialized;
      try {
        deserialized = self.deserialize(data);
      } catch (e) {
        e.code = constants.status.INTERNAL;
        self.emit('error', e);
        return;
      }
      if (self.push(deserialized) && data !== null) {
        var read_batch = {};
        read_batch[grpc.opType.RECV_MESSAGE] = true;
        self.call.startBatch(read_batch, readCallback);
      } else {
        self.reading = false;
      }
    }
    if (self.finished) {
      self.push(null);
    } else {
      if (!self.reading) {
        self.reading = true;
        var batch = {};
        batch[grpc.opType.RECV_MESSAGE] = true;
        self.call.startBatch(batch, readCallback);
      }
    }
  }
  
  ServerReadableStream.prototype._read = _read;
  
  /**
   * Emitted when the call has been cancelled. After this has been emitted, the
   * call's `cancelled` property will be set to `true`.
   * @event grpc~ServerDuplexStream~cancelled
   */
  
  util.inherits(ServerDuplexStream, Duplex);
  
  /**
   * A stream that the server can read from or write to. Used for calls with
   * duplex streaming.
   * @constructor grpc~ServerDuplexStream
   * @extends external:Duplex
   * @borrows grpc~ServerUnaryCall#sendMetadata as
   *     grpc~ServerDuplexStream#sendMetadata
   * @borrows grpc~ServerUnaryCall#getPeer as grpc~ServerDuplexStream#getPeer
   * @param {grpc.internal~Call} call Call object to proxy
   * @param {grpc.Metadata} metadata The request metadata from the client
   * @param {grpc~serialize} serialize Serialization function for requests
   * @param {grpc~deserialize} deserialize Deserialization function for
   *     responses
   */
  function ServerDuplexStream(call, metadata, serialize, deserialize) {
    Duplex.call(this, {objectMode: true});
    this.call = call;
    setUpWritable(this, serialize);
    setUpReadable(this, deserialize);
    /**
     * Indicates if the call has been cancelled
     * @member {boolean} grpc~ServerReadableStream#cancelled
     */
    this.cancelled = false;
    /**
     * The request metadata from the client
     * @member {grpc.Metadata} grpc~ServerReadableStream#metadata
     */
    this.metadata = metadata;
  }
  
  ServerDuplexStream.prototype._read = _read;
  ServerDuplexStream.prototype._write = _write;
  
  /**
   * Send the initial metadata for a writable stream.
   * @alias grpc~ServerUnaryCall#sendMetadata
   * @param {grpc.Metadata} responseMetadata Metadata to send
   */
  function sendMetadata(responseMetadata) {
    /* jshint validthis: true */
    var self = this;
    if (!this.call.metadataSent) {
      this.call.metadataSent = true;
      var batch = {};
      batch[grpc.opType.SEND_INITIAL_METADATA] =
          responseMetadata._getCoreRepresentation();
      this.call.startBatch(batch, function(err) {
        if (err) {
          self.emit('error', err);
          return;
        }
      });
    }
  }
  
  ServerUnaryCall.prototype.sendMetadata = sendMetadata;
  ServerWritableStream.prototype.sendMetadata = sendMetadata;
  ServerReadableStream.prototype.sendMetadata = sendMetadata;
  ServerDuplexStream.prototype.sendMetadata = sendMetadata;
  
  /**
   * Get the endpoint this call/stream is connected to.
   * @alias grpc~ServerUnaryCall#getPeer
   * @return {string} The URI of the endpoint
   */
  function getPeer() {
    /* jshint validthis: true */
    return this.call.getPeer();
  }
  
  ServerUnaryCall.prototype.getPeer = getPeer;
  ServerReadableStream.prototype.getPeer = getPeer;
  ServerWritableStream.prototype.getPeer = getPeer;
  ServerDuplexStream.prototype.getPeer = getPeer;
  
  /**
   * Wait for the client to close, then emit a cancelled event if the client
   * cancelled.
   * @private
   */
  function waitForCancel() {
    /* jshint validthis: true */
    var self = this;
    var cancel_batch = {};
    cancel_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
    self.call.startBatch(cancel_batch, function(err, result) {
      if (err) {
        self.emit('error', err);
      }
      if (result.cancelled) {
        self.cancelled = true;
        self.emit('cancelled');
      }
    });
  }
  
  ServerUnaryCall.prototype.waitForCancel = waitForCancel;
  ServerReadableStream.prototype.waitForCancel = waitForCancel;
  ServerWritableStream.prototype.waitForCancel = waitForCancel;
  ServerDuplexStream.prototype.waitForCancel = waitForCancel;
  
  /**
   * Callback function passed to server handlers that handle methods with unary
   * responses.
   * @callback grpc.Server~sendUnaryData
   * @param {grpc~ServiceError} error An error, if the call failed
   * @param {*} value The response value. Must be a valid argument to the
   *     `responseSerialize` method of the method that is being handled
   * @param {grpc.Metadata=} trailer Trailing metadata to send, if applicable
   * @param {grpc.writeFlags=} flags Flags to modify writing the response
   */
  
  /**
   * User-provided method to handle unary requests on a server
   * @callback grpc.Server~handleUnaryCall
   * @param {grpc~ServerUnaryCall} call The call object
   * @param {grpc.Server~sendUnaryData} callback The callback to call to respond
   *     to the request
   */
  
  /**
   * Fully handle a unary call
   * @private
   * @param {grpc.internal~Call} call The call to handle
   * @param {Object} handler Request handler object for the method that was called
   * @param {grpc~Server.handleUnaryCall} handler.func The handler function
   * @param {grpc~deserialize} handler.deserialize The deserialization function
   *     for request data
   * @param {grpc~serialize} handler.serialize The serialization function for
   *     response data
   * @param {grpc.Metadata} metadata Metadata from the client
   */
  function handleUnary(call, handler, metadata) {
    var emitter = new ServerUnaryCall(call, metadata);
    emitter.on('error', function(error) {
      handleError(call, error);
    });
    emitter.waitForCancel();
    var batch = {};
    batch[grpc.opType.RECV_MESSAGE] = true;
    call.startBatch(batch, function(err, result) {
      if (err) {
        handleError(call, err);
        return;
      }
      try {
        emitter.request = handler.deserialize(result.read);
      } catch (e) {
        e.code = constants.status.INTERNAL;
        handleError(call, e);
        return;
      }
      if (emitter.cancelled) {
        return;
      }
      handler.func(emitter, function sendUnaryData(err, value, trailer, flags) {
        if (err) {
          if (trailer) {
            err.metadata = trailer;
          }
          handleError(call, err);
        } else {
          sendUnaryResponse(call, value, handler.serialize, trailer, flags);
        }
      });
    });
  }
  
  /**
   * User provided method to handle server streaming methods on the server.
   * @callback grpc.Server~handleServerStreamingCall
   * @param {grpc~ServerWritableStream} call The call object
   */
  
  /**
   * Fully handle a server streaming call
   * @private
   * @param {grpc.internal~Call} call The call to handle
   * @param {Object} handler Request handler object for the method that was called
   * @param {grpc~Server.handleServerStreamingCall} handler.func The handler
   *     function
   * @param {grpc~deserialize} handler.deserialize The deserialization function
   *     for request data
   * @param {grpc~serialize} handler.serialize The serialization function for
   *     response data
   * @param {grpc.Metadata} metadata Metadata from the client
   */
  function handleServerStreaming(call, handler, metadata) {
    var stream = new ServerWritableStream(call, metadata, handler.serialize);
    stream.waitForCancel();
    var batch = {};
    batch[grpc.opType.RECV_MESSAGE] = true;
    call.startBatch(batch, function(err, result) {
      if (err) {
        stream.emit('error', err);
        return;
      }
      try {
        stream.request = handler.deserialize(result.read);
      } catch (e) {
        e.code = constants.status.INTERNAL;
        stream.emit('error', e);
        return;
      }
      handler.func(stream);
    });
  }
  
  /**
   * User provided method to handle client streaming methods on the server.
   * @callback grpc.Server~handleClientStreamingCall
   * @param {grpc~ServerReadableStream} call The call object
   * @param {grpc.Server~sendUnaryData} callback The callback to call to respond
   *     to the request
   */
  
  /**
   * Fully handle a client streaming call
   * @access private
   * @param {grpc.internal~Call} call The call to handle
   * @param {Object} handler Request handler object for the method that was called
   * @param {grpc~Server.handleClientStreamingCall} handler.func The handler
   *     function
   * @param {grpc~deserialize} handler.deserialize The deserialization function
   *     for request data
   * @param {grpc~serialize} handler.serialize The serialization function for
   *     response data
   * @param {grpc.Metadata} metadata Metadata from the client
   */
  function handleClientStreaming(call, handler, metadata) {
    var stream = new ServerReadableStream(call, metadata, handler.deserialize);
    stream.on('error', function(error) {
      handleError(call, error);
    });
    stream.waitForCancel();
    handler.func(stream, function(err, value, trailer, flags) {
      stream.terminate();
      if (err) {
        if (trailer) {
          err.metadata = trailer;
        }
        handleError(call, err);
      } else {
        sendUnaryResponse(call, value, handler.serialize, trailer, flags);
      }
    });
  }
  
  /**
   * User provided method to handle bidirectional streaming calls on the server.
   * @callback grpc.Server~handleBidiStreamingCall
   * @param {grpc~ServerDuplexStream} call The call object
   */
  
  /**
   * Fully handle a bidirectional streaming call
   * @private
   * @param {grpc.internal~Call} call The call to handle
   * @param {Object} handler Request handler object for the method that was called
   * @param {grpc~Server.handleBidiStreamingCall} handler.func The handler
   *     function
   * @param {grpc~deserialize} handler.deserialize The deserialization function
   *     for request data
   * @param {grpc~serialize} handler.serialize The serialization function for
   *     response data
   * @param {Metadata} metadata Metadata from the client
   */
  function handleBidiStreaming(call, handler, metadata) {
    var stream = new ServerDuplexStream(call, metadata, handler.serialize,
                                        handler.deserialize);
    stream.waitForCancel();
    handler.func(stream);
  }
  
  var streamHandlers = {
    unary: handleUnary,
    server_stream: handleServerStreaming,
    client_stream: handleClientStreaming,
    bidi: handleBidiStreaming
  };
  
  /**
   * Constructs a server object that stores request handlers and delegates
   * incoming requests to those handlers
   * @memberof grpc
   * @constructor
   * @param {Object=} options Options that should be passed to the internal server
   *     implementation
   * @example
   * var server = new grpc.Server();
   * server.addProtoService(protobuf_service_descriptor, service_implementation);
   * server.bind('address:port', server_credential);
   * server.start();
   */
  function Server(options) {
    this.handlers = {};
    var server = new grpc.Server(options);
    this._server = server;
    this.started = false;
  }
  
  /**
   * Start the server and begin handling requests
   */
  Server.prototype.start = function() {
    if (this.started) {
      throw new Error('Server is already running');
    }
    var self = this;
    this.started = true;
    this._server.start();
    /**
     * Handles the SERVER_RPC_NEW event. If there is a handler associated with
     * the requested method, use that handler to respond to the request. Then
     * wait for the next request
     * @param {grpc.internal~Event} event The event to handle with tag
     *     SERVER_RPC_NEW
     */
    function handleNewCall(err, event) {
      if (err) {
        return;
      }
      var details = event.new_call;
      var call = details.call;
      var method = details.method;
      var metadata = Metadata._fromCoreRepresentation(details.metadata);
      if (method === null) {
        return;
      }
      self._server.requestCall(handleNewCall);
      var handler;
      if (self.handlers.hasOwnProperty(method)) {
        handler = self.handlers[method];
      } else {
        var batch = {};
        batch[grpc.opType.SEND_INITIAL_METADATA] =
            (new Metadata())._getCoreRepresentation();
        batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
          code: constants.status.UNIMPLEMENTED,
          details: 'RPC method not implemented ' + method,
          metadata: {}
        };
        batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
        call.startBatch(batch, function() {});
        return;
      }
      streamHandlers[handler.type](call, handler, metadata);
    }
    this._server.requestCall(handleNewCall);
  };
  
  /**
   * Unified type for application handlers for all types of calls
   * @typedef {(grpc.Server~handleUnaryCall
   *            |grpc.Server~handleClientStreamingCall
   *            |grpc.Server~handleServerStreamingCall
   *            |grpc.Server~handleBidiStreamingCall)} grpc.Server~handleCall
   */
  
  /**
   * Registers a handler to handle the named method. Fails if there already is
   * a handler for the given method. Returns true on success
   * @param {string} name The name of the method that the provided function should
   *     handle/respond to.
   * @param {grpc.Server~handleCall} handler Function that takes a stream of
   *     request values and returns a stream of response values
   * @param {grpc~serialize} serialize Serialization function for responses
   * @param {grpc~deserialize} deserialize Deserialization function for requests
   * @param {('unary'|'client_stream'|'server_stream'|'bidi')} type The streaming type of method that this handles
   * @return {boolean} True if the handler was set. False if a handler was already
   *     set for that name.
   */
  Server.prototype.register = function(name, handler, serialize, deserialize,
                                       type) {
    if (this.handlers.hasOwnProperty(name)) {
      return false;
    }
    this.handlers[name] = {
      func: handler,
      serialize: serialize,
      deserialize: deserialize,
      type: type
    };
    return true;
  };
  
  /**
   * Gracefully shuts down the server. The server will stop receiving new calls,
   * and any pending calls will complete. The callback will be called when all
   * pending calls have completed and the server is fully shut down. This method
   * is idempotent with itself and forceShutdown.
   * @param {function()} callback The shutdown complete callback
   */
  Server.prototype.tryShutdown = function(callback) {
    this._server.tryShutdown(callback);
  };
  
  /**
   * Forcibly shuts down the server. The server will stop receiving new calls
   * and cancel all pending calls. When it returns, the server has shut down.
   * This method is idempotent with itself and tryShutdown, and it will trigger
   * any outstanding tryShutdown callbacks.
   */
  Server.prototype.forceShutdown = function() {
    this._server.forceShutdown();
  };
  
  var unimplementedStatusResponse = {
    code: constants.status.UNIMPLEMENTED,
    details: 'The server does not implement this method'
  };
  
  var defaultHandler = {
    unary: function(call, callback) {
      callback(unimplementedStatusResponse);
    },
    client_stream: function(call, callback) {
      callback(unimplementedStatusResponse);
    },
    server_stream: function(call) {
      call.emit('error', unimplementedStatusResponse);
    },
    bidi: function(call) {
      call.emit('error', unimplementedStatusResponse);
    }
  };
  
  /**
   * Add a service to the server, with a corresponding implementation.
   * @param {grpc~ServiceDefinition} service The service descriptor
   * @param {Object<String, grpc.Server~handleCall>} implementation Map of method
   *     names to method implementation for the provided service.
   */
  Server.prototype.addService = function(service, implementation) {
    if (!_.isObject(service) || !_.isObject(implementation)) {
      throw new Error('addService requires two objects as arguments');
    }
    if (_.keys(service).length === 0) {
      throw new Error('Cannot add an empty service to a server');
    }
    if (this.started) {
      throw new Error('Can\'t add a service to a started server.');
    }
    var self = this;
    _.forOwn(service, function(attrs, name) {
      var method_type;
      if (attrs.requestStream) {
        if (attrs.responseStream) {
          method_type = 'bidi';
        } else {
          method_type = 'client_stream';
        }
      } else {
        if (attrs.responseStream) {
          method_type = 'server_stream';
        } else {
          method_type = 'unary';
        }
      }
      var impl;
      if (implementation[name] === undefined) {
        /* Handle the case where the method is passed with the name exactly as
           written in the proto file, instead of using JavaScript function
           naming style */
        if (implementation[attrs.originalName] === undefined) {
          common.log(constants.logVerbosity.ERROR, 'Method handler ' + name +
              ' for ' + attrs.path + ' expected but not provided');
          impl = defaultHandler[method_type];
        } else {
          impl = implementation[attrs.originalName].bind(implementation);
        }
      } else {
        impl = implementation[name].bind(implementation);
      }
      var serialize = attrs.responseSerialize;
      var deserialize = attrs.requestDeserialize;
      var register_success = self.register(attrs.path, impl, serialize,
                                           deserialize, method_type);
      if (!register_success) {
        throw new Error('Method handler for ' + attrs.path +
            ' already provided.');
      }
    });
  };
  
  /**
   * Add a proto service to the server, with a corresponding implementation
   * @deprecated Use {@link grpc.Server#addService} instead
   * @param {Protobuf.Reflect.Service} service The proto service descriptor
   * @param {Object<String, grpc.Server~handleCall>} implementation Map of method
   *     names to method implementation for the provided service.
   */
  Server.prototype.addProtoService = util.deprecate(function(service,
                                                             implementation) {
    var options;
    var protobuf_js_5_common = require('./protobuf_js_5_common');
    var protobuf_js_6_common = require('./protobuf_js_6_common');
    if (protobuf_js_5_common.isProbablyProtobufJs5(service)) {
      options = _.defaults(service.grpc_options, common.defaultGrpcOptions);
      this.addService(
          protobuf_js_5_common.getProtobufServiceAttrs(service, options),
          implementation);
    } else if (protobuf_js_6_common.isProbablyProtobufJs6(service)) {
      options = _.defaults(service.grpc_options, common.defaultGrpcOptions);
      this.addService(
          protobuf_js_6_common.getProtobufServiceAttrs(service, options),
          implementation);
    } else {
      // We assume that this is a service attributes object
      this.addService(service, implementation);
    }
  }, 'Server#addProtoService: Use Server#addService instead');
  
  /**
   * Binds the server to the given port, with SSL disabled if creds is an
   * insecure credentials object
   * @param {string} port The port that the server should bind on, in the format
   *     "address:port"
   * @param {grpc.ServerCredentials} creds Server credential object to be used for
   *     SSL. Pass an insecure credentials object for an insecure port.
   * @return {number} The bound port number. Negative if binding the port failed.
   */
  Server.prototype.bind = function(port, creds) {
    if (this.started) {
      throw new Error('Can\'t bind an already running server to an address');
    }
    return this._server.addHttp2Port(port, creds);
  };
  
  /**
   * Called with the result of attempting to bind a port
   * @callback grpc.Server~bindCallback
   * @param {Error=} error If non-null, indicates that binding the port failed.
   * @param {number} port The bound port number. If binding the port fails, this
   *     will be negative to match the output of bind.
   */
  
  /**
   * Binds the server to the given port, with SSL disabled if creds is an
   * insecure credentials object. Provides the result asynchronously.
   * @param {string} port The port that the server should bind on, in the format
   *     "address:port"
   * @param {grpc.ServerCredentials} creds Server credential object to be used for
   *     SSL. Pass an insecure credentials object for an insecure port.
   */
  Server.prototype.bindAsync = function(port, creds, callback) {
    /* This can throw. We do not try to catch that error because it indicates an
     * incorrect use of the function, which should not be surfaced asynchronously
     */
    const result = this.bind(port, creds)
    if (result < 0) {
      setImmediate(callback, new Error('Failed to bind port'), result);
    } else {
      setImmediate(callback, null, result);
    }
  }
  
  exports.Server = Server;
  