/**
============================================================ $Keywords ======
  Unit Name     ttApp.js
  Environment   ttScript
  Project       Tutelar Script
					 $AddProject: script $

  Copyright     (c) 2003 - 2005 Tutelar Technologies Inc.

  Author        ejd

  Create Date   May 2003

  -------------------------------------------------------------------------
  Description

* Common functions for Tutelar script applications
* This file should be included in all main html files using:
*     <script src='ttApp.js'></script>
*
* @package       Ujigami
* @subpackage    ttScript
* @version       1.1
* @author        ejd <ejd@watchover.us>
* @copyright     (c) 2005 Tutelar Technologies Inc.
============================================================ $NoKeywords ==*/

if (typeof(ttApp) == 'undefined') {   // make sure this code is only run once

/**
 * Regular expression that returns the directory and filename portions of a path */
splitFilePath = /((?:[^\/\\\?]*[\/\\])*)(([^\.\?]+)\.?([^\?]*))(\?[^#]*)?(#.*)?/;

/**
 * Allows a script function or method to return a reference to itself
 */
function getFunctionSelf()
{
	return getFunctionSelf.caller;
};

/**
 * Make sure these are defined for conditional debugging sections */
if (typeof(ttDebugging) == 'undefined') {
	ttDebugging = false;
}

// NOTE: Namespaces are created using closures as described here: http://www.davidflanagan.com/blog/2005_07.html
/**
 * Application namespace */
ttApp = {};

(function() { // create namespace closure
/*@
	@if (@ttDebugging)
	// Uncomment next line to debug this script file:
//      @set @ttDebuggingScript = true;
	@end
@*/

// Variable used to calculate load time of page - leave this definition here
	var _scriptStartLoadTime = new Date();

/*@
	@if (@ttDebuggingScript)
		DebugPoint('ttScript.js: Loading and initializing scripts');
		DebugMsg('ttScript.js: Application ' + window.document.readyState);
	@end
@*/


	ttApp.globalErrorHandler = function(msg, url, line)
	{
		alert('* * *   Script Error   * * *\r\n\r\nError: ' + msg + '\r\nURL:   ' + url + '\r\nLine:  ' + line +
//            '\r\nApp:   ' + document.URL +
				'\r\n\r\nPlease see www.tutelartechnologies.com for more information.');
		return false;
	};

	ttApp.removeGlobalErrorHandler = function()
	{
		window.onerror = null;
	};

	ttApp.originalPageName = function(pn)
	{
		if (pn)
			_originalPageName = pn;
		return _originalPageName;
	};

	ttApp.documentPath = function(dp)
	{
		if (dp !== undefined) {
			path = '' + path;
			if (path) {
				var ch = path.charAt(path.length - 1);
				if (ch && ch != '\\' && ch != '/')
					path += '/';
			}
			_documentPath = path;
		}
		return _documentPath;
	};

	ttApp.documentHead = function()
	{
		if (_documentHead !== undefined) {
			var els = document.getElementsByTagName('head');
			if (els.length > 0)
				_documentHead = [0];
		}
		return _documentHead;
	};

	ttApp.displayLoadTime = function(val) {
		_displayLoadTime = val;
	};

	ttApp.showScripts = function()
	{
		var s = '' + document.scripts.length + '=';
		if (!ttDebugging)
			var result = '';
		for (var i = 0; i < document.scripts.length; i++) {
			var script = document.scripts(i);
//         s += GetScriptDetails(script);
			if (ttDebugging)
				ttDebug.dumpVar(script.src);
			else
				result += srcript.src;
		}
		if (!ttDebugging)
			alert(result);
	};

	ttApp.getScript = function(name)
	{
		for (var i = 0; i < document.scripts.length; i++) {
			var s = document.scripts(i);
			if (splitFilePath.exec(s.src)[2] == name)
				return s;
		}
		return null;
	};

/**
 * Returns true if the HTML document and all references are fully loaded */
	ttApp.isDocumentReady = function() {
		return (window.document.readyState == 'complete');
	};

/**
 * Sets the location to look for included scripts
 * This is automatically set based on the location of this script */
	ttApp.scriptPath = function(loc)
	{
		if (loc !== undefined) {
			loc = '' + loc;
			if (loc) {
				var ch = loc.charAt(loc.length - 1);
				if (ch && ch != '\\' && ch != '/')
					loc += '/';
			}
			_scriptPath = loc;
		}
		return _scriptPath;
	};

/**
 * Adds an initialization function that is called after IsDocumentReady() */
	ttApp.addInitFn = function(fn)
	{
/*@
		@if (@ttDebuggingScript)
			DebugMsg('ttScript.js: Added init function ' + _getFunctionName(fn) + '()');
		@end
@*/
		_initFns.push(fn);
		if (_running)
			_performInitFns();
	};

/**
 * Adds a startup function that is called after IsDocumentReady() and the InitFns are called */
	ttApp.addStartupFn = function(fn)
	{
		_startupFns.push(fn);
		if (_running)
			_performStartupFns();
	};

/**
 * Adds a finalization function that is called as the page is being unloaded */
	ttApp.addFinalFn = function(fn)
	{
		_finalFns.push(fn);
	};

/**
 * Includes a script file into the browser's document space */
	ttApp.includeScript = function(fn, path)
	{
		var parts = splitFilePath.exec(fn);
		var fnName = parts[2] + ';';
		var sn = parts[3];
		if (!path && parts[1]) {
			path = parts[1];
			fn = parts[2];
		}
		if ((_includedScripts.indexOf(fnName) < 0) && (_loadedScripts.indexOf(fnName) < 0)) {
/*@
			@if (@ttDebuggingScript)
				DebugMsg('ttScript.js: Including script ' + fn);
			@end
@*/
			_includedScripts += fnName;
			_numIncludedScriptsOutstanding++;
			_needToInitScripts.push(sn);

			try {
				var doc = document.documentElement;
				if (doc) {
					var el = document.createElement('SCRIPT');
					doc.insertBefore(el, doc.firstChild);
//               el.onerror = _onScriptLoadError;   - NOT supported
					if (typeof(el.onreadystatechange) != 'undefined') {     // IE
						el.onreadystatechange = _scriptLoaded;
					} else {
						ttApp._initAllScripts = true;
					}
					el.src = (path ? path : ttApp.scriptPath()) + fn;
				} else {
//               document.writeln('<scr' + 'ipt type="text/javascript" src="' + (path ? path : scriptPath()) + fn + '" onreadystatechange="ttApp._scriptLoaded()"></scr' + 'ipt>');
					alert('ERROR: dynamic script loading is not supported by this Browser. Please update your software.');
				}
			}
			catch(e) {
/*@
				@if (@ttDebuggingScript)
					DebugAlert('Error including script ' + fn + (path ? ' from ' + path : '') + ' - ' + e.description);
				@end
@*/
				alert('Error including script ' + fn + (path ? ' from ' + path : '') + ' - ' + e.description);
			}
		} else {
			if (!_running) {  //  make sure scripts are initialized in the correct order
				/** @todo: re-arrange scripts included by these scripts too
				  * e.g. B include C, A include B - final init sequence s/b C, B, A NOT B, A, C which this scheme creates */
				for (var i in _needToInitScripts) {
					if (_needToInitScripts[i] == sn) {
						delete _needToInitScripts[i];
						break;
					}
				}
				_needToInitScripts.push(sn);
			}
		}
	};

/**
 * Returns a string containing all scripts that have been included */
	ttApp.getIncludedScripts = function()
	{
		return _loadedScripts;
	};

/**
 * Checks to see if a script is loaded and ready to run */
	ttApp.isScriptLoaded = function(fn)
	{
		return _loadedScripts.indexOf(fn + ';') >= 0;
	};

/**
 * Imports a behaviour and associates it with at namespace */
	ttApp.importNamespace = function(nameSpace, url)
	{
		try {
	//      document.write('<?import namespace="' + nameSpace + '" implementation="' + GetScriptPath() + url + '" >');
			document.namespaces(nameSpace).doImport(ttApp.scriptPath() + url);
		}
		catch(e) {
			alert('Error importing ' + url + ' into namespace ' + nameSpace);
		}
	};

/* ------------------------  Internal Functions ------------------------ */

/**
 * Variables used to time the loading & initialization of the page */
	var _scriptFinishedLoadTime = 0;
	var _displayLoadTime = false;
	var _documentHead = null;
	var _running = false;

	var _originalPageName = '';

	var _documentPath = splitFilePath.exec(document.URL)[1];

/**
 * Indicates if Tutelar debugging is active (i.e. ttDebug.js is loaded)
 * Default is set here to false in case ttDebug.js is not loaded */

	var _initFns = new Array();
	var _appInitFn = null;
	var _startupFns = new Array();
	var _appStartupFn = null;
	var _finalFns = new Array();
	var _appFinalFn = null;
	var _needToInitScripts = new Array();
	var _includedScripts = '';
	var _numIncludedScriptsOutstanding = 0;
	var _loadedScripts = '';
	var _scriptPath = '';
/**
 * Determines when a script is actually loaded and ready to run */
	function _scriptLoaded(event)
	{
		if (!event)
			event = window.event;

		var el = event.target || event.srcElement;
/*@
		@if (@ttDebuggingScript)
			DebugMsg('ttScript.js: ' + el.src + ' script loading ' + el.readyState);
		@end
@*/
		var rs = el.readyState;
		if (rs == 'complete' || rs == 'loaded') {
			var fn = splitFilePath.exec(el.src);
			var fs = fn[2] + ';';

//         with (ttApp) {
				if (_includedScripts.indexOf(fs) >= 0) {
					_includedScripts = _includedScripts.replace(fs, '');
					_loadedScripts += fs;
					_numIncludedScriptsOutstanding--;

					if (_running)
						_addSpecialFns(fn[3]);
//               else
//                  _needToInitScripts.push(fn[3]);
				}
//         }
			el.onerror = null;
			el.onreadystatechange = null;
		}
	}

	function _addSpecialFns(fn)
	{
/*@
		@if (@ttDebuggingScript)
			DebugMsg('ttScript.js: Adding special fns for ' + fn);
		@end
@*/
		var fPtr;
		try { // see if a {scriptFn}Initialization fn is defined and add it to the InitFns list
			fPtr = eval(fn + 'Initialization');
			if (typeof(fPtr) == 'function')
				ttApp.addInitFn(fPtr);
		}
		catch(e) {
		}

		try { // see if a {scriptFn}Startup fn is defined and add it to the StartupFns list
			fPtr = eval(fn + 'Startup');
			if (typeof(fPtr) == 'function')
				ttApp.addStartupFn(fPtr);
		}
		catch(e) {
		}

		try { // see if a {scriptFn}Finalization fn is defined and add it to the FinalFns list
			fPtr = eval(fn + 'Finalization');
			if (typeof(fPtr) == 'function')
				ttApp.addFinalFn(fPtr);
		}
		catch(e) {
		}
	}

	function _processNeedToInitScripts()
	{
/*@
		@if (@ttDebuggingScript)
			DebugMsg('ttScript.js: Processing needToInitScripts');
		@end;
@*/
		var fn;
		while (_needToInitScripts.length) {
			fn = _needToInitScripts.pop();   // ensures scripts are initialized based on include order - i.e. scripts included by others are initialized first
			if (fn) {
				_addSpecialFns(fn);
			}
		}
	}

	function _onScriptLoadError(msg, url, line)
	{
/*@
		@if (@ttDebuggingScript)
			DebugMsg('ttScript.js: Script Load Error: ' + msg + '<br/>URL  ' + url + '</br>Line ' + line);
		@end
@*/
		if (!event)
			event = window.event;

		var el = event.target || event.srcElement;
		el.onerror = null;
		el.onreadystatechange = null;
	}

/**
 * Waits for DocumentReady then calls the InitFns and StartupFns */
	function _initApp(event)
	{
/*@
		@if (@ttDebuggingScript)
			DebugMsg('ttScript.js: Application ' + window.document.readyState);
		@end
@*/

		if (document.all && !ttApp.isDocumentReady())
			return;
/*@
		@if (@ttDebuggingScript)
			DebugMsg('ttScript.js: Initializing application');
		@end;
@*/
		var fn = splitFilePath.exec(_originalPageName)[3];
		try {
			_appInitFn = eval(fn + 'Initialization');
		}
		catch(e) {
		}
		try {
			_appStartupFn = eval(fn + 'Startup');
		}
		catch(e) {
		}
		try {
			_appFinalFn = eval(fn + 'Finalization');
		}
		catch(e) {
		}

		var missingScripts;
		if (_numIncludedScriptsOutstanding)
			missingScripts = _includedScripts.substr(0, _includedScripts.length - 1);

		_processNeedToInitScripts();
		_running = true;
		_performInitFns();
		_performStartupFns();
		_scriptFinishedLoadTime = new Date();

		if (missingScripts && document.all) /** @todo: add code to check this in FireFox */
			alert('WARNING\n\nThe following script(s) failed to load:          \n\n' +  missingScripts);

		if (ttDebugging || _displayLoadTime) {
			var br = '\n';
			var bs = '';
			var be = '';
			if (ttDebugging) {
				DebugPoint('Page Info');
				br = '<br/>\n';
				bs = '<b>';
				be = '</b>';
			}
			var jsVer = '';
/*@

			@if (@_jscript)
				jsVer = 'JScript Version ' + @_jscript_version + ' Build ' + @_jscript_build;
			@end
@*/
			var s= 'Document Location: ' + bs + document.location + be + br +
					 'Original Page Name: ' + bs + ttApp.originalPageName() + be + br +
					 'Document Path: ' + bs + ttApp.documentPath() + be + br + br +
					 'Page loaded in ' + (_scriptFinishedLoadTime.valueOf() - _scriptStartLoadTime.valueOf()) + ' mSec on ' + _scriptStartLoadTime + br + br + jsVer;
			if (ttDebugging)
				DebugMsg(s);
			else
				alert(s);
			if (ttDebugging)
				DebugPoint();
		}
	}

/**
 * Calls the InitFns */
	function _performInitFns()
	{
		if (/*!IsDocumentReady()*/!_running)
			return;
		var fn;
//      with (this) {
			try {
				while (_initFns.length) {
					fn = _initFns.shift();
	/*@
					@if (@ttDebuggingScript)
						DebugMsg('ttScript.js: Calling init fn ' + __getFunctionName(fn) + '()');
					@end
	@*/
					fn();
					if (fn == _appInitFn)   //make sure page/application initialization function is not called twice
						_appInitFn = null;
				}
				// see if a {htmlPage}Initialization fn is defined and run it last
				if (typeof(_appInitFn) == 'function') {
	/*@
					@if (@ttDebuggingScript)
						DebugMsg('ttScript.js: Calling html page initialization fn ' + __getFunctionName(_appInitFn) + '()');
					@end
	@*/
					fn = _appInitFn;
					_appInitFn = null;  // make sure this can't get called twice
					fn();
				}
			}
			catch(e) {
				alert('WARNING: failed to perform all initialization functions.\n\n' + e.description);
			}
					/* Make sure ttComponents are created/initialized at the right time - between Init and Startup functions */
			if ((typeof(Components) != 'undefined') && (typeof(Components.initialize) == 'function')) {
	/*@
				@if (@ttDebuggingScript)
					DebugMsg('ttScript.js: Initializing components');
				@end
	@*/
				Components.initialize();
			}
//      }
	}

/**
 * Calls the StartupFns */
	function _performStartupFns()
	{
		if (/*!IsDocumentReady()*/!_running)
			return;
		var fn;
//      with (this) {
			try {
				while (_startupFns.length) {
					fn = _startupFns.shift();
	/*@
					@if (@ttDebuggingScript)
						DebugMsg('ttScript.js: Calling startup fn ' + __getFunctionName(fn) + '()');
					@end
	@*/
					if (fn == _appStartupFn)   //make sure page/application startup function is not called twice
						_appStartupFn = null;
					fn();
				}
				// see if a {htmlPage}Startup fn is defined and run it last
				if (typeof(_appStartupFn) == 'function') {
	/*@
					@if (@ttDebuggingScript)
						DebugMsg('ttScript.js: Calling html page startup fn ' + __getFunctionName(_appStartupFn) + '()');
					@end
	@*/
					fn = _appStartupFn;
					_appStartupFn = null;  // make sure this can't get called twice
					fn();
				}
			}
			catch(e) {
				alert('WARNING: failed to perform all startup functions.\n\n' + e.description);
			}
//      }
	}

/**
 * Calls the FinalFns */
	function _finalizeApp()
	{
		if (document.all && !ttApp.isDocumentReady())
			return;
		var fn;
//      with (ttApp) {
			try {
				if (typeof(_appFinalFn) == 'function') {
	/*@
					@if (@ttDebuggingScript)
						DebugMsg('ttScript.js: Calling html page finalization fn ' + __getFunctionName(_appFinalFn) + '()');
					@end
	@*/
					fn = _appFinalFn;
					_appFinalFn = null;  // make sure this can't get called twice
					fn();
				}
				while (_finalFns.length) {
					fn = _finalFns.pop();     // perform in reverse order
					if (fn != _appFinalFn) {
	/*@
						@if (@ttDebuggingScript)
							DebugMsg('ttScript.js: Calling finalization fn ' + __getFunctionName(fn) + '()');
						@end
	@*/
						fn();
					}
				}
			}
			catch(e) {
				alert('WARNING: failed to perform all finalization functions.\n\n' + e.description);
			}
			window.onerror = null;    //ttApp.globalErrorHandler;   // use common method for all browsers

			window.document.onreadystatechange = null;

			window.onbeforeunload = null;

			window.onload = null;   // FireFox
			window.onunload = null;
	/*@
			@if (@ttDebuggingScript)
				DisplayAlertMsg('Application finished');
			@end
	@*/
//      }
	}

/**
 * Sets the default script path based on the path of this script */
	function _determineScriptPath()
	{
		var scripts = document.getElementsByTagName('script');
		for (var i = 0; i < scripts.length; i++) {
			var scrpt = scripts[i];
			if (scrpt.src) {
				var fn = splitFilePath.exec(scrpt.src);
				if (fn[2] == 'ttApp.js') {
					ttApp.scriptPath(fn[1]);
					return;
				}
			}
		}
	}

	// Namespace Initialization

	window.onerror = ttApp.globalErrorHandler;

	if (!_originalPageName) {  // calculate if this is not specified
		var metaTags = document.getElementsByTagName('meta');    // see if specified in a meta tag
		for (var i = 0; i < metaTags.length; i++) {
			var metaTag = metaTags[i];
			if (metaTag.name.toLowerCase() == 'originalpagename') {
				_originalPageName = metaTag.content;
				break;
			}
		}
		if (!_originalPageName)    // no, so calculate from the document location
			_originalPageName = document.URL;
		metaTags = null;  // allow GC
	}
	_originalPageName = splitFilePath.exec(_originalPageName)[2];  //strip off any path

	if (document.all) {
		window.document.onreadystatechange = _initApp;
		window.onbeforeunload = _finalizeApp;
	} else {
		window.onload = _initApp;
		window.onunload = _finalizeApp;
	}

	_determineScriptPath();

})(); // end namespace closure


}

