// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// 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.

/**
 * @fileoverview A pool of forward channel requests to enable real-time
 * messaging from the client to server.
 *
 * @visibility {:internal}
 */


goog.provide('goog.labs.net.webChannel.ForwardChannelRequestPool');

goog.require('goog.array');
goog.require('goog.string');
goog.require('goog.structs.Set');

goog.scope(function() {
/** @suppress {missingRequire} type checking only */
var ChannelRequest = goog.labs.net.webChannel.ChannelRequest;



/**
 * This class represents the state of all forward channel requests.
 *
 * @param {number=} opt_maxPoolSize The maximum pool size.
 *
 * @constructor
 * @final
 */
goog.labs.net.webChannel.ForwardChannelRequestPool = function(opt_maxPoolSize) {
  /**
   * THe max pool size as configured.
   *
   * @private {number}
   */
  this.maxPoolSizeConfigured_ = opt_maxPoolSize ||
      goog.labs.net.webChannel.ForwardChannelRequestPool.MAX_POOL_SIZE_;

  /**
   * The current size limit of the request pool. This limit is meant to be
   * read-only after the channel is fully opened.
   *
   * If SPDY is enabled, set it to the max pool size, which is also
   * configurable.
   *
   * @private {number}
   */
  this.maxSize_ = ForwardChannelRequestPool.isSpdyEnabled_() ?
      this.maxPoolSizeConfigured_ :
      1;

  /**
   * The container for all the pending request objects.
   *
   * @private {goog.structs.Set<ChannelRequest>}
   */
  this.requestPool_ = null;

  if (this.maxSize_ > 1) {
    this.requestPool_ = new goog.structs.Set();
  }

  /**
   * The single request object when the pool size is limited to one.
   *
   * @private {ChannelRequest}
   */
  this.request_ = null;
};

var ForwardChannelRequestPool =
    goog.labs.net.webChannel.ForwardChannelRequestPool;


/**
 * The default size limit of the request pool.
 *
 * @private {number}
 */
ForwardChannelRequestPool.MAX_POOL_SIZE_ = 10;


/**
 * @return {boolean} True if SPDY is enabled for the current page using
 *     chrome specific APIs.
 * @private
 */
ForwardChannelRequestPool.isSpdyEnabled_ = function() {
  return !!(
      goog.global.chrome && goog.global.chrome.loadTimes &&
      goog.global.chrome.loadTimes() &&
      goog.global.chrome.loadTimes().wasFetchedViaSpdy);
};


/**
 * Once we know the client protocol (from the handshake), check if we need
 * enable the request pool accordingly. This is more robust than using
 * browser-internal APIs (specific to Chrome).
 *
 * @param {string} clientProtocol The client protocol
 */
ForwardChannelRequestPool.prototype.applyClientProtocol = function(
    clientProtocol) {
  if (this.requestPool_) {
    return;
  }

  if (goog.string.contains(clientProtocol, 'spdy') ||
      goog.string.contains(clientProtocol, 'quic') ||
      goog.string.contains(clientProtocol, 'h2')) {
    this.maxSize_ = this.maxPoolSizeConfigured_;
    this.requestPool_ = new goog.structs.Set();
    if (this.request_) {
      this.addRequest(this.request_);
      this.request_ = null;
    }
  }
};


/**
 * @return {boolean} True if the pool is full.
 */
ForwardChannelRequestPool.prototype.isFull = function() {
  if (this.request_) {
    return true;
  }

  if (this.requestPool_) {
    return this.requestPool_.getCount() >= this.maxSize_;
  }

  return false;
};


/**
 * @return {number} The current size limit.
 */
ForwardChannelRequestPool.prototype.getMaxSize = function() {
  return this.maxSize_;
};


/**
 * @return {number} The number of pending requests in the pool.
 */
ForwardChannelRequestPool.prototype.getRequestCount = function() {
  if (this.request_) {
    return 1;
  }

  if (this.requestPool_) {
    return this.requestPool_.getCount();
  }

  return 0;
};


/**
 * @param {ChannelRequest} req The channel request.
 * @return {boolean} True if the request is a included inside the pool.
 */
ForwardChannelRequestPool.prototype.hasRequest = function(req) {
  if (this.request_) {
    return this.request_ == req;
  }

  if (this.requestPool_) {
    return this.requestPool_.contains(req);
  }

  return false;
};


/**
 * Adds a new request to the pool.
 *
 * @param {!ChannelRequest} req The new channel request.
 */
ForwardChannelRequestPool.prototype.addRequest = function(req) {
  if (this.requestPool_) {
    this.requestPool_.add(req);
  } else {
    this.request_ = req;
  }
};


/**
 * Removes the given request from the pool.
 *
 * @param {ChannelRequest} req The channel request.
 * @return {boolean} Whether the request has been removed from the pool.
 */
ForwardChannelRequestPool.prototype.removeRequest = function(req) {
  if (this.request_ && this.request_ == req) {
    this.request_ = null;
    return true;
  }

  if (this.requestPool_ && this.requestPool_.contains(req)) {
    this.requestPool_.remove(req);
    return true;
  }

  return false;
};


/**
 * Clears the pool and cancel all the pending requests.
 */
ForwardChannelRequestPool.prototype.cancel = function() {
  if (this.request_) {
    this.request_.cancel();
    this.request_ = null;
    return;
  }

  if (this.requestPool_ && !this.requestPool_.isEmpty()) {
    goog.array.forEach(
        this.requestPool_.getValues(), function(val) { val.cancel(); });
    this.requestPool_.clear();
  }
};


/**
 * @return {boolean} Whether there are any pending requests.
 */
ForwardChannelRequestPool.prototype.hasPendingRequest = function() {
  return (this.request_ != null) ||
      (this.requestPool_ != null && !this.requestPool_.isEmpty());
};


/**
 * Cancels all pending requests and force the completion of channel requests.
 *
 * Need go through the standard onRequestComplete logic to expose the max-retry
 * failure in the standard way.
 *
 * @param {function(!ChannelRequest)} onComplete The completion callback.
 * @return {boolean} true if any request has been forced to complete.
 */
ForwardChannelRequestPool.prototype.forceComplete = function(onComplete) {
  if (this.request_ != null) {
    this.request_.cancel();
    onComplete(this.request_);
    return true;
  }

  if (this.requestPool_ && !this.requestPool_.isEmpty()) {
    goog.array.forEach(this.requestPool_.getValues(), function(val) {
      val.cancel();
      onComplete(val);
    });
    return true;
  }

  return false;
};
});  // goog.scope
