// =============================================================================
// es_lang package -- Java-like language extensions to javascript
// =============================================================================
es_lang = new (function() {
	// Class
	// -----
	var Class = function() {}
	// Creates new supr() and uses that as the prototype for fn.
	Class.extend = /*Function*/ function(
			/*Function*/ supr,
			/*Function*/ fn) {
		var obj;
		var proto;
		var val;

		if (!fn) fn = new Function("");
		obj = fn.prototype;

		fn.supr = supr;
		if (supr && supr.constructor == Function) {
			proto = fn.prototype = new supr();
			for (var p in obj) {
				if (obj[p] != {}[p] && !proto[p]) {
					proto[p] = obj[p];
				}
			}
		} else {
			throw new Error("supr is not a function, or is not defined for: " + fn);
		}
		fn.prototype.constructor = fn;
		return fn;
	};
	// Copies obj's properties to the prototype of fn.
	Class.implement = /*Function*/ function(
			/*Object*/ obj,
			/*Function*/ fn) {
		var proto;

		if (!fn) fn = new Function("");
		proto = fn.prototype;
		for (var p in obj) {
			if (obj[p] != {}[p] && !proto[p]) {
				proto[p] = obj[p];
			}
		}
		return fn;
	};

	// Enum
	// ----
	/**
	 * Usage:
	 * var Digit = es_lang.Enum.enumerate(
	 * 	['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'],
	 * 	function(name, ordinal) {
	 * 		this.append = function(number) {
	 * 			return parseInt("" + ordinal + number);
	 * 		}
	 * 	}
	 * );
	 *
	 * alert('The 0th digit is: ' + Digit.items[0].toString());
	 * alert('The value of one is: ' + Digit.one.ordinal);
	 *
	 * var a_digit_name = 'four';
	 * if (Digit[a_digit_name]) alert(a_digit_name + ' is a digit');
	 * if (Digit[a_digit_name] == Digit.four) alert(a_digit_name + ' is digit ' + Digit.four.toString());
	 * if (!Digit['eleven']) alert('eleven is not a digit');
	 * 
	 * alert("the sum of " + Digit.three.toString() + " and " + Digit.nine.toString() + " = " + (Digit.three + Digit.nine));
	 * alert(Digit.one.toString() + "*100 + " + Digit.nine.toString() + "*10 + " + Digit.three.toString() + " = " + Digit.one.append(Digit.nine.append(Digit.three)));
	 */
	var Enum = function(/*Object*/ name, /*Int*/ ordinal) {
		this.name = name;
		this.ordinal = ordinal;
		this.toString = function() { return new String(name); };
		this.valueOf = function() { return ordinal; };
	}
	Enum.enumerate = function(/*{val1: int1, val2: int2, ... }|Object[]*/ names, /*Function*/ fn, /*Container*/ container) {
		var name;
		var item;
		var index;
		var length;
		var obj;

		fn = Class.extend(Enum, fn);
		obj = (container) ? container : fn;
		if (names.constructor == Array) {
			obj.items = [];
			for (var i = 0; i < names.length; i++) {
				name = names[i];
				obj[new String(name)] = item = new fn(name, i);
				Enum.call(item, name, i);
				obj.items.push(item);
			}
		} else {
			length = 0;
			for (name in names) {
				if (names[name] > length) length = names[name];
			}
			obj.items = new Array(length + 1);
			for (name in names) {
				index = names[name];
				obj[new String(name)] = item = new fn(name, index);
				Enum.call(item, name, index);
				obj.items[index] = item;
			}
		}

		return obj;
	}

	// Package
	// -------
	var Package = function(
			/*boolean*/   autoinit,
			/*String*/    description,
			/*String*/    documentationURI,
			/*Function*/  implementation) {

		// Save the package
		this.constructor.packages.push(this);

		if (autoinit) {
			implementation.apply(this);
		} else {
			this.init = /*Error*/ function() {
				try {
					implementation.apply(this);
					return null;
				} catch (e) {
					return e;
				}
			}
		}

		// Public properties: implementation, description, documentationURI
		this.implementation = implementation;
		this.description = description;
		this.documentationURI = documentationURI;
	};
	// Package Registry
	Package.packages = [];

	/**
	 * nice and simple implementation of the string builder. This class is many
	 * times faster than the str += xxx method, but so far does not incorporate
	 * an 'insert' functionality.
	 */
	var StringBuilder = function(value) {
		var _buffer = [];

		function throwUOE(methodName) {
			throw new Error("UnsupportedOperationException::StringBuilder#" + methodName + " -- this method has not yet been implemented (but you're welcome to write it!)");
		}
		this.append = function(/*Object*/ value) {
			_buffer.push(new String(value));
			return this;
		}
		this.charAt = function(/*int*/ pos) {
			throwUOE('charAt');
		}
		this.insert = function(/*int*/ pos, /*Object*/ value) {
			if (pos == 0) {
				_buffer.unshift(value);
				return this;
			} else {
				throwUOE('insert');
			}
		}
		this.length = function() {
			var len = 0;
			for (var i = 0; i < _buffer.length; i++) {
				len += _buffer[i].length;
			}
			return len;
		}
		this.setLength = function() {
			throwUOE('setLength');
		}
		this.indexOf = function(/*String*/ value) {
			throwUOE('index');
		}
		this.lastIndexOf = function(/*String*/ value) {
			throwUOE('lastIndexOf');
		}
		this.substring = function(/*int*/ start, /*int*/ end) {
			throwUOE('substring');
		}
		this.replace = function(/*int*/ start, /*int*/ end, /*Object*/ value) {
			throwUOE('replace');
		}
		this.toString = function() {
			return _buffer.join("");
		}
		this.clear = function() {
			_buffer = [];
		}
	};
	StringBuilder.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) {
				try {
					value = obj[prop];
				} catch (e) {
					value = '-- unobtainable --';
				}

				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();
	};

	/**
	 * creates a new 'thread' which executes separately from the browser's
	 * user interface.
	 */
	var Thread = function(run) {
		var _started = false;;
		var _timeout = -1;
		var _interval = -1;
		var _seqno = 'thread-' + Thread.threads.seqno;

		Thread.threads.seqno++;

		if (arguments.length > 0) {
			this.run = function() {
				run();
			};
			this.isRunning = function() {
				return _interval > 0 || _timeout > 0;
			};
			this.interrupt = function() {
				if (_interval > 0) {
					window.clearInterval(_interval);
					delete Thread.threads.map[seqno];
				} 
				if(_timeout > 0) {
					window.clearTimeout(_timeout);
				}
				_interval = -1;
				_timeout = -1;
			};
			this.start = function(/*Milliseconds*/ when, period) {
				if (_timeout < 0 && _interval < 0) {
					if (when == undefined) when = 10;

					_timeout = window.setTimeout('es_lang.Thread.execute("' + _seqno + '")', when);
					this.period = period || 0;
				} else {
					throw new Error("This thread has already started");
				}
			};
			this.getInterval = function() {
				return _interval;
			}
			this.setInterval = function(interval) {
				_interval = interval;
			};
			Thread.threads.map[_seqno] = this;
		}
	}
	Thread.threads = {
		seqno: 0,
		map: {}
	};
	Thread.execute = function(seqno) {
		var period;
		var thread = Thread.threads.map[seqno];

		if (thread) {
			period = thread.period || 0;
			if (thread.getInterval() < 0 && period > 0) {
				thread.setInterval(window.setInterval('es_lang.Thread.execute("' + seqno + '")', period));
			} else if (period == 0) {
				delete Thread.threads.map[seqno];
			}

			thread.run();
		}
	}
	Thread.create = function(fn) {
		var thread = new Thread(fn);

		return thread;
	}

	// === Public ===
	this.Class = Class;
	this.Enum = Enum;
	this.Package = Package;
	this.Thread = Thread;
	this.StringBuilder = StringBuilder;
	// === Public ===

	// Bootstrap
	// ---------
	Package.packages = [new Package(false, "Simple class and package utilities for Javascript", null, this.constructor)];
})();




