// ---------------------------
// SsAutocomplete
// ---------------------------

(function() {

var SsAutocomplete = window.SsAutocomplete = function(fieldId, options) {

	var _hasInit = false; // true if init() has already processed
	var _options = this.setDefaultOptions(options);
	var _completeFieldId = fieldId; // element ID of search text box
	var _completeField = null; // element object of search text box
	var _completeTable = null; // element object of suggestion box
	var _completeTablePos = null; // JS object for additional data
	var _acTimerId = null; // timer ID of Autocomplete request timer
//	var _keyTimerId = null; // timer ID of Key stroke timer for Japanese (NO USE)

	// ---------------
	// Accessor
	// ---------------
	this.hasInit = function() {
		return _hasInit;
	}

	this.setHasInit = function(hasInit) {
		_hasInit = hasInit;
	}

	this.getOptions = function() {
		return _options;
	}

	this.getCompleteFieldId = function() {
		return _completeFieldId;
	}

	this.getCompleteField = function() {
		return _completeField;
	}

	this.setCompleteField = function(completeField) {
		_completeField = completeField;
	}

	this.getCompleteTable = function() {
		return _completeTable;
	}

	this.setCompleteTable = function(completeTable) {
		_completeTable = completeTable;
	}

	this.getCompleteTablePos = function() {
		return _completeTablePos;
	}

	this.setCompleteTablePos = function(completeTablePos) {
		_completeTablePos = completeTablePos;
	}

	this.getAcTimerId = function() {
		return _acTimerId;
	}

	this.setAcTimerId = function(acTimerId) {
		_acTimerId = acTimerId;
	}

//	this.getKeyTimerId = function() {
//		return _keyTimerId;
//	}
//
//	this.setKeyTimerId = function(keyTimerId) {
//		_keyTimerId = keyTimerId;
//	}

	if (_options.autoInit) {
		this.install();
	}
};

SsAutocomplete.INSTANCES = {};
SsAutocomplete.IS_IE = window.ActiveXObject != null;
SsAutocomplete.OPTIONS = null;
SsAutocomplete.DEFAULT_OPTIONS = {
	servletPath:     null,
	urlRegex:        null,
	autoInit:        false,
	color:           "#000000",
	bgcolor:         "#FFFFFF",
	selectedColor:   "#FFFFFF",
	selectedBgcolor: "#4169E1",
	baseStyle:       null,
	rowStyle:        null,
	keywordStyle:    null,
	minBoxWidth:     0,
	minLength:       3,
	submitByMouseClick: true,
	delay:           400,
	data:            null,
	browserCache:    false,
	listWindowId:    null
};

SsAutocomplete.prototype = {

	setDefaultOptions: function(options) {
		var newOpts = {};

		// Copy default options
		SsAutocomplete.fn.copyProperties(newOpts, SsAutocomplete.DEFAULT_OPTIONS);

		// Copy global options
		if (SsAutocomplete.OPTIONS != null) {
			SsAutocomplete.fn.copyProperties(newOpts, SsAutocomplete.OPTIONS);
		}

		if (options == null) return newOpts;

		// Copy instance options
		for (var prop in newOpts) {
			var value = options[prop];
			if (value !== undefined) {
				newOpts[prop] = value;
			}
		}
		return newOpts;
	},

	install: function() {
		var options = this.getOptions();
		if (options.servletPath == null) return;
		if (options.urlRegex != null &&
				!options.urlRegex.test(location.href)) {
			return;
		}

		var _self = this;
		SsAutocomplete.fn.addEvent(window, "load", function() {
			_self.init();
		});
	},

	init: function() {
		if (this.hasInit()) return;
		this.setHasInit(true);

		var options = this.getOptions();
		if (options.data == null && options.servletPath == null) return;
		if (options.urlRegex != null && !options.urlRegex.test(location.href)) {
			return;
		}

		var fieldId = this.getCompleteFieldId();
		if (fieldId == null) return;
		var compField = document.getElementById(fieldId);
		if (compField == null) return;

		SsAutocomplete.INSTANCES[fieldId] = this;
		compField.setAttribute("autocomplete", "off");

		var _self = this;
		SsAutocomplete.fn.addEvent(window, "unload", function(e){_self.clearTable();});
		SsAutocomplete.fn.addEvent(compField, "keyup", SsAutocomplete.eh.keyup);
		SsAutocomplete.fn.addEvent(compField, "focus", SsAutocomplete.eh.focus);
		SsAutocomplete.fn.addEvent(compField, "blur", SsAutocomplete.eh.blur);

		var menu = document.getElementById("SsAutocomplete_popupMenu");
		var compTable = null;
		if (menu == null) {
			// Create menu tag (completeTable)
			// <div id="xxx_popupMenu">
			//   <table id="xx_completeTable" border="0" cellPadding="0" cellSpacing="0"></table>
			// </div>
			menu = document.createElement("div");
			menu.setAttribute("id", "SsAutocomplete_popupMenu");
			menu.style["position"] = "absolute";
			menu.style["zIndex"] = "5000";

			var menuTarget = null;
			if (options.listWindowId) menuTarget = document.getElementById(options.listWindowId);
			if (!menuTarget) menuTarget = document.getElementsByTagName("body")[0] || document.body;
			menuTarget.appendChild(menu);

			compTable = document.createElement("table");
			compTable.setAttribute("id", "SsAutocomplete_completeTable");
			compTable.setAttribute("border", "0");
			compTable.setAttribute("cellPadding", "0");
			compTable.setAttribute("cellSpacing", "0"); // Use capital 'S' for IE
			compTable.style["visibility"] = "hidden";
			menu.appendChild(compTable);
		}

		if (compTable == null) {
			compTable = document.getElementById("SsAutocomplete_completeTable");
			compTable.style["visibility"] = "hidden";
		}

		var boxWidth = compField.offsetWidth;
		if (options.minBoxWidth != null && options.minBoxWidth > 0) {
			boxWidth = Math.max(boxWidth, options.minBoxWidth);
		}

		var compTablePos = {
			width: boxWidth + "px",
			rowIndex: -1,
			currentKeyRow: null,
			currentMouseRow: null
		}

		// Set the objects into properties
		this.setCompleteField(compField);
		this.setCompleteTable(compTable);
		this.setCompleteTablePos(compTablePos);
	},

	clearTable: function() {
		this.clearAcTimer();

		var compTable = this.getCompleteTable();
		var compTblPos = this.getCompleteTablePos();
		if (!compTable || !compTblPos) return;

		compTable.style["visibility"] = "hidden";
		for (var loop = compTable.childNodes.length -1; loop >= 0 ; loop--) {
			var row = compTable.childNodes[loop];
			SsAutocomplete.fn.removeEvent(row, "mouseover", SsAutocomplete.eh.mouseover);
			SsAutocomplete.fn.removeEvent(row, "mouseout", SsAutocomplete.eh.mouseout);
			SsAutocomplete.fn.removeEvent(row, "mousedown", SsAutocomplete.eh.mousedown);
			compTable.removeChild(row);
			row = null;
		}

		compTblPos.rowIndex = -1;
		compTblPos.currentKeyRow = null;
		compTblPos.currentMouseRow = null;
	},

	clearAcTimer: function() {
		var timerId = this.getAcTimerId();
		if (timerId != null) {
			this.setAcTimerId(null);
			clearTimeout(timerId);
		}
	},

	moveCursor: function(dir) {
		var options = this.getOptions();
		var compField = this.getCompleteField();
		var compTable = this.getCompleteTable();
		var compTableLength = compTable.childNodes.length;
		if (compTableLength > 0 && compTable.firstChild.tagName != "TR") {
			compTable = compTable.firstChild;
			compTableLength = compTable.childNodes.length;
		}
		if (compTableLength <= 0) return;

		var compTablePos = this.getCompleteTablePos();
		var index = compTablePos.rowIndex;

		// Adjust compTable obj when already selected by mouse
		if (compTablePos.currentMouseRow != null) {
			var currentMouseRowText = SsAutocomplete.fn.getNodeText(compTablePos.currentMouseRow);
			for (var i=0; i < compTableLength; i++) {
				var ch = compTable.childNodes[i];
				var chText = SsAutocomplete.fn.getNodeText(ch);
				if (chText == currentMouseRowText) {
					index = i;
					compTablePos.currentMouseRow = null;
					break;
				}
			}
			compTablePos.currentMouseRow = null;
		}

		// Restore colors in previous cell
		if (index >= 0 && index < compTableLength) {
			var row = compTable.childNodes[index];
			row.style["color"] = options.color;
			row.style["backgroundColor"] = options.bgcolor;
		}

		index += dir;
		if (index < -1) index = compTableLength -1;
		if (index >= compTableLength) index = -1;
		compTablePos.rowIndex = index;

		// Disable key timer while moving cursor
		//if (index >= 0) {
		//	clearInterval(this.getKeyTimerId());
		//} else {
		//	var _self = this;
		//	var timerId = setInterval(function(){_self.autoComplete();}, 100);
		//	this.setKeyTimerId(timerId);
		//}

		// Select an item
		if (index == -1) {
			compField.value = compTablePos.originalKeyword;
			compTablePos.currentKeyRow = null;
		} else if (index >= 0) {
			var row = compTable.childNodes[index];
			row.style["color"] = options.selectedColor;
			row.style["backgroundColor"] = options.selectedBgcolor;
			compTablePos.currentKeyRow = row;

			// Set the selected keyword in text field
			for (var i=0; i < row.childNodes.length; i++) {
				var cell = row.childNodes[i];
				if (cell.tagName == "TD") {
					compField.value = SsAutocomplete.fn.getNodeText(cell);
					break;
				}
			}
		}

	},

	selectCurrent: function() {
		this.clearTable();
	},

	autoComplete: function() {
		var options = this.getOptions();
		var compField = this.getCompleteField();
		var compTablePos = this.getCompleteTablePos();

		if (compField.value.length < options.minLength) {
			compTablePos.previousKeyword = null;
			this.clearTable();
			return;
		}

		// Save the current keyword
		compTablePos.originalKeyword = SsAutocomplete.fn.trim(compField.value);

		// Make a request only if the keyword is different from previous one.
		if (compTablePos.previousKeyword == compTablePos.originalKeyword) return;
		compTablePos.previousKeyword = compTablePos.originalKeyword;

		// Get JSON data
		if (options.data == null) {
			var timerId = this.getAcTimerId();
			if (timerId == null) {
				if (options.delay != null && options.delay > 100) {
					// Call the request with a given delay
					var _self = this;
					timerId = setTimeout(function(){_self.autoComplete_request();}, options.delay);
					this.setAcTimerId(timerId);
				} else {
					// Call the request without delay
					this.autoComplete_request();
				}
			}
		} else {
			// Use JSON data given in the options
			var keywordLC = compTablePos.originalKeyword.toLowerCase();
			var suggestions = [];
			var length = options.data.length;
			for (var i=0; i<length; i++) {
				var suggestionLC = options.data[i].toLowerCase();
				if (suggestionLC.indexOf(keywordLC) >= 0) {
					suggestions.push(options.data[i]);
				}
			}
			this.clearTable();
			this.appendSuggestions(suggestions);
		}
	},

	autoComplete_request: function() {
		this.clearAcTimer();
		var options = this.getOptions();

		var servletPath = options.servletPath;
		if (servletPath == null) return;

		var params = {keyword: this.getCompleteField().value};
		if (!options.browserCache) {
			params["_ssac_t"] = new Date().getTime();
		}

		var _self = this;
		SsAutocomplete.fn.request({
			url: servletPath,
			params: params,
			load: function(req) {
				_self.parseMessages(req.responseText);
			},
			error: function(req) {
				//alert(req.responseText);
				_self.clearTable();
			}
		});
	},

	parseMessages: function(responseText) {
		this.clearTable();
		if (!responseText.length) return;

		var jsonObj;
		try {
			jsonObj = SsAutocomplete_json_parse(responseText);	 	
		} catch(e) {
			//var msg = "An error occured while parsing the JSON-formatted responseText:\n";
			//for (var prop in e) {
			//	msg += prop + ": " + e[prop] + "\n";
			//}
			//alert(msg+"\nresponseText="+responseText);
			return;
		}
		this.appendSuggestions(jsonObj);
	},

	appendSuggestions: function(jsonObj) {
		var options = this.getOptions();
		var length = jsonObj.length;
		if (length == 0) return;

		// Escape special chars of regex
		var orgKeyword = this.getCompleteTablePos().originalKeyword;
		if (orgKeyword != null && orgKeyword.indexOf("*") >= 0) {
			orgKeyword = orgKeyword.replace(/\*/g, "\\*");
		}
		if (orgKeyword != null && orgKeyword.indexOf("[") >= 0) {
			orgKeyword = orgKeyword.replace(/\[/g, "\\[");
		}

		for (var i=0; i<length; i++) {
			this.appendSuggestion(jsonObj[i], orgKeyword);
		}

		var compField = this.getCompleteField();
		var compFieldPos = SsAutocomplete.fn.findPos(compField);
		var compTable = this.getCompleteTable();
		var compTablePos = this.getCompleteTablePos();

		compTable.width = compTablePos.width;

		// apply base style into the table
		compTable.removeAttribute("style");
		SsAutocomplete.fn.copyProperties(compTable.style, {border: "1px solid #808080", color: options.color, backgroundColor: options.bgcolor});
		SsAutocomplete.fn.copyProperties(compTable.style, options.baseStyle);

		// Adjust menu position
		var menuTop = 0;
		var browserSize = SsAutocomplete.fn.getBrowserSize();
		var upperAreaHeight = compFieldPos.top - SsAutocomplete.fn.getScrollTop();
		var lowerAreaHeight = browserSize.height - (upperAreaHeight + compField.offsetHeight);

		if (lowerAreaHeight >= compTable.offsetHeight ||
		    upperAreaHeight < compTable.offsetHeight ||
		    lowerAreaHeight >= (upperAreaHeight-20)) { // adjust error (-20)
			menuTop = compFieldPos.top + compField.offsetHeight;
		} else {
			menuTop = compFieldPos.top - compTable.offsetHeight;
		}

		var menu = compTable.parentNode;
		menu.style["top"] = menuTop + "px";
		menu.style["left"] = compFieldPos.left + "px";

		compTable.style["visibility"] = "visible";
	},

	appendSuggestion: function(suggestion, orgKeyword) {
		var suggestKeyword = suggestion["keyword"];
		if (suggestKeyword == null) suggestKeyword = suggestion;

		var row, queryCell, infoCell;
		var options = this.getOptions();

		if (SsAutocomplete.IS_IE) {
			row = this.getCompleteTable().insertRow(this.getCompleteTable().rows.length);
			queryCell = row.insertCell(0);
			//infoCell = row.insertCell(1);
		} else {
			row = document.createElement("tr");
			this.getCompleteTable().appendChild(row);
			queryCell = document.createElement("td");
			row.appendChild(queryCell);
			//infoCell = document.createElement("td");
			//row.appendChild(infoCell);
		}
		SsAutocomplete.fn.copyProperties(row.style, {cursor: "pointer", color: options.color, backgroundColor: options.bgcolor});
		SsAutocomplete.fn.copyProperties(row.style, options.rowStyle);

		var _self = this;
		row.setAttribute("completeFieldId", this.getCompleteFieldId());
		row.setAttribute("suggestKeyword", suggestKeyword);
		SsAutocomplete.fn.addEvent(row, "mouseover", SsAutocomplete.eh.mouseover);
		SsAutocomplete.fn.addEvent(row, "mouseout", SsAutocomplete.eh.mouseout);
		SsAutocomplete.fn.addEvent(row, "mousedown", SsAutocomplete.eh.mousedown);

		// Bold matched parts
		var displaySuggestKeyword = orgKeyword;
		try {
			var regexKywd = new RegExp("("+displaySuggestKeyword+")", "gi");
			displaySuggestKeyword = suggestKeyword.replace(regexKywd, "<span style='font-weight:normal'>$1</span>");
		} catch (err) {}

		queryCell.innerHTML = displaySuggestKeyword;
		queryCell.setAttribute("title", suggestKeyword);
		queryCell.setAttribute("count", suggestion["count"]);

		SsAutocomplete.fn.copyProperties(queryCell.style, {padding: "3px 4px 3px 4px", textAlign: "left"});
		SsAutocomplete.fn.copyProperties(queryCell.style, options.keywordStyle);
		queryCell.style["fontWeight"] = "bold";

	 	//infoCell.appendChild(document.createTextNode(suggestion["info"]));
		//infoCell.appendChild(document.createTextNode(suggestion["count"]));
		//SsAutocomplete.fn.copyProperties(infoCell.style, {padding: "3px 4px 3px 4px", textAlign: "right"});
	},

	rowOn: function(e) {
		var cell = window.event ? window.event.srcElement : e.target;
		var row  = cell.parentNode;
		if (cell.nodeName == "SPAN") row = row.parentNode;
		var options = this.getOptions();

		// Clear row info for keyup & down
		var compTablePos = this.getCompleteTablePos();
		if (compTablePos.currentKeyRow != null) {
			compTablePos.currentKeyRow.style["backgroundColor"] = options.bgcolor;
			compTablePos.currentKeyRow.style["color"] = options.color;
			compTablePos.rowIndex = -1;
		}
		compTablePos.currentMouseRow = row;

		row.style["backgroundColor"] = options.selectedBgcolor;
		row.style["color"] = options.selectedColor;
	},

	rowOff: function(e) {
		var cell = window.event ? window.event.srcElement : e.target;
		var row  = cell.parentNode;
		if (cell.nodeName == "SPAN") row = row.parentNode;
		var options = this.getOptions();

		// Clear row info for keyup & down
		var compTablePos = this.getCompleteTablePos();
		compTablePos.rowIndex = -1;
		compTablePos.currentMouseRow = null;

		row.style["backgroundColor"] = options.bgcolor;
		row.style["color"] = options.color;
	},

	selectCurrentByMouse: function(newKeyword) {
		this.getCompleteField().value = newKeyword;
		this.selectCurrent();

		// submit the form
		var options = this.getOptions();
		if (options.submitByMouseClick) {
			var parent = this.getCompleteField();
			do {
				parent = parent.parentNode;
				if (parent.nodeType == 1 && parent.nodeName == "FORM") break;
			} while (parent != null);
			if (parent) parent.submit();
		}
	}
};

SsAutocomplete.eh = {

	////////////////////////////
	// Event Handers
	////////////////////////////

	keyup: function(e) {
		e = e || window.event;
		var thisElement = e.target || e.srcElement;
		var id = thisElement.getAttribute("id");
		var _self = SsAutocomplete.INSTANCES[id];

		switch(e.keyCode) {
			case 38: // up
				_self.moveCursor(-1);
				SsAutocomplete.fn.preventEvent(e);
				break;
			case 40: // down
				_self.moveCursor(1);
				SsAutocomplete.fn.preventEvent(e);
				break;
			//case 9:  // tab
			//case 13: // return
			//	_self.selectCurrent();
			//	SsAutocomplete.fn.preventEvent(e);
			//	break;
			default:
				_self.autoComplete();
				break;
		}
	},

	focus: function(e) {
		e = e || window.event;
		var thisElement = e.target || e.srcElement;
		var id = thisElement.getAttribute("id");
		var _self = SsAutocomplete.INSTANCES[id];

		var options = _self.getOptions();

		if (options.data != null &&
		    options.minLength != null && options.minLength <= 0) {
			_self.appendSuggestions(options.data);
		}

		// Check keyword every 100ms for non-ascii chars (e.g. Japanese)
		//var timerId = setInterval(function(){_self.autoComplete();}, 100);
		//_self.setKeyTimerId(timerId);
	},

	blur: function(e) {
		e = e || window.event;
		var thisElement = e.target || e.srcElement;
		var id = thisElement.getAttribute("id");
		var _self = SsAutocomplete.INSTANCES[id];

		//clearInterval(_self.getKeyTimerId());
		_self.clearTable();

		var compTblPos = _self.getCompleteTablePos();
		compTblPos.previousKeyword = null;
		compTblPos.originalKeyword = null;
	},

	mouseover: function(e) {
		e = e || window.event;
		var thisElement = e.target || e.srcElement;
		while (thisElement && thisElement.nodeName != "TR") thisElement = thisElement.parentNode;
		var id = thisElement.getAttribute("completeFieldId");
		var _self = SsAutocomplete.INSTANCES[id];

		_self.rowOn(e);
	},

	mouseout: function(e) {
		e = e || window.event;
		var thisElement = e.target || e.srcElement;
		while (thisElement && thisElement.nodeName != "TR") thisElement = thisElement.parentNode;
		var id = thisElement.getAttribute("completeFieldId");
		var _self = SsAutocomplete.INSTANCES[id];

		_self.rowOff(e);
	},

	mousedown: function(e) {
		e = e || window.event;
		var thisElement = e.target || e.srcElement;
		while (thisElement && thisElement.nodeName != "TR") thisElement = thisElement.parentNode;
		var id = thisElement.getAttribute("completeFieldId");
		var _self = SsAutocomplete.INSTANCES[id];

		var suggestKeyword = thisElement.getAttribute("suggestKeyword");
		_self.selectCurrentByMouse(suggestKeyword);
		SsAutocomplete.fn.preventEvent(e);
	}

};

SsAutocomplete.fn = {

	////////////////////////////
	// Common Utilities
	////////////////////////////

	findPos: function(element) {
		var curleft = 0;
		var curtop  = 0;

		if (element.offsetParent) {
			do {
				curleft += element.offsetLeft;
				curtop  += element.offsetTop;
			} while (element = element.offsetParent);
		}

		return 	{left:curleft, top:curtop};
	},

	copyProperties: function(src, des) {
		if (src == null || des == null) return;
		for (var prop in des) src[prop] = des[prop];
	},

	addEvent: (function() {
		if (typeof addEventListener != 'undefined') {
			return function (elem, eventType, callback) {
				return elem.addEventListener (eventType, callback, false);
			};
		}
		if (typeof attachEvent != 'undefined') {
			return function (elem, eventType, callback) {
				return elem.attachEvent ('on' + eventType, callback);
			};
		}
		return new Function;
	})(),

	removeEvent: (function() {
		if (typeof removeEventListener != 'undefined') {
			return function (elem, eventType, callback) {
				return elem.removeEventListener (eventType, callback, false);
			};
		}
		if (typeof detachEvent != 'undefined') {
			return function (elem, eventType, callback) {
				return elem.detachEvent ('on' + eventType, callback);
			};
		}
		return new Function;
	})(),

	preventEvent: function(e) {
		if (e.preventDefault) { e.preventDefault(); }
		else { e.returnValue = false; }
	},

	getNodeText: function(node) {
		if (node == null) return "";
		var buf = [];
		for (var i=0, chNodes=node.childNodes, len=chNodes.length; i<len; i++) {
			var ch = chNodes[i];
			if (ch.nodeType == 1) { // child element
				buf.push(SsAutocomplete.fn.getNodeText(ch));
			} else if (ch.nodeType == 3 || ch.nodeType == 4) { // text or CDATA
				buf.push(ch.nodeValue);
			}
		}
		return buf.join("");
	},

	trim: function(text) {
		return (text || "").replace(/^\s+|\s+$/g, "");
	},

	getScrollTop: function() {
		return document.documentElement.scrollTop || document.body.scrollTop;
	},

	getBrowserSize: function () {
		var size = {width:0, height:0};

		if (window.innerWidth) {
			size.width = window.innerWidth;
		} else if (document.documentElement && document.documentElement.clientWidth != 0) {
			size.width = document.documentElement.clientWidth;
		} else if (document.body) {
			size.width = document.body.clientWidth;
		}

		if (window.innerHeight) {
			size.height = window.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight != 0) {
			size.height = document.documentElement.clientHeight;
		} else if (document.body) {
			size.height = document.body.clientHeight;
		}

		return size;
	},

	////////////////////////////
	// Ajax Utilities
	////////////////////////////

	// var data = {
	//		url    : URL,
	//		params : {param1 : value1, param2 : value2 },
	//	};
	request: function(data) {
		if (data == null || data.url == null) return;

		var req = SsAutocomplete.fn._createXmlHttpReq();
		if (!req) return;

		req.onreadystatechange = function () {
			if (req.readyState == 4) {
				if (req.status === undefined || req.status == 0 || (req.status >= 200 && req.status < 300) || req.status == 1223) {
					if (data.load != null) data.load(req);
				} else {
					if (data.error != null) data.error(req);
				}
			}
		}

		var urlParams = [];
		if (data.params != null) {
			for (var key in data.params) {
				urlParams.push(key+"="+encodeURIComponent(data.params[key]));
			}
		}
		var queryStr = urlParams.join("&");

		req.open("GET", data.url+"?"+queryStr, true);
		req.send(null);
	},

	_createXmlHttpReq: function() {
		if(window.ActiveXObject) {
			try { return new ActiveXObject("Msxml2.XMLHTTP"); }
			catch (e) {
				try { return new ActiveXObject("Microsoft.XMLHTTP"); }
				catch (e2) {}
			}
		} else if (window.XMLHttpRequest) {
			try { return new XMLHttpRequest(); } catch (e) {}
		}
		return false;
	}
};

})();

// ---------------------------
//  http://www.JSON.org/json_parse.js
//  2009-05-31
// ---------------------------

var SsAutocomplete_json_parse = (function () {

// This is a function that can parse a JSON text, producing a JavaScript
// data structure. It is a simple, recursive descent parser. It does not use
// eval or regular expressions, so it can be used as a model for implementing
// a JSON parser in other languages.

// We are defining the function inside of another function to avoid creating
// global variables.

    var at,     // The index of the current character
        ch,     // The current character
        escapee = {
            '"':  '"',
            '\\': '\\',
            '/':  '/',
            b:    '\b',
            f:    '\f',
            n:    '\n',
            r:    '\r',
            t:    '\t'
        },
        text,

        error = function (m) {

// Call error when something is wrong.

            throw {
                name:    'SyntaxError',
                message: m,
                at:      at,
                text:    text
            };
        },

        next = function (c) {

// If a c parameter is provided, verify that it matches the current character.

            if (c && c !== ch) {
                error("Expected '" + c + "' instead of '" + ch + "'");
            }

// Get the next character. When there are no more characters,
// return the empty string.

            ch = text.charAt(at);
            at += 1;
            return ch;
        },

        number = function () {

// Parse a number value.

            var number,
                string = '';

            if (ch === '-') {
                string = '-';
                next('-');
            }
            while (ch >= '0' && ch <= '9') {
                string += ch;
                next();
            }
            if (ch === '.') {
                string += '.';
                while (next() && ch >= '0' && ch <= '9') {
                    string += ch;
                }
            }
            if (ch === 'e' || ch === 'E') {
                string += ch;
                next();
                if (ch === '-' || ch === '+') {
                    string += ch;
                    next();
                }
                while (ch >= '0' && ch <= '9') {
                    string += ch;
                    next();
                }
            }
            number = +string;
            if (isNaN(number)) {
                error("Bad number");
            } else {
                return number;
            }
        },

        string = function () {

// Parse a string value.

            var hex,
                i,
                string = '',
                uffff;

// When parsing for string values, we must look for " and \ characters.

            if (ch === '"') {
                while (next()) {
                    if (ch === '"') {
                        next();
                        return string;
                    } else if (ch === '\\') {
                        next();
                        if (ch === 'u') {
                            uffff = 0;
                            for (i = 0; i < 4; i += 1) {
                                hex = parseInt(next(), 16);
                                if (!isFinite(hex)) {
                                    break;
                                }
                                uffff = uffff * 16 + hex;
                            }
                            string += String.fromCharCode(uffff);
                        } else if (typeof escapee[ch] === 'string') {
                            string += escapee[ch];
                        } else {
                            break;
                        }
                    } else {
                        string += ch;
                    }
                }
            }
            error("Bad string");
        },

        white = function () {

// Skip whitespace.

            while (ch && ch <= ' ') {
                next();
            }
        },

        word = function () {

// true, false, or null.

            switch (ch) {
            case 't':
                next('t');
                next('r');
                next('u');
                next('e');
                return true;
            case 'f':
                next('f');
                next('a');
                next('l');
                next('s');
                next('e');
                return false;
            case 'n':
                next('n');
                next('u');
                next('l');
                next('l');
                return null;
            }
            error("Unexpected '" + ch + "'");
        },

        value,  // Place holder for the value function.

        array = function () {

// Parse an array value.

            var array = [];

            if (ch === '[') {
                next('[');
                white();
                if (ch === ']') {
                    next(']');
                    return array;   // empty array
                }
                while (ch) {
                    array.push(value());
                    white();
                    if (ch === ']') {
                        next(']');
                        return array;
                    }
                    next(',');
                    white();
                }
            }
            error("Bad array");
        },

        object = function () {

// Parse an object value.

            var key,
                object = {};

            if (ch === '{') {
                next('{');
                white();
                if (ch === '}') {
                    next('}');
                    return object;   // empty object
                }
                while (ch) {
                    key = string();
                    white();
                    next(':');
                    if (Object.hasOwnProperty.call(object, key)) {
                        error('Duplicate key "' + key + '"');
                    }
                    object[key] = value();
                    white();
                    if (ch === '}') {
                        next('}');
                        return object;
                    }
                    next(',');
                    white();
                }
            }
            error("Bad object");
        };

    value = function () {

// Parse a JSON value. It could be an object, an array, a string, a number,
// or a word.

        white();
        switch (ch) {
        case '{':
            return object();
        case '[':
            return array();
        case '"':
            return string();
        case '-':
            return number();
        default:
            return ch >= '0' && ch <= '9' ? number() : word();
        }
    };

// Return the json_parse function. It will have access to all of the above
// functions and variables.

    return function (source, reviver) {
        var result;

        text = source;
        at = 0;
        ch = ' ';
        result = value();
        white();
        if (ch) {
            error("Syntax error");
        }

// If there is a reviver function, we recursively walk the new structure,
// passing each name/value pair to the reviver function for possible
// transformation, starting with a temporary root object that holds the result
// in an empty key. If there is not a reviver function, we simply return the
// result.

        return typeof reviver === 'function' ? (function walk(holder, key) {
            var k, v, value = holder[key];
            if (value && typeof value === 'object') {
                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = walk(value, k);
                        if (v !== undefined) {
                            value[k] = v;
                        } else {
                            delete value[k];
                        }
                    }
                }
            }
            return reviver.call(holder, key, value);
        }({'': result}, '')) : result;
    };
}());

