/**
 * $Id$
 * 
 * Project:		DNI Javascript Common: Core Component
 * Version:		2.0.0
 * Author:		alucas
 * 
 */


if (typeof(discovery) == "undefined") var discovery = {};
if (typeof(discovery.common) == "undefined") discovery.common = {};
if (typeof(discovery.common.object) == "undefined") discovery.common.object = {};
if (typeof(discovery.common.html) == "undefined") discovery.common.html = {};
if (typeof(discovery.common.dom) == "undefined") discovery.common.dom = {};
if (typeof(discovery.common.xhr) == "undefined") discovery.common.xhr = {};
if (typeof(discovery.common.lib) == "undefined") discovery.common.lib = {};
if (typeof(discovery.common.url) == "undefined") discovery.common.url = {};




//#OBJECT OPERATIONS


/**
 * @void
 * Iterates through an Object's properties or an Array's indexes
 * backwards and applys a function to them
 * 
 * @param	{Object}	obj			Required. Object to iterate over
 * @param	{Function}	process			Required. Processor function.
 * @param	{Boolean}	arraysForward	Optional. When True makes array interating
 * count from 0
 */
discovery.common.object.each = function(obj, process, arraysForward) {
		
	if (obj) {
		var e;
		if (obj.constructor == Array)
			e = obj;
		else if (obj.item) {
			e = obj;
			arraysForward = true;
		} else if (Object.prototype.toString.call(obj).slice(-6,-1)
				== "Array")
			e = obj;
		else {
			for (var i in obj) process.call(obj[i],i);
			return;
		}

		var arrLen = e.length;
		if (arrLen > 0)
			if (!arraysForward)
				for (var i=arrLen-1,k;k=e[i];i--) process.call(k,i);
			else
				for (var i=0,k;k=e[i];i++) process.call(k,i);
	}
}

/**
 * Merge Objects into main object
 * @param	{Object}	object		Required. Main object. This object will be altered.
 * @param	{Array}		mergeArray  Required. Array of objects to merge into the main object.
 * @param	{Object}	config		Optional. Advanced configuration.
 * @return Main Object(o)
 */
discovery.common.object.mergeObjects = function (object, mergeArray, config) {
	
	var self = discovery.common.object.mergeObjects;
	var clone = discovery.common.object.cloneObject;
	
	if (config) {
		var shallow = config.shallow||false;
		var clobber = config.clobber||false;
		var ignoreFalse = config.ignoreFalse||false;
	}
	
	discovery.common.object.each(mergeArray, function() {	

		for (var i in this) {
			try {
				if (object[i]) {
					
					switch (object[i].constructor) {
					
						case Object:
							if ((!shallow)&&(object[i]))
								object[i] = self(object[i], [this[i]], config);
							else if ((!clobber)&&(ignoreFalse)
									&&(!this[i])
									&&(this[i].constructor==Boolean)) {
								continue;
								
							} else { object[i] = clone(this[i]); }
							break;
							
						case String || Number:
							object[i] = this[i];
							break;
							
						default: continue;
					}
				} else { object[i] = clone(this[i]);}
			} catch(e) {}
		}
	});
	
	return object;
}


/**
 * Public Domain Code. Clones an object.
 */
discovery.common.object.cloneObject = function (o) {
    if(typeof(o) != "object") return o;
    if(o == null) return o;
	var newO;
  
  	var type = o.constructor;
    if (typeof type == "function") {
		newO = new type();
	  
	    for(var i in o)
	    	newO[i] = discovery.common.object
	    			.cloneObject(o[i]);
	} else {
		newO = null;
	}
    
     return newO;
}


//#HTML OPERATIONS


/**
 * Checks whether a string is a valid HTML className
 * 
 * @param	{String}	_name
 * @return	{Boolean}
 */
discovery.common.html.validateClassName = function(_name) { 
	
	var regex = /^[a-zA-Z0-9_-]+$/;
	
	discovery.common.html.validateClassName = function(name) {
		return name.match(regex);
	}
	
	discovery.common.html.validateId = discovery
			.common.html.validateClassName; 
	
	return discovery.common
			.html.validateClassName(_name);
}

/**
 * Alias for validateClassName
 */
discovery.common.html.validateId = discovery.common
		.html.validateClassName;



//#DOM OPERATIONS


