/******************************************************************************/
/******************************************************************************/
// XML OBJECTS AND FUNCTIONS                                                  //
// Copyright 2007-2008 Collaborative Bike Map Project
/******************************************************************************/
/******************************************************************************/
// Constants
XMLDECL = '<?xml version="1.0" ?>\n';       // first line of XML file
ROUTEFILEVERSION = '1.0';                   // BikeZip route file version
// Message types
MSG_REQUEST = 1;
MSG_RESPONSE = 2;

// Globals
var XmlFldInfo = new XmlFieldInfo;

function dothis () {
	alert ("Whats up?");
}


/*******************************************************************/
// Dump XML to separate window
// THIS DOESN'T WORK NOW BECAUSE genRouteXML() DOESN"T WORK.
/********************************************************************/
function dumpRouteXML (xml) {
	var xml = "";
	var win, elmt;
	var htmlstart, htmlend;		
	
	if (OpenRoute != null) {
		xml = genRouteXML (OpenRoute);
		// alert (xml);				
		dumpTextToWindow (formatXML(xml), "XML representation of the selected route");	
	}
	else {
		alert ("There is no open route.");
	}
}


/*******************************************************************/
// Dump XML and/or specified text to separate window
// txt - HTML string to be displayed verbatim
/********************************************************************/
function dumpTextToWindow (txt, title) {
	var win, elmt;
	var htmlstart, htmlend;		
	
	win = openWin ('textwin.htm');
	if (win != null) {
		htmlstart = "<head><title>BikeZip Output</title></head>" + 
					"<body><h3 style='font:14px arial bold; margin:2px 0'>" + title + "</h3><hr/>" +
					"<p style='font:12px'>";
		htmlend = "</p></body>";
		win.document.write (htmlstart + txt + htmlend);
		win.document.close();
	}
	else
		alert ("Couldn't open window");
}


/*******************************************************************/
// Generate XML representing the specified route (including segments
// and points)
// THIS IS MEANINGLESS NOW.
/********************************************************************/
function genRouteXML (rte) {
	
	var i, entry;
	var seg, pnt;
	var xml;

	// Output route info
	xml = "<rte ";
	for (i=0; i<XmlFldInfo.rtefields.length; i++) {
		entry = XmlFldInfo.rtefields[i];
		if (rte[entry.me] != null)
			xml = xml + entry.xm + "='" + rte[entry.me] + "' ";
	}

	// Do items that require calculation
	xml = xml + "ptct = '" + rte.pointlist.length + "' ";
	xml = xml + "sgct = '" + rte.seglist.length + "' ";
	xml = xml + ">";
	
	// Loop through segments, output segment info
	seg = rte.seglist.firstnode;	
	while (seg != null) {
		xml = xml + genSegmentXML (seg);
		seg = seg.next;
	}

	// Loop through points, output point info
	pnt = rte.pointlist.firstnode;	
	while (pnt != null) {
		xml = xml + genPointXML (pnt);
		pnt = pnt.next;
	}

	// Closing route tag
	xml = xml + "</rte>";
	return xml;
}


/*******************************************************************
// Generate XML representing the specified segment (not points)
// THIS IS MEANINGLESS NOW.
********************************************************************/
function genSegmentXML (seg) {
	var xml;
	var i, entry;

	// Output segment info
	xml = "<seg ";
	for (i=0; i<XmlFldInfo.segfields.length; i++) {
		entry = XmlFldInfo.segfields[i];
		if (seg[entry.me] != null)
			xml = xml + entry.xm + "='" + seg[entry.me] + "' ";
	}
	// Do items that require calculation
	xml = xml + "p1 = '" + seg.firstpoint.id + "' ";
	xml = xml + "p2 = '" + seg.lastpoint.id + "' ";
	xml = xml + "/>";
	return xml;
}

