/*

Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.

For licensing, see LICENSE.html or http://ckeditor.com/license

*/



/**

 * @fileOverview Defines the {@link CKEDITOR.scriptLoader} object, used to load scripts

 *		asynchronously.

 */



/**

 * Load scripts asynchronously.

 * @namespace

 * @example

 */

CKEDITOR.scriptLoader = (function()

{

	var uniqueScripts = {},

		waitingList = {};



	return /** @lends CKEDITOR.scriptLoader */ {

		/**

		 * Loads one or more external script checking if not already loaded

		 * previously by this function.

		 * @param {String|Array} scriptUrl One or more URLs pointing to the

		 *		scripts to be loaded.

		 * @param {Function} [callback] A function to be called when the script

		 *		is loaded and executed. If a string is passed to "scriptUrl", a

		 *		boolean parameter is passed to the callback, indicating the

		 *		success of the load. If an array is passed instead, two array

		 *		parameters are passed to the callback; the first contains the

		 *		URLs that have been properly loaded, and the second the failed

		 *		ones.

		 * @param {Object} [scope] The scope ("this" reference) to be used for

		 *		the callback call. Default to {@link CKEDITOR}.

		 * @param {Boolean} [showBusy] Changes the cursor of the document while

+		 *		the script is loaded.

		 * @example

		 * CKEDITOR.scriptLoader.load( '/myscript.js' );

		 * @example

		 * CKEDITOR.scriptLoader.load( '/myscript.js', function( success )

		 *     {

		 *         // Alerts "true" if the script has been properly loaded.

		 *         // HTTP error 404 should return "false".

		 *         alert( success );

		 *     });

		 * @example

		 * CKEDITOR.scriptLoader.load( [ '/myscript1.js', '/myscript2.js' ], function( completed, failed )

		 *     {

		 *         alert( 'Number of scripts loaded: ' + completed.length );

		 *         alert( 'Number of failures: ' + failed.length );

		 *     });

		 */

		load : function( scriptUrl, callback, scope, showBusy )

		{

			var isString = ( typeof scriptUrl == 'string' );



			if ( isString )

				scriptUrl = [ scriptUrl ];



			if ( !scope )

				scope = CKEDITOR;



			var scriptCount = scriptUrl.length,

				completed = [],

				failed = [];



			var doCallback = function( success )

			{

				if ( callback )

				{

					if ( isString )

						callback.call( scope, success );

					else

						callback.call( scope, completed, failed );

				}

			};



			if ( scriptCount === 0 )

			{

				doCallback( true );

				return;

			}



			var checkLoaded = function( url, success )

			{

				( success ? completed : failed ).push( url );



				if ( --scriptCount <= 0 )

				{

					showBusy && CKEDITOR.document.getDocumentElement().removeStyle( 'cursor' );

					doCallback( success );

				}

			};



			var onLoad = function( url, success )

			{

				// Mark this script as loaded.

				uniqueScripts[ url ] = 1;



				// Get the list of callback checks waiting for this file.

				var waitingInfo = waitingList[ url ];

				delete waitingList[ url ];



				// Check all callbacks waiting for this file.

				for ( var i = 0 ; i < waitingInfo.length ; i++ )

					waitingInfo[ i ]( url, success );

			};



			var loadScript = function( url )

			{

				if ( uniqueScripts[ url ] )

				{

					checkLoaded( url, true );

					return;

				}



				var waitingInfo = waitingList[ url ] || ( waitingList[ url ] = [] );

				waitingInfo.push( checkLoaded );



				// Load it only for the first request.

				if ( waitingInfo.length > 1 )

					return;



				// Create the <script> element.

				var script = new CKEDITOR.dom.element( 'script' );

				script.setAttributes( {

					type : 'text/javascript',

					src : url } );



				if ( callback )

				{

					if ( CKEDITOR.env.ie )

					{

						// FIXME: For IE, we are not able to return false on error (like 404).



						/** @ignore */

						script.$.onreadystatechange = function ()

						{

							if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' )

							{

								script.$.onreadystatechange = null;

								onLoad( url, true );

							}

						};

					}

					else

					{

						/** @ignore */

						script.$.onload = function()

						{

							// Some browsers, such as Safari, may call the onLoad function

							// immediately. Which will break the loading sequence. (#3661)

							setTimeout( function() { onLoad( url, true ); }, 0 );

						};



						// FIXME: Opera and Safari will not fire onerror.



						/** @ignore */

						script.$.onerror = function()

						{

							onLoad( url, false );

						};

					}

				}



				// Append it to <head>.

				script.appendTo( CKEDITOR.document.getHead() );



				CKEDITOR.fire( 'download', url );		// @Packager.RemoveLine

			};



			showBusy && CKEDITOR.document.getDocumentElement().setStyle( 'cursor', 'wait' );

			for ( var i = 0 ; i < scriptCount ; i++ )

			{

				loadScript( scriptUrl[ i ] );

			}

		}

	};

})();

