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

/**
 * @fileoverview Provides a watchdog around a collection of callback functions.
 */
'use strict';

/**
 * Creates a watchdog around a collection of callback functions,
 * ensuring at least one of them is called before the timeout expires.
 * If a timeout function is provided, calls the timeout function upon timeout
 * expiration if none of the callback functions has been called.
 * @param {number} timeoutValueSeconds Timeout value, in seconds.
 * @param {function()=} opt_timeoutCb Callback function to call on timeout.
 * @constructor
 * @implements {Closeable}
 */
function WatchdogRequestHandler(timeoutValueSeconds, opt_timeoutCb) {
  /** @private {number} */
  this.timeoutValueSeconds_ = timeoutValueSeconds;
  /** @private {function()|undefined} */
  this.timeoutCb_ = opt_timeoutCb;
  /** @private {boolean} */
  this.calledBack_ = false;
  /** @private {Countdown} */
  this.timer_ = FACTORY_REGISTRY.getCountdownFactory().createTimer(
      this.timeoutValueSeconds_ * 1000, this.timeout_.bind(this));
  /** @private {Closeable|undefined} */
  this.closeable_ = undefined;
  /** @private {boolean} */
  this.closed_ = false;
}

/**
 * Wraps a callback function, such that the fact that the callback function
 * was or was not called gets tracked by this watchdog object.
 * @param {function(...?)} cb The callback function to wrap.
 * @return {function(...?)} A wrapped callback function.
 */
WatchdogRequestHandler.prototype.wrapCallback = function(cb) {
  return this.wrappedCallback_.bind(this, cb);
};

/** Closes this watchdog. */
WatchdogRequestHandler.prototype.close = function() {
  this.closed_ = true;
  this.timer_.clearTimeout();
  if (this.closeable_) {
    this.closeable_.close();
    this.closeable_ = undefined;
  }
};

/**
 * Sets this watchdog's closeable.
 * @param {!Closeable} closeable The closeable.
 */
WatchdogRequestHandler.prototype.setCloseable = function(closeable) {
  this.closeable_ = closeable;
};

/**
 * Called back when the watchdog expires.
 * @private
 */
WatchdogRequestHandler.prototype.timeout_ = function() {
  if (!this.calledBack_ && !this.closed_) {
    var logMsg = 'Not called back within ' + this.timeoutValueSeconds_ +
        ' second timeout';
    if (this.timeoutCb_) {
      logMsg += ', calling default callback';
      console.warn(UTIL_fmt(logMsg));
      this.timeoutCb_();
    } else {
      console.warn(UTIL_fmt(logMsg));
    }
  }
};

/**
 * Wrapped callback function.
 * @param {function(...?)} cb The callback function to call.
 * @param {...?} var_args The callback function's arguments.
 * @private
 */
WatchdogRequestHandler.prototype.wrappedCallback_ = function(cb, var_args) {
  if (!this.closed_) {
    this.calledBack_ = true;
    this.timer_.clearTimeout();
    var originalArgs = Array.prototype.slice.call(arguments, 1);
    cb.apply(null, originalArgs);
  }
};
