// XML Serialization/Restoration implementation for JavaX
// Require: prototype.js
// Require: types.js
// Require: aserializer.js
// Bugs: Grep TODO's and NOTE's in the code

function XMLRestore(oBuffer) {
	var oRestorer = new AXMLRestorer(oBuffer);
	return oRestorer.Restore();
}

function XMLSerialize(oObj) {
	var oSerializer = new AXMLSerializer();
	oSerializer.Serialize(oObj);
	return oSerializer.Buffer();
}

// ---------------------------------------------
// Restorer
// ---------------------------------------------

var AXMLRestorer = Class.create({
initialize: function(oSource) {
	this.oXML_ = new AXML();
	if (typeof(oSource) == "string") this.oXML_.Parse(oSource);
	else {
		// check if it is a DOM tree (not perfect, but working)
		if (oSource.xml) this.oXML_.SetXML(oSource);
		else throw new Error("AXMLRestorer: Invalid argument passed in constructor. Allowed types are string and DOM tree");
	}
	this.aPath_ = new Array();
	this.aPath_.push(this.oXML_.XML().documentElement.firstChild);
},
BeginType: function(sName) {
	if (!this.aPath_.length) throw new Error("AXMLRestorer::BeginType: Stream is empty! ");
	var oBack = this.aPath_.last();
	if (!oBack) throw new Error("AXMLRestorer::BeginType: Stream is empty! ");
	if (oBack.nodeName != sName) throw new Error("AXMLRestorer::BeginType: Name mismatch detected ("+oBack.nodeName+" != "+sName+")! ");
	this.aPath_.push(this.aPath_.last().firstChild);
},
EndType: function(sName) {
	if (!this.aPath_.length) throw new Error("AXMLRestorer::EndType: Stream is empty! ");
	var oBack = this.aPath_.last();
	if (!oBack || !oBack.nextSibling) {
		// all good
	} else {
		// this condition is added for *long* text nodes (failures were detected on long base64 blocks)
		if (oBack.nodeName != "#text") {
			throw new Error("AXMLRestorer::EndType: Unexpected type end ("+sName+")! ");
		} // else all good
	}
	this.aPath_.pop();
	var oBack = this.aPath_.last();
	if (!oBack || oBack.nodeName != sName) throw new Error("AXMLRestorer::EndType: Name mismatch detected ("+
		((!oBack)?"<null>":oBack.nodeName)+" != "+sName+")! ");
	this.aPath_[this.aPath_.length-1] = oBack.nextSibling;
},
GetType: function() {
	var oBack = this.aPath_.last();
	if (!oBack) return ""; else return oBack.nodeName;
},
RestoreInt: function() {
	this.BeginType("int");
	var oBack = this.aPath_.last();
	var sText = oBack.parentNode.text;
	if (!sText) throw new Error("AXMLRestorer::RestoreInt: Node text content is NULL");
	this.EndType("int");
	return new Int(parseInt(sText));
},
RestoreInt64: function() {
	this.BeginType("int64_t");
	var oBack = this.aPath_.last();
	var sText = oBack.parentNode.text;
	if (!sText) throw new Error("AXMLRestorer::RestoreInt64: Node text content is NULL");
	this.EndType("int64_t");
	return new Int64(sText);
},
RestoreDouble: function() {
	this.BeginType("double");
	var oBack = this.aPath_.last();
	var sText = oBack.parentNode.text;
	if (!sText) throw new Error("AXMLRestorer::RestoreDouble: Node text content is NULL");
	this.EndType("double");
	return new Double(sText);
},
RestoreString: function() {
	this.BeginType("string");
	var oBack = this.aPath_.last();
	// handle empty text node <string/>
	var sV = (oBack && oBack.parentNode.text)?oBack.parentNode.text:"";
	this.EndType("string");
	return sV;
},
RestoreBool: function() {
	this.BeginType("bool");
	var oBack = this.aPath_.last();
	var sText = oBack.parentNode.text;
	if (!sText) throw new Error("AXMLRestorer::RestoreBool: Node text content is NULL");
	this.EndType("bool");
	if (sText == "1") return true;
	if (sText == "0") return false;
	throw new Error("AXMLRestorer::RestoreBool: Invalid bool value. ");
},
RestoreBinary: function() {
	this.BeginType("base64");
	var oBack = this.aPath_.last();
	// handle empty text node <string/>
	var sV = (oBack && oBack.parentNode.text)?oBack.parentNode.text:"";
	this.EndType("base64");
	return Encoder.Base64Decode(sV);
},
RestoreCompound: function() {
	var oObj = null;
	this.BeginType("Value");
	var typeName = this.GetType();
	if (typeName == "Structure") {
		oObj = new Object();
		this.BeginType("Structure");
		while (this.GetType() == "string") {
			var sKey = this.RestoreString();
			//this.BeginType("Value");
			var oVal = this.Restore();
			oObj[sKey] = oVal;
			//this.EndType("Value");
		}
		this.EndType("Structure");
	} else if (typeName == "Array") {
		oObj = new Array();
		this.BeginType("Array");
		while (this.GetType() == "Value") {
			//this.BeginType("Value");
			oObj.push(this.Restore());
			//this.EndType("Value");
		}
		this.EndType("Array");
	} else if (!typeName) {
		oObj = null; // NULL-FIX ('undefined' was here)
	} else {
		oObj = this.Restore();
	}
	this.EndType("Value");
	return oObj;
},
Restore: function() {
	return RestorerIF.restore(this);
},
Buffer: function() {
	return this.oXML_.XML().xml;
}
});