/**
 * Provides getElementsByClassName interface which
 * falls back to a native implementation when available.
 * 
 * @param	{String}      strClassName	Required. The class name to search for
 * @return	{Array}
 */
discovery.common.dom.getElementsByClassName = function (strClassName) {
	
	var arrReturnElements;
	
	if (this.getElementsByClassName) {
		
		arrReturnElements = this
				.getElementsByClassName(strClassName);
		if (arrReturnElements)
			arrReturnElements = Array.prototype.slice.call(arrReturnElements);
		
		return arrReturnElements;
	} else {
		
		var strTagName = "*";
	    var arrElements = (strTagName == "*" && this.all)? this.all :
	        this.getElementsByTagName(strTagName);
	    arrReturnElements = [];
	    
	    strClassName = strClassName.replace(/\-/g, "\\-");
	    var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
	    
	    var oElement;
	    for(var i=0; i<arrElements.length; i++){
	        oElement = arrElements[i];     
	        if(oRegExp.test(oElement.className)){
	            arrReturnElements.push(oElement);
	        }   
	    }
	    return (arrReturnElements)
	}
}


/**
 * Provides a cross-browser compatible method to add
 * dom event handlers. Use this with the call method.
 * 
 * @param	{String}	event	Required. Name of DOM event.
 * @param	{Function}	handler		Required. Event handler.
 * @param	{Boolean}	legacy		Optional. If set to true will
 * fallback to legacy event registration model in unsupported
 * browsers.
 * 
 * @return	{Boolean}	<code>false</code> if unsupported
 * browser.
 */
discovery.common.dom.addEventListener = function(
		event, handler, legacy) {

	var funcDef;
	if (typeof(jQuery) !== "undefined") {	
		funcDef = function(event, handler) {
			event = event.toLowerCase();
			var self;
			if (event == "ready") self = this.document;
			else self = this;
			$(self).bind(event, function(ev) {
				handler.call(self, ev);
			});
		}
	} else if (this.addEventListener) {
		
		funcDef = function (event, handler) {
			var self = this;
			event = event.toLowerCase();
			if (event == "ready") event = "load";
			this.addEventListener(event, function(ev) {
				handler.call(self, ev);
			}, false);
			return true;
		}
	} else if (this.attachEvent) {
		
		funcDef = function (event, handler) {
			var self = this;
			event = event.toLowerCase();
			if (event == "ready") event = "load";
			this.attachEvent("on" + event, function(ev) {
				handler.call(self, ev);
			});
			return true;
		}
	} else {
		
		funcDef = function(event, handler, legacy) {
			event = event.toLowerCase();
			if (legacy) {
				if (event == "ready") event = "load";
				var oldFunc = this["on" + event];
				this["on" + event] = function() {
					if (oldFunc) oldFunc();
					handler();
				}
			}
			return false;
		};
	}
	discovery.common.dom.addEventListener = funcDef;
	return discovery.common.dom
			.addEventListener.call(this, event, handler, legacy);
}


//#XHR OPERATIONS


/**
 * @constructor
 * Cross-browser XMLHttpRequest factory
 */
discovery.common.xhr.XMLHttpRequest = function() {
	
	if (window.ActiveXObject) {
		
		discovery.common.xhr.XMLHttpRequest = function() {
			return new ActiveXObject("Microsoft.XMLHTTP")
		}
		return new ActiveXObject("Microsoft.XMLHTTP")
		
	} else {
		
		discovery.common.xhr.XMLHttpRequest = function() {
			return new XMLHttpRequest();
		}
		return new XMLHttpRequest();
	}
}


/**
 * @void
 * Makes a HTTP GET request and calls <code>callback</code>
 * once readyState reaches 4
 * 
 * @param	{String}	url
 * @param	{Function}	callback
 */
discovery.common.xhr.httpGet = function(url, callback) {
	
	var namespace = discovery.common.xhr;
	
	var xhr = namespace.XMLHttpRequest();
	xhr.open('GET', url, true);
	
	xhr.onreadystatechange = function(ev) {
		if ((xhr.readyState == 4)&&(callback)) {
			callback(xhr);
		}
	}
	xhr.send(null);
}


//#URL OPERATIONS


/**
 * Takes a URL and adds query parameters
 * 
 * @param	{String}	_url
 * @param	{Object}	_data
 * @param	{Boolean}	_noescape	Optionally disables url encoding
 * @return	{String}
 */
