/**
 * Argos utilities package for logical operations, with the following hierarchy:
 *	serializer 	- For object serialization and deserialization
 *	persistence - For store and retrieve data from the persistence layer. 
 *				  Currently only supports cookie persistence.
 *	string 		- For string manipulations.
 *
 * @author Anna Huang (created 20:30 05/12/2007)
 */

// initialize namespace  	
if (!argos) var argos = {};
if (!argos.utils) argos.utils = {}

/**
 * Serialization utility for data objects. 
 * Object functions are ignored. 
 */ 
argos.utils.serializer = {
	ELEM_OPEN : "{",
	ELEM_CLOSE : "}",
	ASSIGN : ":",
	ELEM_OPEN_ENCODE : "[[[",
	ELEM_CLOSE_ENCODE : "]]]",
	ASSIGN_ENCODE : "---",
	
	/**
	 * Serialize a data object into a string, non data properties, 
	 * e.g. function, DOM element etc are ignored
	 * @param {Object} obj Object to be serialized
	 * @return {String} Serialized object
	 */
	serialize : function(obj) {
		if (obj===null || obj === undefined || obj === "undefined") return;

		if (obj.nodeType) return;

		// serialized string
		var objStr = ""; 
		
		// start serialization
		if (typeof obj === "function") {
			return;
		} else if (typeof obj != "object") {
			objStr = argos.utils.serializer.encode(obj.toString());
			
		} else {
			for (var key in obj) {
				var value = obj[key];
				objStr += argos.utils.serializer.ELEM_OPEN + key 
					+ argos.utils.serializer.ASSIGN 
					+ argos.utils.serializer.serialize(value)
					+ argos.utils.serializer.ELEM_CLOSE;
			}
		}

		return objStr;			
	},

	/**
	 * Deserialize from a string into an object. Serialization of the original 
	 * object should be using the serialize(obj) function to ensure valid format.
	 * @param {String} objStr Serialized string
	 * @return {Object} Deserialized object. 
	 * 			Return null if objStr is undefined. 
	 * 			Return empty object is objStr has 0 length
	 */
	deserialize : function(objStr) {
		// private function (closure)
		var _deserialize = function(objStr, index) {	
			if (index === "undefined" || index>=objStr.length) return;		
			
			// string range of object element			
			var elemStart = objStr.indexOf(argos.utils.serializer.ELEM_OPEN, index);
			var elemEnd = objStr.indexOf(argos.utils.serializer.ELEM_CLOSE, index);
			
			if(elemEnd == -1) return; // no object found
			
			// check if next is start of object
			var isOpen = (elemStart < elemEnd) && elemStart>=index; 
			
			var obj = [];
			var key = null;
			var value = null;

			if (isOpen) { // start of object, get object branch
				var assignIndex = objStr.indexOf(argos.utils.serializer.ASSIGN, index);
				var nextIndex = assignIndex + argos.utils.serializer.ASSIGN.length;
				key = objStr.substring(elemStart + argos.utils.serializer.ELEM_OPEN.length, assignIndex);				
				value = arguments.callee(objStr, nextIndex);  // calling this function itself				
			} else if (elemEnd != 0) { // get the leaf value (primitive values)
				value = objStr.substring(index, elemEnd);
				value = argos.utils.serializer.decode(value);				
				return value;
			} else {
				return; // end of branch
			}

			obj[key] = value; 
			
			// get sibling objects
			var serializedStr = argos.utils.serializer.serialize(obj);			
			var nextElemStart = serializedStr.length + index; 
			var nextObj = arguments.callee(objStr, nextElemStart); // calling this function itself
			
			for (var key in nextObj) {
				obj[key] = nextObj[key];
			}			

			return obj;
		};

		return _deserialize(objStr, 0);			
	},
	
	/**
	 * Encode illegal charactors for serialization.
	 * @param {String} str String value to encode
	 * @return {String} Encoded string
	 */
	encode : function(str) {
		if (str == "undefined") return str;
		str = argos.utils.string.replaceAll(str, argos.utils.serializer.ELEM_OPEN, argos.utils.serializer.ELEM_OPEN_ENCODE);
		str = argos.utils.string.replaceAll(str, argos.utils.serializer.ELEM_CLOSE, argos.utils.serializer.ELEM_CLOSE_ENCODE);
		str = argos.utils.string.replaceAll(str, argos.utils.serializer.ASSIGN, argos.utils.serializer.ASSIGN_ENCODE);
		
		return str;
	},
	
	/**
	 * Decode and revert the illegal charators encoded from encode(str).
	 * @param {String} str String value to decode
	 * @return {String} Decoded string
	 */
	decode : function(str) {
		if (str == "undefined") return str;
		str = argos.utils.string.replaceAll(str, argos.utils.serializer.ELEM_OPEN_ENCODE, argos.utils.serializer.ELEM_OPEN);
		str = argos.utils.string.replaceAll(str, argos.utils.serializer.ELEM_CLOSE_ENCODE, argos.utils.serializer.ELEM_CLOSE);
		str = argos.utils.string.replaceAll(str, argos.utils.serializer.ASSIGN_ENCODE, argos.utils.serializer.ASSIGN);
		
		return str;
	}
}

