/**
 * @name    jFrame
 * @version 1.2.0
 * @description
 * A simple, lightweight framework resting atop jQuery to assist in lazy-loading,
 * enforcing convention, organization, namespacing, script caching and automatic 
 * execution when the DOM is ready.
 * 
 * @date      2009-07-05
 * @author    Trey Shugart
 * @copyright Copyright (c) 2009 Trey Shugart (shugartweb.com/jquery/)
 * @license
 * MIT - (http://www.opensource.org/licenses/mit-license.php) 
 * GPL - (http://www.gnu.org/licenses/gpl.txt)
 */
;(function($) {
	
	var 
		// hold instances
		_instances     = {},
		_dispatchStack = [],
		// holds configuration for instances
		_config        = {},
		// default configuration for each instance
		_defaultConfig = {
			controllerPath : '/js/jFrame/',
			cache          : false,
			onReady        : true
		},
		// references to loaded files
		_loaded        = [],
		// cache of loaded files
		_cache         = {},
		_registry      = {};
	
	
	
	/**
	 * @name        jFrame
	 * @description The global jFrame constructor
	 * @return      Object jFrame
	 */
	window.jFrame = function(config, instanceName) {
		// if accessed statically, return false
		if (!this instanceof jFrame) {
			return false;
		}
		
		// so we can reference 'this' out of context
		var self = this;
		
		// if no instance name was set, set it to 'default'
		if (typeof instanceName !== 'string')
			instanceName = 'default';
		
		// reference the instance
		_instances[instanceName] = self;
		
		// holds the parameters set for a dispatch call
		_params = {};
		
		
		
		/*
		 * Config methods are assigned here to use the instanceName so we
		 * don't have to tell the config methods which instance we want
		 * grab the config for. It could be set as a property, but we only
		 * want private access to it.
		 */
		
		/**
		 * @name        setConfig
		 * @description Sets a configuration variable and doing any necessary
		 *              normalization of config variables.
		 * @return      Object jFrame
		 */
		this.setConfig = function(key, val) {
			// if the first argument isn't an object, make it one
			// we do this so we can pass a hash as well as a string 
			// key and value
			if (!typeof key === 'object')
				key = {
					key : val
				};
			
			// normalize the script path
			if (typeof key['controllerPath'] !== 'undefined')
				key['controllerPath'] = _normalizePath(key['controllerPath']);
			
			// set the config
			_config[instanceName] = $.extend(_config[instanceName], key);
			
			return self;
		};
		
		/**
		 * @name        getConfig
		 * @description Gets a configuration variable.
		 * @return      Mixed
		 */
		this.getConfig = function(key) {
			return _config[instanceName][key];
		};
		
		/**
		 * 
		 */
		this.setParam = function(key, val) {
			_params[key] = val;
			
			return this;
		}
		
		/**
		 * 
		 */
		this.getParam = function(key) {
			return _params[key];
		}
		
		/**
		 * 
		 */
		this.getInstanceName = function() {
			return instanceName;
		};
		
		/**
		 * @name        dispatch
		 * @description Dispatches a call for path relative to the root set in the config excluding the .js extension.
		 * @return      Object jFrame
		 */
		this.dispatch = function(routes, params, onError) {
			var 
				cache  = self.getConfig('cache'),
				routes = $.isArray(routes) ? routes : [routes];
			
			// iterate over each route and dispatch in the order given
			$.each(routes, function(index, route) {
				var file = self.getConfig('controllerPath') + '/' + (route || 'index') + '.js';
				
				// automate the jQuery DOM ready event
				if (self.getConfig('onReady')) {
					jQuery(function($) {
						_execute();
					});
				// otherwise just execute it
				} else {
					_execute();
				}
				
				
				
				// executes the loaded file
				function _execute() {
					// add the parameters for this dispatch call if they exists
					if (typeof params === 'object') {
						for (var i in params) {
							self.setParam(i, params[i]);
						}
					}
					
					// add this to the dispatch stack
					_dispatchStack[_dispatchStack.length] = self;
					
					// if the load was successful
					if (!jFrame.load(file, cache) && typeof onError === 'function') {
						onError.apply(self);
					}
					
					// remove it from the dispatch stack
					_dispatchStack.splice(_dispatchStack.length - 1, 1);
					
					// clear the current dispatch params
					_params = {};
				}
			});
			
			return self;
		};
		
		
		
		// extend the default config with the passed config and assign it to
		// this instance
		self.setConfig($.extend(_defaultConfig, config));
		
		return self;
	};
	
	
	
	/**
	 * 
	 */
	jFrame.getActiveInstance = function() {
		return _dispatchStack[_dispatchStack.length - 1];
	};
	
	/**
	 * 
	 */
	jFrame.getDispatchStack = function() {
		return _dispatchStack;
	};
	
	/**
	 * @name        getInstance
	 * @description Returns the specified instance of jFrame.
	 * @return      Object jFrame
	 */
	jFrame.getInstance = function(instanceName) {
		// default instance name
		if (typeof instanceName !== 'string')
			instanceName = 'default';
		
		// return the stored instance
		return _instances[instanceName];
	};
	
	/**
	 * @name        isLoaded
	 * @description Checks to see if a file has been loaded yet.
	 * @return      Boolean
	 * @param       String file - The file to check if it was loaded.
	 */
	jFrame.isLoaded = function(file) {
		return jQuery.inArray(file, _loaded) > -1;
	};
	
	/**
	 * 
	 */
	jFrame.load = function(file, cache) {
		var 
			self = this,
			ret  = false;
		
		// if we are caching and it's in cache, return that
		// notice, that an empty file will return undefined,
		// so we check here to see if it's been loaded only
		if (jFrame.isLoaded(file)) {
			eval(_cache[file]);
			
			return true;
		}
		
		// make a syncrhonious request because we want to have access
		// to the js right after loading without having to use a callback
		jQuery.ajax({
			async      : false,
			cache      : cache,
			dataType   : 'script',
			global     : false,
			success    : function(data, textStatus) {
				_loaded[_loaded.length] = file;
				_cache[file]            = data;
				
				ret = true;
			},
			url        : file
		});
		
		// return the function
		return ret;
	};
	
	
	
	jFrame.registry = {
		get : function(key) {
			return _registry[key];
		},
		
		set : function(key, val) {
			_registry[key] = val;
			
			return jFrame.registry;
		}
		
	};
	
	
	
	/**
	 * 
	 */
	function _camelCase(str, ucFirst) {
		var strs = str.split(/[^a-z^0-9^_]/);
		
		$.each(strs, function(i) {
			strs[i] = strs[i].charAt(0).toUpperCase() + strs[i].substring(1);
		});
		
		var str = strs.join('');
		
		if (!ucFirst)
			str = str.charAt(0).toLowerCase() + str.substring(1);
		
		return str;
	};
	
	/**
	 * @name        _normalizePath
	 * @description Normalizes the input path and returns it.
	 * @return      String
	 */
	function _normalizePath(path) {
		// trim whitespace
		path = $.trim(path);
		
		// nomralize directory separators
		path = path.replace(/\\/g, '/');
		
		// trim off starting forward slash
		if (path.charAt(0) === '/') {
			path = path.substring(1)
		}
		
		// trim off ending forward slash
		if (path.charAt(path.length - 1) === '/') {
			path = path.substring(0, path.length - 1)
		}
		
		return path;
	};
	
})(jQuery);