/**
 * EGroupware clientside API object
 *
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package etemplate
 * @subpackage api
 * @link http://www.egroupware.org
 * @author Andreas Stöckel (as AT stylite.de)
 * @version $Id$
 */

/*egw:uses
	egw_core;
	egw_utils;
	egw_debug;
*/

/**
 * @augments Class
 * @param {string} _app application name object is instanciated for
 * @param {object} _wnd window object is instanciated for
 */
egw.extend('ready', egw.MODULE_WND_LOCAL, function(_app, _wnd)
{
	"use strict";

	var egw = this;

	var registeredCallbacks = [];
	var registeredProgress = [];
	var readyPending = {'readyEvent': true};
	var readyPendingCnt = 1;
	var readyDoneCnt = 0;
	var isReady = false;

	function doReadyWaitFor() {
		if (!isReady)
		{
			var uid = egw.uid();
			readyPending[uid] = true;
			readyPendingCnt++;

			readyProgressChange();

			return uid;
		}

		this.debug('warning', 'ready has already been called!');

		return null;
	}

	function doReadyDone(_token) {
		if (typeof readyPending[_token] !== 'undefined')
		{
			delete readyPending[_token];
			readyPendingCnt--;
			readyDoneCnt++;

			readyProgressChange();

			testCallReady();
		}
	}

	function readyProgressChange()
	{
		// Call all registered progress callbacks
		for (var i = 0; i < registeredProgress.length; i++)
		{
			registeredProgress[i].callback.call(
				registeredProgress[i].context,
				readyDoneCnt,
				readyPendingCnt
			);
		}

		egw.debug('log', 'Ready events, processed %s/%s', readyDoneCnt,
				readyPendingCnt + readyDoneCnt);
	}

	function readyEventHandler() {
		doReadyDone('readyEvent');
	}

	function testCallReady()
	{
		// Check whether no further event is pending
		if (readyPendingCnt <= 1 && !isReady)
		{
			// If exactly one event is pending and that one is not the ready
			// event, abort
			if (readyPendingCnt === 1 && !readyPending['readyEvent'])
			{
				return;
			}

			// Set "isReady" to true, if readyPendingCnt is zero
			var isReady = readyPendingCnt === 0;

			// Call all registered callbacks
			for (var i = registeredCallbacks.length - 1; i >= 0; i--)
			{
				if (registeredCallbacks[i].before || readyPendingCnt === 0)
				{
					registeredCallbacks[i].callback.call(
						registeredCallbacks[i].context
					);

					// Delete the callback from the list
					registeredCallbacks.splice(i, 1);
				}
			}
		}
	}

	// Register the event handler for "ready" (code adapted from jQuery)

	// Mozilla, Opera and webkit nightlies currently support this event
	if (_wnd.document.addEventListener) {
		// Use the handy event callback
		_wnd.document.addEventListener("DOMContentLoaded", readyEventHandler, false);

		// A fallback to window.onload, that will always work
		_wnd.addEventListener("load", readyEventHandler, false);

	// If IE event model is used
	} else if (_wnd.document.attachEvent) {
		// ensure firing before onload,
		// maybe late but safe also for iframes
		_wnd.document.attachEvent("onreadystatechange", readyEventHandler);

		// A fallback to window.onload, that will always work
		_wnd.attachEvent("onload", readyEventHandler);
	}

	return {

		/**
		 * The readyWaitFor function can be used to register an event, that has
		 * to be marked as "done" before the ready function will call its
		 * registered callbacks. The function returns an id that has to be
		 * passed to the "readDone" function once
		 *
		 * @memberOf egw
		 */
		readyWaitFor: function() {
			return doReadyWaitFor();
		},

		/**
		 * The readyDone function can be used to mark a event token as
		 * previously requested by "readyWaitFor" as done.
		 *
		 * @param _token is the token which has now been processed.
		 */
		readyDone: function(_token) {
			doReadyDone(_token);
		},

		/**
		 * The ready function can be used to register a function that will be
		 * called, when the window is completely loaded. All ready handlers will
		 * be called exactly once. If the ready handler has already been called,
		 * the given function will be called defered using setTimeout.
		 *
		 * @param _callback is the function which will be called when the page
		 * 	is ready. No parameters will be passed.
		 * @param _context is the context in which the callback function should
		 * 	get called.
		 * @param _beforeDOMContentLoaded specifies, whether the callback should
		 * 	get called, before the DOMContentLoaded event has been fired.
		 */
		ready: function(_callback, _context, _beforeDOMContentLoaded) {
			if (!isReady)
			{
				registeredCallbacks.push({
					'callback': _callback,
					'context': _context ? _context : null,
					'before': _beforeDOMContentLoaded ? true : false
				});
			}
			else
			{
				setTimeout(function() {
					_callback.call(_context);
				}, 1);
			}
		},

		/**
		 * The readyProgress function can be used to register a function that
		 * will be called whenever a ready event is done or registered.
		 *
		 * @param _callback is the function which will be called when the
		 * 	progress changes.
		 * @param _context is the context in which the callback function which
		 * 	should get called.
		 */
		readyProgress: function(_callback, _context) {
			if (!isReady)
			{
				registeredProgress.unshift({
					'callback': _callback,
					'context': _context ? _context : null
				});
			}
			else
			{
				this.debug('warning', 'ready has already been called!');
			}
		},

		/**
		 * Returns whether the ready events have already been called.
		 */
		isReady: function() {
			return isReady;
		}
	};

});