// Provides the CHRONOS Syndicated Widget Library (CSWL) and
// various useful functions.

// Gets a DOM element by ID
function $(id) {
	if (document.getElementById) {
		return document.getElementById(id);
	} else if (document.layers) {
		return document.layers[id];
	} else if (document.all) {
		return document.all.id;
	}
}

// Gets an array of DOM elements by class name
function $$(clazz) {
    var tags;
    var elms = [];
    
    // get all the tags in the document
    if (document.getElementById) {
        tags = document.getElementsByTagName("*")
    } else if (document.layers) {
        tags = document.layers;
    } else if (document.all) {
        tags = document.all;
    }
    
    // now walk through them and check the class name
    for (i = 0; i < tags.length; i++) {
        if (tags[i].className.indexOf(clazz) > -1) {
            elms.push(tags[i]);   
        } else if (tags[i].id == clazz) {
        	elms.push(tags[i]);
        }
    }
    
    // return the elements
    return elms;
}

/**
 * @name: cswl.js
 * @description: The main CHRONOS Syndicated Widgets Library (CSWL).
 * @param: cswl_debug   - turn on debugging messages                                   [true]
 * @param: cswl_verbose - turn on verbose debugging messages (those generated by cswl) [true]
 */
if (typeof cswl == 'undefined') {
	cswl = {
		// internal variables
		_UNIQUE: 0,
		_pending: [],
		_stylesheets: [],
		_messages: [],
		_loaded: false,
		_namespaces: [],
		
		// holds the request parameters as an associative array
		parameters: {},
		
		// our loading message
		LOADING_MSG: '<img src="http://206.205.251.11:9090/xqe/syndicated/images/loading_small.gif" style="border: 0px solid black"/>',
		
		// Initializes the CSWL library by setting the loaded flag true, including any pending scripts
		// and sending any pending debug messages.  This function is INTERNAL and is not intended 
		// to be called directly.
		_init: function() {
			// set loaded flag true
			this._loaded = true;
			if (cswl_verbose) { this.debug('cswl._init()'); }
			
			// include any pending styleseehts
			for(var i = 0; i < this._stylesheets.length; i++) {
				this.includeStyle(this._stylesheets[i]);
			}
			this._stylesheets = [];
			
			// include any pending scripts
			for(var i = 0; i < this._pending.length; i++) {
				this.include(this._pending[i]);
			}
			this._pending = [];
			
			// write any pending debug messages
			for(var i = 0; i < this._messages.length; i++) {
				this.debug(this._messages[i]);
			}
			this._messages = [];
		},
		
		// Gets a unique id string.
		unique: function() {
			this._UNIQUE++;
			return 'cswl_' + this._UNIQUE;
		},
		
		// Write debug messages out to the page.
		debug: function(message) {
			// check to see if debug is set
			if (!cswl_debug) {
				return;
			}
			
			// check to see if we're loaded
			if (!this._loaded) {
				this._messages.push(message);
			} else {
				// create a new div and attach it to the body
				var div = document.createElement('div');
				div.style.fontSize = '10pt';
				div.innerHTML = 'DEBUG: ' + message;
				document.body.appendChild(div);
			}
		},
		
		// Replaces parameters in of the form %{foo} in strings.
		//   str        - the string to replace parameters in.
		//   pairs      - the parameters and replacements as an associative array.
		//   escapeVals - optional flag that indicates whether the parameter values should be escaped before replacement.
		//   killExtra  - optional flag that indicates whether non-replaced parameters should be removed.
		paramString: function(str, pairs, escapeVals, killExtra) {
			for(var name in pairs) {
				var re = new RegExp("\\%\\{" + name + "\\}", "g");
				if (escapeVals) {
					str = str.replace(re, escape(pairs[name]));
				} else {
					str = str.replace(re, pairs[name]);
				}
			}
	
			if(killExtra) { str = str.replace(/%\{([^\}\s]+)\}/g, ""); }
			return str;
		},
		
		// Checks to see if the parameter object has all the parameters.
		hasAllParameters: function(params) {
			var hasAll = true;
			for (var p in params) {
				if (!(eval('typeof cswl.parameters.' + params[p]) != 'undefined')) {
					hasAll = false;
				} 
			}
			return hasAll;
		},
		
		// Trims leading and trailing spaces from a string.
		trim: function(str) {
	       return str.replace(/^[\s\n]*|[\s\n]*$/g,"");
	    },
		
		// Registers a widget namespace, serviceUrl, and resourceUrl with the CSWL.
		//   ns         - the namespace to use.
		//   widgetsUrl - the widgets URL.
		//   config      - an optional object containing additional config options.
		registerNamespace: function(ns, widgetsUrl, config) {
			var entry = {};
			if (typeof config == 'undefined') {
				entry = {"namespace": ns, "widgetsUrl": widgetsUrl};
			} else {
				entry = config;
				entry.namespace = ns;
				entry.widgetsUrl = widgetsUrl; 
			}
			this._namespaces.push(entry);
			if (cswl_verbose) { this.debug('Registered namespace: ' + entry.namespace + ' with widgetsUrl: ' + entry.widgetsUrl); }
		},
		
		// Registers a widget with the CSWL library.
		registerWidget: function(widget) {
			var split = widget.split('.');
			var foo = '';
			for (var i = 0; i < split.length; i++) {
				if (i > 0) {
					foo += '.';
				}
				foo += split[i];
				if (eval('typeof ' + foo) == 'undefined') {
					if (cswl_verbose) { this.debug('Creating namespace [' + foo + ']'); }
					eval(foo + ' = {}');
				}
			}
		},
		
		// Gets the widgets URL for a particular widget namespace.
		getWidgetsUrl: function(widget) {
			if (!widget) { this.debug('getWidgetUrl: No widget passed!'); }
			for (var i = 0; i < this._namespaces.length; i++) {
				var entry = this._namespaces[i];
				if (widget.indexOf(entry.namespace) == 0) {
					return entry.widgetsUrl;
				}
			}
			this.debug('No namespace registered for widget "' + widget + '"');
			return null;
		},
		
		// Gets a config parameter for a particular widget namespace.
		getConfigParameter: function(widget, param) {
			// get our configuration entry
			var entry = cswl.getNamespaceEntry(widget);
			if (entry) {
				return entry[param];
			}
		},
		
		// Gets the namespace entry for a particular widget namespace
		getNamespaceEntry: function(widget) {
			if (!widget) { this.debug('getNamespaceEntry: No widget passed!'); }
			for (var i = 0; i < this._namespaces.length; i++) {
				var entry = this._namespaces[i];
				if (widget.indexOf(entry.namespace) == 0) {
					return entry;
				}
			}
			this.debug('No namespace registered for widget "' + widget + '"');
			return null;
		},
		
		// Adds a script to the document dynamically.
		include: function(src, queue) {
			// check to see if the body is loaded
			// if not, then save the request in the pending list
			if (!this._loaded) {
				if (cswl_verbose) { this.debug('Body not loaded, adding ' + src + ' to the pending list'); }
				this._pending.push(src);
				return;
			}
			
			// allow for different queues
			var delay = 100;
			if (typeof queue != 'undefined') {
				if (queue == 'fast') {
					delay = 100;
				}
				if (queue == 'medium') {
					delay = 250;
				}
				if (queue == 'slow') {
					delay = 500;
				}
			}
			
			// stochasticly include the script
			//delay = Math.floor(Math.random() * delay); 
			if (cswl_verbose) { cswl.debug('Including ' + src + ' after ' + delay + 'ms'); }
			window.setTimeout('cswl._include("' + src + '");', delay);
		},
		
		_include: function(src) {
			// create the script
			var script = document.createElement('script');
			script.id = this.unique();
			script.setAttribute('type', 'text/javascript');
			script.setAttribute('src', src);
			
			// attach the script to the head
			var head = document.getElementsByTagName('head')[0];
			head.appendChild(script);
			
			// try appending to the body
			//var body = document.body || document.getElementsByTagName('body')[0];
			//body.appendChild(script);
			
			// debug
			if (cswl_verbose) { this.debug('Adding script ' + script.src + ' with id [' + script.id + ']'); }
		},
		
		// Includes a widget.
		//   widget       - the id of the widget.
		//   defaultStyle - optional flag indicating whether to include the default stylesheet for a widget.
		includeWidget: function(widget, defaultStyle) {
			// get our resource url
			var widgetsUrl = this.getWidgetsUrl(widget);
			if (!widgetsUrl) {
				this.debug('Unable to load widget "' + widget + '" because no namespace registered for it');
				return;  
			}
			
			// convert the widget name into a path
			var widgetPath = widget.replace(/\./g, '/');
			
			// check the style
			var style = widgetsUrl + widgetPath + '.css';
			if (typeof defaultStyle == 'undefined' || defaultStyle) {
				this.includeStyle(style);
				if (cswl_verbose) { this.debug('Including stylesheet [' + style + ']'); }
			} 
	
			// include the widget script		
			var script = widgetsUrl + widgetPath + '.js';
			if (cswl_verbose) { this.debug('Including widget [' + widget + '] from ' + script); }
			this.include(script);
		},
		
		// Include a stylesheet.
		includeStyle: function(src) {
			// check to see if the body is loaded
			// if not, then save the request in the pending list
			if (!this._loaded) {
				if (cswl_verbose) { this.debug('Body not loaded, adding ' + src + ' to the stylesheet list'); }
				this._stylesheets.push(src);
				return;
			}
			
			// create the script
			var style = document.createElement('link');
			style.id = this.unique();
			style.setAttribute('rel', 'stylesheet');
			style.setAttribute('type', 'text/css');
			style.setAttribute('href', src);
			
			if (cswl_verbose) { this.debug('Adding stylesheet ' + style.href + ' with id [' + style.id + ']'); }
			
			// attach the script to the head
			var head = document.getElementsByTagName('head')[0];
			head.appendChild(style);
		},
		
		// Updates the contents of an element.
		//   id   - the id of the element to update or the actual element.
		//   html - the new content for the element.
		update: function(id, html) {
			var elm;
			
			// figure out what 'id' was
			if (typeof id == 'string') {
				elm = $(id); // if they passed in a string then look up the element
			} else {
				elm = id; // otherwise assume it was a node
			}
			
			// if we can update the node, then do so
			if (elm && elm.innerHTML) {
				elm.innerHTML = html;
			}
		},
		
		// Updates the contents of all elements of a specific class and id.
		//   clazz - the class of the elements to update or an array of elements.
		//   html  - the new content for the elements.
		updateAll: function(clazz, html) {
			var elms;
			
			// figure out what 'clazz' was
			if (typeof clazz == 'string') {
				elms = $$(clazz);	// if they passed in a string, the look up the elements
			} else {
				elms = clazz;	// otherwise assume it was an array of nodes
			}
			
			// if we have an array, then update each
			if (elms && elms.length) {
				for (var i = 0; i < elms.length; i++) {
					this.update(elms[i], html);
				}
			}
		}
	}
	
	// Sets our debug flag to false if it wasn't specified.
	if (!cswl_debug) {
		var cswl_debug = false;
	}
	
	// Sets our verbosity flag to false if it wasn't specified.
	if (!cswl_verbose) {
		var cswl_verbose = false;
	}
	
	// called when the page is loaded
	function init() {
		cswl._init();
	}
	
	// hook into the window.onload event
	window.onload=init;
}