/**
 * Load scripts in other languages for compilation.
 * 
 * == Description ==
 * LoadForCompile will retrieve alternate-language source code and pass it (in order) to `onLoad`. Any sources that cannot be retrieved will be ignored by default.
 * 
 * If the `onFinish` argument is provided, it will be called when all sources have been loaded and passed.
 * 
 * If the `onError` argument is provided, it will be called with `(src, k)`, where `src` is the offending URL and `k` is a continuation of the loading process, so the relying client may continue or entirely halt the loading process by calling `k` or not calling it, respectively.
 * 
 * == Usage ==
 * 1. Place <script> elements in your HTML document with desired MIME type.
 * 2. Each <script> element may have a `src` attribute, an inline body between the opening and closing tags, or both. The body is *guaranteed* to be called immediately after the associated remote source, or not at all.
 * 3. Finally, call LoadForCompile with the MIME type and callbacks.
 * 
 * == Specification ==
 * LoadForCompile searches the DOM for any <script> nodes bearing the target MIME type. For each one, it records the src attribute and the contents of the text node, if any. It is an error to have non-text elements as children of <script>, and in such a situation the behavior of this library is unspecified.
 * 
 * The library uses asynchronous requests to load any remote resources specified by `src` attributes. This loading occurs in an unspecified order. If any remote source fails to load, the library will mark that source (and any associated inline body) as invalid. If `onError` is provided, it will be called with a continuation function that will resume progress. (If the continuation is not called, no additional callbacks will be made.)
 * 
 * Note that if `onError` is provided and does not call the continuation, `onFinish` will not be called.
 * 
 * @author      timmc
 * @author-url  http://www.brainonfire.net/
 * @license     LGPL
 * @version     0.2
 */
(function _init_LoadForCompile() {

/**
 * Generic file-grab with async XHR. onSuccess and onFailure are called with
 * request object. A non-200 status code is considered to be a failure.
 */
function asyncLoad(href, onSuccess, onFailure) {
	var req = new XMLHttpRequest();
	req.onreadystatechange = function xhrUpdate() {
		if(req.readyState !== 4) {
			return;
		}
		if(req.responseText === null || req.status !== 200) {
			if(onFailure) {
				onFailure(req);
			}
			return;
		} else {
			if(onSuccess) {
				onSuccess(req);
			}
			return;
		}
	};
	req.open('GET', href, true);
	req.send(null);
}

/** Partially apply any extra arguments to the provided function. */
function curry(f)
{
	var args = Array.prototype.slice.call(arguments, 1);
	return function()
	{
		return f.apply(this, args.concat(Array.prototype.slice.call(arguments)));
	};
};


/**
 * Return {src:string|null, body:string|null} for the given script element.
 */
function extractSource(el, i, all) {
	var src, body;
	
	src = el.getAttribute('src');
	if(typeof src === 'undefined') {
		src = null;
	}
	
	body = null;
	if(el.childNodes.length > 0) {
		body = el.textContent;
	}
	
	return {src:src, body:body};
}

/**
 * Gather scripts of the given MIME type, load their source texts, and pass to
 * onLoad in the order they are listed in the page. If a script fails to load,
 * onError is called (if provided) with a continuation argument. When all
 * sources are loaded, onFinish is called (if provided).
 */
function LoadForCompile(type, onLoad, onFinish, onError) {

	/** Return true if element has appropriate MIME type. */
	function isAppropriate(el, i, all) {
		return el.getAttribute('type') === type;
	}

	//Gather sources
	var sources = Array.slice(document.getElementsByTagName('script'))
	                   .filter(isAppropriate)
	                   .map(extractSource);
	
	/** Currently waiting on this index of the desired sources list. */
	var waitingAt = 0;
	
	/** Attempt to advance waiting pointer towards end of list. */
	function tryAdvance() {
		while(waitingAt < sources.length) {
			// Are we blocked? == not ready and can't ignore
			if(!sources[waitingAt].ready && !sources[waitingAt].ignore) {
				return;
			}
			
			// Advance and check again
			waitingAt++;
		}

		finished();
	}
	
	/** Call when all sources have been loaded. */
	function finished() {
		for(srcDex in sources) {
			var src = sources[srcDex];
			if(!src.ignore) {
				if(src.src !== null) {
					setTimeout(curry(onLoad, src.src), 0);
				}
				if(src.body !== null) {
					setTimeout(curry(onLoad, src.body), 0);
				}
			}
		}
		if(onFinish) {
			setTimeout(onFinish, 0);
		}
	}
	
	/** How many loads are waiting for onError to call back. */
	var errorWaits = 0;
	
	// Rip through and attempt to load all sources
	sources.forEach(function(cur, dex, all) {
		if(cur.src === null) {
			cur.ready = true;
			return;
		}

		function onSuccess(xhr) {
			cur.src = xhr.responseText;
			cur.ready = true;
			if(dex === waitingAt) {
				tryAdvance();
			}
		}
		
		var resumed = false;
		function onFailure(xhr) {
			// Ignore this source
			cur.ignore = true;
			
			if(!onError) {
				tryAdvance();
				return;
			}
			
			function ifIgnored() {
				// do not resume this continuation twice!
				if(resumed) { 
					return;
				}
				resumed = true;
				
				// Reduce block count
				errorWaits--;
				if(errorWaits > 0) {
					return;
				}
				
				// Last continuation to resume calls this.
				tryAdvance();
			};
			return onError(cur.src, ifIgnored);
		}

		asyncLoad(cur.src, onSuccess, onFailure);
	});
	
	tryAdvance();
}


// export bindings
window.LoadForCompile = LoadForCompile;

})();
