// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

(function() {
  var internal = mojo.internal;

  function Connector(handle) {
    if (!(handle instanceof MojoHandle))
      throw new Error("Connector: not a handle " + handle);
    this.handle_ = handle;
    this.dropWrites_ = false;
    this.error_ = false;
    this.incomingReceiver_ = null;
    this.readWatcher_ = null;
    this.errorHandler_ = null;
    this.paused_ = false;

    this.waitToReadMore();
  }

  Connector.prototype.close = function() {
    this.cancelWait();
    if (this.handle_ != null) {
      this.handle_.close();
      this.handle_ = null;
    }
  };

  Connector.prototype.pauseIncomingMethodCallProcessing = function() {
    if (this.paused_) {
      return;
    }
    this.paused_= true;
    this.cancelWait();
  };

  Connector.prototype.resumeIncomingMethodCallProcessing = function() {
    if (!this.paused_) {
      return;
    }
    this.paused_= false;
    this.waitToReadMore();
  };

  Connector.prototype.accept = function(message) {
    if (this.error_)
      return false;

    if (this.dropWrites_)
      return true;

    var result = this.handle_.writeMessage(
        new Uint8Array(message.buffer.arrayBuffer), message.handles);
    switch (result) {
      case Mojo.RESULT_OK:
        // The handles were successfully transferred, so we don't own them
        // anymore.
        message.handles = [];
        break;
      case Mojo.RESULT_FAILED_PRECONDITION:
        // There's no point in continuing to write to this pipe since the other
        // end is gone. Avoid writing any future messages. Hide write failures
        // from the caller since we'd like them to continue consuming any
        // backlog of incoming messages before regarding the message pipe as
        // closed.
        this.dropWrites_ = true;
        break;
      default:
        // This particular write was rejected, presumably because of bad input.
        // The pipe is not necessarily in a bad state.
        return false;
    }
    return true;
  };

  Connector.prototype.setIncomingReceiver = function(receiver) {
    this.incomingReceiver_ = receiver;
  };

  Connector.prototype.setErrorHandler = function(handler) {
    this.errorHandler_ = handler;
  };

  Connector.prototype.readMore_ = function(result) {
    for (;;) {
      if (this.paused_) {
        return;
      }

      var read = this.handle_.readMessage();
      if (this.handle_ == null) // The connector has been closed.
        return;
      if (read.result == Mojo.RESULT_SHOULD_WAIT)
        return;
      if (read.result != Mojo.RESULT_OK) {
        this.handleError(read.result !== Mojo.RESULT_FAILED_PRECONDITION,
            false);
        return;
      }
      var messageBuffer = new internal.Buffer(read.buffer);
      var message = new internal.Message(messageBuffer, read.handles);
      var receiverResult = this.incomingReceiver_ &&
          this.incomingReceiver_.accept(message);

      // Dispatching the message may have closed the connector.
      if (this.handle_ == null)
        return;

      // Handle invalid incoming message.
      if (!internal.isTestingMode() && !receiverResult) {
        // TODO(yzshen): Consider notifying the embedder.
        this.handleError(true, false);
      }
    }
  };

  Connector.prototype.cancelWait = function() {
    if (this.readWatcher_) {
      this.readWatcher_.cancel();
      this.readWatcher_ = null;
    }
  };

  Connector.prototype.waitToReadMore = function() {
    if (this.handle_) {
      this.readWatcher_ = this.handle_.watch({readable: true},
                                             this.readMore_.bind(this));
    }
  };

  Connector.prototype.handleError = function(forcePipeReset,
                                             forceAsyncHandler) {
    if (this.error_ || this.handle_ === null) {
      return;
    }

    if (this.paused_) {
      // Enforce calling the error handler asynchronously if the user has
      // paused receiving messages. We need to wait until the user starts
      // receiving messages again.
      forceAsyncHandler = true;
    }

    if (!forcePipeReset && forceAsyncHandler) {
      forcePipeReset = true;
    }

    this.cancelWait();
    if (forcePipeReset) {
      this.handle_.close();
      var dummyPipe = Mojo.createMessagePipe();
      this.handle_ = dummyPipe.handle0;
    }

    if (forceAsyncHandler) {
      if (!this.paused_) {
        this.waitToReadMore();
      }
    } else {
      this.error_ = true;
      if (this.errorHandler_) {
        this.errorHandler_.onError();
      }
    }
  };

  internal.Connector = Connector;
})();