/*******************************************************************
// Generate XML representing the specified point.
// THIS IS MEANINGLESS NOW.
********************************************************************/
function genPointXML (pnt) {
	var xml;
	var i, entry;
	
	// Output point info
	xml = "<pnt ";
	xml = xml + "id = '" + pnt.id + "' ";
	for (i=0; i<XmlFldInfo.pntfields.length; i++) {
		entry = XmlFldInfo.pntfields[i];
		if (pnt[entry.me] != null)
			xml = xml + entry.xm + "='" + pnt[entry.me] + "' ";
	}
	xml = xml + "/>";
	return xml;
}

/*******************************************************************
// Format XML verbatim as HTML
//		& (ampersand) becomes &amp;
//      " (double quote) becomes &quot;
//      ' (single quote) becomes &#039;
//      < (less than) becomes &lt;
//      > (greater than) becomes &gt;  
//  ALSO: <br/> is inserted after each newline  
*******************************************************************/
function formatXML (xml) {
	var re1 = new RegExp ("&", "g");
	var re2 = new RegExp ("\"", "g");
	var re3 = new RegExp ("'", "g");
	var re4 = new RegExp ("<", "g");
	var re5 = new RegExp (">", "g");
	var re6 = new RegExp ("\n", "g");

	var result = xml;
	result = result.replace (re1, "&amp;");
	result = result.replace (re2, "&quot;");
	result = result.replace (re3, "&#039;");
	result = result.replace (re4, "&lt;");
	result = result.replace (re5, "&gt;");
	result = result.replace (re6, "\n<br\/>");
	//alert (result);
	return result;
}

/*******************************************************************
// Describe given XML node in HTML.  Recursive.
// This is a different format than formatXML() uses.
// It lists the subnodes and attributes vertically and indents them.
// It returns the formatted XML as an HTML string.
*******************************************************************/
function describeXMLnode (node, depth) {
	var i, j, nodelist, tempstr;
	var regexp;
	var attr, attrlist;

	var indent = "";
	var outstr = "";
	for (j=0; j<depth*3; j++) {
		indent += ".";
	}

	// outstr += "nt=" + node.nodeType + ". ";

	// Element node //
	if (node.nodeType == 1) {
		outstr = "<br/>" + indent + "Element node. Name = " + node.nodeName; 
		// Display attributes
		attrlist = node.attributes;
		for (i=0; i<attrlist.length; i++) {
			attr = attrlist.item(i);
			outstr += "<br/>" + indent + "...Attribute '" + attr.name + "' = '" + attr.value + "'";	
		}
		// Display child nodes recursively 
		nodelist = node.childNodes;
		if (depth<7) {
			for (i=0; i<nodelist.length; i++) {
				tempstr = describeXMLnode (nodelist[i], depth + 1);
				outstr += tempstr;
			} 
		}
	}

	// Text node //
	else if (node.nodeType == 3) {
		regexp = /^\s*$/;
		if (!regexp.test (node.data)) 
			outstr = "<br/>" + indent + "Text node. Name = " + node.nodeName + ", data = '" + node.data + "'";
	}

	// Other node type //
	else {
		outstr = "<br/>" + indent + "Other node (type = " + node.nodeType + "). Name = " + node.nodeName;
	}  

	return outstr;

}


/*******************************************************************
// Create XML Document object from specified XML string
*******************************************************************/
function getXmlDoc (xml) {
	
	var xmldoc, parser;

	// Get document object
	if (window.ActiveXObject) {    // code for IE
		xmldoc = new ActiveXObject("Microsoft.XMLDOM");
		xmldoc.async = false;
		xmldoc.loadXML (xml);
	}
	else {       // code for Mozilla, Firefox, Opera, etc.
		var parser = new DOMParser();
		var xmldoc = parser.parseFromString (xml, "text/xml");
	}
	return xmldoc;
}


