/**
 * @projectDescription
 * @author John Bell john@novomancy.ANTISPAM.org
 * @version .1
 * @description Provides methods for dynamically loading packages.  Requires sniff.js
 */

function PackageLoader(){
/**
 * @constructor
 * @return {PackageLoader}
 * @type {Object}
 * @classDescription Loads .js and .css packages dynamially.  Also manages 
 * 		call timing so that functions called before their packages are loaded
 * 		will be stored until the required code has been downloaded.
 */
	this.config = {	/* Samples:
					jsDir: 'js/',
					cssDir: 'css/'
					*/
					};
	this.packages = {	/*  Samples:
						artPanel: ['ArtPanel.js', 'artPanel.css', 'artPanelPages.js.php'],
						personPanel: ['PersonPanel.js', 'personPanel.css', 'personPanelPages.js.php'],
						genericPanel: ['genericPanelPages.js.php']
						*/
					};
	
	/******** END CONFIG.  NOTHING BELOW THIS LINE NEED BE CHANGED **********/
	this.fileRegistry = new Array();
	this.packageRegistry = new Array();
	this.callbackQueue = new Array();
	

	//Methods
	this.registerFile = function(fileName){
	/**
	 * @method
	 * @description Registers a file.  This should be called at the end of all
	 * 		files that are part of a package.
	 * @param {string} fileName The name of the file being included.  Should match what is in the packages above.
	 */
		if(!defined(this.fileRegistry[fileName]) || this.fileRegistry[fileName] == false) this.fileRegistry[fileName] = true;
		for(var i=this.callbackQueue.length - 1; i>=0; i--){
			var callback = this.callbackQueue[i];
			var required = 0;
			for(var j=0; j<callback.required.length; j++){
				//alert(fileName+': '+callback.required[j]+' '+this.fileLoaded(callback.required[j]))
				if(this.fileLoaded(callback.required[j])) required++;
			}
			if(callback.required.length == required){
				this.callFunc(callback.func, callback.args);
				this.callbackQueue.splice(i,1);
			}
		}
	}

	this.fileLoaded = function(fileName){ 
		//for(var fn in this.fileRegistry) alert(fn)
		return defined(this.fileRegistry[fileName]) ? this.fileRegistry[fileName] : false; 
	}
	
	this.callFunc = function(func, args){
	/**
	 * @method
	 * @description Runs func with args passed to it, after splitting the args array out into individual variables
	 * @param {function} func The function to run
	 * @param {array} args The array of arguments to enumerate and pass to func
	 */
		var argsArray = new Array();
		for(var i=0; i<args.length; i++) argsArray.push('args['+i+']');
		var argStr = argsArray.join(',');
		eval('func('+argStr+')');
	}	
	
	this.require = function(packageName, callback, args){
	/**
	 * @method
	 * @description Runs the supplied callback function after packageName has been loaded.
	 * 		args should be an array of the arguments you want passed to callback, which should
	 * 		be an actual function and not just the name of a function you want eval'd.
	 * @param {string} packageName This is either the name of a package defined in this.packages or the name of a .js file
	 * @param {function} callback The function to run once everything in packageName is loaded
	 * @param {array} args The array of arguments to pass to callback
	 * @return {bool} Returns true if packageName is loaded and callback is immediately called, false otherwise.
	 */
		//if the package is already loaded, just run the function.
		if(!this.include(packageName)){
			this.callFunc(callback, args);
			return true;
		}
		//otherwise add the function to the queue (calling this.include above will have started the package loading.)
		var callbackArray = {func: callback, args: args};
		callbackArray.required = defined(this.packages[packageName]) ? this.packages[packageName] : [packageName+'.js'];
		this.callbackQueue.push(callbackArray);
		return false;	
	}
	
	this.include = function(packageName){
	/**
	 * @method
	 * @description Adds all files in packageName to the DOM.  If packageName is not defined
	 * 		in this.packages above, assume that there is just one .js file to be loaded named packageName.
	 * 		Files are loaded on a file-by-file basis, so overlap between packages is ok.
	 * @param {string} packageName This is either the name of a package defined in this.packages or the name of a .js file
	 * @return {bool} Returns false if the entire package is already loaded, true if it needs to load at least one file
	 */
		//Sort the package contents and determine if each file has already been loaded.
		var scriptSrc = [];
		var styleSrc = []
		
		if(!defined(this.packages[packageName])){
			if(!this.fileLoaded(packageName + '.js')) scriptSrc.push(packageName + '.js');
		} else {
			var pack = this.packages[packageName];
			for(var i=0; i<pack.length; i++){
				//alert(pack[i]+' loaded = '+this.fileLoaded(pack[i]));
				if(!this.fileLoaded(pack[i])){
					if(pack[i].slice(-4) != '.css') scriptSrc.push(pack[i]);
					else styleSrc.push(pack[i]);
				}
			}
		}
		
		//If there are no unloaded files in the package, just return
		if(scriptSrc.length == 0 && styleSrc.length == 0) return false;
		
		//Otherwise add the tags to the DOM so the files load
		for(var i=0; i<styleSrc.length; i++){
			var newStyle = document.createElement("link");
			newStyle.rel = 'stylesheet';
			newStyle.type = 'text/css';
			newStyle.href = this.config.cssDir + styleSrc[i];
			document.getElementsByTagName('head')[0].appendChild(newStyle);
			//css files are automatically considered loaded because js execution doesn't
			// have to wait for them.  They'll probably load first anyway since they
			// are added to the DOM before the scripts, but if not, their styles will just
			// be applied when they do finish downloading.
			this.fileRegistry[styleSrc[i]] = true;
		}
		for(var i=0; i<scriptSrc.length; i++){
			var newScript = document.body.appendChild(document.createElement('script'));
			newScript.language = 'javascript';
			newScript.type = 'text/javascript';
			newScript.src = this.config.jsDir + scriptSrc[i];
			this.fileRegistry[scriptSrc[i]] = false;			
		}
		return true;
	}

	this.init = function(){
	/**
	 * @method
	 * @description Initialization function to be called onload.  It scans for scripts/stylesheets
	 * 		that are attached through normal html tags and adds them to the registry.  This method assumes
	 * 		that all scripts reside in the directories defined in this.config, or that they at least
	 * 		have different names than any file in the config'd directories.
	 */
		var scripts = document.getElementsByTagName('script');
		for(var i=0; i<scripts.length; i++){
			if(defined(scripts[i].src) && scripts[i].src != ''){
				var nameArray = scripts[i].src.split('/');
				this.registerFile(nameArray.pop());
				
			}
		}
		var styles = document.getElementsByTagName('link');
		for(var i=0; i<styles.length; i++){
			if(defined(styles[i].href) && styles[i].href != ''){
				var nameArray = styles[i].href.split('/');
				this.registerFile(nameArray.pop());
			}
		}
	}
}

Loader = new PackageLoader();
browsers.addEventListener('load', window, function(){Loader.init.apply(Loader, []);}, false);
