﻿/**
* PeriodicalUpdater - jQuery plugin for timed, decaying ajax calls
*
* http://www.360innovate.co.uk/blog/2009/03/periodicalupdater-for-jquery/
* http://enfranchisedmind.com/blog/posts/jquery-periodicalupdater-ajax-polling/
*
* Copyright (c) 2009 by the following:
* Frank White (http://customcode.info)
* Robert Fischer (http://smokejumperit.com)
* 360innovate (http://www.360innovate.co.uk)
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Version: 3.0
*
*/

(function($)
{
	// Now back to our regularly scheduled work
	$.PeriodicalUpdater = function(url, options, callback)
	{
		var settings = jQuery.extend(true, {
			url: url, // URL of ajax request
			cache: false, // By default, don't allow caching
			method: 'GET', // method; get or post
			data: '', // array of values to be passed to the page - e.g. {name: "John", greeting: "hello"}
			minTimeout: 1000, // starting value for the timeout in milliseconds
			maxTimeout: 8000, // maximum length of time between requests
			multiplier: 2, // if set to 2, timerInterval will double each time the response hasn't changed (up to maxTimeout)
			maxCalls: 0, // maximum number of calls. 0 = no limit.
			autoStop: 0 // automatically stop requests after this many returns of the same data. 0 = disabled
		}, options);

		// set some initial values, then begin
		var timer = null;
		var timerInterval = settings.minTimeout;
		var maxCalls = settings.maxCalls;
		var autoStop = settings.autoStop;
		var calls = 0;
		var noChange = 0;

		var reset_timer = function(interval)
		{
			if (timer != null)
			{
				clearTimeout(timer);
			}
			timerInterval = interval;
			timer = setTimeout(getdata, timerInterval);
		}

		// Function to boost the timer
		var boostPeriod = function()
		{
			if (settings.multiplier > 1)
			{
				before = timerInterval;
				timerInterval = timerInterval * settings.multiplier;

				if (timerInterval > settings.maxTimeout)
				{
					timerInterval = settings.maxTimeout;
				}
				after = timerInterval;
				reset_timer(timerInterval);
			}
		};

		// Construct the settings for $.ajax based on settings
		var ajaxSettings = jQuery.extend(true, {}, settings);
		if (settings.type && !ajaxSettings.dataType) ajaxSettings.dataType = settings.type;
		if (settings.sendData) ajaxSettings.data = settings.sendData;
		ajaxSettings.type = settings.method; // 'type' is used internally for jQuery. Who knew?
		ajaxSettings.ifModified = true;

		// Create the function to get data
		// TODO It'd be nice to do the options.data check once (a la boostPeriod)
		function getdata()
		{
			var toSend = jQuery.extend(true, {}, ajaxSettings); // jQuery screws with what you pass in
			if (typeof (options.data) == 'function')
			{
				toSend.data = options.data();
				if (toSend.data)
				{
					// Handle transformations (only strings and objects are understood)
					if (typeof (toSend.data) == "number")
					{
						toSend.data = toSend.data.toString();
					}
				}
			}

			if (maxCalls == 0)
			{
				$.ajax(toSend);
			} else if (maxCalls > 0 && calls < maxCalls)
			{
				$.ajax(toSend);
				calls++;
			}
		}

		// Implement the tricky behind logic
		var remoteData = null;
		var prevData = null;

		ajaxSettings.success = function(data)
		{
			remoteData = data;
			// timerInterval = settings.minTimeout;
		};

		ajaxSettings.complete = function(xhr, success)
		{
			if (maxCalls == -1) return;
			if (success == "success" || success == "notmodified")
			{
				var rawData = $.trim(xhr.responseText);
				if (rawData == 'STOP_AJAX_CALLS')
				{
					maxCalls = -1;
					return;
				}
				if (prevData == rawData)
				{
					if (autoStop > 0)
					{
						noChange++;
						if (noChange == autoStop)
						{
							maxCalls = -1;
							return;
						}
					}
					boostPeriod();
				} else
				{
					noChange = 0;
					reset_timer(settings.minTimeout);
					prevData = rawData;
					if (remoteData == null) remoteData = rawData;
					if (ajaxSettings.dataType == 'json')
					{
						remoteData = JSON.parse(rawData);
					}
					if (settings.success) { settings.success(remoteData, success, xhr); }
					if (callback) callback(remoteData, success, xhr);
				}
			}
			remoteData = null;
		}

		ajaxSettings.error = function(xhr, textStatus)
		{
			if (textStatus == "notmodified")
			{
				boostPeriod();
			} else
			{
				prevData = null;
				reset_timer(settings.minTimeout);
			}
			if (settings.error) { settings.error(xhr, textStatus); }
		};

		// Make the first call
		$(function() { reset_timer(timerInterval); });

		var handle = {
			stop: function()
			{
				maxCalls = -1;
				return;
			}
		};
		return handle;
	};
})(jQuery);
