// 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.

define("mojo/public/js/connection", [
  "mojo/public/js/bindings",
  "mojo/public/js/connector",
  "mojo/public/js/core",
  "mojo/public/js/router",
], function(bindings, connector, core, router) {

  var Router = router.Router;
  var EmptyProxy = bindings.EmptyProxy;
  var EmptyStub = bindings.EmptyStub;
  var ProxyBindings = bindings.ProxyBindings;
  var StubBindings = bindings.StubBindings;
  var TestConnector = connector.TestConnector;
  var TestRouter = router.TestRouter;

  // TODO(hansmuller): the proxy receiver_ property should be receiver$

  function BaseConnection(localStub, remoteProxy, router) {
    this.router_ = router;
    this.local = localStub;
    this.remote = remoteProxy;

    this.router_.setIncomingReceiver(localStub);
    this.router_.setErrorHandler(function() {
      if (StubBindings(this.local) &&
          StubBindings(this.local).connectionErrorHandler)
        StubBindings(this.local).connectionErrorHandler();
    }.bind(this));
    if (this.remote)
      this.remote.receiver_ = router;

    // Validate incoming messages: remote responses and local requests.
    var validateRequest = localStub && localStub.validator;
    var validateResponse = remoteProxy && remoteProxy.validator;
    var payloadValidators = [];
    if (validateRequest)
      payloadValidators.push(validateRequest);
    if (validateResponse)
      payloadValidators.push(validateResponse);
    this.router_.setPayloadValidators(payloadValidators);
  }

  BaseConnection.prototype.close = function() {
    this.router_.close();
    this.router_ = null;
    this.local = null;
    this.remote = null;
  };

  BaseConnection.prototype.encounteredError = function() {
    return this.router_.encounteredError();
  };

  function Connection(
      handle, localFactory, remoteFactory, routerFactory, connectorFactory) {
    var routerClass = routerFactory || Router;
    var router = new routerClass(handle, connectorFactory);
    var remoteProxy = remoteFactory && new remoteFactory(router);
    var localStub = localFactory && new localFactory(remoteProxy);
    BaseConnection.call(this, localStub, remoteProxy, router);
  }

  Connection.prototype = Object.create(BaseConnection.prototype);

  // The TestConnection subclass is only intended to be used in unit tests.
  function TestConnection(handle, localFactory, remoteFactory) {
    Connection.call(this,
                    handle,
                    localFactory,
                    remoteFactory,
                    TestRouter,
                    TestConnector);
  }

  TestConnection.prototype = Object.create(Connection.prototype);

  // Return a handle for a message pipe that's connected to a proxy
  // for remoteInterface. Used by generated code for outgoing interface&
  // (request) parameters: the caller is given the generated proxy via
  // |proxyCallback(proxy)| and the generated code sends the handle
  // returned by this function.
  function bindProxy(proxyCallback, remoteInterface) {
    var messagePipe = core.createMessagePipe();
    if (messagePipe.result != core.RESULT_OK)
      throw new Error("createMessagePipe failed " + messagePipe.result);

    var proxy = new remoteInterface.proxyClass;
    var router = new Router(messagePipe.handle0);
    var connection = new BaseConnection(undefined, proxy, router);
    ProxyBindings(proxy).connection = connection;
    if (proxyCallback)
      proxyCallback(proxy);

    return messagePipe.handle1;
  }

  // Return a handle for a message pipe that's connected to a stub for
  // localInterface. Used by generated code for outgoing interface
  // parameters: the caller  is given the generated stub via
  // |stubCallback(stub)| and the generated code sends the handle
  // returned by this function. The caller is responsible for managing
  // the lifetime of the stub and for setting it's implementation
  // delegate with: StubBindings(stub).delegate = myImpl;
  function bindImpl(stubCallback, localInterface) {
    var messagePipe = core.createMessagePipe();
    if (messagePipe.result != core.RESULT_OK)
      throw new Error("createMessagePipe failed " + messagePipe.result);

    var stub = new localInterface.stubClass;
    var router = new Router(messagePipe.handle0);
    var connection = new BaseConnection(stub, undefined, router);
    StubBindings(stub).connection = connection;
    if (stubCallback)
      stubCallback(stub);

    return messagePipe.handle1;
  }

  // Return a remoteInterface proxy for handle. Used by generated code
  // for converting incoming interface parameters to proxies.
  function bindHandleToProxy(handle, remoteInterface) {
    if (!core.isHandle(handle))
      throw new Error("Not a handle " + handle);

    var proxy = new remoteInterface.proxyClass;
    var router = new Router(handle);
    var connection = new BaseConnection(undefined, proxy, router);
    ProxyBindings(proxy).connection = connection;
    return proxy;
  }

  // Return a localInterface stub for handle. Used by generated code
  // for converting incoming interface& request parameters to localInterface
  // stubs. The caller can specify the stub's implementation of localInterface
  // like this: StubBindings(stub).delegate = myStubImpl.
  function bindHandleToStub(handle, localInterface) {
    if (!core.isHandle(handle))
      throw new Error("Not a handle " + handle);

    var stub = new localInterface.stubClass;
    var router = new Router(handle);
    var connection = new BaseConnection(stub, undefined, router);
    StubBindings(stub).connection = connection;
    return stub;
  }

  /**
   * Creates a messape pipe and links one end of the pipe to the given object.
   * @param {!Object} obj The object to create a handle for. Must be a subclass
   *     of an auto-generated stub class.
   * @return {!MojoHandle} The other (not yet connected) end of the message
   *     pipe.
   */
  function bindStubDerivedImpl(obj) {
    var pipe = core.createMessagePipe();
    var router = new Router(pipe.handle0);
    var connection = new BaseConnection(obj, undefined, router);
    obj.connection = connection;
    return pipe.handle1;
  }

  var exports = {};
  exports.Connection = Connection;
  exports.TestConnection = TestConnection;

  exports.bindProxy = bindProxy;
  exports.bindImpl = bindImpl;
  exports.bindHandleToProxy = bindHandleToProxy;
  exports.bindHandleToStub = bindHandleToStub;
  exports.bindStubDerivedImpl = bindStubDerivedImpl;
  return exports;
});