﻿/**
 * Copyright (c) 2008, Culnou. All rights reserved.
 * http://www.culnou.com/license.txt
 * version: 0.0.1
 * 
 * 	utility class.
 * 
 * @author tsakura
 */
{
	
	/**
	 * A singleton class that implements cross browser helper
	 * methods.
	 *
	 * @class
	 * <p>
	 *		This is a global functionality. To access the functions in this
	 * 		class, use the global classname appended by the functionname.
	 *		For example, the following code displays an error message:
	 * </p>
	 * <pre>
	 * Utils.error('Browser is not supported!', 200, false);</pre>
	 */
	var Utils = new function() {
	
		/**
		 * Defines the image used for error dialogs.
		 */
		this.errorImage = 'mumu_fw/template/image/gif/error.gif';
		
		/**
		 * Evaluates the given expression using eval. Supports
		 * evaluation of expressions that define functions and
		 * returns the function object for these expressions.
		 */
		this.eval = function(expr) {
			var result = null;
			if (expr.indexOf('function') >= 0 && ClientWrapper.isIE()) {
				eval('var f='+expr);
				result = f;
			} else {
				result = eval(expr);
			}
			return result;
		};
		
		/**
		 * Evaluates the xpath expression and returns the first
		 * node of the result.
		 */
		this.selectSingleNode = function(doc, xpath) {
			if (ClientWrapper.isIE()) {
				return doc.selectSingleNode(xpath);
			} else {
				var result = doc.evaluate(xpath,
					doc, null, XPathResult.ANY_TYPE, null);
				return result.iterateNext();
			}
		};
		
		/**
		 * Returns the name of the specified function.
		 */
		this.getFunctionName = function(f) {
			var str = null;
			if (f != null) {
				if (navigator.appName == 'Netscape') {
					str = f.name;
				} else {
					var tmp = f.toString();
					var idx1 = 9;
					while (tmp.charAt(idx1) == ' ') {
						idx1++;
					}
					var idx2 = tmp.indexOf('(', idx1);
					str = tmp.substring(idx1, idx2);
				}
			}
			return str;
		};
	
		/**
		 * Returns the index of obj in array or -1 if the
		 * array does not contain obj.
		 */
		this.indexOf = function(array, obj) {
			if (array != null && obj != null) {
				for (var i=0; i<array.length; i++) {
					if (array[i] == obj) {
						return i;
					}
				}
			}
			return -1;
		};

		/**
		 * Returns the child nodes of node that match the
		 * specified nodeType as an array.
		 */
		this.getChildNodes = function(node, nodeType) {
			nodeType = nodeType || 1;
			var children = new Array();
			var tmp = node.firstChild;
			while (tmp != null) {
				if (tmp.nodeType == nodeType) {
					children.push(tmp);
				}
				tmp = tmp.nextSibling;
			}
			return children;
		};

		/**
		 * Returns the child nodes of node that match the
		 * specified tag name as an array.
		 */
		this.getChildNodesByTagName = function(node, tagName, out) {
			if (!out) {
				out = new Array();
			}
			for (var i=0; i<node.childNodes.length; i++) {
				var tmp = node.childNodes[i];
				if (tmp != null) {
					if (tmp.tagName == tagName) {
						out.push(tmp);
					} else {
						if (tmp.childNodes.length > 0) {
							this.getChildNodesByTagName(tmp, tagName, out);
						}
					}
				}
			}
			return out;
		};

		/**
		 * Creates a new empty XML document instance.
		 * @return A new XML document
		 */
		this.createXMLDocument = function() {
			var doc = null;
			if (document.implementation && document.implementation.createDocument) {
				doc = document.implementation.createDocument("", "", null);
			} else if (window.ActiveXObject) {
				doc = new ActiveXObject("Microsoft.XMLDOM");
		 	}
		 	return doc;
		};
	
		/**
		 * Parses the specified XML string.
		 * @return A new XML document representing
		 * the XML string. 
		 */
		this.parseXML = function(xml) {
			var result = null;
			if (ClientWrapper.isIE()) {
				result = Utils.createXMLDocument();
				result.async="false";
				result.loadXML(xml)
			} else {
				var parser = new DOMParser();
				result = parser.parseFromString(xml, "text/xml");
			}
			return result;
		};
	
		/**
		 * Returns the XML content of the specified node.
		 */
		this.getXML = function(node) {
			var xml = '';
			if (node != null) {
				xml  = node.xml;
				if (xml == null) {
					var xmlSerializer = new XMLSerializer();
					xml = xmlSerializer.serializeToString(node);
				}
			}
			return xml;
		};
	
		/**
		 * Returns the text content of the specified node.
		 */
		this.getTextContent = function(node) {
			if (node != null) {
				if (ClientWrapper.isIE()) {
					if (node.firstChild != null) {
						return node.firstChild.nodeValue;
					}
				} else {
					if (node.textContent != null) {
						return node.textContent;
					}
				}
			}
			return '';
		};
	
		/**
		 * Writes the specified string into the parent DOM
		 * element using document.createTextNode.
		 */
		this.write = function(parent, string) {
			var text = document.createTextNode(string);
			if (parent != null) {
				parent.appendChild(text);
			}
			return text;
		};
		
		/**
		 * Writes the specified string into the parent DOM
		 * element using document.createTextNode and adds
		 * a linefeed.
		 */
		this.writeln = function(parent, string) {
			var text = document.createTextNode(string);
			if (parent != null) {
				parent.appendChild(text);
				parent.appendChild(
					document.createElement('br'));
			}
			return text;
		};
		
		/**
		 * Adds a linefeed to the specified parent DOM
		 * element.
		 */
		this.br = function(parent) {
			var br = document.createElement('br');
			if (parent != null) {
				parent.appendChild(br);
			}
			return br;
		};
		
		/**
		 * Adds a new paragraph to the specified parent
		 * and uses the specified text as the paragraph
		 * content.
		 */
		this.para = function(parent, text) {
			var p = document.createElement('p');
			Utils.write(p, text);
			if (parent != null) {
				parent.appendChild(p);
			}
			return p;
		};
	
		/**
		 * Adds a hyperlink to the specified parent that invokes
		 * action on the specified editor.
		 */
		this.linkAction = function(parent, text, editor, action, pad) {
			var a = Utils.link(parent, text, function() {
				editor.execute(action)
			}, pad);
			Utils.br(parent);
			return a;
		};
	
		/**
		 * Adds a hyperlink to the specified parent that invokes the
		 * specified function on the editor passing along the
		 * specified argument.
		 */
		this.linkInvoke = function(parent, text, editor, functName, arg, pad) {
			var a = Utils.link(parent, text, function() {
				editor[functName](arg)
			}, pad);
			Utils.br(parent);
			return a;
		};
		
		/**
		 * Adds a hyperlin to the specified parent that invokes the
		 * specified function when clicked.
		 */
		this.link = function(parent, text, funct, pad) {
			var a = document.createElement('span');
			a.style.color = 'blue';
			a.style.textDecoration = 'underline';
			a.style.cursor = 'pointer';
			if (pad != null) {
				a.style.paddingLeft = pad+'px';
			}
			EventUtility.addListener(a, 'click', funct);
			Utils.write(a, text);
			if (parent != null) {
				parent.appendChild(a);
			}
			return a;
		};
	
		/**
		 * Uses a browser file dialog to save the specified content
		 * on the local filesystem. Note: This does not work on
		 * all supported browsers!
		 */
		this.save = function(filename, content) {
			// Requests required privileges in Firefox
			if (navigator.appName == 'Netscape') {
				try {
					netscape.security.PrivilegeManager.enablePrivilege('UniversalFileAccess');
				} catch (err) {
					alert('Access denied: '+err);
					return
				}
			}
			// Uses a hidden iframe to create a new
			// document containing the content
			try {
				var iframe = document.createElement('iframe');
				iframe.setAttribute('src', '');
				iframe.setAttribute('id', 'UtilsSaveFrame');
				iframe.setAttribute('visibility', 'hidden');
				iframe.style.display = 'none';
				document.body.appendChild(iframe);
				var doc = iframe.contentDocument;
				if (doc == undefined || doc == null) {
					doc = iframe.contentWindow.document;
				}
				doc.open('dummy.xml');
				doc.write(content);
				doc.close();
				doc.execCommand('SaveAs', false, filename);
				document.body.removeChild(iframe);
			} catch (err) {
				alert(err);
				alert('Data not written, showing in window');
				Utils.popup(content);
			}
		};
		
		/**
		 * Shows the specified content in a new browser window.
		 */
		this.popup = function(content) {
		    var wnd = window.open();
		    var node = wnd.document.createTextNode(content);
		   	wnd.document.body.appendChild(node);
		};
		
		/**
		 * Copies the specified content to the local clipboard.
		 */
		this.copy = function(content) {
		 	if (window.clipboardData) {
				window.clipboardData.setData("Text", content);
			} else if (window.netscape) {
				netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
	
				var clip = Components.classes['@mozilla.org/widget/clipboard;1']
					.createInstance(Components.interfaces.nsIClipboard);
				if (!clip) {
					return;
				}
			   
				var trans = Components.classes['@mozilla.org/widget/transferable;1']
			    	.createInstance(Components.interfaces.nsITransferable);
				if (!trans) {
					return;
				}
			   
				trans.addDataFlavor('text/unicode');
				var str = new Object();
				var len = new Object();
				var str = Components.classes["@mozilla.org/supports-string;1"]
					.createInstance(Components.interfaces.nsISupportsString);
	
				var copytext=content;
				str.data=copytext;
				trans.setTransferData("text/unicode",str,copytext.length*2);
				var clipid=Components.interfaces.nsIClipboard;
				if (!clip) {
					return false;
				}
				clip.setData(trans,null,clipid.kGlobalClipboard);
			} 
		};
	
		/**
		 * Loads the specified URL synchronously and returns the
		 * xml request.
		 */
		this.load = function(url) {
			var req = new XMLRequest(url, null, 'GET', false);
			req.send();
			return req;
		};
	
		/**
		 * Loads the specified URL asynchronously and invokes one
		 * of the passed-in functions depending on the request
		 * status.
		 */
		this.get = function(url, onload, onerror) {
			new XMLRequest(url, null, 'GET').send(onload, onerror);
		};
		
		/**
		 * Posts the specified params to url and invokes the specified
		 * functions to process the server response, depending on the
		 * status of the request.
		 */
		this.post = function(url, params, onload, onerror) {
			new XMLRequest(url, params).send(onload, onerror);
		};
	
		/**
		 * Loads the specified URL asynchronously into the specified
		 * document, invoking onload after the document has been
		 * loaded. Note: This implementation does not use the
		 * XMLRequest, but the document.load method.
		 */
		this.loadInto = function(url, doc, onload) {
			if (ClientWrapper.isIE()) {
				doc.onreadystatechange = function () {
					// TODO: How can we get IE to throw an
					// exception if the file is not found
					// in sync with the caller's flow
					if (doc.readyState == 4) onload()
				};
			} else {
				doc.addEventListener("load", onload, false);
				// TODO: How can we get FF to throw an
				// exception if a remote file is not found
			}
			doc.load(url);
		};
	
		/**
		 * Clones the specified obj, ignoring all variablenames in
		 * transients (an array of strings).
		 */
		this.clone = function(obj, transients) {
			var clone = null;
			if (obj != null && typeof(obj.constructor) == 'function') {
				clone = new obj.constructor();
			    for (var i in obj) {
			    	if (transients == null ||
			    		Utils.indexOf(transients, i) < 0)
			    	{
				    	if (typeof(obj[i]) == 'object') {
				            clone[i] = Utils.clone(obj[i]);
				        } else {
				            clone[i] = obj[i];
				        }
					}
			    }
			}
		    return clone;
		};
		
		/**
		 * Compares two rectangles and returns true, if
		 * they are equal, that is, if x, y, width and
		 * height are equal.
		 */
		this.equals = function(a, b) {
			return b != null &&
				   ((a.x == null || a.x == b.x) &&
				   (a.y == null || a.y == b.y) &&
				   (a.width == null || a.width == b.width) &&
				   (a.height == null || a.height == b.height));
		};
	
		/**
		 * Returns a textual representation of the
		 * specified object.
		 */
		this.toString = function(obj) {
		    var output = '';
		    for (var i in obj) {
			    if (obj[i] == null) {
		            output += i + ' = [null]\n';
			    } else if (typeof(obj[i]) == 'function') { // && obj[i]._visited == null) {
		            output += i + ' => [Function]\n'; // + Utils.toString(obj[i]) + '\n';
		        } else if (typeof(obj[i]) == 'object') { // && obj[i]._visited == null) {
			        //obj[i]._visited = true;
		            output += i + ' => [Object]\n'; // + Utils.toString(obj[i]) + '\n';
			        //obj[i]._visited = null;
		        } else {
		            output += i + ' = ' + obj[i] + '\n';
		        }
		    }
		    return output;
		};
	
		/**
		 * Returns true if the specified point (x, y) is
		 * contained in the specified bounds.
		 */
		this.contains = function(bounds, x, y) {
			return (bounds.x <= x &&
					bounds.x + bounds.width >= x &&
					bounds.y <= y &&
					bounds.y + bounds.height >= y);
		};
	
		/**
		 * Returns true if the two rectangles intersect.
		 */
		this.intersects = function(first, second) {
			return Utils.contains(first,
				second.x, second.y) ||
				Utils.contains(first,
				second.x+second.width,
				second.y+second.height);
		};
	
		/**
		 * Returns the offset for the specified container as
		 * a point with x and y coordinates.
		 */
		this.getOffset = function(container) {
			// TODO: Take scrollbar into account
			var offsetLeft = 0;
			var offsetTop = 0;
			if (container.offsetParent)
			{
				while (container.offsetParent)
				{
					offsetLeft += container.offsetLeft
					offsetTop += container.offsetTop
					container = container.offsetParent;
				}
			}
			return {x: offsetLeft, y: offsetTop};
		};
	
		/**
		 * Converts the specified point (x, y) to the offset
		 * of the specified container and returns a new point
		 * with x and y coordinates.
		 */
		this.convertPoint = function(container, x, y) {
			var offset = Utils.getOffset(container);
			offset.x -= document.body.scrollLeft;
			offset.y -= document.body.scrollTop;
			return {x: x-offset.x, y: y-offset.y};
		};
		
		/**
		 * Creates a new point object using the specified
		 * coordinate pair as the x and y variable.
		 */
		this.createPoint = function(x, y) {
			return {x: x, y: y};
		};
	
		/**
		 * Creates a new rectangle object using the specified
		 * fields.
		 */
		this.createBounds = function(x, y, width, height) {
			var bounds = Utils.createPoint(x, y);
			bounds.width = width;
			bounds.height = height;
			return bounds;
		};
		
		/**
		 * Returns true if str is numeric, that is, if
		 * it only contains numeric characters or a
		 * decimal point.
		 */
		this.isNumeric = function(str) {
			if (str == null ||
				typeof(str) == 'object' ||
				typeof(str) == 'function')
			{
				return false;
			}
			var validChars = '0123456789.';
			var c = null;
			for (i = 0; i < str.length; i++) {
				c = str.charAt(i);
				if (validChars.indexOf(c) == -1) {
					return false;
				}
			}
			return true;
		};
		
		//@@ added by tsakura
		/**
		 * Returns true if str is numeric or hyphen.
		 */
		this.isNumericOrHyphen = function(str) {
			if (str == null ||
				typeof(str) == 'object' ||
				typeof(str) == 'function')
			{
				return false;
			}
			var validChars = '0123456789-.';
			var c = null;
			for (i = 0; i < str.length; i++) {
				c = str.charAt(i);
				if (validChars.indexOf(c) == -1) {
					return false;
				}
			}
			return true;
		};
	
		/**
		 * Returns the intersection of two lines as
		 * a point.
		 */
		this.intersection = function (x0, y0, x1, y1, x2, y2, x3, y3) {
			// m = delta y / delta x, the slope of a line
			// b = y - mx, the axis intercept
			var m1 = (y1 - y0) / (x1 - x0);
			var b1 = y0 - m1 * x0;
			var m2 = (y3 - y2) / (x3 - x2);
			var b2 = y2 - m2 * x2;
			var x = (b1 - b2) / (m2 - m1);
			var y = m1 * x + b1;
			return Utils.createPoint(x, y);
		};
		
		/**
		 * Asynchronous animated move operation.
		 */
		this.morph = function(graph, cells, dx, dy, step, delay) {
			step = step || 30;
			delay = delay || 30;
			var current = 0;
			var f = function() {
			    current = Math.min(100, current+step);
		    	mxLog.debug('step: '+current);
			    for (var i=0; i<cells.length; i++) {
			    	if (!graph.model.isEdge(!cells[i])) {
						var state = graph.view.getState(cells[i]);
					    state.x += step * dx / 100;
					    state.y += step * dy / 100;
					    graph.cellRenderer.redraw(state);
					}
				}
				if (current < 100) {
					window.setTimeout(f, delay);
				} else {
					graph.move(cells, dx, dy);
				}
			};
			window.setTimeout(f, delay);
		};
		
		/**
		 * Asynchronous fade-in operation.
		 */
		this.fadeIn = function(node, to, step, delay) {
			to = (to != null) ? to : 100;
			step = step || 40;
			delay = delay || 30;
			var opacity = 0;
			Utils.setOpacity(node, opacity);
			node.style.display = 'inline';
			var f = function() {
			    opacity = Math.min(opacity+step, to);
				Utils.setOpacity(node, opacity);
				if (opacity < to) {
					window.setTimeout(f, delay);
				}
			};
			window.setTimeout(f, delay);
		};
		
		/**
		 * Asynchronous fade-out operation.
		 */
		this.fadeOut = function(node, from, remove, step, delay) {
			step = step || 40;
			delay = delay || 30;
			var opacity = from || 100;
			Utils.setOpacity(node, opacity);
			var f = function() {
			    opacity = Math.max(opacity-step, 0);
				Utils.setOpacity(node, opacity);
				if (opacity > 0) {
					window.setTimeout(f, delay);
				} else {
					node.style.display = 'none';
					if (remove && node.parentNode) {
						node.parentNode.removeChild(node);
					}
				}
			};
			window.setTimeout(f, delay);
		};
		
		/**
		 * Sets the opacity of the specified DOM node to
		 * value (%).
		 */
		this.setOpacity = function(node, value) {
		    if (ClientWrapper.isIE()) {
			    node.style.filter = "alpha(opacity=" + value + ")";
			} else {
			    node.style.opacity = (value / 100);
			}
		};
		
		/**
		 * Returns just the stylename in a style definition of a
		 * cell.
		 */
		this.getStylename = function(style) {
			var pairs = style.split(';');
			var stylename = pairs[0];
			if (stylename.indexOf('=') < 0) {
				return stylename;
			}
		};
		
		/**
		 * Sets the stylekey to value on the specified cells using
		 * the model's setStyle method.
		 */
		this.setCellStyles = function(model, cells, key, value) {
			if (cells != null && cells.length > 0) {
				model.beginUpdate();
				for (var i=0; i<cells.length; i++) {
					var style = Utils.setStyle(cells[i].getStyle(), key, value);
					model.setStyle(cells[i], style);
				}
				model.endUpdate();
			}
		};
		
		/**
		 * Sets the specified stylekey to value in style and returns
		 * the updates style string.
		 */
		this.setStyle = function(style, key, value) {
			if (style == null || style.length == 0) {
				return key+'='+value;
			} else {
				var index = style.indexOf(key+'=');
				if (index < 0) {
					var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
					return style + sep + key+'='+value;
				} else {
					var cont = style.indexOf(';', index);
					if (cont < 0) {
						return style.substring(0, index) +
							key + '=' + value;
					} else {
						return style.substring(0, index) +
							key + '=' + value +
							style.substring(cont);
					}
				}
			}
		};
		
		/**
		 * Displays the specified error message in a PopupWindow.
		 */
		this.error = function(message, width, close, icon) {
			var div = document.createElement('div');
			div.style.padding = '20px';
			var img = document.createElement('img');
			img.setAttribute('src', icon || Utils.errorImage);
			img.setAttribute('valign', 'bottom');
			img.style.verticalAlign = 'middle';
			div.appendChild(img);
			div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
			div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
			div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
			Utils.write(div, message);
			var w = document.body.clientWidth;
			var h = document.body.clientHeight;
			var warn = new PopupWindow('Graph Error', div,
				(w-width)/2, h/4, width, null, false, true);
			if (close) {
				warn.setCloseAction(function() {
					warn.destroy();
				});
			}
			warn.setVisible(true);
		};
	
		//@@ added by tsakura
		this.sleep = function(time) {
			var d1 = new Date().getTime();
			var d2 = new Date().getTime();
			while( d2 < d1 + time ){
				d2=new Date().getTime();
			}
			return;
		};

		this.replace2ByteHyphen = function(value) {
			var tmpValue = value.replace(/－/g, '-');
			var tmpValue2 = tmpValue.replace(/ー/g, '-');
			var tmpValue3 = tmpValue2.replace(/―/g, '-');
			var tmpValue4 = tmpValue3.replace(/‐/g, '-');
			var tmpValue5 = tmpValue4.replace(/-/g, '-');

			return tmpValue5;
		};

		this.immigrateTextNode = function(tag, value) {
			if (tag.firstChild) {
				tag.removeChild(tag.firstChild);
			}
			var text = document.createTextNode(value);
			tag.appendChild(text);
		};

		/**
		 * 
		 * @xmlpath : xml file path after host name (ex: /webcontents/pages/ones/header.xml)
		 * @parent : parent html node
		 */
		this.appendXmlNode = function(xmlpath, parent) {

			if ((xmlpath == null) || (xmlpath == "")) {
				return;
			}
			if (parent == null) {
				return;
			}

			var xmlUrl = "http://" + location.host + xmlpath;
			var rootXmlnode = Utils.load(xmlUrl).getXML().documentElement;

			var createHtmlNode = function(xmlNode, parent) {

				if (xmlNode.nodeType == 1) {
					var htmlNode = document.createElement(xmlNode.nodeName);
					parent.appendChild(htmlNode);
					if ((xmlNode.attributes != null) && (xmlNode.attributes.length > 0)) {
						for (var i=0; i<xmlNode.attributes.length; i++) {
							if ((xmlNode.attributes[i].nodeName == "class") || (xmlNode.attributes[i].nodeName == "className")) {
			    			if (ClientWrapper.isIE()) {
									htmlNode.setAttribute("className", xmlNode.attributes[i].nodeValue);
								} else {
									htmlNode.setAttribute(xmlNode.attributes[i].nodeName, xmlNode.attributes[i].nodeValue);
								}
							} else {
								htmlNode.setAttribute(xmlNode.attributes[i].nodeName, xmlNode.attributes[i].nodeValue);
							}

						}
					}
					if (xmlNode.childNodes.length > 0) {
						for (var i=0; i<xmlNode.childNodes.length; i++) {
							createHtmlNode(xmlNode.childNodes[i], htmlNode);
						}
					}
				} else if(xmlNode.nodeType == 3) {
					var htmlNode = document.createTextNode(xmlNode.nodeValue);
					parent.appendChild(htmlNode);
				} else if(xmlNode.nodeType == 4) {
					var htmlNode = document.createTextNode(xmlNode.data);
					parent.appendChild(htmlNode);
				}
			}
			createHtmlNode(rootXmlnode, parent);
		};
	}

}

