(function() {

	//Check if arguments.callee.caller is suported. Currently it is not suported in Safari and Opera.
	var callerSupported = (function() {
		return typeof arguments.callee.caller == 'function';
	})();
	
	/* This function is relatively complex. It loops through the __proptable property of the
	 * class, aiming to find who called a given function. This is used by __call... magic functions.
	 */
	var detectCaller = function (cls, caller, realParent) {
		for (var i = cls.__proptable.length - 1; i >= 0; i--) {
			var o = cls.__proptable[i];
			for (var prop in o[1]) {
				if (o[1][prop] == caller) {
					par = undefined;
					for (var j = i - 1; realParent && j >= 0; j--) {
						var po = cls.__proptable[j];
						if (po[1][prop] != caller) {
							break;
						}
					}
					if (j >= 0) par = cls.__proptable[j][1];
					return { caller : prop, callerClass : o[0], callerParent : par };
				}
			}
		}		
	}
	
	/* 
	 * This function finds a class in the class hierarchy hidden in the __proptable property.
	 */
	var detectClass = function (cls, dClass) {
		for (var i = 0; i < cls.__proptable.length; i++) {
			var o = cls.__proptable[i];
			if (o[0] == dClass) return o[1];
		}		
	}
	
	/*
	 * This is the base class for all classes.
	 */
	var baseClass = function() {
		this.__construct.apply (this, arguments);
	}
	
	// Here we keep all information about the properties and the methods for every class along with its parent classes.
	baseClass.prototype.__proptable = [];
	// This is the default constructor that gets executed when no other is provided.
	baseClass.prototype.__construct = function() {};
	
	/* Call a method of a base class. They are specified as first and second argument (class, then method) and 
	 * the rest of the arguments are passed through.
	 */
	baseClass.prototype.__callBaseMethod = function () {
		return detectClass (this, arguments[0])[arguments[1]].apply (this, Array.prototype.slice.apply (arguments, [2]));
	}
	
	/* This check is needed because some browsers like Opera and Safari do not support arguments.callee.caller
	 * that is used for detecting who calls a given function
	 */
	if (callerSupported) {
		/* Call same method from a base class. The class is specified as first argument and 
		 * the rest of the arguments are passed through. Method name is the same as the name of the caller function.
		 */
		baseClass.prototype.__callBase = function () {
			var info = detectCaller (this, arguments.callee.caller);
			return detectClass (this, arguments[0])[info.caller].apply (this, Array.prototype.slice.apply (arguments, [1]));
		}
		/* Call a method of the parent class. IT is specified as first argument and 
		 * the rest of the arguments are passed through.
		 */
		baseClass.prototype.__callParentMethod = function () {
			var info = detectCaller (this, arguments.callee.caller);
			return info.callerParent[arguments[0]].apply (this, Array.prototype.slice.apply (arguments, [1]));
		}
		/* Call same method from the parent class. Method name is the same as the name of the caller function.
		 */   
		baseClass.prototype.__callParent = function () {
			var info = detectCaller (this, arguments.callee.caller, true);
			return info.callerParent[info.caller].apply (this, arguments);
		}	
	} else { //There we use this.__caller that is set thanks to some magic in the class inheritance routine.
				
		baseClass.prototype.__callBase = function () {
			var info = detectCaller (this, this.__caller);
			return detectClass (this, arguments[0])[info.caller].apply (this, Array.prototype.slice.apply (arguments, [1]));
		}
		baseClass.prototype.__callParentMethod = function () {
			var info = detectCaller (this, this.__caller);
			return info.callerParent[arguments[0]].apply (this, Array.prototype.slice.apply (arguments, [1]));
		}
		baseClass.prototype.__callParent = function () {
			var info = detectCaller (this, this.__caller, true);
			return info.callerParent[info.caller].apply (this, arguments);
		}	
	}
	
	/* The class object itself. First argument is an object containing all the new methods and properties
	 * and the second argument is the base class (optional).
	 */
	Class = function (properties, base) {
		base = base || baseClass;
				
		var cls = function() {
			this.__construct.apply (this, arguments);
		}
		
		//Copy everything from the base class.
		for (var property in base.prototype) {
			cls.prototype[property] = base.prototype[property];
		}
		//Keep a reference to the parent class.
		cls.prototype.__parent = base;
		//This section handles the copying of all the new class properties and methods.		
		if (callerSupported) { 
			//This branch is used for all browsers that support arguments.callee.caller
			for (var property in properties) {
				cls.prototype[property] = properties[property];
			}			
		} else {
			
			var functionWrapper = function (prop, property) {
				return function() {
					this.__caller = arguments.callee;
					return prop.apply (this, arguments);
				}
			}
			
			/* There we replace all the new function with a wrapper functions that sets this.__caller 
			 * for a further use in __call... methods.
			 * This is an overhead but only for browsers like Opera and Safari and thus not so many
			 * people are affected.
			 */
			for (var property in properties) {
				var prop = properties[property];
				if (typeof prop == 'function') {
					cls.prototype[property] = functionWrapper (prop, property);
				} else {
					cls.prototype[property] = prop;
				}
			}
		}

		//Create a new record in the __propTable property for the newly created class.
		var pt = [];
		var ppt = base.prototype.__proptable;
		for (var i = 0; i < ppt.length; i++) pt.push (ppt[i]);
		pt.push ([cls, cls.prototype]);
		
		cls.prototype.__proptable = pt;
		
		return cls;
	}
	
})();

