/**
 * GSerializer - JavaScript Serialization library.
 * 
 * @project GSerializer
 * @author Matt Fellows <matt.fellows@onegeek.com.au>, OneGeek Software (http://www.onegeek.com.au)
 * @version $Rev:  $
 * @license GNU general public license
 * @description JavaScript serialisation library
 * 
 * Acknowledgements  
 * 
 * - http://dotnetremoting.com/ for the original idea
 * - John Griffin <john.griffin@vardentech.com> for some updates for recursive object levels
 * 
 */

/**
 * Create the ONEGEEK global namespace object
 * 
 * @package ONEGEEK
 */
if (typeof (ONEGEEK) == "undefined") {
  /**
   * The ONEGEEK global namespace object.
   * 
   * @class ONEGEEK
   */
  ONEGEEK = {};
}

/**
 * Serialize/Deserializer any JavaScript object to a valid XML String (NOT JSON)
 * 
 * @class ONEGEEK.GSerializer
 */
ONEGEEK.GSerializer = function() {
  
	  
  /**
   * Are we in IE mode or not.
   * 
   * @var {protected Boolean} isIE
   */
  var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1;
  
  /**
   * Are we in Mozilla mode or not.
   * 
   * @var {protected Boolean} isMoz
   */
  var isMoz = document.implementation && document.implementation.createDocument;
  
  /**
   * Serialize a JS object into an XML String for storage / transmission
   * (i.e. cookie, download etc.)
   * 
   * @function {public String} serialize
   * @param {Object} objectToSerialize - The object to be serialized
   * @param {Object} objectName - (Optional) Name of the object being passed in
   * @param {Object} indentSpace - (Optional) Use this as an indentSpace
   * @return {String} An String (XML document compressed and/or encrypted) specifying the object
   */
  this.serialize = function(objectToSerialize, objectName, indentSpace) {
     indentSpace = indentSpace?indentSpace:'';
     
     // Get object type name to serialize
     var type = getTypeName(objectToSerialize);
     
     // string to store serialized XML
     var s = indentSpace  + '<' + objectName +  ' type="' + type + '">';
  
     switch(type) {
      case "number":
      case "string":
      case "boolean": 
        s += objectToSerialize; 
        break;       
      case "date":
        s += objectToSerialize.toLocaleString(); 
        break;       
      case "Function":
          s += "\n";        
          s += "<![CDATA["+objectToSerialize+"]]>";
          s += indentSpace;
          break;            
      case "array": 
    	  s += "\n";          
	      for(var a in objectToSerialize){
	        s += this.serialize(objectToSerialize[a], ('index' + a ), indentSpace + "   ");
	      }
	      s += indentSpace;      
	      break;          
      default:
        s += "\n";
        
        for ( var o in objectToSerialize) {
          s += this.serialize(objectToSerialize[o], o, indentSpace + "   ");
        }        
        s += indentSpace;
        break;  
     }
     
    s += "</" + objectName + ">\n"; 
       
      return s;
  };
  
  /**
   * Deserialize a serialized XML object into a javascript object
   * Uses deserial recursively to rebuild the javascript
   * 
   * @function {public Object} deserialize
   * @see deserial
   * @param {Object} XmlText
   * @return {Object}
   */
  this.deserialize = function(XmlText) {
    var _doc = getDom(XmlText); 
    return deserial(_doc.childNodes[0]);
  };
  
  /**
   * Get the DOM object from an XML doc
   * NB: Works for IE and Mozilla
   * 
   * @function {private String} getDom
   * @param {Object} strXml
   * @return {Object} The browser specific DOM object 
   */
  function getDom(strXml) {
    var _doc = null;
  
    if (isIE) {
      _doc = new ActiveXObject("Msxml2.DOMDocument.3.0");
      _doc.loadXML(strXml); 
    }
    else {
      var parser = new DOMParser();
      _doc = parser.parseFromString(strXml, "text/xml");
    }
  
    return _doc;
  }
  
  /**
   * Deserialize an XML DOM object into a javascript object
   * 
   * NB: This function uses recursion
   * @function {private Object} deserial
   * @param {Object} domObject - The DOM object to deserialize into a JS Object
   * @return {Object} The deserialized object
   */
  function deserial(domObject) {
    var retObj; 
    var nodeType = getNodeType(domObject);
     
    if (isSimpleVar(nodeType)) {
      if (isIE) {
        return stringToObject(domObject.text, nodeType);
      }
      else {
        return stringToObject(domObject.textContent, nodeType);
      }
    }
    
    switch(nodeType) {
      case "array":
        return deserializeArray(domObject);
      case "Function":        
        return deserializeFunction(domObject);      
      default:
        try {
          retObj = eval("new "+ nodeType + "()");
        }
        catch(e) {
          // create generic class
          retObj = {};
        }
      break;
    }
    
    for(var i = 0; i < domObject.childNodes.length; i++) {
      var Node = domObject.childNodes[i];
      retObj[Node.nodeName] = deserial(Node);
    }
  
    return retObj;
  }
  
  /**
   * Check if the current element is one of the primitive data types
   * 
   * @function {private Boolean} isSimpleVar
   * @param {String} type - The "type" attribute of the current node
   * @return {Boolean} Returns true if a boolean or false otherwise
   */
  function isSimpleVar(type)
  {
    switch(type) {
      case "int":
      case "string":
      case "String":
      case "Number":
      case "number":
      case "Boolean":
      case "boolean":
      case "bool":
      case "dateTime":
      case "Date":
      case "date":
      case "float":
        return true;
    }
    
    return false;
  }
  
  /**
   * Convert a string to an object
   * 
   * @function {private Object} stringToObject
   * @param {String} text - The text to parse into the new object
   * @param {String} type - The type of object that you wish to parse TO
   * @return {Object} The Object representation of the string
   */
  function stringToObject(text, type) {
    var retObj = null;
  
    switch(type.toLowerCase())
    {
      case "int":
        return parseInt(text, 10);   
         
      case "number":
        var outNum;
        
        if (text.indexOf(".") > 0) {
          return parseFloat(text);    
        } else {
          return parseInt(text, 10);    
        }      
           
      case "string":
        return text;      
         
      case "dateTime":
      case "date":
        return new Date(text);
          
      case "float":
        return parseFloat(text, 10);
        
      case "bool":
          if (text == "true" || text == "True") {
            return true;
          }
          else {
            return false;
          } 
    }
  
    return retObj;  
  }
  
  /**
   * Get the name of an object by extracting it from it's constructor attribute
   * 
   * @function {private String} getClassName
   * @param {Object} obj - The object for which the name is to be found
   * @return {String} The class name of the object passed in if found
   */
  function getClassName(obj) {   
    try {
      var ClassName = obj.constructor.toString();
      ClassName = ClassName.substring(ClassName.indexOf("function") + 8, ClassName.indexOf('(')).replace(/ /g,'');
      return ClassName;
    }
    catch(e) {
      return "NULL";
    }
  }
  
  /**
   * Get the type of Object by checking against the Built-in objects.
   * If no built in object is found, call getClassName
   * 
   * @function {private String} getTypeName
   * @see getClassName
   * @param {Object} obj - The object for which the type is to be found
   * @return {String} The type of the passed in var
   */ 
  function getTypeName(obj) {
    if (obj instanceof Array) {
      return "array";
    }
      
    if (obj instanceof Date) {
      return "date";  
    }
      
    var type = typeof(obj);
  
    if (isSimpleVar(type)) {
      return type;
    }
    
    type = getClassName(obj); 
    
    return type;
  }
  
  /**
   * Deserialize an Array
   * 
   * @function {private Object} deserializeArray
   * @param {XML String} node - The node to deserialize into an Array
   * @return {Array} The deserialized Array 
   */
  function deserializeArray(node) {
    var retObj = [];
          
    // Cycle through the array's TOP LEVEL children
    while ((child = node.firstChild) != null) {

      // delete child so it's children aren't recursed
      node.removeChild(node.firstChild);
                
      var nodeType = getNodeType(child);
      
      if(isSimpleVar(nodeType)) {
        retObj[retObj.length] = child.textContent;
      } else {
        var tmp = child.textContent;
        if(child.textContent.trim() != '') {
          retObj[retObj.length] = deserial(child);
        }           
      }                   
    }     
    return retObj;      
  }
  
  /**
   * Deserialize a Function
   * 
   * @function {private Function} deserializeFunction
   * @param {XML String} node - The node to deserialize into a Function
   * @return {Function} The deserialized Function
   */ 
  function deserializeFunction(func) {
    if(func && func.textContent) {
      return eval(func.textContent);
    }
  }
  
  /**
   * Get the type attribute of an element if there is one,
   * otherwise return generic 'object'
   * 
   * NB: This function is used on the resulting serialized XML and not on
   *     any actual javascript object
   *     
   * @function {private String} getNodeType    
   * @param {XML} node - The node for which the type is to be found
   * @return {String} The type of the node
   */   
  function getNodeType(node) {
    var nodeType = "object";
    
    if (node.attributes != null && node.attributes.length != 0) {
      var tmp = node.attributes.getNamedItem("type");
      if (tmp != null) {
        nodeType = node.attributes.getNamedItem("type").nodeValue;
      }
    }
    
    return nodeType;  
  } 
};

/**
 * Trim spaces from a string
 * 
 * Usage:  stringObject.trim();
 * 
 * @function {public String} trim
 * @return {String} The trimmed String
 */
if(!String.prototype.trim) {
  String.prototype.trim = function() {
	  a = this.replace(/^\s+/, '');
	  return a.replace(/\s+$/, '');
  };
}