/**
 * eGroupWare eTemplate2 - JS code for implementing inheritance
 *
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package etemplate
 * @subpackage api
 * @link http://www.egroupware.org
 * @author Andreas Stöckel
 * @copyright Stylite 2011
 * @version $Id$
 */

"use strict";

/*egw:uses
	et2_core_common;
*/

/**
 * Usage of the JS inheritance system
 * ----------------------------------
 *
 * To create a class write
 * 
 * MyClass = Class.extend([interfaces, ] functions);
 *
 * where "interfaces" is a single interface or an array of interfaces and
 * functions an object containing the functions the class implements.
 *
 * An interface has to be created in the following way:
 *
 * var IBreathingObject = new Interface({
 * 		breath: function() {}
 * });
 * 
 * var Human = Class.extend(IBreathingObject, {
 * 		walk: function() {
 * 			console.log("Walking");
 * 		},
 * 		speak: function(_words) {
 * 			console.log(_words);
 * 		}
 * });
 * 
 * As "Human" does not implement the function "breath", "Human" is treated as
 * abstract. Trying to create an instance of "Human" will throw an exception.
 * However
 * 
 * Human.prototype.implements(IBreathingObject);
 * 
 * will return true. Lets create a specific class of "Human":
 *
 * var ChuckNorris = Human.extend({
 * 		breath: function() {
 * 			console.log("Chuck Norris does not breath, he holds air hostage.");
 * 		},
 * 		speak: function(_words) {
 * 			console.warn("Chuck Norris says:");
 * 			this._super(_words);
 * 		}
 * });
 */

// The following code is mostly taken from 
// http://ejohn.org/blog/simple-javascript-inheritance/
// some parts were slightly changed for better understanding. Added possiblity
// to use interfaces.

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed
 */
