/** The Base.Comp class is the base class for all of the components. It provides the
 * signal/slot framework that the components use, and allows access to components using
 * their unique identifier.
 */
Base.Comp = Base.inherits({

	/** The constructor initialises the component by assigning its identifier, creating the
	 * tables listing the slots, events and children, and creating the low-level slots and
	 * events common to all components.
	 */
	constructor: function () {
		this._cid	= ++ Base.Comp.lastId;
		this._slots	= new Base.Util.Hashtable();
		this._evts	= new Base.Util.Hashtable();
		this._chld	= new Base.Util.Hashtable();
		this._cnt	= null;

		Base.Comp.list.put(this._cid, this);
		Base.Comp.count ++;

		this.destroyChildren = false;

		this.addEvent('Destroy');
		this.addSlot('addChild');
		this.addSlot('removeChild');
	},

	/** This method is responsible for the destruction of a component. It
	 * starts by launching the onDestroy() event; it then detaches the
	 * component's children (or destroy them if the destroyChildren member
	 * is set to true), then detaches all of the component's slots and
	 * events. Finally, it removes the reference to the component in the
	 * global components list.
	 */
	destroy: function () {
		this.onDestroy(this._cid);

		var k = this._chld.keys();
		for (var i in k) {
			var c = this._chld.get(k[i]);
			if (this.destroyChildren) {
				c.destroy();
			} else {
				this.removeChild(c);
			}
		}

		k = this._slots.keys();
		for (var i in k) {
			this._slots.get(k[i]).destroy();
		}
		this._slots.clear();

		k = this._evts.keys();
		for (var i in k) {
			this._evts.get(k[i]).destroy();
		}
		this._evts.clear();

		Base.Comp.list.remove(this._cid);
		Base.Comp.count --;

		for (var i in this) {
			this[i] = null;
		}
	},


	/** This method declares a new slot for the current component.
	 * @param	name	the name of the slot to be declared
	 */
	addSlot: function (name) {
		if (this._slots.containsKey(name) || !this[name]) {
			return;
		}
		this._slots.put(name, new Base.Comp.Slot(name, this));
	},

	/** This method generates a new event handler for the current component.
	 * @param	name		the name of the event to create
	 * @param	propagate	a boolean indicating the event propagates to the component's children
	 */
	addEvent: function (name, propagate) {
		if (this._evts.containsKey(name) || this["on" + name]) {
			return;
		}

		this._evts.put(name, new Base.Comp.Evt(name, propagate));
		this["on" + name] = function() {
			this.triggerEvent(name, arguments);
		};
		this.addSlot('on' + name);
	},


	/** This method binds an event from the current component to a slot on either the current
	 * component or, if the last parameter is present, another component.
	 * @param	eName		the name of the event to bind
	 * @param	sName		the name of the slots to bind an event to
	 * @param	cmp		the component on which to bind (if this parameter isn't passed, the current component is used)
	 */
	bindEvent: function (eName, sName, cmp) {
		var e = this._evts.get(eName), c = (cmp ? cmp : this), s = c._slots.get(sName);
		if (!(e && s)) {
			return;
		}
		e.bind(s);
	},

	/** This method detaches an event from the current component from a slot to which it had been bound.
	 * @param	eName		the name of the event to detach
	 * @param	sName		the name of the slot to detach
	 * @param	cmp		the component on which the slot to detach is located
	 */
	detachEvent: function (eName, sName, cmp) {
		var e = this._evts.get(eName), c = (cmp ? cmp : this), s = c._slots.get(sName);
		if (!(e && s)) {
			return;
		}
		e.detach(s);
	},


	/** This method triggers the execution of an event on the current component.
	 * @param	eName		the name of the event to trigger
	 * @param	args		an array containing the arguments to be passed to the event's handlers
	 */
	triggerEvent: function (eName, args) {
		var e = this._evts.get(eName);
		if (!e) {
			return;
		}

		e.trigger.apply(e, args);

		if (!e.propagate) {
			return;
		}

		var k = this._chld.keys();
		for (i=0;i<k.length;i++) {
			this._chld.get(k[i]).triggerEvent(eName, args);
		}
	},


	/** This method adds a child to the current component.
	 * @param	cmp	the component to add as a child of the current component
	 */
	addChild: function (cmp) {
		if (!cmp || cmp._cid == this._cid || cmp.container != null) {
			return;
		}
		cmp.container = this;
		this._chld.put(cmp._cid, cmp);
		cmp.bindEvent('Destroy', 'removeChild', this);
	},

	/** This method removes a child from the current component's list of children.
	 * @param	cmp	the child component to remove
	 */
	removeChild: function (cmp) {
		if (!(cmp && this._chld.containsKey(cmp._cid))) {
			return;
		}
		this._chld.remove(cmp._cid);
		cmp.container = null;
		cmp.detachEvent('Destroy', 'removeChild', this);
	},

	/** This method checks whether a component is a child of the current component.
	 * @param	cmp	the component to verify
	 * @returs		true if the specified component is a child of the current component, false otherwise
	 */
	hasChild: function (cmp) {
		return (cmp && cmp.container && cmp.container._cid == this._cid);
	}

}, {

	/** This class property contains the list of all available components;
	 * the components' identifiers are used as the key.
	 */
	list: new Base.Util.Hashtable(),

	/** This class property stores the last identifier assigned to a
	 * component.
	 */
	lastId:	-1,

	/** This class property stores the amount of components currently
	 * being used.
	 */
	count: 0,

	/** This static method returns a component from its identifier.
	 * @param	id	the component's identifier
	 */
	get: function (id) {
		if (!Base.Comp.list.containsKey(id)) {
			return null;
		}
		return Base.Comp.list.get(id);
	},

	/** This static method destroys all of the components.
	 */
	onUnload: function () {
		var l = Base.Comp.list.values();
		for (var i in l) {
			if (l[i] && l[i].destroy) {
				l[i].destroy();
			}
		}
		Base.Comp.list.clear();
	}
});