// =============================================================================
// 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 = [];
		}
	}

	/**
	 * creates a new 'thread' which executes separately from the browser's
	 * user interface.
	 */
	var Thread = function(run) {
		this.id = 'thread-' + Thread.threads.seqno;
		Thread.threads.seqno++;

		if (arguments.length > 0) {
			this.run = run;
			this.start = function(/*Milliseconds*/ when) {
				if (!when) {
					when = 10;
				}
				window.setTimeout('es_lang.Thread.execute("' + this.id + '")', when);
			}

			Thread.threads.map[this.id] = this;
		}
	}
	Thread.threads = {
		seqno: 0,
		map: {}
	};
	Thread.execute = function(threadId) {
		var thread = Thread.threads.map[threadId];

		if (thread) {
			delete Thread.threads.map[threadId];
			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)];
})();



