/*
Copyright (c) 2007, Caridy Patiño. All rights reserved.
Portions Copyright (c) 2007, Yahoo!, Inc. All rights reserved.
Code licensed under the BSD License:
http://www.bubbling-library.com/eng/licence
version: 1.3.2
*/
(function() {
  var $C = YAHOO.util.Connect,
	  $L = YAHOO.lang,
	  $E = YAHOO.util.Event,
	  $D = YAHOO.util.Dom,
	  $  = YAHOO.util.Dom.get;

  /**
  * Constants
  */
  var constants = { LOADING: 1, DISPATCHED: 2, ERROR: 3, EMPTY: 4, proxy: '/dispatcher.php?uri=', CSSNODE: 1, JSNODE: 2 };

  var reScriptTag = /<script([^>]*)>([\s\S]*?)<\/script>/igm,
  	  reScriptTagSrc = /src=(['"]?)([^"']*)\1/i,
  	  reLinkTag = /<link([^>]*)(>[\s]*<\/link>|>)/igm,
  	  reLinkTagSrc = /href=(['"]?)([^"']*)\1/i,
	  reStyleTag = /<style([^>]*)>([\s\S]*?)<\/style>/igm,
	  reTagParams = new RegExp('([\\w-\.]+)\\s*=\\s*(".*?"|\'.*?\'|\\w+)*', 'im');

  var reCSS3rdFile    = new RegExp('url\\s*\\(([^\\)]*)', 'igm');                // [url(image.gif] - also can include quotes

  var reURI = new RegExp('^((?:http|https)://)((?:\\w+[\.|-]?)*\\w+)(/.*)$', 'i');  // full url: [http://www.domain.com/path/file.html]

  /**
  * @class Dispatcher
  */
  YAHOO.util.Dispatcher = function () {
  	var obj = {},
		_threads = {}, // each thread represent an area...
		_hashtable = [],
		_oDefaultConfig = {relative: false, baseURI:document.location},
		_classname = 'yui-dispatchable';

	// utilities
	function _eraseQuotes( str ) {
	  if ($L.isString(str)) {
	  	str = str.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");  // trim
		str = str.replace(/^(['|"])*(\S*(?:\s+\S+)*)\1$/, "$2"); // un-quotes
	  }
	  return str;
	}
	function _getParams ( str, validator ) {
		var p = null, r = {};
		validator = validator || {};
		// capturing the params into an object literal (r)
		if($L.isString(str)){
		  while(p = reTagParams.exec(str)){
		  	// apply validation if exists, if the value is null will be discarted
			p[2] = (validator.hasOwnProperty(p[1])?validator[p[1]]:p[2]);
			if (p[2]) {
			   r[p[1]] = _eraseQuotes(p[2]);
			}
			str = str.replace(reTagParams, '');
		  }
		}
		return r;
	}
	function _baseURI ( uri ) {
		uri = (($L.isString(uri) && (uri.indexOf('/') > -1))?uri:_oDefaultConfig.baseURI) + ''; // the default base is the current document uri (hack to convert to string)
		return uri.substr (0, uri.lastIndexOf('/')+1); // forget the file name
	}
	function _relativeURI( base, uri ) {
	  	// is the url is relative (not http..., not / at the begining)
		if(uri && !reURI.test(uri) && (uri.indexOf('/') !== 0)){
		  uri = base+uri;
		}
	    return uri;
	}
	// private stuff
	/**
	* Analyze the uri before start the downloading process (if the uri isn't in over the current domain name, the dispatcher can use a proxy)
	* @public
	* @param {String} uri     	Remote URL
	* @param {Object} config    Used to pass the user configuration thru the chain of execution
	* @return String
	*/
	function firewall( uri, config ) {
		var sDomain = null,
			sProtocol = null,
			m = null;
		// AJAX url donŽt work with &amp;
		while (uri.indexOf ( '&amp;' ) > -1) {
		    uri = uri.replace ( '&amp;', '&' );
		}
		// defining the proxy for cross-domain scripting...
		config.proxy = config.proxy || constants.proxy;
		if ($L.isFunction(config.firewall)) {
		  // external verification only...
		  uri = config.firewall.apply ( config, [uri] );
		} else {
			// internal verification only...
			// monolithic execution discard the cross-domain capabilities
			if (!config.monolithic && config.proxy) {
				m = uri.match(reURI); // checking the RE to verify if the url is external...
				if (m && (m[2] !== document.domain)) {
					// the uri is external, escaping especial chars and use a proxy
					uri = config.proxy + escape(uri);
				}
			}
		}
		return uri;
	}
	/**
	* Dispatching the next node of the handle
	* @public
	* @param {Object} hd     		Thread's handle
	* @param {Object} config    Used to pass the user configuration thru the chain of execution
	* @return boolean
	*/
	function dispatch( hd, config ) {
	  var callback = null, flag = true, node = null, uri = '', i = 0;
	  config = config || {};
	  if (obj.isAlive(hd)) {
	  	node = _threads[hd].chunks.shift ();
	  	if ($L.isObject(node) && node.src) {
		  // the node represent a remote script
		  callback =
		  {
			success: function (o) {
				if ($L.isString(o.responseText)) {
					if (node.type === constants.JSNODE) {
						// execution the script and continue with the thread
						exec (hd, o.responseText, config);
					} else if ((node.type === constants.CSSNODE) && (obj.applyCSS (o.responseText, node.params, config))) {
						_hashtable[config.hash].status = constants.DISPATCHED;
						// continue with the thread
						dispatch( hd, config );
					}
				}
			},
			failure:function (o) {
				// continue with the thread
				dispatch( hd, config );
			}
		  };
		  // cheching the uri in the hashtable
		  config.hash = _hashtable.length; // default hash (at the end of the table)
		  for (i=0; i<_hashtable.length; i++) {
		  	if (_hashtable[i].uri == node.src) {
				if ((_hashtable[i].status == constants.DISPATCHED) && !config.override) {
					// this uri was already dispatched by this plugin and will be discarted...
					flag = false;
				} else {
					// the uri already exists in the table
				}
		  		config.hash = i;
				break;
			}
		  }
		  if (flag) {
		  	// fetching the remote script
		    uri = firewall (node.src, config);
			if ($L.isString(uri) && (uri !== '')) {
				_hashtable[config.hash] = {uri: node.src, proxy: uri, status: constants.LOADING};
				config.handle = $C.asyncRequest('GET', uri, callback);
			}
		  } else {
		  	// continue with the thread execution
			dispatch( hd, config );
		  }
		}
		else {
		  // the node represent an inline script (don't have hash value)
		  config.hash = null;
		  exec (hd, node.content, config);
		}
	  } else {
	  	// ending the execution thread
	  	obj.kill(hd);
		// onLoad (after the execution)
		config.onLoad = config.after || config.onLoad;
		if ($L.isFunction(config.onLoad)) {
		  config.onLoad.apply ( config, [config.element] );
		}
	  }
	}

	/**
	* Executing a javascript script segment
	* @public
	* @param {Object} hd     		Thread's handle
	* @param {string} c     		Content to execute
	* @param {Object} config    User configuration (useful for future implementations)
	* @return boolean
	*/
	function exec( hd, c, config ) {
	  var status = constants.EMPTY;
	  if (c && (c !== '')) {
		config.scope = (config.scope?config.scope:window);
		try{
		  // initialize a new anonymous container for the script, dont make it part of this object scope chain
		  status = constants.DISPATCHED;
		  // instead send in a variable that points to this object...
		  this.scriptScope = null;
		  if (!config.hash || (_hashtable[config.hash].status != constants.DISPATCHED)) {
			 obj.area = hd;
			 obj.destroyer = _threads[hd].destroyer;
			 // hacking the new Function utility to create a gateway to the current execution var...
			 this.scriptScope = new (new Function('_container_', c+'; return this;'))(config.scope);
		  }
		}catch(e){
		  status = constants.ERROR;
		  if ($L.isFunction(config.error)) {
			  config.error.apply ( config, [hd, c, _hashtable] );
		  } else {
			  throw new
			    Error ("Dispacher: Script Execution Error ("+e+")");
		  }
		}
	  }
	  // updating the status of the remote script in the hashtable
	  if ($L.isNumber(config.hash)) {
		_hashtable[config.hash].status = status;
		config.hash = null; // resetting the config.hash for the next iteration...
	  }
	  dispatch( hd, config );
	}

	/**
	* Display the content inside the element
	* @public
	* @param {Object} el    		Element reference
	* @param {string} c     		Content to display
	* @param {Object} config    User configuration (useful for future implementations)
	* @return boolean
	*/
	function display( el, c, config ) {
		config.action = (config.action?config.action:'replace');
		switch (config.action)
		{
			case 'tabview':
				// onDestroy event... used to release the memory inside the tab...
				destroy (el.get('contentEl'), config);
				try { el.set('content', c); } catch (e1) {return false;}
				break;
			case 'update':
				c = el.innerHTML + c;
				try { el.innerHTML = c; } catch (e2) {return false;}
				break;
			case 'replace':
			default:
				// onDestroy event... used to release the memory inside the element...
			    destroy (el, config);
				// changing the content
				try { el.innerHTML = c; } catch (e3) {return false;}
				break;
		}
		return true;
	}
	/**
	* "Destroy Custom Event" will be fired before remove the innerHTML in the displaying process
	* @public
	* @param {Object} el    	DOM Element reference
	* @param {Object} config    User configuration (useful for future implementations)
	* @return void
	*/
    function destroy( el, config ) {
		var hd = config.guid, i = 0;
		if ($L.isObject(_threads[hd].destroyer)) {
			_threads[hd].destroyer.fire (el, config);
		}
		if ($D.inDocument(el)) {
			// purge the child elements and the events attached...
		  	for (i=0;i<el.childNodes.length;i++) {
			  $E.purgeElement ( el.childNodes[i], true );
			}
		}
		$D.addClass (el, _classname);
		_threads[hd].destroyer = new YAHOO.util.CustomEvent('destroyer');
		// checking for default onDestroy (usually passed to the methods fetch, delegate or process)
		if ($L.isFunction(config.onDestroy)) {
			_threads[hd].destroyer.subscribe (config.onDestroy);
		}
		// other subscribers can be add using: YAHOO.util.Dispatcher.destroyer.subscribe ( mySubscriber );
	}
	/**
	* Parse the string, remove the script tags, and create the execution thread...
	* @public
	* @param {Object} hd    		Element reference
	* @param {string} s     		String with the script tags inside...
	* @return string
	*/
    function parse( hd, s, config ) {
		config = config || {};
		config.uri = config.uri || null;
		config.relative = config.relative || _oDefaultConfig.relative;
		var m = true, attr = false,
		    base = _baseURI(config.uri); // calculation of the base path...
		// searching and cut out all style tags, push them into thread
		// ej. <style title="" type="text/css"></style>
		// not supported yet. <!--@import url("http://us.js2.yimg.com/us.js.yimg.com/lib/hdr/ygma_2.19.css");body{margin:0px 4px;}-->
		s = s.replace(reStyleTag,
		  function (str,p1,p2,offset,s) {
			// apply the inline style automatically
			if (p2) {
			    obj.applyCSS(p2, _getParams(p1), config);
			}
		    return "";
	      }
		);

		// searching and cut out all Link tags, push them into thread
		// ej. <link rel="stylesheet" type="text/css" href='/themes/bubbling/css/common.css' />
		s = s.replace(reLinkTag,
		  function (str,p1,p2,offset,s) {
			// add a remote style to buffer
			if(p1){
			  attr = p1.match(reLinkTagSrc);
			  if(attr) {
			    if (config.relative) {
					attr[2] = _relativeURI(base, attr[2]); // path correction process...
				}
			    _threads[hd].chunks.push ({
					src: attr[2],
					content: '',
					type: constants.CSSNODE,
					params: _getParams(p1)
				});
			  }
			}
		    return "";
	      }
		);

		// searching and cut out all script tags, push them into thread
		s = s.replace(reScriptTag,
		  function (str,p1,p2,offset,s) {
			// add a remote script to buffer
			if(p1){
			  attr = p1.match(reScriptTagSrc);
			  if(attr) {
			    if (config.relative) {
					attr[2] = _relativeURI(base, attr[2]); // path correction process...
				}
			    _threads[hd].chunks.push ({
					src: attr[2],
					content: '',
					type: constants.JSNODE,
					params: _getParams(p1)
				});
			  }
			}
			// add a inline script to buffer
			if (p2) {
			    _threads[hd].chunks.push ({
					src: null,
					content: p2,
					type: constants.JSNODE,
					params: _getParams(p1)
				});
			}
		    return "";
	      }
		);
		return s;
    }

	// public vars
	obj.area = null; // current thread, useful to get the current DOM element...
	obj.destroyer = null; // current destroyer, useful to add new subscriber during the execution...
	// public methods
	/**
	* * Fetching a remote file that will be processed thru this object...
	* @public
	* @param {object} el     	{HTMLElement | String | Object} The html element that represents the dynamic area.
	* @param {object} uri       Remote file that will be loaded using AJAX
	* @param {object} config    Literal object with the user configuration vars
	* @return object  Reference to the connection handle
	*/
	obj.fetch = function( el, uri, config ){
	   config = config || {};
	   config.uri = uri;
	   var callback = {
			success: function (o) {
				if (o.responseText != 'undefined') {
					obj.process( el, o.responseText, config );
				}
			},
			failure:function (o) {
			    if ($L.isFunction(config.onError)) {
        		   config.onError.apply ( config, [config.element] );
        		}
			}
	   };
	   if (uri) {
		 uri = firewall (uri, config);
		 config.handle = $C.asyncRequest('GET', uri, callback);
		 config.element = el;
		 // onStart (before the loading)
		 config.onStart = config.before || config.onStart;
		 if ($L.isFunction(config.onStart)) {
		   config.onStart.apply ( config, [config.element] );
		   config.onStart = null; // because the process method will try to executed again...
		 }
		 return config.handle;
	   }
	   return null;
	};
	/**
	* * Starting the process for a content...
	* @public
	* @param {object} el     		{HTMLElement | String | Object} The html element that represents the dynamic area.
	* @param {string} content       Content to be processed
	* @param {object} config    	Literal object with the user configuration vars
	* @return object  reference to the thread handle...
	*/
	obj.process = function( el, content, config ){
		var hd = null;
		config = config || {};
		if ($L.isObject(el) || (el = $( el ))) {
			hd = config.guid || $E.generateId (el); // by default, one thread by element, use the GUID to discard this rule...
			this.kill(hd); // kill the previous process for this handle...
			config.element = el;
			config.content = content;
			config.guid = hd;
			// before the process
			config.onStart = config.before || config.onStart;
			if ($L.isFunction(config.onStart)) {
			   config.onStart.apply ( config, [config.element] );
			}
			// processing
			if (display(el, parse (hd, content, config), config)) {
				dispatch (hd, config); // starting the execution chain
			}
		}
		return hd;
	};
	/**
	* * TABVIEW: delegate the set content method...
	* @public
	* @param {object} tab		reference to the tab...
	* @param {object} tabview   reference to the tabview (optional)...
	* @param {object} config    Literal object with the user configuration vars
	* @return void
	*/
	obj.delegate = function ( tab, tabview, config ) {
		config = config || {};
		config.action = 'tabview';
		config.uri = tab.get ('dataSrc') || null; // getting the base url for the execution...
		tab.loadHandler.success = function(o) {
			obj.process( tab, o.responseText, config );
		};
		if ($L.isObject(tabview)) {
			tabview.addTab(tab);
		}
	};
	/**
	* * Injecting CSS in the current page
	* @public
	* @param {string} cssCode   CSS content that will be injected
	* @param {object} params    Literal object with the tag configuration params
	* @param {object} config    Literal object with the dispatcher configuration values
	* @return boolean  the operation result
	*/
	obj.applyCSS = function( cssCode, params, config ) {
		params = params || {};
		var styleElement = document.createElement("style"), base = params.href || '';
		// calculation of the base path...
		config = config || {};
		config.uri = config.uri || _oDefaultConfig.baseURI;
		config.relative = config.relative || _oDefaultConfig.relative;
		if (config.relative) {
			base = _baseURI(config.uri); // calculation of the base path...
			base = _relativeURI(base, params.href); // path correction process...
		}
		base = _baseURI(base);
		// path correction process...
		// ej: (image.gif) or ("image.gif") or ('image.gif') or (http://fullpath/image.gif)
		/*  In the case of DXImageTransform.Microsoft.AlphaImageLoader(src='image.png',sizingMethod='scale');
		    The use of the AlphaImageLoader ultimately proved to be
			an unreliable solution for IE 6 as it never handled relative paths well.
			It seems like the AlphaImageLoader always expects the paths to be relative
			to the page the CSS is included in, rather than relative to the CSS file itself. */
  		cssCode = cssCode.replace(reCSS3rdFile,
		  function (str,p1,offset,s) {
			p1 = _eraseQuotes (p1);
			p1 = 'url('+_relativeURI(base, p1);
			return p1;
		  }
		);

		// the CSS is ready...
	    styleElement.type = "text/css";
	    if ($L.isObject(styleElement.styleSheet)) {
	      styleElement.styleSheet.cssText = cssCode;
	    } else {
	      styleElement.appendChild(document.createTextNode(cssCode));
	    }
	    try{
	      document.getElementsByTagName("head")[0].appendChild(styleElement);
	    }catch(e){
		  throw new
		    Error ("Dispacher: CSS Processing Error ("+e+")");
		  return false;
	    }
	    return true;
	};
	/**
	* * Fetching a remote javascript file that will be processed thru this object...
	* @public
	* @param {object} uri       Remote js file that will be loaded using AJAX
	* @param {object} config    Literal object with the user configuration vars
	* @return string  ID reference to the new dispatcher's thread
	*/
	obj.jsLoader = function( uri, config ){
	   if ($L.isString(uri) && (uri !== '')) {
	   	 config = config || {};
		 $E.generateId ( config ); // generating an unique ID for the thread (config.id)
		 obj.kill (config.id);
	   	 // add a remote script to buffer
 	     _threads[config.id].chunks = [{
			src: uri,
			content: '',
			type: constants.JSNODE,
			params: {href: uri}
		 }];
		 dispatch (config.id, config); // starting the execution chain
		 return config.id;
	   }
	   return null;
	};
	/**
	* * Fetching a remote CSS file that will be processed thru this object...
	* @public
	* @param {object} uri       Remote CSS file that will be loaded using AJAX
	* @param {object} config    Literal object with the user configuration vars
	* @return string  ID reference to the new dispatcher's thread
	*/
	obj.cssLoader = function( uri, config ){
	   if ($L.isString(uri) && (uri !== '')) {
	   	 config = config || {};
		 $E.generateId ( config ); // generating an unique ID for the thread (config.id)
		 obj.kill (config.id);
	   	 // add a remote script to buffer
	     _threads[config.id].chunks = [{
			src: uri,
			content: '',
			type: constants.CSSNODE,
			params: {href: uri}
		 }];
		 dispatch (config.id, config); // starting the execution chain
		 return config.id;
	   }
	   return null;
	};
	/**
	* * Verify if the a process is still alive
	* @public
	* @param {object} hd   Process handle
	* @return boolean
	*/
	obj.isAlive = function ( hd ) {
		return (hd && $L.isObject(_threads[hd]) && (_threads[hd].chunks.length > 0));
	};
	/**
	* * Kill a process...
	* @public
	* @param {object} handle   Process handle
	* @return void
	*/
	obj.kill = function ( hd ) {
		if (hd && !$L.isObject(_threads[hd])) {
			_threads[hd] = {chunks: [], destroyer: null};
		} else if (this.isAlive (hd)) {
			_threads[hd].chunks = []; // discarding the handle...
		}
	};
	/**
	* * destroy an area...
	* @public
	* @param {object} handle   Process handle
	* @return void
	*/
	obj.destroy = function ( hd ) {
		this.kill(hd);
		if (hd && !$L.isObject(_threads[hd])) {
			_threads[hd].destroyer.fire( $(hd), {} );
		}
	};
	/* * onDestroy - subscribe a new destroyer for a certain area...
	* @public
	* @param {object} handle   Process handle
	* @return boolean
	*/
	obj.onDestroy = function ( hd, bh, scope ) {
        var params = (scope?[bh, scope, true]:[bh]); // params for scope corrections
        if ($L.isObject(_threads[hd]) && $L.isObject(_threads[hd].destroyer)) {
			if ($L.isObject(scope)) {
			  _threads[hd].destroyer.subscribe(bh, scope, true);  // correcting the default scope
			} else {
			  _threads[hd].destroyer.subscribe(bh);  // use the default scope
			}
			return true;
		}
		return false;
	};
	obj.init = function (c) {
		c = c || {};
		c.relative = c.relative || false;
		_oDefaultConfig = c;
	};
	/**
	* @method toString
	* @description Returns a string representing the dispatcher plugin.
	* @return {String}
	*/
	obj.toString = function() {
	    return ("Dispatcher Manager Plugin (Singlenton)");
	};
	return obj;
  }();
})();
YAHOO.register("dispatcher", YAHOO.util.Dispatcher, {version: "1.3.2", build: "209"}); 