// Inspired by base2 and Prototype
(function(){
	var initializing = false;

	/**
	 * Turn this to "true" to track creation and destruction of elements
	 */
	var getMem_freeMem_trace = false;

	var tracedObjects = {};

	// Check whether "function decompilation" works - fnTest is normally used to
	// check whether a 
	var fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

	// Base "Class" for interfaces - needed to check whether an object is an
	// interface
	this.Interface = function(fncts) {
		for (var key in fncts)
		{
			this[key] = fncts[key];
		}
	};

	/**
	 * The addInterfaceFunctions function adds all interface functions the class has
	 * to implement to the class prototype.
	 */
	function addInterfaceFunctions(prototype, interfaces)
	{
		// Remember all interface functions in the prototype
		var ifaces = ((typeof prototype["_ifacefuncs"] == "undefined") ? [] :
			prototype["_ifacefuncs"]);

		prototype["_ifacefuncs"] = [];

		for (var i = 0; i < interfaces.length; i++)
		{
			var iface = interfaces[i];
			if (iface instanceof Interface)
			{
				for (var key in iface)
				{
					prototype["_ifacefuncs"].push(key);
				}
			}
			else
			{
				throw("Interfaces must be instance of Interface!");
			}
		}

		for (var i = 0; i < ifaces.length; i++)
		{
			prototype["_ifacefuncs"].push(ifaces[i]);
		}
	};

	function addAttributeFunctions(prototype, _super)
	{
		function _copyMerge(_new, _old)
		{
			var result = {};

			// Copy the new object
			if (typeof _new != "undefined")
			{
				for (var key in _new)
				{
					result[key] = _new[key];
				}
			}

			// Merge the old object
			for (var key in _old)
			{
				if (typeof result[key] == "undefined")
				{
					result[key] = _old[key];
				}
			}

			return result;
		}

		var attributes = {};

		// Copy the old attributes
		for (var key in prototype.attributes)
		{
			attributes[key] = _copyMerge({}, prototype.attributes[key]);
		}

		// Add the old attributes to the new ones. If the attributes already
		// exist, they are merged.
		for (var key in _super.attributes)
		{
			var _old = _super.attributes[key];
			var _new = {};

			attributes[key] = _copyMerge(attributes[key], _old);
		}

		// Validate the attributes
		for (var key in attributes)
		{
			et2_validateAttrib(key, attributes[key]);
		}

		prototype.attributes = attributes;
	};

	function classExtend(interfaces, prop) {

		if (typeof prop == "undefined")
		{
			prop = interfaces;
			interfaces = [];
		}

		// If a single interface is given, encapsulate it in an array
		if (!(interfaces instanceof Array))
		{
			interfaces = [interfaces];
		}

		if (typeof prop.attributes == "undefined")
		{
			prop.attributes = {};
		}

		var _super = this.prototype;

		// Instantiate a base class (but only create the instance,
		// don't run the init constructor)
		initializing = true;
		var prototype = new this();
		initializing = false;

		// Copy the properties over onto the new prototype
		for (var name in prop) {
			// Check if we're overwriting an existing function and check whether
			// the function actually uses "_super" - the RegExp test function
			// silently converts the funciton prop[name] to a string.
			if (typeof prop[name] == "function" &&
			    typeof _super[name] == "function" && fnTest.test(prop[name]))
			{
				prototype[name] = (function(name, fn){
					return function() {
						var tmp = this._super;

						// Add a new ._super() method that is the same method
						// but on the super-class
						this._super = _super[name];

						// The method only need to be bound temporarily, so we
						// remove it when we're done executing
						var ret = fn.apply(this, arguments);
						this._super = tmp;

						return ret;
					};
				})(name, prop[name]);
			}
			else
			{
				prototype[name] = prop[name];
			}
		}

		// Add the interface functions and the "implements" function to the
		// prototype
		addInterfaceFunctions(prototype, interfaces);

		// Merge the attributes and create the functions corresponding to the
		// attributes
		addAttributeFunctions(prototype, _super);

		// The dummy class constructor
		function Class() {
			// All construction is actually done in the init method
			if (!initializing)
			{
				// Check whether the object implements all interface functions
				for (var i = 0; i < this._ifacefuncs.length; i++)
				{
					var func = this._ifacefuncs[i];
					if (!(typeof this[func] == "function"))
					{
						throw("Trying to create abstract object, interface " + 
							"function '" + func + "' not implemented.");
					}
				}

				// Do some tracing of the getMem_freeMem_trace is activated
				if (getMem_freeMem_trace)
				{
					this.__OBJ_UID = "obj_" + egw.uid();
					var className = this.className();
					tracedObjects[this.__OBJ_UID] = {
						"created": new Date().getTime(),
						"class": className
					}
					egw.debug("log", "*" + this.__OBJ_UID + " (" + className + ")");
				}

				if (this.init)
				{
					this.init.apply(this, arguments);
				}
			}
		}

		// Populate our constructed prototype object
		Class.prototype = prototype;

		// Enforce the constructor to be what we expect
		Class.prototype.constructor = Class;

		// And make this class extendable
		Class.extend = classExtend;

		return Class;
	};

	// The base Class implementation (does nothing)
	this.Class = function(){};

	// Create a new Class that inherits from this class. The first parameter
	// is an array which defines a set of interfaces the object has to
	// implement. An interface is simply an object with named functions.
	Class.extend = classExtend;

	// The base class has no attributes
	Class.prototype.attributes = {};

	// Add the basic functions

	/**
	 * Destructor function - it calls "destroy" if it has been defined and then
	 * deletes all keys of this element, so that any access to this element will
	 * eventually throw an exception, making it easier to hunt down memory leaks.
	 */
	Class.prototype.free = function() {
		if (this.destroy)
		{
			this.destroy();
		}

		// Trace the freeing of the object
		if (getMem_freeMem_trace)
		{
			delete(tracedObjects[this.__OBJ_UID]);
			egw.debug("log", "-" + this.__OBJ_UID);
		}

		// Delete every object entry
		for (var key in this)
		{
			delete(this[key]);
		}

		// Don't raise an exception when attempting to free an element multiple
		// times.
		this.free = function() {};
	};

	// Some debug functions for memory leak hunting
	if (getMem_freeMem_trace)
	{
		/**
		 * Prints a list of all objects UIDs which have not been freed yet.
		 */
		Class.prototype.showTrace = function() {
			console.log(tracedObjects);
		},

		/**
		 * VERY slow - for debugging only!
		 */
		Class.prototype.className = function() {
			for (var key in window)
			{
				if (key.substr(0, 3) == "et2" && this.constructor == window[key])
				{
					return key;
				}
			}

			return "?";
		}
	}

	/**
	 * Returns the value of the given attribute. If the property does not
	 * exist, an error message is issued.
	 */
	Class.prototype.getAttribute = function(_name) {
		if (typeof this.attributes[_name] != "undefined" &&
		    !this.attributes[_name].ignore)
		{
			if (typeof this["get_" + _name] == "function")
			{
				return this["get_" + _name]();
			}
			else
			{
				return this[_name];
			}
		}
		else
		{
			egw.debug("error", this, "Attribute '" + _name  + "' does not exist!");
		}
	};

	/**
	 * The setAttribute function sets the attribute with the given name to
	 * the given value. _override defines, whether this[_name] will be set,
	 * if this key already exists. _override defaults to true. A warning
	 * is issued if the attribute does not exist.
	 */
	Class.prototype.setAttribute = function(_name, _value, _override) {
		if (typeof this.attributes[_name] != "undefined")
		{
			if (!this.attributes[_name].ignore)
			{
				if (typeof _override == "undefined")
				{
					_override = true;
				}

				var val = et2_checkType(_value, this.attributes[_name].type,
					_name);

				if (typeof this["set_" + _name] == "function")
				{
					this["set_" + _name](val);
				}
				else if (_override || typeof this[_name] == "undefined")
				{
					this[_name] = val;
				}
			}
		}
		else
		{
			egw.debug("warn", this, "Attribute '" + _name + "' does not exist!");
		}
	};

	/**
	 * generateAttributeSet sanitizes the given associative array of attributes
	 * (by passing each entry to "et2_checkType" and checking for existance of
	 * the attribute) and adds the default values to the associative array.
	 *
	 * @param _attrs is the associative array containing the attributes.
	 */
	Class.prototype.generateAttributeSet = function(_attrs) {

		// Sanity check and validation
		for (var key in _attrs)
		{
			if (typeof this.attributes[key] != "undefined")
			{
				if (!this.attributes[key].ignore)
				{
					_attrs[key] = et2_checkType(_attrs[key], this.attributes[key].type,
						key);
				}
			}
			else
			{
				// Key does not exist - delete it and issue a warning
				delete(_attrs[key]);
				egw.debug("warn", this, "Attribute '" + key +
					"' does not exist in " + _attrs.type+"!");
			}
		}

		// Include default values or already set values for this attribute
		for (var key in this.attributes)
		{
			if (typeof _attrs[key] == "undefined")
			{
				var _default = this.attributes[key]["default"];
				if (_default == et2_no_init)
				{
					_default = undefined;
				}

				_attrs[key] = _default;
			}
		}

		return _attrs;
	};

	/**
	 * The initAttributes function sets the attributes to their default
	 * values. The attributes are not overwritten, which means, that the
	 * default is only set, if either a setter exists or this[propName] does
	 * not exist yet.
	 */
	Class.prototype.initAttributes = function(_attrs) {
		for (var key in _attrs)
		{
			if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined))
			{
				this.setAttribute(key, _attrs[key], false);
			}
		}
	};

	/**
	 * The implements function can be used to check whether the object
	 * implements the given interface.
	 */
	Class.prototype.implements = function(_iface) {
		for (var key in _iface)
		{
			if (this._ifacefuncs.indexOf(key) < 0)
			{
				return false;
			}
		}
		return true;
	};

	/**
	 * The instanceOf function can be used to check for both - classes and
	 * interfaces. Please don't change the case of this function as this
	 * affects IE and Opera support.
	 */
	Class.prototype.instanceOf = function(_obj) {
		if (_obj instanceof Interface)
		{
			return this.implements(_obj);
		}
		else
		{
			return this instanceof _obj;
		}
	};

}).call(window);