/**
 * WDDX Deserializer for Javascript
 * File: *.mod_wddx_des.js
 * Author: Bertold von Dormilich (Dormilich@netscape.net), 2009
 * Contributor: Nate Weiss (nweiss@icesinc.com), 1999
 * See www.wddx.org for usage and updates
 */

// Most of the code used in the simple data type conversion (bool, null, number, 
// date) is taken from the original code. considerable changes were made for 
// string (the text is already parsed), array and object (using a different 
// parser model).

// And of course the OOP style was changed to a more modern approach.
// The main advantage over Nate's WDDX string parser is, that I don't 
// have to parse the WDDX file, thus I can use DOM.

// used K&R indent style, so it looks somewhat condensed...

"use strict";

/**
 * Prototyping into the DOM interface. Although Firefox provides some 
 * properties that do the same, I can't rely on those yet.
 */
Element.prototype.getElementChildren = function (tagname)
{
	var c, i = 0, elem = [], tag;
	tagname = tagname || "*";
	while (c = this.childNodes[i++]) {
		tag = ("*" === tagname) ? true : (tagname === c.tagName);
		if (c instanceof Element && tag) {
			elem.push(c);
		}
	}
	return elem;
};

Element.prototype.getFirstElementChild = function (tagname)
{
	var c, i = 0, tag;
	tagname = tagname || "*";
	while (c = this.childNodes[i++]) {
		tag = ("*" === tagname) ? true : (tagname === c.tagName);
		if (c instanceof Element && tag) {
			return c;
		}
	}
	return undefined;
};

/**
 * Setting a global for using timezone info.
 *
 * @param (bool) use            the useTimezonInfo variable used in getDate()
 * @return (void)
 */
WDDX.prototype.setTimezoneInfo = function (use)
{
	window.useTimezoneInfo = Boolean(use);
};

/**
 * Setting a global for object classname detection. (some languages save it
 * as type attribute, some in the first <var>)
 *
 * @param (bool) app            application id setting the classname. defaults
 *                              to javascript.
 * @return (void)
 */
WDDX.prototype.setApplicationType = function (app)
{
	window.wddx_application = app || "javascript";
};

/**
 * Start the deserialization.
 *
 * @return (void)
 */
WDDX.prototype.deserialize = function ()
{
	return this.content.parse();
};

/**
 * Object representing a node of the XML document. Checks for DOM 
 * compliance and extracting some core data (tag name, text content, 
 * element children).
 *
 * @param (Element) knoten      an XML Element node
 * @return (void)
 * @throws (Error)              source not conforming to the DOM
 */
function WDDXNode(knoten)
{
	if (!(knoten instanceof Element)) {
		throw new Error("Source is not an Element.");
	}
	// save the node for DOM manipulations
	this.node = knoten;
	// get basic properties
	this.name = knoten.tagName.toLowerCase();
	this.text = (knoten.firstChild) ? knoten.firstChild.data : null;
	this.elements = knoten.getElementChildren();
}

/**
 * Get the Javascript types & values from the WDDX element.
 *
 * @return (mixed)              deserialized value/object
 * @throws (Error)              unsupported data type
 */
WDDXNode.prototype.parse = function ()
{
	switch (this.name) {
		case "array": 
			return this.getArray();
		case "boolean": 
			return this.getBoolean();
		case "datetime": 
			return this.getDate();
		case "null": 
			return this.getNull();
		case "number": 
			return this.getNumber();
		case "string": 
			return this.getString();
		case "struct": 
			return this.getStruct();
		// just in case...
		case "var": 
			var vn = new WDDXNode(this.elements[0]);
			return vn.parse();
		default: 
			throw new Error("This type is not supported yet.");
	}
};

/**
 * return the boolean value.
 *
 * @return (bool)               deserialized Boolean
 * @throws (Error)              WDDX element not a boolean type
 */
WDDXNode.prototype.getBoolean = function ()
{
	if ("boolean" !== this.name) {
		throw new Error("WDDX node is not a Boolean.");
	}
	return ("true" === this.node.getAttribute("value"));
};

/**
 * return the numeric value.
 *
 * @return (float)              deserialized Number
 * @throws (Error)              WDDX element not a number type
 */
WDDXNode.prototype.getNumber = function ()
{
	if ("number" !== this.name) {
		throw new Error("WDDX node is not a Number.");
	}
	return parseFloat(this.text);
};

/**
 * return null.
 *
 * @return (null)               deserialized value
 * @throws (Error)              WDDX element not a null type
 */
WDDXNode.prototype.getNull = function ()
{
	if ("null" !== this.name) {
		throw new Error("WDDX node is not a Null type.");
	}
	return null;
};

/**
 * return a Date object.
 *
 * @return (Date)               deserialized dateTime
 * @throws (Error)              WDDX element not a dateTime type
 */