/**
 * Simple persistence utility for managing data storage and retrieval.
 * Implementation agnostic - it currently store data to a browser cookie. 
 */
argos.utils.persistence = {
	/**
	 * Retrieve a persisted object. 
	 * @param {String} name Name associated with the persisted object
	 * @return {Object} The persisted object. Return null if not found
	 */
	retrieve : function(name) {
		var obj = null;
		var data = argos.page.getCookie(name);
		if (data) {
			obj = argos.utils.serializer.deserialize(data);
		}

		return obj;
	},
	
	/**
	 * Persist an object with the associated name. If there is already an
	 * object associated with the same name, this new object will overwrite 
	 * the existing object.
	 * @param {String} name Name to associated with the object in persistence
	 * @param {Object} value The object to be persisted
	 */
	persist : function(name, obj) {
		if(obj) {
			argos.page.setCookie(name, argos.utils.serializer.serialize(obj));
		}
	}	
}

argos.utils.string = {
	/**
	 * Replace all occurence of a substring with another.
	 * @param {String} oriStr The full string to be proccessed
	 * @param {String} oldStr The occurring string to be replaced
	 * @param {String} replacement The string replacement
	 * @return {String} The process string
	 */
	replaceAll : function(oriStr, oldStr, replacement) {
		if (!oriStr|| oriStr==="undefined" 
				|| !oldStr || oldStr==="undefined" 
				|| replacement==="undefined") return oriStr;	
				
		var newStr = oriStr.toString();			
		for (var i=newStr.indexOf(oldStr); i>-1; i=newStr.indexOf(oldStr)) {		
			newStr = newStr.replace(oldStr, replacement); 	
		}	
	
		return newStr;
	},
	
	/** 
	 * Removing leading and trailing spaces.
	 * @param {String} str The string to be trimmed.
	 * @return {String} The processed string 
	 */
	trim : function(str) {	
		var lead = /\s*((\S+\s*)*)/;
		var trail = /((\s*\S+)*)\s*/;
		return str.replace(lead, "$1").replace(trail, "$1");
	}	
}

argos.utils.browser = {
	//Detect available browser functionlities
	supportsPositionFixed : function()	{
	    var testDiv = document.createElement("div");
	    testDiv.id = "testingPositionFixed";
	    testDiv.style.position = "fixed";
	    testDiv.style.top = "0px";
	    testDiv.style.right = "0px";
	    document.body.appendChild(testDiv);
	    var offset = 1;
	    if (typeof testDiv.offsetTop == "number"
	        && testDiv.offsetTop != null 
	        && testDiv.offsetTop != "undefined")
	    {
	        offset = parseInt(testDiv.offsetTop);
	    }
	    if (offset == 0)
	    {
	        return true;
	    }
	
	    return false;
	}
}
