///<reference path="../TESCO.js" />
///<reference path="exception.js" />
///<reference path="DOM.node.js" />
/*

TODO:
	1) serialize - if xmldocument then serialize this to a string instead of trying to iterate...
	2) use the StringBuilder object? performance is currently improved over normal concatenation as it buffers the value for each node...
	   (does not seem to improve the performance (using a inner buffer for each node (var nodeXml = {value : ""};) at the moment... need to do some proper performance testing...)
	3) add support for embedded nodes within a text value. e.g. "<p>this is <strong>some</strong> text</p>"
	   at the moment this will probably be deserialized into the object so that will look like "<p><strong>some</strong>this is  text</p>"

*/

TESCO.system.serialization = (function() {
	
	//	constants
	//#region
	this.NAME = "TESCO.system.serialization";
	this.CONST_TEXTNODENAME = "nodeValue";
	var NODE = TESCO.system.DOM.node;
	var SERIALIZE = this;
	//#endregion
	
	//	exceptions
	//#region
	var _exceptions = {
		"invalidObject" : function(functionName) {
			this.exception("Invalid object passed. Must be an XMLDocument.", "TESCO.system.serialization", functionName);
		},
		"invalidXML" : function(functionName, exception) {
			this.exception= TESCO.system.Exception;
			this.exception("There was a problem parsing the XML. (" + exception + ")", "TESCO.system.serialization", functionName);
		}
	}
	//#endregion
	
	//	DOMParser
	//#region
	/*@cc_on
	if (!window.DOMParser) {
		
		function DOMParser() {}
		
		DOMParser.prototype.parseFromString = function(str, contentType) {
			var _xml = new ActiveXObject("MSXML.DomDocument");
			_xml.async = false;
			_xml.loadXML(str);
			return _xml;
		}
	}
	@*/
	//#endregion

	//	XMLSerializer
	//#region
	/*@cc_on
	if (!window.XMLSerializer) {
		function XMLSerializer() {}
		
		XMLSerializer.prototype.serializeToString = function(node) {
			return node.xml || _extractXML(node);
		}
	}
	@*/
	//#endregion

	//	extract XML
	//#region
	this.getSafeXMLValue = function(s) {
		if (s instanceof Boolean) {
			return (s ? "true" : "false");
		} else if (s instanceof String) {
			return s;
		} else if (s && s.replace) {
			var _safe = s.replace(/&/g,"&amp;");
			_safe = _safe.replace(/</g,"&lt;");
			_safe = _safe.replace(/>/g,"&gt;");
			return _safe.replace(/"/g,"&quot;");
		} else {
			return s;
		}
	}
	
	function _extractXHTML(node) {
		var _xhtml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
		_xhtml += "<fragment>";
		_xhtml += "<content xmlns=\"" + NODE.CONST_XHTMLNAMESPACE + "\">";
		_xhtml += _extractNodeXML(node, true);
		_xhtml += "</content>";
		_xhtml += "</fragment>";
		return _xhtml;
	}
	
	function _extractXML(node, useLower) { 
		switch (node.nodeType) { 
			case 1: //	node
				return _extractNodeXML(node, useLower); 
			case 2: //	attribute
				return _extractAttributeXML(node, useLower); 
			case 3: //	Text node 
				return this.getSafeXMLValue(node.nodeValue); 
			case 9: //	document element
				return _extractDocumentNodeXML(node, useLower); 
			case 10: //	doctype
				return _extractDocumentTypeXML(node); 
			default:
				return "";
		} 
	}

	function _extractNodeXML(node, useLower) {
		var _xml = "<" + (useLower ? node.nodeName.toLowerCase() : node.nodeName);
		NODE.each(node.attributes,
			function() {
				_xml += _extractXML(this, useLower);
			}
		);
		_xml += ">"
		NODE.each(node.childNodes,
			function() {
				_xml += _extractXML(this, useLower);
			}
		);
		/*@cc_on
		if (!node.canHaveChildren && node.nodeName.toLowerCase() === "script") {
			_xml += node.text;
		}
		@*/		
		_xml += "</" + (useLower ? node.nodeName.toLowerCase() : node.nodeName) + ">"
		return _xml;
	}

	function _extractAttributeXML(node, useLower) {
		return (!node.nodeValue) ? "" : (" " + (useLower ? node.nodeName.toLowerCase() : node.nodeName) + "=\"" + this.getSafeXMLValue(node.nodeValue) + "\"");
	}

	function _extractDocumentNodeXML(node, useLower) {
		var _xml = "<?xml version=\"" + node.xmlVersion + "\" encoding=\"" + node.xmlEncoding + "\"?>"
		NODE.each(node.childNodes,
			function() {
				_xml += _extractXML(this, useLower);
			}
		);
		return _xml;
	}
	
	function _extractDocumentTypeXML(node) {
		return "<!DOCTYPE " + node.name + " PUBLIC \"" + node.publicId + "\" \"" + node.systemId + "\">"
	}
	//#endregion
	
	//	serialize
	//#region
	this.serializeXMLToString = function(node) {
		return new XMLSerializer().serializeToString(node);
	}
	
	this.serializeXHTMLToString = function(node) {
		return _extractXHTML(node);
	}
	
	this.serializeStringToXML = function(string) {
		return new DOMParser().parseFromString(string, "text/xml");
	}
	//#endregion
	
	//	node
	//#region
	this.node = function(text) {
		this[this.CONST_TEXTNODENAME] = text;
	}

	this.node.prototype.toString = function() {
		var _nodeValue = this[this.CONST_TEXTNODENAME];
		return (_nodeValue ? _nodeValue : "");
	}
	
	this.node.prototype.getObject = function() {
		var _nodeValue = this[this.CONST_TEXTNODENAME];
		return (_nodeValue && typeof _nodeValue == "object" ? _nodeValue : null);
	}
	//#endregion
	
	//	Deserialize an XMLDocument object into a native object
	//#region
	this.deserialize = function(xmlDocument, stripNamespaces) {

		this.VERSION = "1.0.0";
		this.NAME = this.NAME + ".deserialize";

		function _deserialize(xmlNode, obj) {
			//#region
			var _o;
			if (obj[xmlNode.nodeName]) {
				if(!obj[xmlNode.nodeName].ISARRAY) {
					obj[xmlNode.nodeName] = [obj[xmlNode.nodeName]];
				}
				_o = new TESCO.system.serialization.node();
				obj[xmlNode.nodeName].push(_o);
			} else {
				_o = obj[xmlNode.nodeName] = new SERIALIZE.node();
			}

			var _textValue = "";
			if (xmlNode.namespaceURI == NODE.CONST_XHTMLNAMESPACE) {
				_textValue = NODE.create(xmlNode.nodeName);
				if (xmlNode.xml) {
					for (var i = 0; i < xmlNode.childNodes.length; i++) {
						_textValue.appendChild(NODE.innerXML(NODE.create(xmlNode.nodeName), xmlNode.childNodes[i].xml).childNodes[0]);
					}
				} else if (xmlNode.innerHTML) {
					_textValue = NODE.innerXML(_textValue, xmlNode.innerHTML);
				}
			} else {
				if (xmlNode.attributes) {
					for (var i = 0; i < xmlNode.attributes.length; i++) {
						if (!stripNamespaces || (stripNamespaces && !xmlNode.attributes[i].prefix)) {	// Ignore namespaced nodes
							_o[xmlNode.attributes[i].nodeName] = xmlNode.attributes[i].nodeValue;
						}
					}
				}

				var currentContext;
				for (var i = 0; i < xmlNode.childNodes.length; i++) {
					currentContext = xmlNode.childNodes[i];
					if (currentContext.nodeType == 1) {	// Element
						_deserialize(currentContext, _o);
					} else if (currentContext.nodeType == 3 && currentContext.nodeValue) {	// Text
						_textValue += currentContext.nodeValue;
					}
				}
			}

			if (_textValue) {
				_o[SERIALIZE.CONST_TEXTNODENAME] = _textValue;
			}
			//#endregion
		}

		if (xmlDocument && xmlDocument.documentElement) {
			var obj = new this.node();
			try {
				_deserialize(xmlDocument.documentElement, obj);
			} catch(e) {
				throw new _exceptions.invalidXML("deserialize", e);
			}
			return obj;
		} else {
			throw new _exceptions.invalidObject("deserialize");
		}
		
	}
	//#endregion
	
	return this;
})();

// Serialize a native object into an XML string
Object.prototype.serialize = function(rootName) {
	//#region
	var xml = { 
		"value" : ""
	}

	var _objectToSerialize = this;

	if (rootName) {
		_objectToSerialize = {};
		_objectToSerialize[rootName] = this;
	}

	var sValue = _serialize(_objectToSerialize, xml);

	function _serialize(o, xml) {
		//#region
		if (o) {
			for (var i in o) {
				if (o[i] && typeof o[i] == "object" && !(o[i] instanceof String || o[i] instanceof Number)) {
					if (xml.value.length > 0 && !xml.value.endsWith(">")) {
						xml.value += ">";
					}
					if (o[i].ISARRAY) {
						for (var ii = 0; ii < o[i].length; ii++) {
							_serializeNode(i, o[i][ii], xml);
						}
					} else {
						_serializeNode(i, o[i], xml);
					}
				}
			}
		}
		//#endregion
	}

	function _serializeNode(name, o, xml) {
		//#region
		if (name != TESCO.system.serialization.CONST_TEXTNODENAME) {
			var bEmpty = true;
			xml.value += "<" + name;
			var nodeXml = {
				value : ""
			}

			if (o.tagName && o.style) {
				nodeXml.value = o.outerHTML;
			} else {
				_serializeAttributes(o, xml);
				_serialize(o, nodeXml);
			}

			if (nodeXml.value != "") {
				if (xml.value.length > 0 && !xml.value.endsWith(">")) {
					xml.value += ">";
				}
				xml.value += nodeXml.value;
				bEmpty = false;
			}

			if (o[TESCO.system.serialization.CONST_TEXTNODENAME] !== undefined) {
				if (typeof o[TESCO.system.serialization.CONST_TEXTNODENAME] != "object") {
					if (xml.value.length > 0 && !xml.value.endsWith(">")) {
						xml.value += ">";
					}
					bEmpty = false;
					xml.value += o[TESCO.system.serialization.CONST_TEXTNODENAME];
				}
			}
			if (bEmpty) {
				xml.value += "/>";
			} else {
				xml.value += "</" + name + ">";
			}
		} else {
			if (o.tagName && o.style) {
				xml.value = o.outerHTML;
			} else {
				_serialize(o, nodeXml);
			}
		}
		//#endregion
	}

	function _serializeAttributes(o, xml) {
		for (var ii in o) {
			if (ii != TESCO.system.serialization.CONST_TEXTNODENAME && ((typeof o[ii] != "object" && typeof o[ii] != "function") || (o[ii] instanceof String || o[ii] instanceof Number || o[ii] instanceof Boolean))) {
				xml.value += " " + ii + "=\"" + TESCO.system.serialization.getSafeXMLValue(o[ii]) + "\"";
			}
		}
	}

	return xml.value;
	//#endregion
}