/**
 * Miscellaneous browser utilities
 */
com_pagasg_www_util = new es_lang.Package(true,
	/*Title*/   'Testing Package',
	/*Docs */   '-- none --',
	/*Package*/ function() {
		// Imports
		var LANG = es_lang;
		var LOG = es_util_logging;
		var NET = es_net;

		// Constructor imports
		var Class = es_lang.Class;
		var Enum = es_lang.Enum;
		var StringBuilder = LANG.StringBuilder;

		// Function imports
		var extend = Class.extend;
		var implement = Class.implement;
		var enumerate = Enum.enumerate;

		var Event = implement({
				type: /*String*/ undefined,
				target: /*Object*/ undefined,
				context: /*Object*/ undefined,
				timestamp: /*Date*/ undefined
			},
			function(type, target) {
				if (arguments.length > 0) {
					this.timestamp = new Date();
					this.type = type;
					this.target = target;
					this.init = function(context) {
						this.context = context;
					}
				}
			}
		);

		// ****** Event Manager ******
		var EventManager = implement({
				types: /*String*/ undefined,
				target: /*String*/ undefined,
				Event: /*constructor*/ undefined
			},
			function(/*constructor*/ Event, /*Object*/ target, /*String ...*/ types) {
				var _types;
				var _listenersMap;
				var _logger = LOG.Logger.getLogger('com.pagasg.www.util.EventManager');

				if (arguments.length > 0) {
					_listenersMap = {}
					_types = Objects.argsOrArray2Array(arguments, 2);

					for (var i = 0; i < _types.length; i++) {
						target[_types[i].toString()] = {
							type: _types[i].toString(),
							newEvent: function(context) { return newEvent(this.type, context); },
							addListener: function(listener) { addListener(this.type, listener); },
							removeListener: function(listener) { removeListener(this.type, listener); },
							fire: function(context) { fireEvent(newEvent(this.type, context)); },
							fireEvent: function(event) { fireEvent(event); }
						};
					}

					function touchListeners(type, create) {
						var listeners = _listenersMap[type];
						if (create && !(listeners)) {
							_listenersMap[type] = listeners = [];
						}
						return listeners;
					}
					function addListener(type, listener) {
						touchListeners(type, true).push(listener);
					};
					function removeListener(type, listener) {
						var listeners = touchListeners(type);
						if (listeners) {
							for (var i = 0; i < listeners.length; i++) {
								if (listeners[i] == listener) {
									listeners.splice(i, 1);
									break;
								}
							}
						}
					};
					var newEvent = function(type, context) {
						var event = new Event(type, target);
						if (context) {
							event.init.call(event, context);
						}
						return event;
					};
					var fireEvent = function(event) {
						var listeners = touchListeners(event.type);
						if (listeners) {
							for (var i = 0; i < listeners.length; i++) {
								try {
									listeners[i](event);
								} catch (e) {
									_logger.warning(Objects.display(e));
								}
							}
						}
					};

					// public
					this.newEvent = newEvent;
					this.fireEvent = fireEvent;
					this.size = function() {
						return _listeners.length;
					};
				}
			}
		);

		// ****** Index ******
		var Index = implement({
			extractor: /*function(value)*/ undefined,
			add: /*function(key, entry)*/ undefined,
			remove: /*function(key, entry)*/ undefined,
			change: /*function(key, oldEntry, newEntry)*/ undefined,
			getKeys: /*function(ref)*/ undefined,
			getRefs: /*function()*/ undefined
		});
		// ****** HashIndex ******
		// note: for a given hash, the keys are ordered.
		var HashIndex = function(extractor, hasher) {
			var _hashTable = {};
			var _extractor = extractor || function(e) { return e; };
			var _hasher = hasher || Strings.stringValue;

			function getLogger() {
				return LOG.Logger.getLogger('com.pagasg.www.util.HashIndex');
			};
			var add = function(key, value) {
				var entries;
				hash = _hasher(value);
				entries = _hashTable[hash];
				if (!entries) {
					entries = [];
					_hashTable[hash] = entries;
				}
				Arrays.insertOrdered(entries, key, true);
			};
			var remove = function(key, value) {
				hash = _hasher(value);

				entries = _hashTable[hash];
				found = false;
				if (entries) {
					Arrays.removeOrdered(entries, key);
				}

				if (found) {
					if (entries.length == 0) {
						delete _hashTable[hash];
					}
				} else {
					getLogger('Invalid indexed removal for key: ' + key + ', value: ' + Objects.display(value));
				}
			};
			var change = function(key, oldValue, newValue) {
				remove(key, oldValue);
				add(key, oldValue);
			};
			var getKeys = function(value) {
				var hash = _hasher(value);
				return _hashTable[hash] || [];
			};
			var getRefs = function() {
				return Objects.getProperties(_hashTable);
			}

			// Public
			this.extractor = _extractor;
			this.add = function(key, entry) {
				add(key, _extractor(entry));
			};
			this.remove = function(key, entry) {
				remove(key, _extractor(entry));
			};
			this.change = function(key, oldEntry, newEntry) {
				change(key, _extractor(oldEntry), _extractor(newEntry));
			};
			this.getKeys = getKeys;
			this.getRefs = getRefs;
		}
		// ****** OrderedIndex ******
		var OrderedIndex = function(extractor, comparator) {
			var _index = [];
			var _extractor = extractor || function(e) { return e; };
			var _comparator = comparator || Objects.compare;
			var _entryComparator = function(entry1, entry2) {
				return _comparator(entry1.value, entry2.value);
			};

			function getLogger() {
				return LOG.Logger.getLogger('com.pagasg.www.util.OrderedIndex');
			};
			var add = function(key, value) {
				var entry = { value: value, key: key };
				var pos = Arrays.insertOrdered(_index, entry, false, _entryComparator);
			};
			var remove = function(key, value) {
				var found = false;
				var entry = { value: value, key: key };
				var pos = Arrays.search(_index, entry, _entryComparator);

				if (pos >= 0) {
					for (var i = pos; i < _index.length; i++) {
						entry = _index[i];
						if (_comparator(entry.value, value) == 0) {
							if (entry.key == key) {
								_index.splice(i, 1);
								found = true;
								break;
							}
						}
					}
				}

				if (!found) getLogger('Invalid indexed removal for key: ' + key + ', value: ' + Objects.display(value));
			};
			var change = function(key, oldValue, newValue) {
				remove(key, oldValue);
				add(key, newValue);
			};
			var getKeys = function(value) {
				var keys = [];
				var entry = { value: value, key: null };
				var pos = Arrays.search(_index, entry, _entryComparator);

				if (pos >= 0) {
					for (var i = pos; i < _index.length; i++) {
						entry = _index[pos];
						if (_comparator(value, entry.value) == 0) {
							keys.push(entry.key);
						}
					}
				}

				return keys;
			}
			var getRefs = function() {
				throw new Error("Not yet supported");
			}

			// Public
			this.extractor = _extractor;
			this.add = function(key, entry) {
				add(key, _extractor(entry));
			};
			this.remove = function(key, entry) {
				remove(key, _extractor(entry));
			};
			this.change = function(key, oldEntry, newEntry) {
				change(key, _extractor(oldEntry), _extractor(newEntry));
			};
			this.getKeys = getKeys;
			this.getRefs = getRefs;
		}

		// ****** Container ******
		// TODO: this is supposed to be ordered
		var Container = implement({
				selectedKey: undefined
			}, 
			function() {
				var _size = 0;
				var _map = {};
				var _indexes = {};
				var _transactional;
				var _prototype;
				var _transactions;
				var _transactionReason;
				var _selectedKey = null;

				// Init
				_transactional = false;
				_eventMgr = new EventManager(Event, this, Container.Event.items);

				// Private
				function getLogger() {
					return LOG.Logger.getLogger('com.pagasg.www.util.Container');
				}

				// protected
				var addIndex = function(name, index) {
					if (!_indexes) _indexes = {};
					_indexes[name] = index;
				};
				var getIndex = function(name) {
					if (_indexes) return _indexes[name];
				};
				var getKeys = function(filter) {
					var keys = [];

					for (var key in _map) {
						if (_map.hasOwnProperty(key)) {
							if (!filter || filter(key, _map[key])) {
								keys.push(key);
							}
						}
					}

					return keys;
				};
				var getEntries = function(filter) {
					var value;
					var entries = [];

					for (var key in _map) {
						if (_map.hasOwnProperty(key)) {
							value = _map[key];
							if (!filter || filter(key, value)) {
								entries.push({ key: key, value: value });
							}
						}
					}

					return entries;
				};
				var getValues = function(filter) {
					var values = [];
					var value;

					for (var key in _map) {
						if (_map.hasOwnProperty(key)) {
							value = _map[key];
							if (!filter || filter(key, value)) {
								values.push(value);
							}
						}
					}

					return values;
				};
				var startTransaction = function(reason) {
					if (!_transactional) {
						_transactional = true;
						_transactionReason = reason;
						_transactions = [];
						this.onStartTransaction.fire(reason);
					} else {
						throw new Error('The container is already in a transaction: ' + Objects.display(reason));
					}
				};
				var endTransaction = function() {
					if (_transactional) {
							this.onEndTransaction.fire(_transactions);
							_transactionReason = null;
							_transactional = false;
							_transactions = null;
					} else {
						throw new Error('Not in a transaction');
					}
				};
				var put = function(key, value) {
					var context;
					var event;
					var priorValue;
					var strKey;

					if (key != null) {
						strKey = (key != undefined) ? key.toString() : key;
					} else {
						strKey = null;
					}
					
					if (strKey == null) {
						priorValue = _prototype;
						_prototype = value;

						if (priorValue != undefined) {
							context = new Container.Event.Context(strKey, value, strKey, priorValue);
							if (_transactional) {
								_transactions.push(this.onChange.newEvent(context));
							} else {
								this.onChange.fire(context);
							}
						} else {
							context = new Container.Event.Context(null, value);
							if (_transactional) {
								_transactions.push(this.onAdd.newEvent(context));
							} else {
								this.onAdd.fire(context);
							}
						}
					} else if (_map.hasOwnProperty(strKey)) {
						priorValue = _map[strKey];
						_map[strKey] = value;
						if (_indexes) {
							for (var idx in _indexes) {
								if (_indexes.hasOwnProperty(idx)) {
									try {
										_indexes[idx].change(key, priorValue, value);
									} catch (e) {
										getLogger().info(e);
									}
								}
							}
						}
						context = new Container.Event.Context(strKey, value, strKey, priorValue);
						if (_transactional) {
							_transactions.push(this.onChange.newEvent(context));
						} else {
							this.onChange.fire(context);
						}
					} else {
						_map[strKey] = value;
						_size++;
						if (_indexes) {
							for (var idx in _indexes) {
								if (_indexes.hasOwnProperty(idx)) {
									try {
										_indexes[idx].add(key, value);
									} catch (e) {
										getLogger.info(e);
									}
								}
							}
						}
						context = new Container.Event.Context(strKey, value);
						if (_transactional) {
							_transactions.push(this.onAdd.newEvent(context));
						} else {
							this.onAdd.fire(context);
						}
					}

					return priorValue;
				};
				var remove = function(key) {
					var context;
					var value
					var strKey;

					if (arguments.length == 0) {
						strKey = _selectedKey;
					} else if (key != null) {
						strKey = (key != undefined && key != null) ? key.toString() : key;
					} else {
						strKey = null;
					}
					
					if (strKey == null) {
						_prototype = undefined;
					} else if (_map.hasOwnProperty(strKey)) {
						value = _map[strKey];
						delete _map[strKey];
						_size--;
						if (_indexes) {
							for (var idx in _indexes) {
								if (_indexes.hasOwnProperty(idx)) {
									try {
										_indexes[idx].remove(key, value);
									} catch (e) {
										getLogger.info(e);
									}
								}
							}
						}
						context = new Container.Event.Context(strKey, value, null, null);
						if (_transactional) {
							_transactions.push(onRemove.newEvent(context));
						} else {
							this.onRemove.fire(context);
						}
					}

					return value;
				};
				var select = function(key) {
					var event;
					var context;
					var value;
					var priorKey = _selectedKey;
					var strKey = Strings.stringValue(key);

					this.selectedKey = _selectedKey = strKey;
					value = _map[strKey];

					context = new Container.Event.Context(strKey, value, priorKey, _map[priorKey]);
					this.onSelect.fire(context);
					return value
				};

				// Public
				this.getValues = getValues; 
				this.getKeys = getKeys;
				this.getEntries = getEntries;
				this.remove = remove;
				this.put = put;
				this.select = select;
				this.addIndex = addIndex;
				this.getIndex = getIndex;
				this.startTransaction = startTransaction;
				this.endTransaction = endTransaction;
				this.asMap = function() {
					var map = {};

					for (var p in _map) {
						if (_map.hasOwnProperty(p)) {
							map[p] = p;
						}
					}

					return map;
				}
				this.computeUnion = function(keys) {
					var key;
					var set = {};
					var arr = Objects.argsOrArray2Array(arguments);

					for (var p in _map) {
						if (_map.hasOwnProperty(p)) {
							set[p] = p;
						}
					}

					for (var i = 0; i < arr.length; i++) {
						key = arr[i];
						if (!set.hasOwnProperty(key)) {
							set[key] = key;
						}
					}

					return Objects.getProperties(set);
				};
				this.computeIntersection = function(keys) {
					var key;
					var set = {};
					var arr = Objects.argsOrArray2Array(arguments);

					for (var i = 0; i < arr.length; i++) {
						key = arr[i];
						if (_map.hasOwnProperty(key)) {
							set[key] = _map[key];
						}
					}

					return Objects.getProperties(set);
				};
				this.computeExclusion = function(keys) {
					var set = {};
					var arr = Objects.argsOrArray2Array(arguments);

					for (var p in _map) {
						if (_map.hasOwnProperty(p)) {
							set[p] = p;
						}
					}

					for (var i = 0; i < arr.length; i++) {
						if (set.hasOwnProperty(arr[i])) {
							delete set[arr[i]];
						}
					}

					return Objects.getProperties(set);
				};
				this.getTransactionReason = function() {
					return _transactionReason;
				};
				this.isTransactional = function() {
					return _transactional;
				};
				this.size = function() {
					return _size;
				};
				this.get = function(key) {
					var strKey = Strings.stringValue(key);

					if (arguments.length == 1) {
						if (key != null) {
							return _map[strKey];
						} else {
							return _prototype;
						}
					} else if (_selectedKey) {
						return _map[_selectedKey]
					} else {
						return null;
					}
				};
				this.contains = function(key) {
					if (arguments.length == 1) {
						if (key != null) {
							return _map.hasOwnProperty(Strings.stringValue(key));
						} else {
							return _prototype != undefined;
						}
					} else {
						return _map[_selectedKey] != undefined;
					}
				};
				this.intersect = function(/*String ...*/ keys) {
					var context;
					var key;
					var size = 0;
					var map = {};
					var wrap = !_transactional;
					var items = Objects.argsOrArray2Array(arguments);

					if (keys) {
						size = 0;
						for (var i = 0; i < items.length; i++) {
							key = Strings.stringValue(items[i]);

							if (_map.hasOwnProperty(key)) {
								size++;
								map[key] = _map[key];
							}
						}

						if (_size != size) {
							if (wrap) {
								startTransaction(this.intersect);
							}
							try {
								for (var p in _map) {
									if (!map.hasOwnProperty(p)) {
										context = new Container.Event.Context(p, _map[p], null, null);
										_transactions.push(this.onRemove.newEvent(context));
									}
								}

								_map = map;
								_size = size;
							} finally {
								if (wrap) {
									endTransaction();
								}
							}
							if (_selectedKey && !_map.hasOwnProperty(_selectedKey)) {
								select(undefined);
							}
						}
					}
				}
			}
		);
		// ****** Container.Events ******
		Container.Event = enumerate([ 'onAdd', 'onRemove', 'onChange', 'onSelect', 'onEndTransaction', 'onStartTransaction' ]); 
		Container.Event.Context = implement({
				key: /*String*/ undefined,
				value: /*Object*/ undefined,
				priorKey: /*Object*/ undefined,
				priorValue: /*Object*/ undefined
			},
			function(key, value, priorKey, priorValue) {
				this.key = key;
				this.value = value;
				this.priorKey = priorKey;
				this.priorValue = priorValue;
				this.toString = function() {
					str = 'Entry: ' + this.key + '="' + Strings.testEmpty(this.value) + '"';
					if (priorKey != null) {
						str += ' (Prior entry: ' + this.priorKey + '="' + Strings.testEmpty(this.priorValue) + '")';
					}
					return str;
				}
			}
		);

		// ****** Cycle *******
		var Cycle = implement({
				name: undefined,
				method: undefined,
				period: undefined,
				count: undefined,
				repeat: undefined,
				wait: undefined,
				startedOn: /*Date*/ undefined,
				interval: undefined,
				counter: undefined,
				repeatCounter: undefined,
				timeout: undefined,
				state: /*Cycle.State*/ undefined
			},
			function(/*function(cycle)*/ method) {
				var _name;
				var _intervalHandle;
				var _timeoutHandle;
				var _onceHandle;
				var _logger;
				var _eventMgr;

				if (arguments.length > 0) {
					_logger = LOG.Logger.getLogger('com.pagasg.www.util.Cycle');
					_eventMgr = new EventManager(Event, this, Cycle.Event.items);
					_name = "cycle." + Cycle.sequenceNumber++;
					Cycle.cycles[_name] = this;

					this.method = method;
					this.state = Cycle.State.stopped;
					this.name = _name
					this.once = function(wait, message) {
						if (_onceHandle) {
							clearTimeout(_onceHandle);
						}
						_onceHandle = window.setTimeout('com_pagasg_www_util.Cycle.cycles["' + _name + '"].hit(true, "' + message + '")', wait);
					};
					this.start = function(/*millis*/ period, /*int=0*/ count, /*int=1*/ repeat, /*millis=0*/ wait, /*millis=0*/ timeout) {
						this.period = period;
						this.count = count || 0;
						this.repeat = (repeat != 0 && !repeat) ? 1 : repeat;
						this.wait = wait || 0;
						this.timeout = timeout || 0;
						this.repeatCounter = 0;
						this.counter = 0;

						if (!_intervalHandle) {
							if (wait && wait > 0) {
								this.state = Cycle.State.waiting;
								_timeoutHandle = window.setTimeout('com_pagasg_www_util.Cycle.cycles["' + _name + '"].start(' + period + ', ' + count + ', ' + repeat + ', 0, ' + timeout + ')', this.wait);
							} else {
								this.startedOn = new Date();
								this.state = Cycle.State.started;
								this.onStart.fire();
								_timeoutHandle = null;
								_intervalHandle = this.interval = window.setInterval('com_pagasg_www_util.Cycle.cycles["' + _name + '"].hit()', this.period);
							}
						} else {
							_logger.warning('cycle: ' + _name + ' has already started');
						}
					};
					this.stop = function() {
						if (_intervalHandle) {
							clearInterval(_intervalHandle);
							this.interval = _intervalHandle = undefined;
							this.onStop.fire();
						} else if (_timeoutHandle) {
							clearTimeout(_timeoutHandle);
							_timeoutHandle = undefined;
							this.onStop.fire();
						} else {
							_logger.warning('cycle: ' + _name + ' has not yet started');
						}

						this.state = Cycle.State.stopped;
					};
					this.dispose = function() {
						if (this.state == Cycle.state.started) {
							this.stop();
						}
						delete Cycle.cycles[_name];
					}
					this.hit = function(once, message) {
						try {
							method.call(this, this, message);
						} catch (e) {
							_logger.warning(Objects.display(e, 1));
						}

						if (once) {
							_onceHandle = null;
						} else {
							this.counter++;
							if (this.counter >= this.count) {
								this.repeatCounter++;
								this.counter = 0;
							}
							if (this.repeat > 0 && this.count > 0 && this.repeatCounter >= this.repeat && (_intervalHandle || _timeoutHandle)) {
								if (_intervalHandle) clearInterval(_intervalHandle);
								if (_timeoutHandle) clearInterval(_timeoutHandle);

								_timeoutHandle = null;
								this.interval = _intervalHandle = null;
								if (_name) delete Cycle.cycles[_name];

								this.state = Cycle.State.stopped;
								this.onStop.fire();
							}
						}
					};
				}
			}
		);
		Cycle.cycles = {};
		Cycle.sequenceNumber = 0;
		Cycle.State = enumerate([ 'stopped', 'waiting', 'started' ]);
		Cycle.Event = enumerate([ 'onStart', 'onStop' ]);

		// ****** Datetime Range ******
		var DatetimeRange = function(start, end) {
			var _start = Objects.dateValue(start);
			var _end = Objects.dateValue(end);
			var _utcstart = (_start) ? _start.getTime() : 0;
			var _utcend = (_end) ? _end.getTime() : 0;

			this.includes = function(date) {
				var utc;
				var dte = Objects.dateValue(date);

				if (dte) {
					utc = dte.getTime();
					return (utc >= _utcstart && utc <= _utcend);
				} else {
					return null;
				}
			}
		}

		// ****** Numbers ******
		var Formatter = function(decimalSep) {
			var _decimalSep;
			var _thousandsSep;

			this.decimalSep = _decimalSep = decimalSep || '.';
			this.thousandsSep = _thousandsSep = (decimalSep == '.') ? ',' : '.';

			function padl(intnum, len) {
				var strnum = intnum.toString();

				while (strnum.length < len) {
					strnum = '0' + strnum;
				}
				return strnum;
			}
			this.formatUSDate = function(dateValue, format) {
				var date;
				var bldr = new StringBuilder();

				if (dateValue) {
					if (dateValue.constructor == Date) {
						date = dateValue;
					} else if (dateValue.constructor == String) {
						date = Date.parse(dateValue);
					} else {
						date = new Date(dateValue);
					}
					bldr.append(padl(date.getMonth() + 1, 2)).append('/');
					bldr.append(padl(date.getDate(), 2)).append('/');
					bldr.append(date.getFullYear());

					return bldr.toString();
				} else {
					return null;
				}
			};
			// TODO: call this formatUTCDate
			this.formatDate = function(dateValue, format) {
				var date;
				var bldr = new StringBuilder();

				// TODO: I don't have time to write the real-deal yet -- this is the SQL version
				if (dateValue) {
					if (dateValue.constructor == Date) {
						date = dateValue;
					} else if (dateValue.constructor == String) {
						date = Date.parse(dateValue);
					} else {
						date = new Date(dateValue);
					}
					bldr.append(date.getUTCFullYear()).append('-');
					bldr.append(padl(date.getUTCMonth() + 1, 2)).append('-');
					bldr.append(padl(date.getUTCDate(), 2)).append(' ');
					bldr.append(padl(date.getUTCHours(), 2)).append(':');
					bldr.append(padl(date.getUTCMinutes(), 2)).append(':');
					bldr.append(padl(date.getUTCSeconds(), 2));
					bldr.append(' GMT');

					return bldr.toString();
				} else {
					return null;
				}
			};
			this.formatNumber = function(num, /*int*/ decimals, /*bool*/ fill, /*int*/ padl, /*bool*/ thousands) {
				var period;
				var digit;
				var str;
				var numString;
				var digits = [];
				var mantissa = -1;
				var decimalPt = decimalPt || '.';
				var decimals = Math.max(decimals, 0);
				
				if (num != undefined && num != null) {
					numString = num.toString();
					for (var i = 0; i < numString.length; i++) {
						digit = numString.charAt(i);
						if (digit == '.') {
							period = digits.length;
							if (decimals > 0) {
								mantissa = 0;
								digits.push(digit);
							} else {
								break;
							}
						} else if (digit >= '0' && digit <= '9') {
							if (mantissa >= 0) {
								if (mantissa >= decimals) break;
								mantissa++;
							}
							digits.push(digit);
						}
					}
					if (!period) {
						period = digits.length;
					}
					if (padl) {
						while (period < padl) {
							digits.unshift('0');
							period++;
						}
					}
					if (fill) {
						if (period == digits.length) {
							digits.push('.');
						}
						while (digits.length - period <= decimals) {
							digits.push('0');
						}
					}
					if (thousands) {
						for (i = period - 3; i > 0; i -= 3) {
							digits.splice(i, 0, ',');
							period++;
						}
					}

					return digits.join('');
				} else {
					return null;
				}
			};
			this.formatAge = function(date, oldSuffix) {
				var str;
				var now = (new Date()).getTime();
				var then = (date.constructor == Date) ? date.getTime() : date;
				var diff = (now - then) / 1000;
				
				if (diff < 120) {
					str = 'current';
				} else if (diff < 7200) {
					str = '&gt; ' + parseInt(diff / 60) + ' mins' + oldSuffix;
				} else if (diff < 170000) {
					str = '&gt; ' + parseInt(diff / 3600) + ' hrs' + oldSuffix;
				} else {
					str = '&gt; ' + parseInt(diff / 86400) + ' days' + oldSuffix;
				}

				return str;
			};
		};

		// ****** Strings ******
		var Strings = {
			trim: function(val) {
				var str = Strings.stringValue(val);

				if (str) {
					str = str.replace(/(^\s*)|(\s*$)/gi,'');
					return str.replace(/\n /,"\n");
				} else {
					return null;
				}
			},
			getPart: function(value, delimiter, num) {
				var parts;
				var del = delimiter || '-';
				var str = Strings.stringValue(value);

				if (num == undefined || num == null) {
					return str;
				} else if (str) {
					parts = str.split(del);
					if (parts.length > num) {
						return parts[num];
					} else {
						return null;
					}
				} else {
					return null;
				}
			},
			indexOf: /*String*/ function(/*Object*/ val, /*Object ...*/ list) {
				var item;
				var str = Strings.stringValue(val);
				var items = Objects.argsOrArray2Array(arguments, 1);
				
				for (var i = 0; i < items.length; i++) {
					item = Strings.stringValue(items[i]);
					if (str == item) {
						return i;
					}
				}

				return -1;
			},
			before: /*String*/ function(val, delimiter, occurrence) {
				var index;
				var str = Strings.stringValue(val);
				
				if (str) {
					if (!occurrence) occurrence = 0;
					if (occurrence >= 0) {
						index = -1;
						while (occurrence >= 0) {
							index = str.indexOf(delimiter, index + 1);
							if (index < 0) break;
							occurrence--;
						}
					} else {
						index = str.length;
						while (occurrence < 0) {
							index = str.lastIndexOf(delimiter, index - 1);
							if (index < 0) break;
							occurrence++;
						}
					}

					if (index >= 0) {
						return str.substring(0, index);
					}
				}

				return null;
			},
			after: /*String*/ function(val, delimiter, occurrence) {
				var index;
				var str = Strings.stringValue(val);
				
				if (str) {
					if (!occurrence) occurrence = 0;
					if (occurrence >= 0) {
						index = -1;
						while (occurrence >= 0) {
							index = str.indexOf(delimiter, index + 1);
							if (index < 0) break;
							occurrence--;
						}
					} else {
						index = str.length;
						while (occurrence < 0) {
							index = str.lastIndexOf(delimiter, index - 1);
							if (index < 0) break;
							occurrence++;
						}
					}

					if (index >= 0) {
						return str.substring(index + delimiter.length);
					}
				}

				return null;
			},
			equalsIC: /*bool*/ function(obj1, obj2) {
				if (obj1 == obj2) {
					return true;
				} else {
					return Strings.toLower(obj1) == Strings.toLower(obj2);
				}
			},
			toUpper: function(obj) {
				var str;

				if (obj == null || obj == undefined) {
					return null;
				} else if (obj.constructor == String) {
					return obj.toUpperCase();
				} else {
					str = obj.toString();

					if (str && str.constructor == String) {
						return str.toUpperCase();
					} else {
						return new String(obj).toUpperCase();
					}
				}
			},
			toLower: function(obj) {
				var str;

				if (obj == null || obj == undefined) {
					return null;
				} else if (obj.constructor == String) {
					return obj.toLowerCase();
				} else {
					str = obj.toString();

					if (str && str.constructor == String) {
						return str.toLowerCase();
					} else {
						return new String(obj).toLowerCase();
					}
				}
			},
			stringValue: function(obj) {
				var str;

				if (obj == null || obj == undefined) {
					return null;
				} else if (obj.constructor == String) {
					return obj;
				} else {
					str = obj.toString();

					if (str && str.constructor == String) {
						return str;
					} else {
						return new String(obj);
					}
				}
			},
			testEmpty: function(/*Object...*/ obj) {
				arr = Objects.argsOrArray2Array(arguments);

				for (var i = 0; i < arr.length; i++) {
					obj = arr[i];

					if (!Strings.isEmpty(obj)) {
						if (obj.constructor == String) {
							return obj;
						} else {
							return new String(obj.toString());
						}
					}
				}

				return null;
			},
			testDefault: function(/*Object...*/ obj) {
				arr = Objects.argsOrArray2Array(arguments);

				for (var i = 0; i < arr.length; i++) {
					obj = arr[i];

					if (!Strings.isEmpty(obj)) {
						if (obj.constructor == String) {
							return obj;
						} else {
							return new String(obj.toString());
						}
					}
				}

				return '';
			},
			isEmpty: function(obj) {
				var str;
				if (obj == null || obj == undefined) {
					return true;
				} else if (obj.constructor == String) {
					str = obj
				} else {
					str = new String(obj.toString());
				}

				return str == null || str == undefined || str.length == 0;
			},
			tail: undefined			// for IE so I don't make mistakes :)
		}

		// ****** Arrays ******
		Arrays = {
			removeOrdered: function(arr, elm, comparator) {
				var comparator = comparator || Objects.compare;
				var pos = Arrays.search(arr, elm, comparator);

				if (pos > 0) {
					return arr.splice(pos, 1);
				} else {
					return null;
				}
			},
			insertOrdered: function(arr, elm, nodups, comparator) {
				var comparator = comparator || Objects.compare;
				var pos = Arrays.search(arr, elm, comparator);

				if (pos < 0) {
					pos = -1 * (pos + 1);
					arr.splice(pos, 0, elm);
				} else if (!nodups) {
					for (pos = pos + 1; pos < arr.length; pos++) {
						if (comparator(elm, arr[pos]) != 0) {
							pos = pos - 1;
							break;
						}
					}
					arr.splice(pos, 0, elm);
				}

				return pos;
			},
			search: function(arr, find, comparator) {
				var index;
				var high;
				var low;
				var mid;
				var element;
				var comparison;

				if (arr && arr.constructor == Array) {
					comparator = comparator || Objects.compare;

					high = arr.length - 1;
					low = 0;

					while (low <= high) {
						mid = Math.round((low + high) / 2)
						element = arr[mid];
						comparison = comparator(element, find);

						if (comparison > 0) {
							high = mid - 1;
						} else if (element < find) {
							low = mid + 1;
						} else {
							return mid;
						}
					}

					return (-1 * Math.max(low, 0)) - 1;
				} else {
					return null;
				}
			}
		};

		// ****** Objects ******
		var Objects = {
			dateValue: function(date) {
				if (!date) {
					return null;
				} else if (date.constructor == Date) {
					return date;
				} else if (date.constructor == String) {
					return Date.parse(date);
				} else {
					return new Date(date);
				}
			},
			getProperties: function(map, filter) {
				var keys = [];

				for (var key in map) {
					if (map.hasOwnProperty(key)) {
						if (!filter || filter(key, value)) {
							keys.push(key);
						}
					}
				}

				return keys;
			},
			getEntries: function(map, filter) {
				var entries = [];

				for (var key in map) {
					if (map.hasOwnProperty(key)) {
						if (!filter || filter(key, value)) {
							entries.push({ key: key, value: map[key] });
						}
					}
				}

				return entries;
			},
			getValues: function(map, filter) {
				var values = [];
				var value;

				for (var key in map) {
					if (map.hasOwnProperty(key)) {
						value = map[key];
						if (!filter || filter(key, value)) {
							values.push(value);
						}
					}
				}

				return values;
			},
			compare: function(val1, val2) {
				if (val1 != undefined && val1 != null && val2 != undefined && val2 != null) {
					if (val1 == val2) {
						return 0;
					} else {
						return (val1 > val2) ? 1 : -1;
					}
				} else if (!val1 && !val2) {
					return 0;
				} else if (val1) {
					return 1;
				} else {
					return -1;
				}
			},
			argsOrArray2Array: function(arglist, index, nocheckFirst) {
				var arr;
				var start = (index) ? parseInt(index) : 0;
				var first = arglist[start];

				if (!nocheckFirst && first && first.constructor == Array) {
					arr = first;
				} else {
					arr = [];
					for (var i = start; i < arglist.length; i++) {
						arr.push(arglist[i]);
					}
				}

				return arr;
			}, 
			display: function(/*Object*/ obj, /*int*/ recurse, /*bool*/ showfns, /*String*/ indent, /*String*/ fullIndent) {
				var con;
				var value;
				var nextIndent;
				var count;
				var first = (fullIndent) ? false : true;
				var buffer = new StringBuilder();

				recurse = recurse || 0;
				indent = indent || '  ';
				fullIndent = fullIndent || '';
				nextIndent = fullIndent + indent;

				if (obj == null) {
					buffer.append('[null]');
				} else if (obj == undefined) {
					buffer.append('[null]');
				} else if (obj.constructor == String) {
					buffer.append(obj);
				} else if (obj.constructor == Function && showfns) {
					buffer.append(obj);
				} else {
					count = 0;
					for (var prop in obj) {
						value = obj[prop];

						if (!value || value.constructor != Function || showfns) {
							if (!first) {
								if (count > 0) buffer.append(',');
								buffer.append('\r\n');
							} else {
								first = false;
							}
							count++;
							buffer.append(fullIndent + prop + ': ');
						}

						if (value == undefined || value == null) {
							buffer.append(new String(value));
						} else if (value.constructor == Function) {
							if (showfns) {
								buffer.append('function() {...}');
							}
						} else if (value.constructor == Array) {
							buffer.append('[');
							if (recurse == 0) {
								if (value.length > 0) {
									buffer.append(value.join(',\r\n' + nextIndent + indent));
									if (value.length > 1) buffer.append('\r\n' + nextIndent);
								}
							} else {
								for (var i = 0; i < value.length; i++) {
									if (i > 0) buffer.append(',');
									buffer.append('{');
									buffer.append(Objects.display(value[i], recurse - 1, indent, showfns, nextIndent + indent));
									buffer.append('\r\n' + nextIndent + '}');
								}
								buffer.append('\r\n' + fullIndent);
							}
							buffer.append(']');
						} else if (recurse == 0
								|| value.constructor == undefined
								|| value.constructor == String
								|| value.constructor == Boolean
								|| value.constructor == Number) {
							if (value.toString) {
								buffer.append(value.toString());
							} else {
								buffer.append('???');
							}
						} else {
							buffer.append('{');
							buffer.append(Objects.display(value, recurse - 1, indent, showfns, nextIndent));
							buffer.append('\r\n' + fullIndent + '}');
						}
					}
				}
				return buffer.toString();
			},
			serialize: function() {
				// TODO: write this
			},
			deserialize: function(method, namedValues) {
				var params;
				var obj;
				var result = {};

				for (var name in namedValues) {
					if (namedValues.hasOwnProperty(name)) {
						obj = new method();
						method.apply(obj, namedValues[name]);
						result[name] = obj;
					}
				}
				return result;
			}
		}

		// ****** Inputs ******
		var Inputs = {
			// Init
			getLogger: function() {
				return LOG.Logger.getLogger('com.pagasg.www.util.Inputs');
			},
			// <select>
			setSelectOption: function (selectElm, value) {
				var option;
				var found;
				var options = selectElm.options;

				found = false;
				for (var i = 0; i < options.length; i++) {
					option = options[i];
					if (option.value == value) {
						selectElm.selectedIndex = i;
						found = true;
						break;
					}
				}

				if (!found) {
					selectElm.selectedIndex = -1;
				}

				return false;
			},
			setSelectValue: function (selectElm, value, index) {
				var option;
				var options = selectElm.options;

				index = index || parseInt(selectElm.selectedIndex);
				if (index < options.length && index >= 0) {
					option = options[index];
					option.value = value;
					return true;
				}

				return false;
			},
			setSelectText: function(selectElm, newText, value) {
				value = value || parseInt(selectElm.selectedIndex);
				
				for (var i=0; i < selectElm.length; i++) {
					if (selectElm[i].value == value) {
						selectElm[i].text = newText;
						return true;
					}
				};
				return false;
			},
			addSelectOption: function(selectElm, value, text) {
				var option = new Option();
				option.value = value;
				option.text = text;

				selectElm.options[selectElm.options.length] = option;
			},
			recurseSelectOptions: function(selectElm, /*boolean function(option, index, isselected)*/ fn) {
				var option;
				var selectedIndex = parseInt(selectElm.selectedIndex);
				var options = selectElm.options;

				for (var i = 0; i < options.length; i++) {
					option = options[i];
					if (fn(option, i, selectedIndex == i)) {
						selectElm.selectedIndex = i;
						return i;
					}
				}
			},
			getSelectOption: function(selectElm, value) {
				var option;
				var options = selectElm.options;

				for (var i = 0; i < options.length; i++) {
					option = options[i];
					if (option.value == value) {
						return option;
					}
				}

				return null;
			},
			getSelectValue: function(selectElm) {
				var index = parseInt(selectElm.selectedIndex);

				if (isNaN(index)) {
					return null;
				} else if (index >= 0 && index < selectElm.options.length) {
					return selectElm.options[index].value;
				} else {
					return null;
				}
			},
			getSelectText: function(selectElm) {
				var index = parseInt(selectElm.selectedIndex);

				if (isNaN(index)) {
					return null;
				} else if (index >= 0 && index < selectElm.options.length) {
					return selectElm.options[index].text;
				} else {
					return null;
				}
			},

			// <input>
			validateInt: function(/*<input>*/ inputElement, /*int*/ min, /*int*/ max, /*String*/ message) {
				var intValue;
				var isError = false;
				var value = inputElement.value;
				var msg = (message) ? new String(message) : "$input is not a valid integer";

				value = value.replace(/[^0-9\.\-]/g, '');
				value = value.replace(/\..*/g, '');

				isError = true;
				intValue = parseInt(value);
				if (!isNaN(intValue)) {
					if (min == undefined || intValue >= min) {
						if (max == undefined || intValue <= max) {
							isError = false;
						} else {
							intValue = max;
						}
					} else {
						intValue = min;
					}
				} else {
					intValue = '';
				}

				if (isError) {
					msg = msg.replace(/\$input/g, new String(inputElement.value));
					alert(msg);
					value = '';
				} else {
					value = new String(intValue);
				}

				inputElement.value = value;
				return true;
			},
			validatePhoneNumber: function(/*<input>*/ inputElement, /*String*/ message) {
				var msg = (message) ? new String(message) : "Please enter a full phone number with area code";
				var value = inputElement.value;

				value = value.replace(/[^0-9]/g, '');

				if (value != '') {
					if (value.length == 10) {
						value = '(' + value.substring(0, 3) + ') ' + value.substring(3, 6) + '-' + value.substring(6, 10);
					} else {
						value = '';
						msg = msg.replace(/\$input/g, new String(inputElement.value));
						alert(msg);
					}
				}

				inputElement.value = value;
				return true;
			},
			validateEmailAddress: function(/*<input>*/ inputElement, /*String*/ message) {
				var dotPos;
				var atPos;
				var value = inputElement.value;
				var msg = (message) ? new String(message) : "Please enter a valid email address using the form name@server.xx";
				
				value = value.replace(/\s+/, '');

				if (value != '') {
					atPos = value.indexOf('@');
					dotPos = value.lastIndexOf('.');

					if (atPos < 1 || dotPos <= atPos + 1 || dotPos >= value.length - 2) {
						value = '';
						msg = msg.replace(/\$input/g, new String(inputElement.value));
						alert(msg);
					}
				}

				inputElement.value = value;
			},
			// Generic
			isEmpty: function(inp) {
				return Strings.isEmpty(Inputs.getValue(inp));
			},
			clear: function(inp) {
				var type;
				var tag = Strings.toLower(inp.tagName);

				if (tag == 'textarea') {
					inp.value = '';
				} else if (tag == 'select') {
					inp.selectedIndex = 0;
				} else if (tag == 'input') {
					type = Strings.toLower(inp.type);
					if (type == 'text') {
						inp.value = '';
					} else if (type == 'password') {
						inp.value = '';
					} else if (type == 'checkbox') {
						inp.checked = false;
					}
				}
			},
			getValue: function(inp) {
				var type;
				var radiobtn;
				var tag = Strings.toLower(inp.tagName);

				if (tag) {
					if (tag == 'textarea') {
						return inp.value;
					} else if (tag == 'select') {
						return Inputs.getSelectValue(inp);
					} else if (tag == 'input') {
						type = Strings.toLower(inp.type);
						if (type == 'text' || type == 'password' || type == 'hidden') {
							return inp.value;
						} else if (type == 'checkbox' || type == 'radio') {
							if (inp.checked) {
								return inp.value;
							} else {
								return null;
							}
						}
					}
				} else if (inp.length > 0) {
					for (var i = 0; i < inp.length; i++) {
						radiobtn = inp[i];
						if (radiobtn.checked) {
							return radiobtn.value;
						}
					}
				} else {
					throw new Error('Unsupported input type');
				}
			},
			setValue: function(inp, value) {
				var type;
				var radiobtn;
				var tag = Strings.toLower(inp.tagName);

				if (tag) {
					if (tag == 'textarea') {
						inp.value = Strings.testDefault(value);
					} else if (tag == 'select') {
						Inputs.setSelectOption(inp, Strings.testDefault(value));
					} else if (tag == 'input') {
						type = Strings.toLower(inp.type);
						if (type == 'text' || type == 'password' || type == 'hidden') {
							inp.value = Strings.testDefault(value);
						} else if (type == 'checkbox') {
							inp.checked = !(!value);
						}
					}
				} else if (inp.length > 0) {
					value = Strings.testDefault(value);
					for (var i = 0; i < inp.length; i++) {
						radiobtn = inp[i];
						if (radiobtn.value == value) {
							radiobtn.checked = true;
							break;
						} else {
							radiobtn.checked = false;
						}
					}
				}
			},
			/**
			 * posts the supplied form to a hidden frame, optionally only including the specified
			 * fields.
			 *
			 * Parameters:
			 * - frm: the form to post
			 * - fields: the list of fields on the form to send -- if empty send all fields.
			 */
			postForm: function(frm, fields) {
				var frmElm = Elements.idOrElm2Elm(frm);
				var conn = new NET.URLConnection(frmElm.action);
				
				if (arguments.length > 0) {
					fields = Objects.argsOrArray2Array(arguments, 1);
				}

				conn.outputForm(frmElm);
				conn.open();
			},
			/**
			 * copies the values from one form to another, excepting any listed in the except array.
			 * The types of form elements do not need to be the same. Note: use initForm to initialize
			 * or clear the form prior to invoking this method if necessary.
			 *
			 * Parameters:
			 * - inputForm: the input form from which data was entered by the user.
			 * - outputForm: the output form to which data will be posted.
			 * - except: varargs listing of any fields not to be moved.
			 *
			 * Returns:
			 * - a map of the input fields/names moved from the one form to the other.
			 */
			copyForm: function(outputForm, inputForm, except) {
				var value;
				var elm;
				var input;
				var elements;
				var map = {};
				var values = {};
				var exceptions = {};

				elements = outputForm.elements;
				for (var i = 0; i < elements.length; i++) {
					elm = elements[i];
					if (!Strings.isEmpty(elm.name)) {
						map[elm.name.toLowerCase()] = elm.name;
					}
				}

				except = Objects.argsOrArray2Array(arguments, 3);

				for (var i = 0; i < except.length; i++) {
					exceptions[except[i]] = true;
				}

				input = inputForm.elements;
				for (var i = 0; i < input.length; i++) {
					elm = input[i];
					if (!Strings.isEmpty(elm.name) && map[elm.name.toLowerCase()] && !exceptions[elm.name]) {
						value = Inputs.getValue(elm);
						Inputs.setValue(outputForm[map[elm.name.toLowerCase()]], value);
						values[elm.name] = value;
					}
				}

				return values;
			},
			updateForm: function(frm, map) {
				var elm;
				var value;
				var frmElm = Elements.idOrElm2Elm(frm);

				for (var prop in map) {
					value = map[prop];
					elm = frm[prop];

					if (elm) {
						if (value == null || value == undefined) {
						} else if (value.constructor == Function) {
							value.call(frmElm, elm);
						} else {
							Inputs.setValue(elm, value);
						}
					}
				}
			},
			initForm: function(frm, map, disable, except) {
				var elm;
				var value;
				var result = {};
				var exceptions = {};
				var frmElm = Elements.idOrElm2Elm(frm);
				var elements = frmElm.elements;

				except = Objects.argsOrArray2Array(arguments, 3);

				for (var i = 0; i < except.length; i++) {
					exceptions[except[i]] = true;
				}
				disable = (disable) ? true : false;

				for (var i = 0; i < elements.length; i++) {
					elm = elements[i];
					elm.disabled = (exceptions[elm.name]) ? !disable : disable;
					if (map) {
						value = map[elm.name];
						if (value == undefined) {
							Inputs.clear(elm);
						} else if (value == null) {
						} else if (value.constructor == Function) {
							value.call(frmElm, elm);
						} else {
							Inputs.setValue(elm, value);
						}
						result[elm.name] = Inputs.getValue(elm);
					} else {
						result.push[elm.name] = '';
						Inputs.clear(elm);
					}
				}

				return result;
			},
			printForm: function(frm, showhidden, showUnnamed, showAlert) {
				var inp;
				var value;
				var show;
				var elements = Elements.idOrElm2Elm(frm).elements;
				var logger = Inputs.logger;
				var bldr = new StringBuilder();

				if (elements) {
					bldr.append('Printout for: ' + frm.id + ', action="' + frm.action + '", method="' + (frm.method || 'get') + '"');
					for (var i = 0; i < elements.length; i++) {
						inp = elements[i];
						if ((!Strings.isEmpty(inp.name) || showUnnamed) && (inp.type != 'hidden' || showhidden)) {
							inp = elements[i];
							value = Inputs.getValue(inp);
							show = true;
							if (value == undefined || value == null) {
								value = 'null';
								show = !Strings.equalsIC(inp.tagName, 'radio');
							} else if (!parseFloat(value)) {
								value = '"' + value + '"';
							}
							if (show) {
								bldr.append('\r\n  ' + (inp.name || inp.id) + ' (' + inp.tagName + '/' + inp.type + ') = ' + value);
							}
						}
					}

					if (!showAlert) {
						Inputs.getLogger().info(bldr.toString());
					} else {
						alert(bldr.toString());
					}
				} else {
					Inputs.getLogger().warning(frm + " does not have any elements or is not a &lt;form&gt;");
				}
			},
			tail: undefined
		}

		// ****** UserAgent ******
		var UserAgent = implement({
				browser: undefined,
				version: undefined,
				OS: undefined
			}, 
			function() {
				this.browser = searchString(UserAgent.browsers) || undefined;
				this.version = searchVersion(navigator.userAgent) || searchVersion(navigator.appVersion) || undefined;
				this.OS = searchString(UserAgent.OS) || undefined;

				function searchString(data) {
					for (var i=0;i<data.length;i++)	{
						var dataString = data[i].string;
						var dataProp = data[i].prop;
						this.versionSearchString = data[i].versionSearch || data[i].identity;
						if (dataString) {
							if (dataString.indexOf(data[i].subString) != -1)
								return data[i].identity;
						}
						else if (dataProp)
							return data[i].identity;
					}
				};
				function searchVersion(dataString) {
					var index = dataString.indexOf(this.versionSearchString);
					if (index == -1) return;
					return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
				};
			}
		)
		UserAgent.browsers = [
				{ string: navigator.userAgent, subString: "Chrome", identity: "Chrome" },
				{	string: navigator.userAgent, subString: "OmniWeb", versionSearch: "OmniWeb/", identity: "OmniWeb" },
				{ string: navigator.vendor, subString: "Apple", identity: "Safari", versionSearch: "Version" },
				{ prop: window.opera, identity: "Opera" },
				{ string: navigator.vendor, subString: "iCab", identity: "iCab" },
				{ string: navigator.vendor, subString: "KDE", identity: "Konqueror" },
				{ string: navigator.userAgent, subString: "Firefox", identity: "Firefox" },
				{ string: navigator.vendor, subString: "Camino", identity: "Camino" },
				{ string: navigator.userAgent, subString: "Netscape", identity: "Netscape" },
				{ string: navigator.userAgent, subString: "MSIE", identity: "MSIE", versionSearch: "MSIE" },
				{ string: navigator.userAgent, subString: "Gecko", identity: "Mozilla", versionSearch: "rv" },
				{ string: navigator.userAgent, subString: "Mozilla", identity: "Netscape", versionSearch: "Mozilla" }
		];
		UserAgent.OS = [
				{ string: navigator.platform, subString: "Win", identity: "Windows" },
				{ string: navigator.platform, subString: "Mac", identity: "Mac" },
				{ string: navigator.platform, subString: "Linux", identity: "Linux" }
		];

		// ****** Element Watcher ******
		var ElementWatcher = function(elm, main) { 
			var _index;
			var _eventMgr;
			var _left;
			var _top;
			var _height;
			var _width;
			var _defaultResizeHandler;
			var _resizing;

			if (arguments.length > 0) {
				_resizing = true;
				_eventMgr = new EventManager(Event, this, 'onResizeStart', 'onResize', 'onResizeEnd');
				_left = -1;
				_top = -1;
				_height = -1;
				_width = -1;

				function getLogger() {
					return LOG.Logger.getLogger('com.pagasg.www.util.ElementWatcher');
				}
				function endResizeAll(force) {
					var watcher;
					var watchers = ElementWatcher.watchers;

					for (var p in watchers) {
						if (watchers.hasOwnProperty(p)) {
							watcher = watchers[p];
							watcher.endResize(force);
						}
					}
				}
				function startResizeAll() {
					var watcher;
					var watchers = ElementWatcher.watchers;

					for (var p in watchers) {
						if (watchers.hasOwnProperty(p)) {
							watcher = watchers[p];
							watcher.startResize();
						}
					}
				}
				function resizeAll() {
					var watcher;
					var watchers = ElementWatcher.watchers;

					for (var p in watchers) {
						if (watchers.hasOwnProperty(p)) {
							watcher = watchers[p];
							watcher.resize();
						}
					}
				}

				var getLeft = function() {
					return Elements.getLeft(elm);
				}
				var getTop = function() {
					return Elements.getTop(elm);
				}
				var getWidth = function() {
					return elm.clientWidth;
				}
				var getHeight = function() {
					return elm.clientHeight;
				}

				// Public
				this.element = elm;
				this.left = _left;
				this.top = _top;
				this.height = _height;
				this.width = _width;
				this.checkSize = function() {
					var left = getLeft();
					var top = getTop();
					var height = getHeight();
					var width = getWidth();

					if (!_resizing) {
						if (left != _left || top != _top || _height != height || _width != width) {
							this.startResize();
							_resizing = true;
						}
					} else {
						if (left == _left && top == _top && _height == height && _width == width) {
							_resizing = false;
							this.endResize();
						} else {
							this.resize();
						}
					}

					_left = left;
					_top = top;
					_height = height;
					_width = width;
				}
				this.startResize = function() {
					_resizing = true;

					//getLogger().info('START RESIZE: ' + elm.id);
					if (main) {
						startResizeAll();
					}

					this.onResizeStart.fire();
				}
				this.resize = function() {
					this.onResize.fire();

					//getLogger().info('RESIZING: ' + elm.id + ' (' + width + ',' + height + ')/(' + _width + ',' + _height + ')');
					if (main) {
						resizeAll();
					}
				}
				this.endResize = function(force) {
					var left = getLeft();
					var top = getTop();
					var height = getHeight();
					var width = getWidth();

					//getLogger().info('END RESIZE: ' + elm.id);

					if (force || this.left != left || this.top != top || this.height != height || this.width != width) {
						this.left = left;
						this.top = top;
						this.height = height;
						this.width = width;

						this.onResizeEnd.fire({ priorLeft: _left, priorTop: _top, priorWidth: _width, priorHeight: _height });

						if (main) {
							endResizeAll(force);
						}

						_left = this.left = left;
						_top = this.top = top;
						_height = this.height = height;
						_width = this.width = width;
					}
				}
			}
		};
		// Global
		ElementWatcher.main = null;
		ElementWatcher.watchers = {};
		ElementWatcher.start = function(monitorPeriod) {
			monitorPeriod = monitorPeriod || 300;
			if (!ElementWatcher.main) {
				ElementWatcher.main = new ElementWatcher(window.top.document.body, true);
				// This is thanks to Explorer...
				window.setInterval('com_pagasg_www_util.ElementWatcher.main.checkSize()', monitorPeriod);
			}
		}
		ElementWatcher.add = function(elementId) {
			var watcher = new ElementWatcher(document.getElementById(elementId));

			ElementWatcher.watchers[elementId] = watcher;
			return watcher;
		};
		ElementWatcher.remove = function(elementId) {
			var watcher = ElementWatcher.watchers[elementId];

			if (watcher) {
				delete ElementWatcher.watchers[elementId];
			}
			return watcher;
		};

		// ****** URL Watcher ******
		var URLWatcher = new (function() { 
			var _hash;
			var _evtMgr;
			var _onchangeAddListener;
			var _onchangeRemoveListener;
			var _onchangeFire;
			var _onchange;
			var _logger = LOG.Logger.getLogger('com.pagasg.www.util.URLWatcher');

			var _cycle = new Cycle(function(cycle) {
				var event;
				var priorHash;
				
				if (window.location.hash != _hash) {
					priorHash = _hash;
					try {
						_hash = window.location.hash;
						_onchangeFire({ hash: _hash, priorHash: priorHash });
					} catch (e) {
						_logger.warning(Objects.display(e, 1));
					}
				}
			});

			_evtMgr = new EventManager(Event, this, 'onChange');
			_onchangeAddListener = this.onChange.addListener;
			_onchangeRemoveListener = this.onChange.removeListener;
			_onchangeFire = this.onChange.fire;

			// Public
			this.getHash = function() {
				return _hash;
			}
			this.getLogger = function() {
				return _logger;
			};
			this.onChange.addListener = function(listener) {
				if (_cycle.state != Cycle.State.started) {
					_logger.info('Starting the URLWatcher');
					_cycle.start(100);
				}
				_onchangeAddListener(listener);
			};
			this.onChange.removeListener = function(listener) {
				_onchangeRemoveListener(listener);
				if (this.size() == 0) {
					_cycle.stop();
				}
			}
		})();

		// ****** Frames ******
		var Frames = {
			setHash: function(hash, frame) {
				var pageUrl;
				var win = frame || window.top;
				var href = win.location.href;

				pageUrl = WU.Strings.before(href, '#') || href;

				if (hash == null || hash == undefined) {
					hash = '';
				}
				win.location.href = pageUrl + '#' + hash;
			},
			getHash: function(frame) {
				var win = frame || window.top;

				return WU.Strings.after(win.location.href, '#');
			}
		}

		// ****** Elements DEPRECATED: use Block instead ******
		var Elements = {
			getLogger: function() {
				return LOG.Logger.getLogger('com.pagasg.www.util.Elements');
			},
			setContents: function(idOrElm, contents) {
				var tagName;
				var elm = Elements.idOrElm2Elm(idOrElm);

				if (elm) {
					tagName = Strings.toLower(elm.tagName);
					if (tagName == 'img') {
						elm.src = contents;
					} else if (tagName == 'textarea' || tagName == 'select' || tagName == 'input') {
						Inputs.setValue(elm, contents);
					} else {
						elm.innerHTML = contents;
					}
				}
			},
			getContents: function(idOrElm) {
				var elm = Elements.idOrElm2Elm(idOrElm);

				if (elm.tagName == 'img') {
					return elm.src;
				} else if (elm.tagName == 'textarea'
						|| elm.tagName == 'select'
						|| elm.tagName == 'input') {
					return Inputs.getValue(elm);
				} else {
					return elm.innerHTML;
				}
			},
			deleteElement: function(idOrElm) {
				var parent;
				var elm = Elements.idOrElm2Elm(idOrElm, true);
				
				if (elm) {
					parent = elm.parentNode;
					parent.removeChild(elm);
				}
			},
			idOrElm2Elm: function(idOrElm, nocheck, nowarning) {
				var elm;
				if (idOrElm && idOrElm.constructor == String) {
					elm = document.getElementById(idOrElm);
				} else {
					elm = idOrElm;
				}

				if (elm || nocheck) {
					if (!elm) Elements.getLogger().info('idOrElm arg: (' + idOrElm + ') could not be located');
					return elm;
				} else if (idOrElm) {
					throw new Error('idOrElm arg: (' + idOrElm + ') could not be located');
				} else {
					throw new Error('idOrElm arg was null, empty, or undefined');
				}
			},
			toDescendant: /*element*/ function(top, filter) {
				function into(elm) {
					var child;
					var children = elm.childNodes;

					if (children) {
						for (var i = 0; i < children.length; i++) {
							child = children[i];
							if (child.nodeType == 1 && filter(child)) {
								return child;
							}
							into(child);
						}
					}
				};

				return into(top);
			},
			getDescendants: /*elements[]*/ function(idOrElm, filter) {
				var result = [];
				var top = Elements.idOrElm2Elm(idOrElm) || window.document;

				function into(elm) {
					var child;
					var children = elm.childNodes;
					if (children) {
						for (var i = 0; i < children.length; i++) {
							child = children[i];
							if (child.nodeType == 1) {
								if (!filter || filter(child)) {
									result.push(child);
								}
								into(child);
							}
						}
					}
				};

				into(top);
				return result;
			},
			toParent: function(idOrElm) {
				var elm = this.idOrElm2Elm(idOrElm);

				if (elm) {
					return (elm.parentElement || elm.parentNode)
				} else {
					return elm;
				}
			},
			getAncestors: function(idOrElm, /*function(elm,depth)*/filter) {
				var ancestor;
				var depth;
				var result = [];
				var elm = Elements.idOrElm2Elm(idOrElm);

				for (ancestor = elm; ancestor; ancestor = (ancestor.parentElement || ancestor.parentNode)) {
					if (ancestor.nodeType == 1) {
						if (!filter || filter(ancestor, depth)) {
							result.push(ancestor);
						}
					}
				}

				return result;
			},
			hasClassname: /*bool*/ function(name, idOrElm) {
				var str;
				var names;
				var elm = this.idOrElm2Elm(idOrElm);

				name = name.toLowerCase();
				str = elm.className || '';
				if (str.indexOf(name) >= 0) {
					names = str.toLowerCase().split(/\s+/);
					for (var i = 0; i < names.length; i++) {
						if (names[i] == name) {
							return true;
						}
					}
				}

				return false;
			},
			addClassname: function(name, elements) {
				var str;
				var names;
				var elm;
				var idOrElm;
				var found;
				var elms = Objects.argsOrArray2Array(arguments, 1);

				name = name.toLowerCase();
				for (var i = 0; i < elms.length; i++) {
					elm = this.idOrElm2Elm(elms[i], true);
					if (elm) {
						str = elm.className || '';
						found = false;
						if (str.indexOf(name) >= 0) {
							names = str.toLowerCase().split(/\s+/);
							for (var j = 0; j < names.length; j++) {
								if (names[j] == name) {
									found = true;
									break;
								}
							}
						}

						if (!found) {
							elm.className = ((str != '') ? elm.className + ' ' : '') + name;
						}
					}
				}
			},
			removeClassname: function(name, elements) {
				var str;
				var names;
				var newNames = [];
				var idOrElm;
				var elms = Objects.argsOrArray2Array(arguments, 1);

				name = name.toLowerCase();
				for (var i = 0; i < elms.length; i++) {
					elm = this.idOrElm2Elm(elms[i], true);
					if (elm) {
						str = elm.className || '';
						if (str.indexOf(name) >= 0) {
							names = str.toLowerCase().split(/\s+/);
							for (var j = 0; j < names.length; j++) {
								if (names[j] != name) {
									newNames.push(names[j]);
								}
							}
							elm.className = newNames.join(' ');
						}
					}
				}
			},
			toggleClass: /*bool*/ function(idOrElm, name) {
				var str;
				var names;
				var found = false;
				var newNames = [];
				var elm = this.idOrElm2Elm(idOrElm);

				name = name.toLowerCase();
				str = elm.className || '';
				if (str == name) {
					elm.className = '';
					return false;
				} else if (str.indexOf(name) >= 0) {
					names = str.toLowerCase().split(' ');
					for (var i = 0; i < names.length; i++) {
						if (names[i] != name) {
							newNames.push(names[i]);
						} else {
							found = true;
						}
					}
					if (!found) newNames.push(name);
					elm.className = newNames.join(' ');
					return !found;
				} else {
					
				}
			},
			toggleImageState: function(idOrElm) {
				var dash;
				var ext;
				var src;
				var toggle = null;
				var elm = this.idOrElm2Elm(idOrElm);

				src = elm.src;
				if ((dash = src.lastIndexOf('-')) > 0) {
					ext = src.lastIndexOf('.');
					if (ext > dash) {
						toggle = src.substring(dash + 1, ext);
						if (toggle == 'left') {
							toggle = 'right';
						} else if (toggle == 'right') {
							toggle = 'left';
						} else if (toggle == 'up') {
							toggle = 'down';
						} else if (toggle == 'down') {
							toggle = 'up';
						} else if (toggle == 'open') {
							toggle = 'closed';
						} else if (toggle == 'closed') {
							toggle = 'open';
						} else {
							throw new Error('The current state: ' + toggle + ' was not understood.');
						}
					}
				}

				elm.src = src.substring(0, dash + 1) + toggle + src.substring(ext);
			},
			getImageState:  function(idOrElm) {
				var dash;
				var ext;
				var src = this.idOrElm2Elm(idOrElm).src;

				ext = src.lastIndexOf('.');
				if (ext > 0) {
					if ((dash = src.lastIndexOf('-')) > 0) {
						return src.substring(dash + 1, ext);
					}
				}

				return null;
			},
			changeImageState: function(idOrElm, state) {
				var pos;
				var ext;
				var newSrc;
				var src;
				var elm = this.idOrElm2Elm(idOrElm);

				src = elm.src;
				pos = src.lastIndexOf('.');
				ext = src.substring(pos);
				src = src.substring(0, pos);
				pos = src.lastIndexOf('/');
				pos = src.indexOf('-', pos + 1);
				if (pos > 0) {
					src = src.substring(0, pos);
				}

				if (state && state != '') {
					newSrc = src + '-' + state.toString() + ext;
				} else {
					newSrc = src + ext;
				}

				if (elm.src != newSrc) {
					elm.src = newSrc;
				}
				return newSrc;
			},
			getOuterHTML: /*String*/ function(idOrElm, replaceAttrs) {
				var contents = new StringBuilder();
				var elm = Elements.idOrElm2Elm(idOrElm);

				contents.append(Elements.getStartTag(elm, replaceAttrs)).append('>');
				contents.append(elm.innerHTML);
				contents.append(Elements.getEndTag(elm));

				return contents.toString();
			},
			getLogger: function() {
				return LOG.Logger.getLogger('com.pagasg.www.util.Elements');
			},
			getStartTag: /*String*/ function(idOrElm, replaceAttrs, notagName) {
				var attr;
				var value;
				var bldr = new StringBuilder();
				var elm = Elements.idOrElm2Elm(idOrElm);
				var attributes = elm.attributes;

				if (!notagName) {
					bldr.append('<').append(elm.nodeName.toLowerCase());
				}

				if (replaceAttrs) {
					for (var p in replaceAttrs) {
						if (replaceAttrs.hasOwnProperty(p)) {
							value = Strings.stringValue(replaceAttrs[p]);
							if (value != null && value != undefined && value.length > 0) {
								bldr.append(' ').append(p).append('="');
								bldr.append(value.replace(/\"/, '&quot;')).append('"');
							}
						}
					}
				}
				for (var i = 0; i < attributes.length; i++) {
					attr = attributes[i];
					if (!replaceAttrs || !replaceAttrs.hasOwnProperty(attr.nodeName)) {
						value = attr.nodeValue;
						if (value != null && value != undefined && value.length > 0) {
							bldr.append(' ').append(attr.name).append('="');
							bldr.append(value.replace(/\"/, '&quot;')).append('"');
						}
					}
				}

				return bldr.toString();
			},
			getEndTag: function(idOrElm) {
				var elm = Elements.idOrElm2Elm(idOrElm);
				return '</' + elm.nodeName.toLowerCase() + '>';
			},
			getLeft: function(elm) {
				if(!elm) return 0;
				return elm.offsetLeft + Elements.getLeft(elm.offsetParent);
			},
			getTop: function(elm) {
				if (!elm) return 0;
				return elm.offsetTop + Elements.getTop(elm.offsetParent);
			},
			bind: function(idOrElm, type, retvalue, fn) {
				var elm = Elements.idOrElm2Elm(idOrElm);

				elm[type] = function() {
					var result;
					try {
						result = fn(elm);
						return (result != undefined) ? result : retvalue;
					} catch (e) {
						Elements.getLogger().warning(Objects.display(e, 1));
						return retvalue;
					}
				};
			},
			tail: /*Placeholder for Explorer*/ undefined
		};

		// ****** Block ******
		var Block = function(name, separator, doc) {
			var _logger;
			var _doc = doc || window.document;
			var _sep = (separator == undefined || separator == null) ? '-' : separator;
			var _prefix = (name || '') + _sep;

			var getLogger = function() {
				if (_logger) {
					return _logger;
				} else {
					var suffix = (name) ? '.' + name : '';
					return LOG.Logger.getLogger('com.pagasg.www.util.Block' + suffix);
				}
			}
			var get = function(idOrElm, nocheck, nowarning) {
				var elm;

				if (idOrElm == undefined || idOrElm == null) {
					elm = _doc.getElementById(name);
				} else if (idOrElm && idOrElm.constructor == String) {
					elm = _doc.getElementById(_prefix + idOrElm);
				} else {
					elm = idOrElm;
				}

				if (elm || nocheck) {
					if (!elm && !nowarning) getLogger().info('idOrElm arg: (' + _prefix + idOrElm + ') could not be located');
					return elm;
				} else if (idOrElm) {
					throw new Error('[' + _prefix + ']' + idOrElm + ' could not be located');
				} else {
					throw new Error('idOrElm arg was null, empty, or undefined');
				}
			}
			var getLeft = function(elm) {
				if(!elm) return 0;
				return elm.offsetLeft + getLeft(elm.offsetParent);
			}
			var getTop = function(elm) {
				if (!elm) return 0;
				return elm.offsetTop + getTop(elm.offsetParent);
			}

			// Public
			this.getLogger = getLogger;
			this.get = get;
			this.getDescendants = function(idOrElm, filter) {
				var result = [];
				var top = get(idOrElm, true, true) || _doc;

				function into(elm) {
					var child;
					var children = elm.childNodes;
					if (children) {
						for (var i = 0; i < children.length; i++) {
							child = children[i];
							if (child.nodeType == 1) {
								if (!filter || filter(child)) {
									result.push(child);
								}
								into(child);
							}
						}
					}
				};

				into(top);
				return result;
			}
			this.toParent = function(idOrElm) {
				var elm = get(idOrElm);

				if (elm) {
					return (elm.parentElement || elm.parentNode)
				} else {
					return elm;
				}
			}
			this.setContents = function(idOrElm, contents) {
				var elm = get(idOrElm);
				var tagName = Strings.toLower(elm.tagName);

				if (tagName == 'img') {
					elm.src = contents;
				} else if (Strings.indexOf(tagName, 'textarea', 'select', 'input') >= 0) {
					Inputs.setValue(elm, contents);
				} else {
					elm.innerHTML = contents;
				}
			}
			this.getContents = function(idOrElm) {
				var elm = get(idOrElm);
				var tagName = Strings.toLower(elm.tagName);

				if (tagName == 'img') {
					return elm.src;
				} else if (Strings.indexOf(tagName, 'textarea', 'select', 'input') >= 0) {
					return Inputs.getValue(elm);
				} else {
					return elm.innerHTML;
				}
			}
			this.remove = function(idOrElm) {
				var parent;
				var elm = get(idOrElm, true);
				
				if (elm) {
					parent = elm.parentNode;
					parent.removeChild(elm);
				}
			}
			this.toDescendant = function(top, filter) {
				function into(elm) {
					var child;
					var children = elm.childNodes;

					if (children) {
						for (var i = 0; i < children.length; i++) {
							child = children[i];
							if (child.nodeType == 1 && filter(child)) {
								return child;
							}
							into(child);
						}
					}
				};

				return into(top);
			}
			this.getAncestors = function(idOrElm, /*function(elm,depth)*/filter) {
				var ancestor;
				var depth;
				var result = [];
				var elm = get(idOrElm);

				for (ancestor = elm; ancestor; ancestor = (ancestor.parentElement || ancestor.parentNode)) {
					if (ancestor.nodeType == 1) {
						if (!filter || filter(ancestor, depth)) {
							result.push(ancestor);
						}
					}
				}

				return result;
			}
			this.hasClassname = function(name, idOrElm) {
				var str;
				var names;
				var elm = get(idOrElm);

				name = name.toLowerCase();
				str = elm.className || '';
				if (str.indexOf(name) >= 0) {
					names = str.toLowerCase().split(/\s+/);
					for (var i = 0; i < names.length; i++) {
						if (names[i] == name) {
							return true;
						}
					}
				}

				return false;
			}
			this.addClassname = function(name, elements) {
				var str;
				var names;
				var elm;
				var idOrElm;
				var found;
				var elms = Objects.argsOrArray2Array(arguments, 1);

				name = name.toLowerCase();
				for (var i = 0; i < elms.length; i++) {
					elm = this.get(elms[i], true);
					if (elm) {
						str = elm.className || '';
						found = false;
						if (str.indexOf(name) >= 0) {
							names = str.toLowerCase().split(/\s+/);
							for (var j = 0; j < names.length; j++) {
								if (names[j] == name) {
									found = true;
									break;
								}
							}
						}

						if (!found) {
							elm.className = ((str != '') ? elm.className + ' ' : '') + name;
						}
					}
				}
			}
			this.removeClassname = function(name, elements) {
				var str;
				var names;
				var newNames = [];
				var idOrElm;
				var elms = Objects.argsOrArray2Array(arguments, 1);

				name = name.toLowerCase();
				for (var i = 0; i < elms.length; i++) {
					elm = this.get(elms[i], true);
					if (elm) {
						str = elm.className || '';
						if (str.indexOf(name) >= 0) {
							names = str.toLowerCase().split(/\s+/);
							for (var j = 0; j < names.length; j++) {
								if (names[j] != name) {
									newNames.push(names[j]);
								}
							}
							elm.className = newNames.join(' ');
						}
					}
				}
			}
			this.toggleClass = function(idOrElm, name) {
				var str;
				var names;
				var found = false;
				var newNames = [];
				var elm = get(idOrElm);

				name = name.toLowerCase();
				str = elm.className || '';
				if (str == name) {
					elm.className = '';
					return false;
				} else if (str.indexOf(name) >= 0) {
					names = str.toLowerCase().split(' ');
					for (var i = 0; i < names.length; i++) {
						if (names[i] != name) {
							newNames.push(names[i]);
						} else {
							found = true;
						}
					}
					if (!found) newNames.push(name);
					elm.className = newNames.join(' ');
					return !found;
				} else {
					return false;			// TODO: ???
				}
			}
			this.getImageState = function(idOrElm) {
				var dash;
				var ext;
				var src = get(idOrElm).src;

				ext = src.lastIndexOf('.');
				if (ext > 0) {
					if ((dash = src.lastIndexOf('-')) > 0) {
						return src.substring(dash + 1, ext);
					}
				}

				return null;
			}
			this.changeImageState = function(idOrElm, state) {
				var dash;
				var pos;
				var ext;
				var newSrc;
				var src;
				var elm = get(idOrElm);

				src = elm.src;
				pos = src.lastIndexOf('.');
				ext = src.substring(pos);
				src = src.substring(0, pos);
				pos = src.lastIndexOf('/');
				dash = src.lastIndexOf('-');
				if (dash > pos) {
					src = src.substring(0, dash);
				}

				if (state && state != '') {
					newSrc = src + '-' + state.toString() + ext;
				} else {
					newSrc = src + ext;
				}

				if (elm.src != newSrc) {
					elm.src = newSrc;
				}
				return newSrc;
			}
			this.getOuterHTML = function(idOrElm, replaceAttrs) {
				var contents = new StringBuilder();
				var elm = get(idOrElm);

				contents.append(getStartTag(elm, replaceAttrs)).append('>');
				contents.append(elm.innerHTML);
				contents.append(getEndTag(elm));

				return contents.toString();
			}
			this.getStartTag = function(idOrElm, replaceAttrs, notagName) {
				var attr;
				var value;
				var bldr = new StringBuilder();
				var elm = get(idOrElm);
				var attributes = elm.attributes;

				if (!notagName) {
					bldr.append('<').append(elm.nodeName.toLowerCase());
				}

				if (replaceAttrs) {
					for (var p in replaceAttrs) {
						if (replaceAttrs.hasOwnProperty(p)) {
							value = Strings.stringValue(replaceAttrs[p]);
							if (value != null && value != undefined && value.length > 0) {
								bldr.append(' ').append(p).append('="');
								bldr.append(value.replace(/\"/, '&quot;')).append('"');
							}
						}
					}
				}
				for (var i = 0; i < attributes.length; i++) {
					attr = attributes[i];
					if (!replaceAttrs || !replaceAttrs.hasOwnProperty(attr.nodeName)) {
						value = attr.nodeValue;
						if (value != null && value != undefined && value.length > 0) {
							bldr.append(' ').append(attr.name).append('="');
							bldr.append(value.replace(/\"/, '&quot;')).append('"');
						}
					}
				}

				return bldr.toString();
			}
			this.getEndTag = function(idOrElm) {
				var elm = get(idOrElm);
				return '</' + elm.nodeName.toLowerCase() + '>';
			}
			this.bind = function(idOrElm, type, retvalue, fn) {
				var elm = get(idOrElm);

				elm[type] = function() {
					var result;
					try {
						result = fn(elm);
						return (result != undefined) ? result : retvalue;
					} catch (e) {
						getLogger().warning(Objects.display(e, 1));
						return retvalue;
					}
				};
			};
			this.getTop = function(idOrElm) {
				return getTop(get(idOrElm));
			};
			this.getLeft = function(idOrElm) {
				return getLeft(get(idOrElm));
			};
		}

		// ****** HtmlPainter ******
		var HtmlPainter = function(scratchpad) {
			var _scratchpad = (scratchpad) ? scratchpad : createScratchpad();

			// Private
			function createScratchpad() {
				var elm
				var _doc = window.top.document;

				if (elm = _doc.getElementById('scratchpad')) {
					return elm;
				} else {
					elm = _doc.createElement('div');
					elm.id = "scratchpad";
					_doc.body.appendChild(elm);
					return elm;
				}
			}
			function paint_table(area, html) {
				var attrib;
				var startTableTag = '';

				_scratchpad.innerHTML = '';
				for (var i = 0; i < area.attributes.length; i++) {
					attrib = area.attributes.item(i);
					if (attrib.nodeValue) {
						startTableTag += ' ' + attrib.nodeName + '="' + attrib.nodeValue + '"';
					}
				}
				startTableTag = '<table' + startTableTag + '>';
				_scratchpad.innerHTML = startTableTag + html + '</table>';
				area.parentNode.replaceChild(_scratchpad.removeChild(_scratchpad.firstChild), area);
			}
			function paint_row(area, html) {
				var attrib;
				var node;
				var startRowTag = '';
				var parent = area.parentNode;

				for (var i = 0; i < area.attributes.length; i++) {
					attrib = area.attributes.item(i);
					if (attrib.nodeValue) {
						startRowTag += ' ' + attrib.nodeName + '="' + attrib.nodeValue + '"';
					}
				}
				area.id = '';
				startRowTag = '<tr' + startRowTag + '>';
				_scratchpad.innerHTML = '<table border="1"><tbody>' + startRowTag + html + '</tr></tbody></table>';
				for (node = _scratchpad; node.nodeName.toLowerCase() != 'tr'; node = node.firstChild);
				area.parentNode.replaceChild(node.parentNode.removeChild(node), area);
			}

			this.paint = function(area, xml) {
				if (area.tagName.toLowerCase() == 'table') {
					paint_table(area, xml);
				} else if (area.tagName.toLowerCase() == 'tr') {
					paint_row(area, xml);
				} else if (area.tagName.toLowerCase() == 'textarea') {
					area.value = xml;
				} else {
					area.innerHTML = xml;	
				}
			}
		};

		// ====== PACKAGE PUBLIC ======
		this.Objects = Objects;
		this.Arrays = Arrays;
		this.Strings = Strings;
		this.Elements = Elements;
		this.Block = Block;
		this.Inputs = Inputs;
		this.Formatter = Formatter;
		this.Cycle = Cycle;
		this.Event = Event;
		this.DatetimeRange = DatetimeRange;
		this.EventManager = EventManager;
		this.HtmlPainter = HtmlPainter;
		this.UserAgent = UserAgent;
		this.URLWatcher = URLWatcher;
		this.ElementWatcher = ElementWatcher;
		this.Container = Container;
		this.HashIndex = HashIndex;
		this.OrderedIndex = OrderedIndex;
		this.Frames = Frames;
		// ====== PACKAGE PUBLIC ======
	}
)

