/**
 * @name throttler.js
 * @fileOverview This file has functions related to documenting the Throttler
 * utility object.
 
Copyright 2009 Palm, Inc.  All rights reserved.

*/

/**
 * @Class
 * @description The Throttler object will throttle requests that it receives.
*/
Throttler = Class.create({
	/** @public */
	initialize: function(callback, normalTime, slowTime, backoffTime) {
		//console.log("*************************** Throttler.initialize()");

		// Handlers:
		this.doRequestHandler = this.doRequest.bind(this);
		this.resetBackoffHandler = this.resetBackoff.bind(this);

		// Throttling parameters:
		this.minSamplePeriod = normalTime || 1500;  // in ms
		this.slowSamplePeriod = slowTime || 30000;  // in ms
		this.backoffTime = backoffTime || 5000;     // in ms
		this.currentSamplePeriod = this.minSamplePeriod;

		this.callback = callback;

		// Runtime state:
		this.isEnabled = false;
		this.hasPendingRequest = false;
		this.hasSentRequest = false;
		this.scheduledRequest = null;
		this.scheduledBackoffReset = undefined;
		this.needBackoff = false;

		/* @Debug
		this.targetRequestTime = 0;
		this.requestedTime = 0;
		// @End Debug*/
	},

	/** @public */
	// We will want to skip the backoff in the cases where notifySentRequest()
	// is called during initial subscriptions or during recovery from failures.
	// However, we'll let the app decide if that is meaningful.
	// We should probably not skipp the back off in the interactive case because
	// we don't want throttled events to get in the way of interactive events.
	notifySentRequest: function(skipBackoff) {
		//console.log("*************************** Throttler.notifySentRequest()");
		this.hasSentRequest = true;
		/* @Debug
		this.requestedTime = Date.now();
		// @End Debug */
		if (!skipBackoff) {
			this.needBackoff = true;
			// The backoff needs to be reset.  If we have a rest scheduled, cancel
			// it.  It will get rescheduled in notifyRequestFulfilled() later.
			if (this.scheduledBackoffReset !== undefined) {
				window.clearTimeout(this.scheduledBackoffReset);
				this.scheduledBackoffReset = undefined;
			}
		}
		if (this.scheduledRequest) {
			// If we already have an event scheduled, let's reset it a litte longer.
			// It will get rescheduled in notifyRequestFulfilled() later.
			this.cancelRequest();
		}
	},
	/** @public */
	notifyRequestFulfilled: function() {
		//console.log("*************************** Throttler.notifyRequestFulfilled()");
		/* @Debug
		var roundTripTime = Date.now() - this.requestedTime;
		console.log("THROTTLER RECEIVED DATA @ " + Date.now() + " roundTrip= " + roundTripTime);
		// @End Debug */

		this.hasSentRequest = false; // The request has been fulfilled.
		if (this.needBackoff) {
			// Just in case the user does not want a backoff period (i.e. sets it to 0),
			// we should still not do the reset any sooner than the currentSamplePeriod
			// because the reset will fire off any pending requests immediately.
			// The only case when we don't need a backoff is when both the app requested
			// backoffTime and samplePeriod are both 0.
			if (this.scheduledBackoffReset === undefined) {
				var backoffTime = Math.max(this.backoffTime, this.currentSamplePeriod);
				this.scheduledBackoffReset = this.resetBackoffHandler.delay(backoffTime /1000);
			}

		// Only re-schedule the pending request here if we're not in a backoff mode.
		// Otherwise, it will get re-scheduled in resetBackoff() later.
		} else if (this.hasPendingRequest) {
			this.request();
		}
	},

	/** @public */
	enable: function() {
		//console.log("*************************** Throttler.enable()");
		this.isEnabled = true;
		if (this.hasPendingRequest) {
			this.request(true);
		}
	},
	/** @public */
	disable: function() {
		//console.log("*************************** Throttler.disable()");
		if (this.scheduledRequest) {
			this.cancelRequest();
			this.scheduledRequest = undefined;
		}
		this.isEnabled = false;
	},
	/** @public */
	maximize: function() {
		//console.log("*************************** Throttler.maximize()");
		this.currentSamplePeriod = this.minSamplePeriod; // Get data as fast as we can.

		// If we have new data that is waiting to be processed, then get it immediately
		// because we're maximizing the screen and need up to date data:
		if (this.hasPendingRequest) {
			this.cancelRequest(); // Don't wait.  Request data immediately.
			this.request(true);
		}
	},
	/** @public */
	minimize: function() {
		//console.log("*************************** Throttler.minimize()");
		this.currentSamplePeriod = this.slowSamplePeriod; // Get data leisurely.
	},

	/** @public */
	request: function(doImmediately, backoffTime) {
		//console.log("*************************** Throttler.request()");
		if (doImmediately) {
			if (this.scheduledRequest) {
				this.cancelRequest();  // This replaces any previously scheduled requests.
			}
			this.doRequest();
			return;
		}

		this.hasPendingRequest = true;

		if (!this.isEnabled || this.hasSentRequest || this.needBackoff) {
			/* @Debug
		    console.log("THROTTLER PURGED REQUEST enabled= " + this.isEnabled +
						" hasSentRequest= " + this.hasSentRequest);
			// @End Debug */
			return;
		}

		// Check to see if we have already scheduled a request.  If not, then schedule one:
		if (!this.scheduledRequest) {
			this.scheduledRequest =
				this.doRequestHandler.delay(this.currentSamplePeriod / 1000);
			/* @Debug
			this.targetRequestTime = Date.now() + this.currentSamplePeriod;
			console.log("THROTTLER SCHEDULED REQUEST @ " + this.targetRequestTime + " in " + this.currentSamplePeriod);
		} else {
			var currentTime = Date.now();
			console.log("THROTTLER COLLATED REQUEST @ " + currentTime +
						" wait " + (this.targetRequestTime - currentTime) +
						" till " + this.targetRequestTime);
			// @End Debug */
		}
	},

	/** @private */
	resetBackoff: function() {
		//console.log("*************************** Throttler.resetBackoff()");
		this.needBackoff = false;

		if (this.hasPendingRequest) {
			this.request(true);
		}
	},
	/** @private */
	cancelRequest: function() {
		//console.log("*************************** Subscriber.cancelRequest()");
		/* @Debug
		console.log("THROTTLER CANCELLED REQUEST @ " + this.targetRequestTime);
		// @End Debug */
		window.clearTimeout(this.scheduledRequest);
		this.scheduledRequest = undefined;
	},
	/** @private */
	doRequest: function() {
		//console.log("*************************** Throttler.doRequest()");
		this.callback();

		/* @Debug
		this.requestedTime = Date.now();
		// @End Debug */

		this.scheduledRequest = undefined; // Clear the request.
		this.hasSentRequest = true;
		this.hasPendingRequest = false;
	}
});