/**** TEST
			var q1 = new Class ({
				prop:1,
				func1:function(y) { return ('q1' + y); },
				func2:function(y) { return ('q1' + y); },
				func3:function(y) { return ('q1' + y); },
				func4:function(y) { return ('q1' + y); },
				func5:function(y) { return ('q1' + y); },
				func6:function(y) { return ('q1' + y); }
			})
			var q2 = new Class ({
				prop:2,
				func1:function(y) { return ('q2' + y ) + this.__callBaseMethod (q1, 'func1', y); },
				func2:function(y) { return ('q2' + y ) + this.__callBase (q1, y); },
				func3:function(y) { return ('q2' + y ) + this.__callParentMethod ('func3', y); },
				func4:function(y) { return ('q2' + y ) + this.__callParent (y); }
			}, q1);
			var q3 = new Class ({
				prop:3,
				func1:function(y) { return ('q3' + y ) + this.__callBaseMethod (q2, 'func1', y); },
				func2:function(y) { return ('q3' + y) + this.__callBase (q2, y); },
				func3:function(y) { return ('q3' + y ) + this.__callParentMethod ('func3', y); },
				func4:function(y) { return ('q3' + y ) + this.__callParent (y); },
				func5:function(y) { return ('q3' + y ) + this.__callParentMethod ('func5', y); },
				func6:function(y) { return ('q3' + y ) + this.__callParent (y); }
			}, q2);
			r1 = new q1();
			r2 = new q2();
			r3 = new q3();
			console.log ("r1:" + r1.prop + "," + r1.func1(4));
			console.log ("r2:" + r2.prop + "," + r2.func1(5));
			console.log ("r3:" + r3.prop + "," + r3.func1(6));
			console.log ("r1:" + r1.prop + "," + r1.func2(4));
			console.log ("r2:" + r2.prop + "," + r2.func2(5));
			console.log ("r3:" + r3.prop + "," + r3.func2(6));
			console.log ("r1:" + r1.prop + "," + r1.func4(4));
			console.log ("r2:" + r2.prop + "," + r2.func4(5));
			console.log ("r3:" + r3.prop + "," + r3.func4(6));
			console.log ("r1:" + r1.prop + "," + r1.func3(4));
			console.log ("r2:" + r2.prop + "," + r2.func3(5));
			console.log ("r3:" + r3.prop + "," + r3.func3(6));
			console.log ("r1:" + r1.prop + "," + r1.func5(4));
			console.log ("r2:" + r2.prop + "," + r2.func5(5));
			console.log ("r3:" + r3.prop + "," + r3.func5(6));
			console.log ("r1:" + r1.prop + "," + r1.func6(4));
			console.log ("r2:" + r2.prop + "," + r2.func6(5));
			console.log ("r3:" + r3.prop + "," + r3.func6(6));
*/