discovery.common.url.buildUrl = function(_url, _data, _noescape) {
	

	buildUrl = function(url, data, noescape) {
	
		var params = "";
		for (var i in data) {
			var val;
			if (!noescape) val = escape(data[i]);
			else val = data[i];
			params += i + "=" + val + "&";
		}
		
		var urlParts = url.match(_URL_PARTS);
		var start;
		
		if (urlParts[2] !== "?")
			start = "?";
		else start = "&";
			
		return urlParts[1] + start
				+ params.slice(0,params.length-1)
				+ urlParts[3];
		
	}
	
	var _URL_PARTS = /([^\?]*)(\?|)([^#]*)/;
	
	return buildUrl(_url, _data, _noescape);
}


//#TYPE LIBRARY


/**
 * <code>EventManager</code> factory.
 * 
 * @return	{EventManager}
 */
discovery.common.lib.EventManager = function () {

		
	/**
	 * @constructor
	 * EventManager. Stores and manages the creation and firing of custom events
	 * Example:
	 * # var evManager = discovery.common.lib.EventManager();
	 * # evManager.createEvent("onNavToggle");
	 * # evManager.atttachListener("onNavToggle", alert, ["onNavToggle event fired"])
	 * ...then in some flash using ExternalInterface.call() or in some javascript...
	 * # evManager.broadcast("onNavToggle");
	 */
	function EventManager(object) {
		this.lib = {}
		this.self = object||window;
	}
	
	/**
	 * Registers an event object with the manager.
	 * @param {EventObject} eventInstance  The EventObject type instance to register. 
	 */
	EventManager.prototype.register = function (eventInstance) {
		var returnValue = null;
		var name = eventInstance.eventName;
		if (name) {
			this.lib[name] = eventInstance;
			returnValue = eventInstance;
		}
		return returnValue;
	}
	/**
	 * Creates a new event object and registers it with the manager.
	 * @param {String} eventName  Name of the new event.
	 * @return EventInstance
	 */
	EventManager.prototype.create = function (eventName) {
		var ev = new EventObject(eventName);
		var ret = this.register(ev);
		return ret;
	}
	/**
	 * Causes an event to be fired.
	 * @param {String} eventName  Name of the event to be fired.
	 * @return Boolean
	 */
	EventManager.prototype.broadcast = function (eventName) {
		var returnValue = false;
		if (typeof this.lib[eventName] == "object") {
			returnValue = this.lib[eventName]
					.triggerEvent(this.self);
		}
		return returnValue;
	}
	/**
	 * Attaches a listener function to a specified event.
	 * @param {String}          eventName   Name of the event to attach it to.
	 * @param {String|Function} func        Function object or global function name in string form.
	 * @param {Array}           args        Array of items to be passed to listener in order.
	 * @return Boolean
	 */
	EventManager.prototype.attachListener = function (eventName, func, args, scope) {
		var returnValue = false;
		if ((typeof this.lib[eventName] == "object")
				&&(typeof eval(func) == "function")) {
			returnValue = this.lib[eventName].attachListener(func, args, scope);
		}
		return returnValue;
	}
	
	
	/**
	 * @constructor
	 * Event Object. Stores functions and their arguments to be executed;
	 * @param {String}  name   A name for the event.
	 */
	function EventObject (name) {
		this.listeners = [];
		this.eventName = name;
	}
		/**
		 * Attach a listener function to the event
		 * @param {String|Function} func  Function object or global function name in string form.
		 * @param {Array}           args  Array of items to be passed to listener in order.
		 * @return Boolean
		 */
		EventObject.prototype.attachListener = function (func, args, scope) {
			var ret = false;
			if (!args) var args = [];
			if (typeof func == "string") { var func = eval(func);}
			switch (typeof func) {
				case "string":
					var func = eval("window." + func);
				case "function"||"string":
					this.listeners.push(function(self) {
						func.apply(self, args);							
					});
					ret = true;
					break;
			}
			return ret;
		}
		/**
		 * Fires the event
		 */
		EventObject.prototype.triggerEvent = function (object) {
			if (!object) var object = window;
			for (var i = 0, k; k = this.listeners[i]; i++) {
				k(object);
			}
			return true;
		}
	
	
	
	this.EventManager = function () {
		return new EventManager();
	}
	
	return this.EventManager();
}


