﻿/**
* @name jquery.sigslot.js
* @version 1.0.0  2008/7/16
* @author KIM, SAHNG YOUNG <aj@ajaxian.kr> 
* @require jquery.js
*/
(function ($) {
	/* Throw exception if there's no jquery.js */
	if (typeof $ != 'function' || typeof $.fn == typeof __undefined__) throw new Error("jquery library should included first of all. ");

	/* If this is second including of jquery.sigslot.js, simply return */
	if (typeof $.signal == 'function') return;

	/***********************************************************
	* @class signalPropagation
	* @purpose delevering signal to other windows
	*/
	var signalPropagation = function (sigId, args, opts) {
		this.sigId = sigId;
		this.args = args;
		this.containers = [];
		this.opts = opts;
		this.opts.direction |= $.signal.SELF;
		this.opts.delegated = true;
	};

	signalPropagation.prototype.add = function (w) {
		try {
			w.jQuery.slot.exists();
			this.containers.push(w.jQuery);

		} catch (e) { return false; };

	};
//	signalPropagation.prototype.findContainers = function (w) {
//		if (w != window) this.add(w);
//		for (var n = 0; n < w.frames.length; n++) {
//			//this.findContainers(($.browser.msie) ? w.frames[n].contentWindow : w.frames[n]);
//			this.findContainers(w.frames[n].contentWindow || w.frames[n]);
//		};
//	};
	signalPropagation.prototype.delegate = function () {
//		switch (cmd.toLowerCase()) {
//			case 'uplink':  /* signaling to windows following '.parent' link. It dosn't propagate to siblings. */
//				if (window == top) return;  /* if current window is top, simply return */
//				for (var w = window.parent; w; ) {
//					this.add(w);
//					if (w == top) w = null;
//					else w = w.parent;
//				};
//				break;

//			case 'followlink': /* singnaling to childs. */
//				this.findContainers(window);

//				break;

//			case 'broadcast': /*  Broadcast signal to all windows  with no order. Starting from 'top' and childs serially */
//				this.findContainers(top);
//				break;

//			default:
//				break;
//		};

		var chain;
		while ((chain = this.containers.shift())) chain.signal(this.sigId, this.args, this.opts);
	};

	signalPropagation.prototype.clear = function () {
//		for (var i = 0; i < this.containers.length; i++) delete this.containers[i];
		this.args = null;
	};
	/* SLOT is just a simple associated array. It's base of arbitrary string ID. */
	var SLOT = {};

	var trimingIdRegex = /^\s+|\s+$/g;
	/****************************************************************
	* @param {Object} id - uniq. signal id
	* @param {Object} arg - passed to slot function. if you want to give more than one, wrap them as Array. e.g: $.signal( 'some-id', [a,b,c,d,], direction);
	* @param {Object} opts - defaults is wrapped by '( )'
	* 		- delegated: true | (false)
	* 		- sync: true | (false)
	*/
	$.signal = function (id, arg, opts) {
		if (typeof id != typeof 'string' || (id = id.replace(trimingIdRegex, "")).length <= 0) throw new Error("signal id is not valid - " + id);
		arg = (typeof arg == typeof __undefined__) ? '' : arg;
		opts = $.extend({}, { direction: $.signal.BROADCAST, delegated: false, sync: false, scope: window, hops: $.signal.MAXHOPS }, opts);
		if (--opts.hops < -1) return;

		/* STEP #1, signaling current window environment, first of all. */
		if ((opts.direction & $.signal.SELF) && $.slot.countOf(id)) {
			for (var idx = 0; idx < SLOT[id].length; idx++) {
				var __sigFunc = SLOT[id][idx], __reservation;

				if (typeof __sigFunc != 'function') continue;

				if (arg && typeof (arg) == 'object' && typeof (arg.pop) == 'function') {
					__reservation = (function (__sigFunc, __vargs) {
						return function () {
							__sigFunc.apply(opts.scope, __vargs);
						};
					})(__sigFunc, arg);
				}
				else {
					__reservation = (function (__sigFunc, __vargs) {
						return function () {
							__sigFunc.call(opts.scope, __vargs);
						};
					})(__sigFunc, arg);
				};

				/* _reservation function would be called synchronously in the same window, even if we call by async. */
				if (opts.sync) __reservation();
				else setTimeout(__reservation, 0);
			};
		};

		/* This acts like 'gate of signal delegation. Only allowed to first process and others will be blocked. */
		if (opts.delegated) return;

		/* STEP #2,  chaining with frames in current document and parent window */
//		var signalChain = new signalPropagation(id, arg, opts);
//		if (opts.direction == $.signal.BROADCAST || opts.direction == $.signal.OTHER) signalChain.delegate('broadcast');
//		else {
//			if (opts.direction & $.signal.UPLINK) signalChain.delegate('uplink');
//			else if (opts.direction & $.signal.FOLLOWLINK) signalChain.delegate('followlink');
//		};

		/* STEP3, clearing remains. This required for memory management of Browser */
//		signalChain.clear();
//		signalChain = null;
	},
    $.slot = {
    	countOf: function (id) {
    		try {
    			return SLOT[id].length;
    		} catch (e) {
    			return 0;
    		};
    	},
    	/**
    	* @param {Object} id - It is true if there's at least one callback function for the slot id
    	*/
    	exists: function (id) {
    		return (typeof id == 'string') && ((id = id.replace(trimingIdRegex, "")).length >= 1) && (typeof SLOT[id] == 'array') && (SLOT[id].length > 0);
    	},
    	/**
    	* @param {Object} id - Uniq. signal id
    	* @param {Object} cb - callback function
    	* @param {Object} repl - if set, replace exist all callback to given one.
    	*/
    	add: function (id, cb, repeatable) {
    		if (typeof cb != 'function') return false;

    		if (!(id in SLOT) || SLOT[id].constructor != Array) SLOT[id] = [];

    		if (!repeatable) {
    			var _found = false;
    			for (var n = 0; n < SLOT[id].length; n++)
    				if (SLOT[id][n] === cb) {
    					if (_found) { delete SLOT[id][n]; n--; }
    					else { SLOT[id][n] = cb; _found = true; };
    				};
    			if (_found) return true;
    		};
    		SLOT[id].push(cb);
    	},
    	remove: function (id, g) {
    		try {
    			if (typeof g == typeof __undefined__) {
    				delete SLOT[id];
    				return;
    			};
    			var j = SLOT[id];
    			for (var p = 0; p < j.length; p++)
    				if (j[p] === g) {
    					delete j[p];
    				};

    		} catch (e) {
    			alert(id + ': ' + e.message);
    		};
    	}
    };

	$.signal.SELF = 1 << 1;
//	$.signal.UPLINK = 1 << 2;
//	$.signal.FOLLOWLINK = 1 << 3;
	$.signal.BROADCAST = $.signal.SELF | $.signal.UPLINK | $.signal.FOLLOWLINK;
//	$.signal.OTHER = $.signal.BROADCAST ^ $.signal.SELF;
	$.signal.__debug__ = true;
	$.signal.MAXHOPS = 5;
})(jQuery);
