/* Copyright (C) 2010 Hailoo
 *
 *	This file is part of the Hailoo Search Engine source code library.
 *	This code is proprietary and may not be redistributed, modified,
 *	or shared, without express permission from Hailoo.
 *
 *	Author(s): Charles Salvia
 *	Last Modified: 4/30/2010
 *
 *	Description: Virtual Keyboard
*/

// The global virtual keyboard object
//
var vkb_instance = null;

function vkb_create(textbox, kb_elem, langString)
{
	if (!vkb_instance) {
		vkb_instance = new VirtualKeyboard(textbox, kb_elem, langString);
		vkb_instance.createKeyboard();
	}
}

function vkb_display(textbox, kb_elem, langString)
{
	if (!vkb_instance) {
		vkb_instance = new VirtualKeyboard(textbox, kb_elem, langString);
		vkb_instance.createKeyboard();
	}

	document.getElementById(kb_elem).style.display = "block";
	document.getElementById(textbox).focus();
}

function vkb_set_target(textbox)
{
	if (vkb_instance) vkb_instance.assignTarget(textbox);
}

// Virtual Keyboard class
//
function VirtualKeyboard(tg, elem, langString)
{
	this.targetId = tg; // the textbox element ID
	this.elementId = elem; // the ID of the DOM element containing the keyboard
	this.keyMap = null;
	this.timeOut = 0;
	this.pressedKey = null;
	this.MSIE = (document.getElementById(this.targetId).setSelectionRange == null);

	this.hide = function()
	{
		document.getElementById(this.elementId).style.display="none";
		this.hideBigKey();
		document.getElementById(this.targetId).focus();
	}

	this.assignTarget = function(tg)
	{
		var currentTarget = document.getElementById(this.targetId);

		// Restore old event handlers
		//
		if (currentTarget) 
		{
			currentTarget.onkeydown = this.oldkeydown;
			currentTarget.onkeyup = this.oldkeyup;
			currentTarget.onkeypress = this.oldkeypress;
	
			if (currentTarget.createTextRange) {
				currentTarget.onclick = this.oldclick;
				currentTarget.onselect = this.oldselect;
			}
		}

		this.targetId = tg;
		this.assignTargetKeyEvents(document.getElementById(this.targetId));
	}

	// ============ Create keyboard and keycode map ============ //
	//
	this.createKeyboard = function()
	{
		var keys = new Array();
		this.keyMap = new Array();

		var keyMap = this.keyMap;
		var virtualKey = this.virtualKey;
		var makeKey = this.makeKey;

		keys[0] = keyMap[192] = new virtualKey("\u0630", "_backtick");
		keys[1] = keyMap[49] = new virtualKey("\u0661", "_0");
		keys[2] = keyMap[50] = new virtualKey("\u0662", "_1");
		keys[3] = keyMap[51] = new virtualKey("\u0663", "_2");
		keys[4] = keyMap[52] = new virtualKey("\u0664", "_3");
		keys[5] = keyMap[53] = new virtualKey("\u0665", "_4");
		keys[6] = keyMap[54] = new virtualKey("\u0666", "_5");
		keys[7] = keyMap[55] = new virtualKey("\u0667", "_6");
		keys[8] = keyMap[56] = new virtualKey("\u0668", "_7");
		keys[9] = keyMap[57] = new virtualKey("\u0669", "_8");
		keys[10] = keyMap[48] = new virtualKey("\u0660", "_9");

		keys[11] = keyMap[81] = new virtualKey("\u0636", "_q");
		keys[12] = keyMap[87] = new virtualKey("\u0635", "_w");
		keys[13] = keyMap[69] = new virtualKey("\u062B", "_e");
		keys[14] = keyMap[82] = new virtualKey("\u0642", "_r");
		keys[15] = keyMap[84] = new virtualKey("\u0641", "_t");
		keys[16] = keyMap[89] = new virtualKey("\u063A", "_y");
		keys[17] = keyMap[85] = new virtualKey("\u0639", "_u");
		keys[18] = keyMap[73] = new virtualKey("\u0647", "_i");
		keys[19] = keyMap[79] = new virtualKey("\u062E", "_o");
		keys[20] = keyMap[80] = new virtualKey("\u062D", "_p");
		keys[21] = keyMap[91] = keyMap[219] = new virtualKey("\u062C", "_[");
		keys[22] = keyMap[93] = keyMap[221] = new virtualKey("\u062F", "_]");

		keys[23] = keyMap[65] = new virtualKey("\u0634", "_a");
		keys[24] = keyMap[83] = new virtualKey("\u0633", "_s");
		keys[25] = keyMap[68] = new virtualKey("\u064A", "_d");
		keys[26] = keyMap[70] = new virtualKey("\u0628", "_f");
		keys[27] = keyMap[71] = new virtualKey("\u0644", "_g");
		keys[28] = keyMap[72] = new virtualKey("\u0627", "_h");
		keys[29] = keyMap[74] = new virtualKey("\u062A", "_j");
		keys[30] = keyMap[75] = new virtualKey("\u0646", "_k");
		keys[31] = keyMap[76] = new virtualKey("\u0645", "_l");
		keys[32] = keyMap[59] = new virtualKey("\u0643", "_;");
		keys[33] = keyMap[222] = new virtualKey("\u0637", "_apos");

		keys[34] = keyMap[90] = new virtualKey("\u0626", "_z");
		keys[35] = keyMap[88] = new virtualKey("\u0621", "_x");
		keys[36] = keyMap[67] = new virtualKey("\u0624", "_c");
		keys[37] = keyMap[86] = new virtualKey("\u0631", "_v");
		keys[38] = keyMap[66] = new virtualKey("\u0644\u0627", "_b");
		keys[39] = keyMap[78] = new virtualKey("\u0649", "_n");
		keys[40] = keyMap[77] = new virtualKey("\u0629", "_m");
		keys[41] = keyMap[44] = keyMap[188] = new virtualKey("\u0648", "_,");
		keys[42] = keyMap[190] = new virtualKey("\u0632", "_.");
		keys[43] = keyMap[47] = keyMap[191] = new virtualKey("\u0638", "_/");

		var html = "<table cellpadding='0' cellspacing='0' border='0' style='width:100%'><tr>";
		html += "<td colspan='13' style='padding:3px; text-align:right; height:2em'>"
			+ "<img src='/com_img/x.jpg' " 
			+ "onmouseover='this.src=\"/com_img/x_ov.jpg\"' onmouseout='this.src=\"/com_img/x.jpg\"' style='cursor:pointer'" 
			+ "onclick='vkb_instance.hide()'></td>";
		html += "</tr><tr>";

		// First row (Backtick, 0-9, and backspace)
		//
		for (var i = 0; i <= 10; ++i) html += makeKey(keys[i].character, keys[i].keyid, 1.5, "3.0em", 20);
		html += makeKey("\u2192", "vkb_backspace", 1.5, "3.0em", 20); // backspace key
		html += "</tr><tr>";

		// Second row (Q through P)
		//
		for (var i = 11; i <= 22; ++i) html += makeKey(keys[i].character, keys[i].keyid, 1.5, "3.0em", 20);
		html += "</tr><tr>";

		// Third row (A through L)
		//
		html += makeKey("&nbsp;", "", 1, "1.8em", false, 20);
		for (var i = 23; i <= 33; ++i) html += makeKey(keys[i].character, keys[i].keyid, 1.5, "3.0em", 20);
		html += "</tr><tr>";

		// Fourth row (Z through M)
		//
		html += makeKey("&nbsp;", "", 1, "1.8em", false, 20);
		for (var i = 34; i <= 43; ++i) html += makeKey(keys[i].character, keys[i].keyid, 1.5, "3.0em", 20);

		html += "</tr><tr>"; 
		html += makeKey("&nbsp;", "", 2, "1.5em", false, 20); 	// invisible key
		html += makeKey("&nbsp;", "vkb_space", 13, "0.5em", false, 16); // spacebar

		html += "</tr></table>";

		// Create pop-over big key
		//
		html += 
			"<div id='vkb_bk' style='width: 3.7em; display:none'>"
			+ "<b class='xtop'>"
			+ "<b id = 'vkb_bk_xt1' class='xt1'></b>"
			+ "<b id = 'vkb_bk_xt2' class='xt2'></b>"
			+ "<b id = 'vkb_bk_xt3' class='xt3'></b>"
			+ "<b id = 'vkb_bk_xt4' class='xt4'></b>"
			+ "</b>"
			+ "<div id='vkb_bk_inner' class='key' style='height:1.4em; font-size: 2.5em' align='center' ></div>"
			+ "<b class='xbottom'>"
			+ "<b id = 'vkb_bk_xb4' class='xb4'></b>"
			+ "<b id = 'vkb_bk_xb3' class='xb3'></b>"
			+ "<b id = 'vkb_bk_xb2' class='xb2'></b>"
			+ "<b id = 'vkb_bk_xb1' class='xb1'></b>"
			+ "</b>"
			+ "</div></div>";

		document.getElementById(this.elementId).innerHTML = html;

		// Set event handlers for pop-up key
		//
		var bigKey = document.getElementById("vkb_bk");

		bigKey.onmouseout = function(event) 
		{
			if (event === undefined) event = window.event;

			var other = "relatedTarget" in event? event.relatedTarget : event.toElement;
			while (other && (other = other.parentNode).nodeType === 1) 
				if (other === this) return;

			vkb_instance.hideBigKey();
		}

		bigKey.onmousedown = function() {
			vkb_instance.rollOverRoundedEdges("vkb_bk", "#FFF1C4", "#FFF1C4", "transparent");
			if (!vkb_instance.MSIE) vkb_instance.printCharacter();
		}

		bigKey.onmouseup = function() {
			vkb_instance.rollOverRoundedEdges("vkb_bk", "#ededed", "#FFF", "url('/com_img/keyshade.jpg')");
			document.getElementById(vkb_instance.targetId).focus();
		}

		if (vkb_instance.MSIE) {
			bigKey.onclick = function() {
				vkb_instance.printCharacter();
			}
		}
		
		this.assignTargetKeyEvents(document.getElementById(this.targetId));
	}

	// ==================== Keystroke Event Handling ==================== //	
	//
	this.assignTargetKeyEvents = function(target)
	{
		// Save old event handlers
		//
		this.oldkeydown = target.onkeydown;
		this.oldkeyup = target.onkeyup;
		this.oldkeypress = target.onkeypress;

		if (target.createTextRange) {
			this.oldclick = target.onclick;
			this.oldselect = target.onselect;
		}

		// Set key down event to intercept key strokes
		//
		target.onkeydown = function(ev)
		{
			vkb_instance.pressedKey = null;

			ev = (ev || window.event);
			var keycode = (ev.keyCode || ev.which);
			if (ev.ctrlKey || ev.altKey) return true;

			if (document.getElementById(vkb_instance.elementId).style.display == "block") 
			{
				if (keycode == 32) {
					vkb_instance.rollOverRoundedEdges("vkb_space", "#FFF1C4", "#FFF1C4", "transparent");
					return true;
				}

				if (keycode == 8) {
					vkb_instance.rollOverRoundedEdges("vkb_backspace", "#FFF1C4", "#FFF1C4", "transparent");
					return true;
				}

				vkb_instance.pressedKey = vkb_instance.keyMap[keycode];
				if (vkb_instance.pressedKey == null) return true;

				if (this.value.length == 0) vkb_instance.adjustTextDirection(vkb_instance.pressedKey.character);
				vkb_instance.rollOverRoundedEdges(vkb_instance.pressedKey.keyid, "#FFF1C4", "#FFF1C4", "transparent");
				vkb_instance.insertChar(vkb_instance.pressedKey.character);

				return false;
			}

			return true;
		}

		// Set key up event
		//
		target.onkeyup = function(ev)
		{
			if (target.createTextRange) target.range = document.selection.createRange();

			ev = (ev || window.event);
			var keycode = (ev.keyCode || ev.which);
			if (ev.ctrlKey || ev.altKey) return true;

			if (keycode == 8 || keycode == 46) vkb_instance.adjustTextDirection(this.value);

			if (document.getElementById(vkb_instance.elementId).style.display == "block") 
			{
				if (keycode == 32) {
					vkb_instance.rollOverRoundedEdges("vkb_space", "#ededed", "#FFF", "url('/com_img/keyshade.jpg')");
					return true;
				}

				if (keycode == 8) {
					vkb_instance.rollOverRoundedEdges("vkb_backspace", "#ededed", "#FFF", "url('/com_img/keyshade.jpg')");
					return true;
				}

				var key = vkb_instance.keyMap[keycode];
				if (key != null) 
					vkb_instance.rollOverRoundedEdges(key.keyid, "#ededed", "#FFF", "url('/com_img/keyshade.jpg')");
			}

			return true;
		}

		target.onkeypress = function(ev) 
		{
			ev = (ev || window.event);
			var c = (ev.charCode || ev.keyCode);

			if (this.value.length == 0) {
				if (!vkb_instance.pressedKey) 
					vkb_instance.adjustTextDirection(String.fromCharCode(c)); 
			}

			// For Opera, we need to explicitly cancel key-press events
			// if the key-stroke was intercepted
			//
			if (window.opera) return !vkb_instance.pressedKey;
		}

		// Set initial state of target text box for MSIE
		//
		if (target.createTextRange) {
			target.onclick = target.onselect = function(e) {
				this.range = document.selection.createRange();
			}

			if (!target.range) {
				target.range = target.createTextRange();
				target.range.moveStart('character', target.value.length);
			}
			target.range.select();
		}
	}

	// Create the HTML for an individual key
	//
	this.makeKey = function(label, id, cspan, ht, visible, fontsize)
	{
		if (id == null) id == "";
		if (visible == null) visible = true;

		this.width = 0;
		this.height = (ht == null ? "1.8em" : ht);

		if (cspan == null) {
			cspan = 1;
			this.width = "1.8em";
		}
		else this.width = 1.8 * cspan + "em";	

		if (id == "vkb_space") {
			return "<td colspan=" + cspan + " style='padding:10px 2px 1px 2px'>"
				+ "<div onmousedown='vkb_instance." + id + "_down()' onmouseup='vkb_instance." + id + "_up()' "
				+ "style='width: " + width + "; height:" + height + ";' id='" + id + "'>"
				+ "<b class='xtop'><b id='" + id + "_xt1' class='xt1'></b>" 
				+ "<b id='" + id + "_xt2' class='xt2'></b><b id='" + id + "_xt3' class='xt3'></b><b id='" + id + "_xt4' class='xt4'></b></b>"
				+ "<div id = '" + id + "_inner' class='key' style='font-size: " + fontsize + "pt' align='center'>" 
				+ label 
				+ "</div>"
				+ "<b class='xbottom'><b id='" + id + "_xb4' class='xb4'></b>"
				+ "<b id='" + id + "_xb3' class='xb3'></b><b id='" + id + "_xb2' class='xb2'></b>"
				+ "<b id='" + id + "_xb1' class='xb1'></b></b>"
				+ "</div></td>";
		}

		var bigKeyWidth = 3.7;

		if (visible) {
			return "<td colspan=" + cspan + " style='padding:10px 2px 1px 2px'>"
				+ "<div onmouseover='vkb_instance.keyMouseOver(this, \"" + label + "\", " + bigKeyWidth + ")' onclick='vkb_instance.printCharacter()'" 
				+ "style='width: " + width + "; height:" + height + ";' id='" + id + "'>"
				+ "<b class='xtop'><b class='xt1'></b><b class='xt2' id='" + id + "_xt2'></b>"
				+ "<b class='xt3' id='" + id + "_xt3'></b><b class='xt4' id='" + id + "_xt4'></b></b>"
				+ "<div id = '" + id + "_inner' class='key' align='center'>"
				+ label
				+ "</div>"
				+ "<b class='xbottom'><b class='xb4' id='" + id + "_xb4' >"
				+ "</b><b class='xb3' id ='" + id + "_xb3'></b><b class='xb2' id='" + id + "_xb2'></b><b class='xb1'></b></b>"
				+ "</div></td>";
		}

		return "<td colspan=" + cspan + " style='padding:1px'><div style='width: "
				+ width + "; height:" + height + ";' id='" + id + "'></div></td>";
	}

	// Key class
	//
	this.virtualKey = function(c, keyid)
	{
		this.character = c;
		this.keyid = keyid;
	}

	this.keyMouseOver = function(key, label, width)
	{
		clearTimeout(this.timeOut);

		// Show big key
		//
		var bigkey = document.getElementById("vkb_bk");
		bigkey.style.position = "absolute";

		var pos = this.getElementPosition(key);
		bigkey.style.top = (pos.y - 8) + "px";
		bigkey.style.left = (pos.x - 6) + "px";
		bigkey.style.width = width + "em";

		var bigkeyLabel = document.getElementById("vkb_bk_inner");
		if (bigkeyLabel) bigkeyLabel.innerHTML = "<b>" + label + "</b>"

		bigkey.style.display = "block";
	}

	this.keyMouseOut = function(elem)
	{
		this.timeOut = setTimeout('vkb_instance.hideBigKey()', 500);
	}

	this.hideBigKey = function() 
	{
		var bigkey = document.getElementById("vkb_bk");
		bigkey.style.display="none";
	}

	// Write clicked character to textbox
	//
	this.printCharacter = function()
	{
		var key = document.getElementById("vkb_bk_inner");

		// Remove bold tag
		//
		var character = key.innerHTML.substr(3, key.innerHTML.indexOf("<", 3) - 3);
		var target = document.getElementById(this.targetId);

		if (character == "\u2192") {
			this.deleteChar();
			vkb_instance.adjustTextDirection(target.value);
		}
		else {
			if (target.value.length == 0) vkb_instance.adjustTextDirection(character);
			this.insertChar(character);
		}
	}

	// Insert a character into the target textbox at the cursor position
	//
	this.insertChar = function(c)
	{
		var target = document.getElementById(this.targetId);
		target.focus();

		if (target.maxLength) target.maxlength = target.maxLength;
		if (typeof target.maxlength == "undefined" || target.maxlength < 0 
		|| target.value.length < target.maxlength)
		{
			if (target.setSelectionRange) {
				var pos1 = target.selectionStart;
				var pos2 = target.selectionEnd;
				var str = target.value;
				target.value = str.substr(0, pos1) + c + str.substr(pos2);
				target.focus();
				target.setSelectionRange(pos1 + c.length, pos1 + c.length)
			}
			else if (target.createTextRange)
			{
				if (!target.range) target.range = target.createTextRange();
				else target.range.select();

				target.range.text = c;
				target.range.collapse(true);
				target.range.select();
				target.focus();
				target.range.select();
			}
			else target.value += c;
		}
		else if (target.createTextRange && target.range) target.range.select();

		target.focus();
	}

	// Delete a character from the target textbox at the cursor position
	//
	this.deleteChar = function()
	{
		var target = document.getElementById(this.targetId);

		if (target.setSelectionRange) {
			var pos1 = target.selectionStart;
			var pos2 = target.selectionEnd;
			if (pos2 - pos1 == 0) --pos1;

			var str = target.value;
			target.value = str.substr(0, pos1) + str.substr(pos2);
			target.setSelectionRange(pos1, pos1);
		}
		else if (target.createTextRange) {
			if (!target.range) target.range = target.createTextRange();
			else target.range.select();

			if (!target.range.text.length) target.range.moveStart('character', -1);
			target.range.text = "";
			target.focus();
			target.range.select();
		}
		else target.value = target.value.substr(0, target.value.length - 1);

		target.focus();
	}

	// Return the position of the specified element
	//
	this.getElementPosition = function(e) 
	{
		var left = 0;
		var top = 0;

		var containerElement = document.getElementById(this.elementId);
		if (containerElement.style.position == "fixed" || containerElement.style.position == "absolute")
		{
			while (e && e != containerElement) {
				left += e.offsetLeft;
				top += e.offsetTop;
				e = e.offsetParent;
			}

			return {x:left, y:top};
		}

		while (e.offsetParent) {
			left += e.offsetLeft;
			top  += e.offsetTop;
			e = e.offsetParent;
		}

		left += e.offsetLeft;
		top += e.offsetTop;

		return {x:left, y:top};
	}

	this.vkb_space_down = function()
	{
		vkb_instance.rollOverRoundedEdges("vkb_space", "#FFF1C4", "#FFF1C4", "transparent");
		this.insertChar(" ");
	}

	this.vkb_space_up = function()
	{
		vkb_instance.rollOverRoundedEdges("vkb_space", "#ededed", "#FFF", "url('/com_img/keyshade.jpg')");
		document.getElementById(this.targetId).focus();
	}

	this.rollOverRoundedEdges = function(elemString, topColor, bottomColor, background)
	{
		document.getElementById(elemString + "_xt2").style.backgroundColor = topColor;
		document.getElementById(elemString + "_xt3").style.backgroundColor = topColor;
		document.getElementById(elemString + "_xt4").style.backgroundColor = topColor;

		var keyElement = document.getElementById(elemString + "_inner");
		if (keyElement) {
			keyElement.style.background = background;
			keyElement.style.backgroundColor = topColor;
		}

		document.getElementById(elemString + "_xb4").style.backgroundColor = bottomColor;
		document.getElementById(elemString + "_xb3").style.backgroundColor = bottomColor;
		document.getElementById(elemString + "_xb2").style.backgroundColor = bottomColor;
	}

	// ============ Automatic text-direction adjustment ============ //

	this.getTextFieldDirection = function(target)
	{
		// If the target textbox has no 'dir' property, we travel up the DOM
		// tree until we find an element with a defined 'dir' property.
		//
		var trav = target;
		while (trav && (!trav.dir || trav.dir == "")) 
			trav = trav.parentNode;

		if (!trav || (!trav.dir || trav.dir == "")) return "ltr";	
		else return trav.dir;
	}

	this.adjustTextDirection = function(text)
	{
		var target = document.getElementById(this.targetId);
		var textDir = this.getTextDirection(text);

		if (textDir != "bi") 
		{
			if (!target.dir || target.dir == "") 
				target.dir = this.getTextFieldDirection(target);

			if (target.dir != textDir) {
				target.dir = textDir;
				target.style.textAlign = (textDir == "rtl" ? "right" : "left");
			}
		}

		target.focus();
	}

	this.getTextDirection = function(text)
	{
		// Checks text direction of the specified codepoint.  Returns 1 if direction
		// is RTL, 0 if LTR, and -1 if the codePoint could be either.
		//
		this.checkDir = function(codePoint) 
		{
			// Check for whitespace characters
			//
			if (codePoint <= 0x0020) return -1; // Whitespace and control chars
			if (codePoint < 0x0080) return 0; // English ASCII

			if ((codePoint >= 0x0590 && codePoint <= 0x074F) // Hebrew, Arabic, Syriac
				|| (codePoint >= 0x0780 && codePoint <= 0x07BF)) // Thaana
				return 1;

			return 0;
		}

		var rtl = 0, ltr = 0;
		var result;

		for (var i = 0; i < text.length; ++i) 
		{
			result = this.checkDir(text.charCodeAt(i));
			if (result == 0) {
				++ltr;
				if (rtl > 0) return "bi";
			}
			else if (result == 1) {
				++rtl;
				if (ltr > 0) return "bi";
			}
		}

		if (ltr == 0 && rtl == 0) return this.defaultTextDirection;
		return (ltr > rtl ? "ltr" : "rtl");
	}

	this.defaultTextDirection = this.getTextFieldDirection(document.getElementById(this.targetId));
};