// ---------------------------------------------
// Serializer
// ---------------------------------------------

var AXMLSerializer = Class.create({
initialize: function() {
	this.oXML_ = new AXML("xmlstream");
	this.xCurrent_ = this.oXML_.XML().documentElement;
},
Buffer: function() {
	if (this.xCurrent_ != this.oXML_.XML().documentElement) {
		throw new Error("AXMLSerializer: Type serialization not finished!");
	}
	return this.oXML_.Serialize();
},
BeginType: function(sName) {
	var xChild = this.oXML_.XML().createElement(sName);
	this.xCurrent_.appendChild(xChild);
	this.xCurrent_ = xChild;
},
EndType: function(sName) {
	if (this.xCurrent_.nodeName != sName) {
		throw new Error("Begin-End names do not match ('" + this.xCurrent_.nodeName + "' != '" + sName + "'");
	}
	this.xCurrent_ = this.xCurrent_.parentNode;
},
SerializeInt: function(oObj) {
	var xChild = this.oXML_.XML().createElement("int");
	// TODO: toString()?
	var xText = this.oXML_.XML().createTextNode(oObj.toString());
	xChild.appendChild(xText);
	this.xCurrent_.appendChild(xChild);
},
SerializeInt64: function(oObj) {
	var xChild = this.oXML_.XML().createElement("int64_t");
	// TODO: toString()?
	var xText = this.oXML_.XML().createTextNode(oObj.toString());
	xChild.appendChild(xText);
	this.xCurrent_.appendChild(xChild);
},
SerializeDouble: function(oObj) {
	var xChild = this.oXML_.XML().createElement("double");
	var xText = this.oXML_.XML().createTextNode(oObj.toString());
	xChild.appendChild(xText);
	this.xCurrent_.appendChild(xChild);
},
SerializeString: function(oObj) {
	var xChild = this.oXML_.XML().createElement("string");
	var xText = this.oXML_.XML().createTextNode(oObj);
	xChild.appendChild(xText);
	this.xCurrent_.appendChild(xChild);
},
SerializeBool: function(oObj) {
	var xChild = this.oXML_.XML().createElement("bool");
	var sV;
	if (oObj === true) sV = "1";
	else if (oObj === false) sV = "0";
	else {
		throw new Error("ERROR: Invalid value for Boolean!");
	}
	var xText = this.oXML_.XML().createTextNode(sV);
	xChild.appendChild(xText);
	this.xCurrent_.appendChild(xChild);
},
SerializeBinary: function(oObj) {
	alert("Serialization of binary types (bas64-encoded) is not implemented!");
},
SerializeObject: function(oObj, bValueElement) {
	if (!bValueElement) this.BeginType("Value");
	var nCount = 0;
	for (var sKey in oObj) {
		if (typeof(oObj[sKey]) == "function") continue;
		++nCount;
	}
	if (nCount) {
		this.BeginType("Structure");
		for (var sKey in oObj) {
			if (typeof(oObj[sKey]) == "function") continue;
			var oItem = oObj[sKey];
			// put key
			this.Serialize(String(sKey));
			// put value as Value
			this.BeginType("Value");
			this.Serialize(oItem, true);
			this.EndType("Value");
		}
		this.EndType("Structure");
	}
	if (!bValueElement) this.EndType("Value");
},
SerializeArray: function(oObj, bValueElement) {
	if (!bValueElement)this.BeginType("Value");
	this.BeginType("Array");
	for (var i = 0; i < oObj.length; i++) {
		var oItem = oObj[i];
		// put value as Value
		this.BeginType("Value");
		this.Serialize(oItem, true);
		this.EndType("Value");
	}
	this.EndType("Array");
	if (!bValueElement)this.EndType("Value");
},
Serialize: function(oObj, bValueElement) {
	SerializerIF.serialize(this, oObj, bValueElement);
}
});