/*******************************************************************
// Create XML string from an XML Document object or element node 
// aka serialize the XML entity
*******************************************************************/
function getXmlString (xmlnode) {

	// code for Mozilla
	if (typeof XMLSerializer != "undefined")
		return (new XMLSerializer()).serializeToString (xmlnode);
	
	// code for IE
	else if (xmlnode.xml) return xmlnode.xml;
	
	else return null;
}



/*******************************************************************/
// Build and return the first few lines of Route File XML
// Its output is a matched set with the output of buildRouteFileEnd().
// Inputs:
//  cmd -- Command such as "LOAD_ROUTES", "SAVE_ROUTES", etc.  
//         A request and its corresponding response will matching command values. 
//  msgtype -- Whether request or response.  Use constants MSG_REQUEST and MSG_RESPONSE.
/********************************************************************/
function buildRouteFileStart (cmd, msgtype) {
	return (XMLDECL + '<routefile cmd="' + cmd + '" + type="' + msgtype + '" version="' + ROUTEFILEVERSION + '">\n');
}


/*******************************************************************/
// Build and return the last lines of Route File XML.   
// Its output is a matched set with the output of buildRouteFileStart().
/********************************************************************/
function buildRouteFileEnd () {
	return ("</routefile>");
}



/*******************************************************************
// XMLFieldInfo Object mapping memory model fields (members) to 
// XML attributes.
********************************************************************/
// Constructor sets up hash maps (arrays)
function XmlFieldInfo () {
	this.pnlfields = new Array ();      // or new Object() -- doesn't matter
	this.rtefields = new Array ();
	this.segfields = new Array ();
	this.pntfields = new Array ();
	/* this.rte_load_routes_rsp = ["bzid", "name", "desc", "bikeway", "creator", "editdate", 
				"len", "minlat", "maxlat", "minlng", "maxlng",
				"minlatcode", "maxlatcode", "minlngcode", "maxlngcode", "zoom"];
	this.rte_save_routes_req = ["id", "name", "desc", "bikeway", 
				"len", "minlat", "maxlat", "minlng", "maxlng",
				"minlatcode", "maxlatcode", "minlngcode", "maxlngcode", "zoom"];    */
	// this.notfields = new Array ();     // too difficult to softcode everything

	// Note: xmlel is currently not used.  Users of these arrays 
	// hardcode tag names (like <route>) instead of looking them up here.
	
	// Panel fields -- dl and ul are ignored; they are included for completeness
	this.pnlfields ["latcode"] = {me:"latcode", xmlattr:"latcode"};
	this.pnlfields ["lngcode"] = {me:"lngcode", xmlattr:"lngcode"};
	this.pnlfields ["zoom"] = {me:"zoom", xmlattr:"z"};

	// Route fields -- 
	// Isn't there a way to extract the key from an array element???  
	// That would avoid having to have the "me" member.
	// Try: for x in rtefields {key = x; value = rtefields[x];}
	this.rtefields ["id"] = {me:"id", xmlattr:"id"};        // ID field names MUST be same for rte, seg and pnt fields
	this.rtefields ["bzid"] = {me:"bzid", xmlattr:"bz"};    // ID field names MUST be same for rte, seg and pnt fields
	this.rtefields ["name"] = {me:"name", xmlattr:"nm"};
	this.rtefields ["desc"] = {me:"desc", xmlattr:"de"};
	// this.rtefields ["bikeway"] = {me:"bikeway", xmlattr:"bw"};        // removed because mm field removed
	this.rtefields ["creator"] = {me:"creator", xmlattr:"cr"};
	// this.rtefields ["createdate"] = {me:"createdate", xmlattr:"cd"};    // ADD THIS WHEN READY
	this.rtefields ["editdate"] = {me:"editdate", xmlattr:"ed"};
	this.rtefields ["len"] = {me:"len", xmlattr:"ln"};
	this.rtefields ["minlat"] = {me:"minlat", xmlattr:"s"};
	this.rtefields ["maxlat"] = {me:"maxlat", xmlattr:"n"};
	this.rtefields ["minlng"] = {me:"minlng", xmlattr:"w"};
	this.rtefields ["maxlng"] = {me:"maxlng", xmlattr:"e"};
	this.rtefields ["minlatcode"] = {me:"minlatcode", xmlattr:"sc"};
	this.rtefields ["maxlatcode"] = {me:"maxlatcode", xmlattr:"nc"};
	this.rtefields ["minlngcode"] = {me:"minlngcode", xmlattr: "wc"};
	this.rtefields ["maxlngcode"] = {me:"maxlngcode", xmlattr: "ec"};
	this.rtefields ["zoom"] = {me:"zoom", xmlattr:"z"};

	// Segment fields
	this.segfields ["id"] = {me:"id", xmlattr:"id"};           // ID field names MUST be same for rte, seg and pnt fields
	this.segfields ["bzid"] = {me:"bzid", xmlattr:"bz"};       // ID field names MUST be same for rte, seg and pnt fields
	this.segfields ["name"] = {me:"name", xmlattr:"nm"};
	this.segfields ["desc"] = {me:"desc", xmlattr:"de"};
	this.segfields ["bikeway"] = {me:"bikeway", xmlattr:"bw"};
	this.segfields ["roadtype"] = {me:"roadtype", xmlattr:"rt"};
	this.segfields ["sidewalk"] = {me:"sidewalk", xmlattr:"sw"};
	// this.segfields ["hide"] = {me:"hide", xmlattr:"hi"};    // ADD THIS WHEN READY	
	this.segfields ["skill"] = {me:"skill", xmlattr:"sk"};
	this.segfields ["speed"] = {me:"speed", xmlattr:"sp"};
	this.segfields ["numlanes"] = {me:"numlanes", xmlattr:"nl"};
	this.segfields ["dir"] = {me:"dir", xmlattr:"di"};
	this.segfields ["len"] = {me:"len", xmlattr:"ln"};
	this.segfields ["minlat"] = {me:"minlat", xmlattr:"s"};
	this.segfields ["maxlat"] = {me:"maxlat", xmlattr:"n"};
	this.segfields ["minlng"] = {me:"minlng", xmlattr:"w"};
	this.segfields ["maxlng"] = {me:"maxlng", xmlattr:"e"};
	this.segfields ["zoom"] = {me:"zoom", xmlattr:"z"};

	// Point fields
	this.pntfields ["id"] = {me:"id", xmlattr:"id"};           // ID field names must be same for rte, seg and pnt fields
	this.pntfields ["bzid"] = {me:"bzid", xmlattr:"bz"};       // ID field names must be same for rte, seg and pnt fields
	this.pntfields ["lat"] = {me:"lat", xmlattr:"lt"};
	this.pntfields ["lng"] = {me:"lng", xmlattr:"lg"};
	this.pntfields ["intid"] = {me:"intid", xmlattr:"in"};
	this.pntfields ["heading"] = {me:"heading", xmlattr:"hd"};
	
	// this.notfields.push ({me:"notecat", xmlattr:"nc"});
	// this.ntefields.push ({rt:PNT, me:"pntnote", xmlattr:null});
}


/*******************************************************************
// Convert memory model field name to XML attribute name.
// Inputs: 
//   membername -- name of member in the appropriate memory model object.
//   rectype -- RTE, SEG or PNT indicating record type (object type).
********************************************************************/
XmlFieldInfo.prototype.getXmlName = function (rectype, membername) {
	if (rectype == RTE)
		return XmlFldInfo.rtefields[membername].xmlattr;
	else if (rectype == SEG)
		return XmlFldInfo.segfields[membername].xmlattr;
	else if (rectype == PNT)
		return XmlFldInfo.pntfields[membername].xmlattr;
	else
		return null;
}


/*******************************************************************
// Convert XML attribute name to memory model field name.
********************************************************************/
XmlFieldInfo.prototype.getMemberName = function (rectype, xmlname) {
    // TBD
}
