/**
 * defineClass() -- a utiity function for defining JavaScript classes
 *
 * This function expects a single object as its only argument. It defines 
 * a new JavaScript class based on the data in that object and returns the 
 * constructor function of the new class. This function handles the repetitive
 * task f defining classes: setting up the prototype object for correct
 * inheritance, copying methods from other type, and so on.
 *
 * The oject passes as an argument should have some or all of the
 * following properties:
 *
 *      name: 	The name of the class being defined.
 *		 If specified, this value will be stored in the classname
 *    		 property of the prototype object.
 *
 *    extend: The constructor of the class to be extended. If omitted,
 *     		the Object constructor will be used. This value will 
 *		be stored in the superclass property of the prototype object.
 * 
 * construct: The constructor function for the Class. If omitted, a new 
 *		empty function will be used. This value becomes the return
 *		value of the function, and is also stored in the constructor 
 *		prototype of the prototype object.
 *
 *   methods: An object that specifies the instance methods (and other shared
 *		properties) for the class. The properties of this object are 
 *		copied innto the prototype object of the class. If omitted,
 *		an empty object is used instead. Properties named 
 *		"classname", "superclass", and "constructor" are reserved 
 *		and should not be used in this object.
 *			
 *     statics: An object that specifies the static methods (and other static 
 *		properties) for the class. The properties of this object become
 *		properties of the constructor function. If omitted, an empty
 *		object is used instead.
 *
 *   borrows: A constructor function or array of cconstructor functions.
 *		The instance methods of each of the specified classes are copied
 *		into the prototype object of this new class so that the 
 *		new class borrows the methods of each specified class.
 *		Constructors are processed in the order they are specified,
 *		so the methods of a class listed at the end of the array may
 *		overwrite the methods of those specified earlier. Note that
 *		borrowed methods are stored in the prototype object before
 *		the properties of the methods object above. Therefore,
 *		methods specified in the methods object can overwrite borrowed
 *		methods. If this property is not specified, no method are borrowed.
 *
 *  provides:  A constructor function or array of constructor functions.
 *		After the prototype object is fully initialized, this function
 *		verifies that the prototype includes methods whose names and
 *		number of arguments match the instance methods defined by each
 *		of these classes. No methods are copied, this is simply an 
 *		assertion that this class "provides" the functionality of the
 *		specified classes. If the assertion fails, this method will
 *		throw an exception. If no exception is thrown, any
 *		instance of the new class can also  be considered (using "duck
 *		typing") to be an instance of these other types. If this
 *		property is not specified, no such verification is performed.
 **/
function defineClass(data) {
	// Extract the fields we'll use from the argument object.
	// Set up default values.
	var classname = data.name;
	var superclass = data.extend || Object;
	var constructor = data.construct || function(){};
	var methods = data.methods || {};
	var statics = data.statics || {};
	var borrows;
	var provides;
	
	// Borrows may be a single constructor or an array of them.
	if (!data.borrows) borrows = [];
	else if (data.borrows instanceof Array) borrows = data.borrows;
	else borrows = [ data.borrows ];

	// Ditto for the provides property
	if (!data.provides) provides = [];
	else if (data.provides instanceof Array) provides = data.provides;
	else provides = [ data.provides ];

	// Create the object that will become the prototype of our class
	var proto = new superclass();
	
	// Delete any noninherited properties of this new prototype object
	for (var p in proto)
		if (proto.hasOwnProperty(p)) delete proto(p);
		
	// Borrow methods from "mixin" classes by copying to our prototype
	for ( var i=0; i<borrows.length; i++) {
		var c = data.borrows[i];
		borrows[i] = c;
		// Copy method properties from the prototype of c to our prototype
		for ( var p in c.prototype) {
			if (typeof c.prototype[p] != "function") continue;
			proto[p] = c.prototype[p];
		}
	}
	
	// Copy instance methods to the prototype object
	// This my overwrite methods of the mixin clauses
	for (var p in methods) proto[p] = methods[p];

	// Set up the reserved "constructor", "superclass", and "classname"
	// properties of the prototype.
	proto.constructor = constructor;
	proto.superclass = superclass;
	// classname is set only if a name was actually specified
	if (classname) proto.classname = classname;
	
	// Verify that our prototype provides all of the methods is supposed to.
	for (var i=0;i<provides.length; i++) { // for each class
		var c = provides[i];
		for (var p in c.prototype) { // for each property
			if (typeof c.prototype[p] != "function") continue; // methods only
			if (p == "constructor" || p == "superclass") continue;
			// Check that we have a method with the same name and that
			// it has the same number of declared arguments. If so,  move on
			if (p in proto &&
				typeof proto[p] == "function" &&
				proto[p].length == c.prototype[p].length) continue;
			// Otherwise, throw an exception
			throw new Error("Class "+ classname + "does not provide method " +
					c.classname + "." + p);
		}
	}
					
	// Associate the prototype object with the constructor function
	constructor.prototype = proto;
	
	// Copy static properties into the constructor
	for (var p in statics) constructor[p] = data.statics[p];
	
	// Finally, return the constructor function
//	alert(constructor);
	return constructor;
}

