
/**
 * The main base class. Manages the signal system.
 *
 * @param signals Map with all connected signal handlers
 **/
module.exports = new Class
({
	Tag: 'vn-object'
	,Properties: {}

	,_refCount: 1
	,_signalData: null

	,initialize: function (props)
	{
		this.setProperties (props);
	}
	
	,setProperties: function (props)
	{
		for (var prop in props)
			this[prop] = props[prop];
	}
	
	,ref: function ()
	{
		this._refCount++;
		return this;
	}
	
	,unref: function ()
	{
		this._refCount--;
		
		if (this._refCount === 0)
			this._destroy ();
	}
	
	,loadXml: function (builder, node) {}
	,appendChild: function (child) {}
	
	,_signalInit: function ()
	{
		if (!this._signalData)
			this._signalData = {
				signals: {},
				links: {}
			};
	}

	/**
	 * Conects a signal with a function.
	 *
	 * @param {String} id The signal identifier
	 * @param {Function} callback The callback
	 * @param {Object} instance The instance
	 **/
	,on: function (id, callback, instance)
	{
		if (!(callback instanceof Function))
		{
			console.warn ('Vn.Object: Invalid callback for signal \'%s\'', id);
			return;
		}
		
		this._signalInit ();
		var callbacks = this._signalData.signals[id];

		if (!callbacks)
			callbacks = this._signalData.signals[id] = [];
			
		callbacks.push
		({
			 blocked: false
			,callback: callback
			,instance: instance
		});
	}

	/**
	 * Locks/Unlocks a signal emission to the specified object.
	 *
	 * @param {String} id The signal identifier
	 * @param {Function} callback The callback
	 * @param {Boolean} block %true for lock the signal, %false for unlock
	 **/
	,blockSignal: function (id, callback, block, instance)
	{
		if (!this._signalData)
			return;

		var callbacks = this._signalData.signals[id];
		
		if (!callbacks)
			return;
			
		for (var i = 0; i < callbacks.length; i++)
		if (callbacks[i].callback == callback
		&& callbacks[i].instance == instance)
			callbacks[i].blocked = block;
	}
	
	/**
	 * Emits a signal in the current object.
	 *
	 * @param {String} id The signal identifier
	 **/
	,signalEmit: function (id)
	{
		if (!this._signalData)
			return;

		var callbacks = this._signalData.signals[id];
		
		if (!callbacks)
			return;

		var callbackArgs = [];
		callbackArgs.push (this);
	
		for (var i = 1; i < arguments.length; i++)
			callbackArgs.push (arguments[i]);

		for (var i = 0; i < callbacks.length; i++)
		if (!callbacks[i].blocked)
			callbacks[i].callback.apply (callbacks[i].instance, callbackArgs);
	}
	
	/**
	 * Disconnects a signal from current object.
	 *
	 * @param {String} id The signal identifier
	 * @param {Function} callback The connected callback
	 * @param {Object} instance The instance
	 **/
	,disconnect: function (id, callback, instance)
	{
		if (!this._signalData)
			return;

		var callbacks = this._signalData.signals[id];
		
		if (!callbacks)
			return;

		for (var i = 0; i < callbacks.length; i++)
		if (callbacks[i].callback == callback
		&& callbacks[i].instance == instance)
			callbacks.splice (i--, 1);
	}
	
	/**
	 * Disconnects all signals for the given instance.
	 *
	 * @param {Object} instance The instance
	 **/
	,disconnectByInstance: function (instance)
	{
		if (!this._signalData)
			return;
	
		var signals = this._signalData.signals;

		for (var signalId in signals)
		{
			var callbacks = signals[signalId];

			for (var i = 0; i < callbacks.length; i++)
			if (callbacks[i].instance == instance)
				callbacks.splice (i--, 1);
		}
	}

	/**
	 * Destroys the object, this method should only be called before losing
	 * the last reference to the object.
	 **/
	,_destroy: function ()
	{
		if (!this._signalData)
			return;
	
		var links = this._signalData.links;
	
		for (var key in links)
			links[key].disconnectByInstance (this);
		
		this._signalData = null;
	}
	
	,link: function (prop, handlers)
	{
		this._signalInit ();
		var links = this._signalData.links;
	
		for (var key in prop)
		{
			var newObject = prop[key];
			var oldObject = this[key];
		
			if (oldObject)
			{
				oldObject.disconnectByInstance (this);
				oldObject.unref ();
			}

			this[key] = newObject;

			if (newObject)
			{
				links[key] = newObject.ref ();

				for (var signal in handlers)
					newObject.on (signal, handlers[signal], this);
			}
			else if (oldObject)
				delete links[key];
		}
	}
});

