/**
 * $Id::                                                                       $
 * 
 * This file creates the Phase4-namespace for JavaScript-objects.
 * Most classes will need script.aculo.us, so make sure it is loaded!
 * Same goes for Prototype!
 * Note: We are not able to provide a namespace-loader (a Script.aculo.us does).
 * 		 For some reason, FireFox complains that the Phase4-Object does not
 *       exist when we try to use dynamic script-loading. Thus, make sure to
 *       load all needed resources in your document.head!!!! 
 * 
 * @copyright 2008 Phase 4
 * @version 1.0
 * @link $URL::                                                                $
 * @author $Author::                                                           $
 * @package Phase4 
 */

/**
 * The base-object of our namespace.
 * Technically there are no static objects in JS, but this being a
 * variable holding an anonymous object, we can speak of a static class
 * containing static methods & datamembers.
 * 
 * @static
 */
var Phase4 = {
	/*
	 *  Datamembers
	 */
	
	/**
	 * The version of this piece of art
	 * 
	 * @staticvar String
	 */
	Version: '1.0.0',
	
	/*
	 * Exceptions
	 */
	
	/**
	 * Thrown when a class is requested that is not registered
	 * 
	 * @staticvar Exception
	 */
	UNKNOWN_CLASS_EXCEPTION: 'Class "%s" is not registed',
	
	/**
	 * Thrown when a subpackage is requested that is not registered
	 * 
	 * @staticvar Exception
	 */
	UNKNOWN_SUBPACKAGE_EXCEPTION: 'Subpackage "%s" is not known',
	
	/*
	 * Private methods
	 */
	
	/**
	 * Tries to guess the path to the file where this object resides
	 * 
	 * @return {String} The path if  it could be found, empty string otherwise
	 * @private
	 */
	_getCurrentPath: function() {
		var scripts = $A(document.getElementsByTagName('script')).collect(function(s) { return s.src; });
		var path = scripts.grep(/phase4\.js/);
		
		if (path.length) {
			return path[0].replace(/phase4\.js.*$/, '');
		} else {
			return '';
		}
	},
	
	/**
	 * Used to late bind classes into namespace.
	 * Tries to load a script-file from our own directory.
	 * Performs a check whether class is already loaded before anything else.
	 * Note: BROKEN CODE!!!
	 * 
	 * @param {String} subpackage The subpackage in which the specified class resides
	 * @param {String} className The name of the class that shall be loaded
	 * @param {bool} [isCallback] Set by internal call while waiting for scriptfile to be loaded
	 * @return {bool} True if class could be loaded, false otherwise
	 * @private
	 */
	_loadClass: function(subpackage, className, isCallback) {
		//if class is present, we return true
		//otherwise, if this is the callback, we return false
		if (Phase4[subpackage] && Phase4[subpackage][className]) {
			return true;
		} else if (isCallback) {
			return false;
		}
		
		//still here, so class is not there, let's try to fetch the current path
		var path = this._getCurrentPath();
		if (!path) return false;			//no path, no cookies (so to say ^^)
		
		//time to load that stuff
		//if subpackage does not exist, load that one
		//otherwise load class
		if (!Phase4[subpackage]) {
			document.write('<script language="JavaScript" type="text/javascript" src="' + path + subpackage.toLowerCase() + '.js"></script>');
		} else {
			document.write('<script language="JavaScript" type="text/javascript" src="' + path + className.toLowerCase() + '.js"></script>');
		}
		
		//and return what this function returns after a second
		return Phase4._loadClass.delay(1, subpackage, className, true);
	},
	
	/* 
	 * Public methods
	 */
	
	/**
	 * Checks whether subpackage.class is registered in our namespace and returns
	 * an instance of it.
	 * Tries to load the class if not present
	 * 
	 * @throws UNKNOWN_SUBPACKAGE_EXCEPTION if subpackage is not registered
	 * @throws UNKNOWN_CLASS_EXCEPTION exception if subpackage.class is not registered
	 * 
	 * @param {String} subpackage The subpackage that our class resides in
	 * @param {String} className The name of the class to instatiate 
	 * @param {Any} [...] The parameters that will be passed to the classes constructor
	 * @return {Object} An instance of the desired class
	 */
	getInstanceOf: function(subpackage, className) {
		var args = $A(arguments).slice(2);
		
		//if subpackage or class does not exist, we try to load it
		/* Disabled, as loading fails to work!
		if (!Phase4[subpackage] || !Phase4[subpackage][className]) {
			debugPrint('[getInstanceOf] Trying to load class...');
			Phase4._loadClass(subpackage, className);
			debugPrint('might be loaded\n')
		}*/
		
		//check for subpackage
		if (!Phase4[subpackage]) {
			var exception = Phase4.UNKNOWN_SUBPACKAGE_EXCEPTION.replace(/%s/, subpackage);
			throw(exception);
		}
		
		//check for class in subpackage
		if (!Phase4[subpackage][className]) {
			var exception = Phase4.UNKNOWN_CLASS_EXCEPTION.replace(/%s/, className);
			throw(exception);
		}
		
		//still here, so let's return an instance by temporarily
		//subclassing the desired class
		return new (Class.create(Phase4[subpackage][className], {
			initialize: function($super) {
				$super.apply(this, args);
			}
		}))();
	},
	
	/**
	 * Checks whether an exception is of a specific type
	 * 
	 * @param {String} exception The exception that was caught
	 * @param {String} exceptionType The exceptions type that shall be tested against
	 * @return {bool} True if exception is of specified type, false otherwise
	 */
	isExceptionOfType: function(exception, exceptionType) {
		//TODO: implement this
		return false;
	},

	/**
	 * Registers classes into our namespace.
	 * Additionally creates the desired subpackage if it does not already exist.
	 *
	 * @param {String} subpackage The name of the subpackage in which the classes will reside 
	 * @param {Array} classNames The names of the classes that we need to register
	 * @return void
	 * @static
	 */
	registerClasses: function(subpackage, classNames) {
		/* ensure that second parameter is an array */
		if (!(classNames instanceof Array)) {
			classNames = new Array(classNames);
		}
		
		/* just in case subpackage does not already exist, we create it */
		if (!Phase4[subpackage]) Phase4[subpackage] = {};
		
		/* great and now let's register those classes */
		for (var i=0; i<classNames.length; ++i) {
			var className = classNames[i];
			Phase4[subpackage][className] = Class.create();
		}
	},

	/**
	 * Returns the namespace as newline-separated string.
	 * 
	 * @return {String} The namespace as newline-separated string
	 */
	toString: function() {
		return this.traceNamespace().join('\n');
	},
	
	/**
	 * Traces the namespace using a DFS-algorithm.
	 * 
	 * @return {Array} A sorted array containing all subpackages and classes registered in this namespace
	 */	
	traceNamespace: function() {
		function isPackageOrClass (thingy) {
			/*
			 * if thingy is an object, it is very likely to be a (sub-)package
			 * otherwise it is a class if it is a function and has the function 'addMethods'
			 */
			if (typeof thingy == 'object') {
				return true;
			}
			else {
				return ((typeof thingy == 'function') && (thingy.addMethods));
			} 
		}
		
		function recurseOnNode(node, prefix) {
			var retArray = new Array();
			
			for (var child in node) {
				if (!isPackageOrClass(node[child])) continue;	//skip entries that are neither package nor class
				if (!child.match(/^[A-Z]{1}/)) continue;		//skip help-classes
				retArray.push(prefix + '.' + child);
				retArray.push(recurseOnNode(node[child], prefix + '.' + child));
			}
			
			return retArray;
		}
		
		return $A(recurseOnNode(Phase4, "Phase4")).flatten().sort();
	}
};