// Copyright 2017 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;

  var AssociationEvent = {
    // The interface has been associated with a message pipe.
    ASSOCIATED: 'associated',
    // The peer of this object has been closed before association.
    PEER_CLOSED_BEFORE_ASSOCIATION: 'peer_closed_before_association'
  };

  function State(interfaceId, associatedGroupController) {
    if (interfaceId === undefined) {
      interfaceId = internal.kInvalidInterfaceId;
    }

    this.interfaceId = interfaceId;
    this.associatedGroupController = associatedGroupController;
    this.pendingAssociation = false;
    this.disconnectReason = null;
    this.peerState_ = null;
    this.associationEventHandler_ = null;
  }

  State.prototype.initPendingState = function(peer) {
    this.pendingAssociation = true;
    this.peerState_ = peer;
  };

  State.prototype.isValid = function() {
    return this.pendingAssociation ||
        internal.isValidInterfaceId(this.interfaceId);
  };

  State.prototype.close = function(disconnectReason) {
    var cachedGroupController;
    var cachedPeerState;
    var cachedId = internal.kInvalidInterfaceId;

    if (!this.pendingAssociation) {
      if (internal.isValidInterfaceId(this.interfaceId)) {
        cachedGroupController = this.associatedGroupController;
        this.associatedGroupController = null;
        cachedId = this.interfaceId;
        this.interfaceId = internal.kInvalidInterfaceId;
      }
    } else {
      this.pendingAssociation = false;
      cachedPeerState = this.peerState_;
      this.peerState_ = null;
    }

    if (cachedGroupController) {
      cachedGroupController.closeEndpointHandle(cachedId,
          disconnectReason);
    } else if (cachedPeerState) {
      cachedPeerState.onPeerClosedBeforeAssociation(disconnectReason);
    }
  };

  State.prototype.runAssociationEventHandler = function(associationEvent) {
    if (this.associationEventHandler_) {
      var handler = this.associationEventHandler_;
      this.associationEventHandler_ = null;
      handler(associationEvent);
    }
  };

  State.prototype.setAssociationEventHandler = function(handler) {
    if (!this.pendingAssociation &&
        !internal.isValidInterfaceId(this.interfaceId)) {
      return;
    }

    if (!handler) {
      this.associationEventHandler_ = null;
      return;
    }

    this.associationEventHandler_ = handler;
    if (!this.pendingAssociation) {
      setTimeout(this.runAssociationEventHandler.bind(this,
          AssociationEvent.ASSOCIATED), 0);
    } else if (!this.peerState_) {
      setTimeout(this.runAssociationEventHandler.bind(this,
          AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION), 0);
    }
  };

  State.prototype.notifyAssociation = function(interfaceId,
                                               peerGroupController) {
    var cachedPeerState = this.peerState_;
    this.peerState_ = null;

    this.pendingAssociation = false;

    if (cachedPeerState) {
      cachedPeerState.onAssociated(interfaceId, peerGroupController);
      return true;
    }
    return false;
  };

  State.prototype.onAssociated = function(interfaceId,
      associatedGroupController) {
    if (!this.pendingAssociation) {
      return;
    }

    this.pendingAssociation = false;
    this.peerState_ = null;
    this.interfaceId = interfaceId;
    this.associatedGroupController = associatedGroupController;
    this.runAssociationEventHandler(AssociationEvent.ASSOCIATED);
  };

  State.prototype.onPeerClosedBeforeAssociation = function(disconnectReason) {
    if (!this.pendingAssociation) {
      return;
    }

    this.peerState_ = null;
    this.disconnectReason = disconnectReason;

    this.runAssociationEventHandler(
        AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION);
  };

  function createPairPendingAssociation() {
    var handle0 = new InterfaceEndpointHandle();
    var handle1 = new InterfaceEndpointHandle();
    handle0.state_.initPendingState(handle1.state_);
    handle1.state_.initPendingState(handle0.state_);
    return {handle0: handle0, handle1: handle1};
  }

  function InterfaceEndpointHandle(interfaceId, associatedGroupController) {
    this.state_ = new State(interfaceId, associatedGroupController);
  }

  InterfaceEndpointHandle.prototype.isValid = function() {
    return this.state_.isValid();
  };

  InterfaceEndpointHandle.prototype.pendingAssociation = function() {
    return this.state_.pendingAssociation;
  };

  InterfaceEndpointHandle.prototype.id = function() {
    return this.state_.interfaceId;
  };

  InterfaceEndpointHandle.prototype.groupController = function() {
    return this.state_.associatedGroupController;
  };

  InterfaceEndpointHandle.prototype.disconnectReason = function() {
    return this.state_.disconnectReason;
  };

  InterfaceEndpointHandle.prototype.setAssociationEventHandler = function(
      handler) {
    this.state_.setAssociationEventHandler(handler);
  };

  InterfaceEndpointHandle.prototype.notifyAssociation = function(interfaceId,
      peerGroupController) {
    return this.state_.notifyAssociation(interfaceId, peerGroupController);
  };

  InterfaceEndpointHandle.prototype.reset = function(reason) {
    this.state_.close(reason);
    this.state_ = new State();
  };

  internal.AssociationEvent = AssociationEvent;
  internal.InterfaceEndpointHandle = InterfaceEndpointHandle;
  internal.createPairPendingAssociation = createPairPendingAssociation;
})();