WDDXNode.prototype.getDate = function ()
{
	if ("datetime" !== this.name) {
		throw new Error("WDDX node is not a Date.");
	}
	var NewDate, TempDate = new Date(), Value,
	    dtDateParts, dtTimeParts, dtTimeTZParts, dtTZParts, dtOffsetHours, dtOffsetMins;
	this.timezoneOffset = TempDate.getTimezoneOffset();
	this.timezoneOffsetHours = Math.round(this.timezoneOffset / 60);
	this.timezoneOffsetMinutes = (this.timezoneOffset % 60);
	
	// Split date string into component parts
	Value = this.text.split("T");
	dtDateParts = Value[0].split("-");
	if ((Value[1].indexOf("-") === -1) && (Value[1].indexOf("+") === -1)) {
		// create native JS Date object
		dtTimeParts = Value[1].split(":");
		NewDate = new Date(dtDateParts[0], dtDateParts[1] - 1, dtDateParts[2], dtTimeParts[0], dtTimeParts[1], dtTimeParts[2]);
	} 
	else {  
		// There is timezone info for this <dateTime></dateTime> element.
		// Get just the timezone info by getting everything after the "-" or "+"
		if (Value[1].indexOf("-") > -1) {
			dtTimeTZParts = Value[1].split("-");
		} 
		else {
			dtTimeTZParts = Value[1].split("+");
		}
		dtTimeParts = dtTimeTZParts[0].split(":");
		
		// Create a new JS date object (no timezone offsetting has happened yet)
		NewDate = new Date(dtDateParts[0], dtDateParts[1] - 1, dtDateParts[2], dtTimeParts[0], dtTimeParts[1], dtTimeParts[2]);
		
		// If we are supposed to do timezone offsetting
		if (true === window.useTimezoneInfo) { 
			dtTZParts     = dtTimeTZParts[1].split(":");
			dtOffsetHours = parseInt(dtTZParts[0], 10);
			dtOffsetMins  = parseInt(dtTZParts[1], 10);
			if (Value[1].indexOf("-") > -1) {
				dtOffsetHours = this.timezoneOffsetHours - dtOffsetHours;
				dtOffsetMins  = this.timezoneOffsetMinutes - dtOffsetMins;
			} 
			else {
				dtOffsetHours += this.timezoneOffsetHours;
				dtOffsetMins  += this.timezoneOffsetMinutes;
			}
			NewDate.setHours(NewDate.getHours() - dtOffsetHours);
			NewDate.setMinutes(NewDate.getMinutes() - dtOffsetMins);
		}  
	}
	return NewDate;
};

/**
 * return the string value.
 *
 * @return (strin)              deserialized value
 * @throws (Error)              WDDX element not a string type
 */
WDDXNode.prototype.getString = function ()
{
	if ("string" !== this.name) {
		throw new Error("WDDX node is not a String.");
	}
	var i, knoten, Value = this.text, l = this.node.childNodes.length;
	// check for text and CDATA nodes
	if (l > 1) {
		Value = "";
		for (i = 0; i < l; i++) {
			knoten = this.node.childNodes[i];
			if (knoten instanceof Text) {
				Value += String(knoten.data);
			}
		}
	}
	return Value;
};

/**
 * return an Array object.
 *
 * @return (Array)              deserialized array
 * @throws (Error)              WDDX element not a boolean type
 */
WDDXNode.prototype.getArray = function ()
{
	var JSArray = [], ArrayLength, leng, c;
	// ArrayLength is the length of the WDDX-style array to parse
	ArrayLength = parseInt(this.node.getAttribute("length"), 10);
	// check the real size of the WDDX array
	leng = this.elements.length;
	// For each element in the WDDX array, set the corresponding
	// Element in the JS array to the WDDX element's parsed value  
	for (c = 0; c < ArrayLength; c++) { 
		// if the denoted array length is larger than the real length, 
		// pad the array with undefined
		if (c >= leng) {
			JSArray[c] = undefined;
		} 
		else {
			// parse the array elements
			var item = new WDDXNode(this.elements[c]);
			JSArray[c] = item.parse();
		} 
	}
	return JSArray;
};

/**
 * return an Object object.
 *
 * @return (Object)             deserialized object
 * @throws (Error)              WDDX element not a boolean type
 */
WDDXNode.prototype.getStruct = function ()
{
	var JSObject, StructIndex, item, items, pcn, kn, l, c, type;
	// Call object constructor if a "type" attribute has been provided
	if ("javascript" === window.wddx_application) {
		type = String(this.node.getAttribute("type"));
	}
	// check if a php_class_name is provided--and should be used
	pcn = this.node.getFirstElementChild("var");
	if ("php_class_name" === pcn.getAttribute("name")) {
		if ("php" === window.wddx_application) {
			try {
				kn   = new WDDXNode(pcn.getFirstElementChild());
				type = String(kn.getString());
			} 
			catch (e) {
				type = false;
			}
		}
		// always remove php_class_name variable
		this.node.removeChild(pcn);
	}
	// call object constructor if provided
	if (type && window[type] instanceof Function) {
		JSObject = new window[type]();
	} 
	else {
		JSObject = {};
	}

	// there should only be <var> children, but you never know...
	items = this.node.getElementChildren("var");
	for (c = 0, l = items.length; c < l; c++) { 
		// use the <var> element's child node 
		item = new WDDXNode(items[c].getFirstElementChild());
		StructIndex = items[c].getAttribute("name");
		JSObject[StructIndex] = item.parse();
	}
	return JSObject;
};

/**
 * Wrapper object for deserialization.
 *
 * @param (Document) xmldoc     an XML document
 * @return (void)
 * @throws (Error)              source not conforming to the DOM
 */
function WDDX(xmldoc)
{
	if (!(xmldoc instanceof Document)) {
		throw new Error("Source is not a Document.");
	}
	var data, comt;
	// get the data & comment element
	data = xmldoc.getElementsByTagName("data")[0];
	comt = xmldoc.getElementsByTagName("comment")[0];
	if (!data) {
		throw new Error("WDDX document does not contain data.");
	}
	this.content = new WDDXNode(data.getFirstElementChild());
	if (comt) {
		this.comment = comt.firstChild.data;
	}
	this.setApplicationType();
}

