/**
 * @author David Flanagan
 */
/**
 * defineClass() -- a utility 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
 * tasks of defining classes: setting up the prototype object for correct
 * inheritance, copying methods from other types, and so on.
 * 
 * The object passed 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 funciton
 *            will be used.  This value becomes the return value of the function, and 
 *            is also stored in the constructor property 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 into the prototype
 *            object of the class.  If omitted, an empty object is used instead.  Properties
 *            names "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 constructor functions.  The instance methods
 *            of each of the specified classes are copied into the prototype object of this new
 *            so that the new class burrows 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 methods
 *            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.

 */


if (!com) var com = new Object();
if (!com.CS) com.CS = new Object();
if (!com.CS.Class) com.CS.Class = new Object();
if (!Class) var Class = new Object();

Class = function(data) {
	var classname = data.name;
	var superclass = data.extend || Object;
	var constructor = data.constructor || function() {};
	var methods = data.methods || {};
	var statics = data.statics || {};
	var borrows;
	var provides;
	
	if (!data.borrows) borrows = [];
	else if (data.borrows instanceof Array) borrows = data.borrows;
	else borrows = [ data.borrows ];
	
	if (!data.provides) provides = [];
	else if (data.provides instanceof Array) provides = data.provides;
	else provides = [data.provides];
	
	var proto = new superclass();
	
	for (var p in proto)
		if (proto.hasOwnProperty(p)) delete proto[p];
	
	for (var i=0;i<borrows.length;i++) {
		var c = data.borrows[i];
		borrows[i] = c;
		for (var p in c.prototype) {
			if (typeof c.prototype[p] != "function") continue;
			proto[p] = c.prototype[p];
		}	
	}
	
	for (var p in methods) proto[p] = methods[p];
	
	proto.constructor = constructor;
	proto.superclass = superclass;
	
	if (classname) proto.classname = classname;
	
	for (var i = 0; i < provides.length; i++) {
		var c = provides[i];
		for (var p in c.prototype) {
			if (typeof c.prototype[p] != "function") continue;
			if (p == "constructor" || p == "superclass") continue;
			if (p in proto && typeof proto[p] == "funciton" && proto[p].length == c.prototype[p].length) continue;
			throw new Error("Class " + classname + " does not provide method " + c.classname + "." + p);
		}
		
	}
	
	constructor.prototype = proto;
	
	for (var p in statics) constructor[p] = data.statics[p];
	
	return constructor;
	
}


