const WCX_TOPIC_ID = "deskperience_word_capture_x_topic_id";
var Cc             = Components.classes;
var Ci             = Components.interfaces;
var g_wcxObserver  = null;


main();

function main()
{
	// Create WCX communication service.
	var wcxComm = Cc["@deskperience.com/wcxcomm;1"].getService(Ci.IWcxComm);

	if (wcxComm.isFF4orLater)
	{
	    // Do not try to add a callback until the browser window has been initialised. We add a callback to the tabbed browser when the browser's window gets loaded.
	    window.addEventListener("load",
	        function () {
				g_wcxObserver = new WcxObserver();

	            // Add a callback to be run every time a document loads. Note that this includes frames/iframes within the document
	            gBrowser.addEventListener("load", OnInit, true);
	        },
	        false
	    );

		window.addEventListener("unload", CleanUp, false);
	}
}


function OnInit()
{
	window.addEventListener('mousemove', OnMouseMove, true);
}


function OnMouseMove(evt)
{
	var actionElem = evt.target;
	if (actionElem instanceof Components.interfaces.nsIDOMHTMLElement)
	{
		//WcxLogMessage("OnMouseMove on HTML element");
		window.lastWcxNode   = evt.rangeParent;
		window.lastWcxOffset = evt.rangeOffset;
		window.lastWcxElem   = actionElem;
	}
}


function CleanUp(evt)
{
	if (g_wcxObserver)
	{
		g_wcxObserver.unregister();
		g_wcxObserver = null;
	}

	// TODO: it might be necessary to shut down wcxComm service as in WR.
}


function GetParaghraphElement(node)
{
	while (true)
	{
		if (node.nodeType == 1)
		{
			break;
		}

		node = node.parentNode;
		if (!node)
		{
			break;
		}
	}

	var tagName = node.tagName.toUpperCase();
	switch (tagName)
	{
		case "A":
		case "B":
		case "I":
		case "U":
		case "LABEL":
		case "FONT":
		{
			node = node.parentNode;
			break;
		}
	}

	return node;
}


function WcxObserver()
{
    this.register();
}


WcxObserver.prototype =
{
    observe: function(subject, topic, data)
    {
		WcxLogMessage("wcx observed event");
		OnCapture();
    },

    register: function()
    {
        var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
        observerService.addObserver(this, WCX_TOPIC_ID, false);
    },

    unregister: function()
    {
        var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
        observerService.removeObserver(this, WCX_TOPIC_ID);
    }
}


function GetStartOffsetInParent(node, parentNode)
{
	var parentRange = window.content.document.createRange();

	parentRange.selectNode(parentNode);
	parentRange.setEndBefore(node);

	return parentRange.toString().length;
}


function OnCapture()
{
	WcxLogMessage("OnCapture start");

	var wcxComm = null;
	try
	{
		
		wcxComm = Cc["@deskperience.com/wcxcomm;1"].getService(Ci.IWcxComm);

		var crntWnd = window.content;
		var crntDoc = crntWnd.document;

		var selNode   = window.lastWcxNode;
		var selOffset = window.lastWcxOffset;

		if (!selNode)
		{
			WcxLogMessage("OnCapture !selNode");
			return;
		}

		var initialRange  = crntDoc.createRange();
		initialRange.setStart(selNode, selOffset);
		initialRange.collapse(true);

		var actionElem   = window.lastWcxElem;

		// Input
		var separators = wcxComm.separators;
		var leftCtxNo  = wcxComm.contextLeft;
		var rightCtxNo = wcxComm.contextRight;

		WcxLogMessage("OnCapture input: ", leftCtxNo, rightCtxNo);

		// Output
		var currentWord  = "";
		var fullText     = "";
		var leftContext  = "";
		var rightContext = "";
		var startIndex   = selOffset;
		var leftStart    = 0;
		var rightStart   = 0;
		var paragraph    = "";

		if (!wcxComm.IsWcxCurrentTarget(crntDoc))
		{
			// It seems that hasFocus can be true even if another frame got focus?
			WcxLogMessage("OnCapture !wcxComm.IsWcxCurrentTarget");
			return;
		}

		// Get paragraph text.
		var paraNode     = GetParaghraphElement(selNode);
		var captureRange = crntDoc.createRange();

		captureRange.selectNode(paraNode);
		paragraph = captureRange.toString();
		fullText  = paragraph;

		startIndex += GetStartOffsetInParent(selNode, paraNode);

		//WcxLogMessage("OnCapture start processing, fullText=", fullText, " startIndex=", startIndex);

		leftStart  = startIndex;
		rightStart = startIndex + 1;

		var wasCJK = false;
		var isRTL  = false;

		while (leftStart > 0)
		{
			var crntChar     = fullText[leftStart];
			var crntCharCode = fullText.charCodeAt(leftStart);
			var isCJKChar    = 
				(
					((crntCharCode >= 0x3300) && (crntCharCode <= 0x33FF)) || 
					((crntCharCode >= 0x3400) && (crntCharCode <= 0x4DB5)) ||
					((crntCharCode >= 0x4E00) && (crntCharCode <= 0x9FFF)) ||
					((crntCharCode >= 0xF900) && (crntCharCode <= 0xFAFF)) ||
					((crntCharCode >= 0xFE30) && (crntCharCode <= 0xFE4F))
				);

			if (!isRTL)
			{
				// Hebrew or arab.
				isRTL = (((crntCharCode >= 0x0590) && (crntCharCode <= 0x05FF)) || ((crntCharCode >= 0x0600) && (crntCharCode <= 0x06FF)));
			}
			
			if (separators.indexOf(crntChar) != -1)
			{
				// crntChar is a separator.
				leftStart++;
				break;
			}
			else if (isCJKChar)
			{
				wasCJK = true;
				break;
			}

			leftStart--;
		}

		while (rightStart < fullText.length)
		{
			var crntChar     = fullText[rightStart];
			var crntCharCode = fullText.charCodeAt(rightStart);
			var isCJKChar    = 
				(
					((crntCharCode >= 0x3300) && (crntCharCode <= 0x33FF)) || 
					((crntCharCode >= 0x3400) && (crntCharCode <= 0x4DB5)) ||
					((crntCharCode >= 0x4E00) && (crntCharCode <= 0x9FFF)) ||
					((crntCharCode >= 0xF900) && (crntCharCode <= 0xFAFF)) ||
					((crntCharCode >= 0xFE30) && (crntCharCode <= 0xFE4F))
				);

			if (!isRTL)
			{
				// Hebrew or arab.
				isRTL = (((crntCharCode >= 0x0590) && (crntCharCode <= 0x05FF)) || ((crntCharCode >= 0x0600) && (crntCharCode <= 0x06FF)));
			}

			if ((separators.indexOf(crntChar) != -1) || isCJKChar)
			{
				// crntChar is a separator.
				break;
			}

			rightStart++;
		}

		currentWord = fullText.substring(leftStart, rightStart);

		// If hebrew word (contains at least one hewbrew char) then reverse it.
		var isWordRTL = false;
		if (isRTL)
		{
			var revWord = "";
			for (var x = currentWord.length - 1; x >= 0; x--)
			{
				revWord = revWord + currentWord.charAt(x);
			}
			
			currentWord = revWord;
			isWordRTL   = true;
		}

		isRTL = false;

		if (wcxComm.highlight)
		{
			// Select the word.
			if (IsTextContainer(actionElem))
			{
				WcxLogMessage("OnCapture actionElem IsTextContainer");
				actionElem.focus();
				actionElem.setSelectionRange(leftStart, rightStart);
				
				// Set a timeout for clearing the selected word so the user will get some feedback.
				crntWnd.setTimeout(
					function (){
						try
						{
							actionElem.selectionEnd = actionElem.selectionStart;
						}
						catch (ex)
						{
						}
					},
					250
				);
			}
			else
			{
				var selObj = crntWnd.getSelection();
				if ((selObj != null) && (initialRange != null))
				{
					selObj.removeAllRanges();
					selObj.addRange(initialRange);
					
					for (var moveLeft = 0; moveLeft < startIndex - leftStart; ++moveLeft)
					{
						selObj.modify("move", "backward", "character");
					}

					for (var extRight = 0; extRight < rightStart - leftStart; ++extRight)
					{
						selObj.modify("extend", "forward", "character");
					}

					// Set a timeout for clearing the selected word so the user will get some feedback.
					crntWnd.setTimeout(
						function (){
							try
							{
								crntWnd.getSelection().removeAllRanges();
							}
							catch (ex)
							{
							}
						},
						250
					);
				}
			}
		}

		// Compute right context.
		WcxLogMessage("OnCapture Compute right context");
		
		if (wasCJK)
		{
			if (separators.indexOf(fullText[rightStart]) != -1)
			{
				rightStart++;
			}
		}
		
		var rightCtxEnd = rightStart;
		while ((rightCtxEnd < fullText.length) && (rightCtxNo > 0))
		{
			rightCtxEnd++;
			var crntChar     = fullText[rightCtxEnd];
			var crntCharCode = fullText.charCodeAt(rightCtxEnd);
			var isCJKChar    = 
				(
					((crntCharCode >= 0x3300) && (crntCharCode <= 0x33FF)) || 
					((crntCharCode >= 0x3400) && (crntCharCode <= 0x4DB5)) ||
					((crntCharCode >= 0x4E00) && (crntCharCode <= 0x9FFF)) ||
					((crntCharCode >= 0xF900) && (crntCharCode <= 0xFAFF)) ||
					((crntCharCode >= 0xFE30) && (crntCharCode <= 0xFE4F))
				);

			if (!isRTL)
			{
				// Hebrew or arab.
				isRTL = (((crntCharCode >= 0x0590) && (crntCharCode <= 0x05FF)) || ((crntCharCode >= 0x0600) && (crntCharCode <= 0x06FF)));
			}

			if ((separators.indexOf(crntChar) != -1) || isCJKChar)
			{
				rightCtxNo--;
			}
		}

		if (rightStart <= rightCtxEnd)
		{
			rightContext = fullText.substring(rightStart, rightCtxEnd);

			// If hebrew word (contains at least one hewbrew char) then reverse it.
			if (isRTL)
			{
				var revRightWord = "";
				for (var y = rightContext.length - 1; y >= 0; y--)
				{
					revRightWord = revRightWord + rightContext.charAt(y);
				}
				
				rightContext = revRightWord;
			}				
		}
		
		isRTL = false;

		// Compute left context.
		WcxLogMessage("OnCapture Compute left context");
		
		if (wasCJK)
		{
			if ((leftStart > 1) && (separators.indexOf(fullText[leftStart - 1]) != -1))
			{
				leftStart--;
			}
		}

		var leftContextStart = (wasCJK ? leftStart : leftStart - 1);
		while ((leftContextStart > 0) && (leftCtxNo > 0))
		{
			leftContextStart--;

			var crntChar     = fullText[leftContextStart];
			var crntCharCode = fullText.charCodeAt(leftContextStart);
			var isCJKChar    = 
				(
					((crntCharCode >= 0x3300) && (crntCharCode <= 0x33FF)) || 
					((crntCharCode >= 0x3400) && (crntCharCode <= 0x4DB5)) ||
					((crntCharCode >= 0x4E00) && (crntCharCode <= 0x9FFF)) ||
					((crntCharCode >= 0xF900) && (crntCharCode <= 0xFAFF)) ||
					((crntCharCode >= 0xFE30) && (crntCharCode <= 0xFE4F))
				);

			if (!isRTL)
			{
				// Hebrew or arab.
				isRTL = (((crntCharCode >= 0x0590) && (crntCharCode <= 0x05FF)) || ((crntCharCode >= 0x0600) && (crntCharCode <= 0x06FF)));
			}

			if ((separators.indexOf(crntChar) != -1) || isCJKChar)
			{
				leftCtxNo--;
			}
		}

		if (!wasCJK)
		{
			if ((leftContextStart <= leftStart - 1) && (leftStart > 0))
			{
				leftContext = fullText.substring(leftContextStart, leftStart - 1);
				
				// If hebrew word (contains at least one hewbrew char) then reverse it.
				if (isRTL)
				{
					var revLeftWord = "";
					for (var z = leftContext.length - 1; z >= 0; z--)
					{
						revLeftWord = revLeftWord + leftContext.charAt(z);
					}

					leftContext = revLeftWord;
				}					
			}
		}
		else
		{
			if ((leftContextStart <= leftStart) && (leftStart > 0))
			{
				leftContext = fullText.substring(leftContextStart, leftStart);
			}			
		}

		isRTL = false;
		
		if (isWordRTL)
		{
			// For RTL languages invert right ctx with left ctx.
			var tempCtx = leftContext;
			leftContext  = rightContext;
			rightContext = tempCtx;
		}
	}
	catch (ex)
	{
		// On exception don't send anything back.
		WcxLogMessage("OnCapture catch ya", ex);
		wcxComm.PushTextResult("", -1, "", "", "");
		
		return;
	}

	WcxLogMessage("OnCapture PushTextResult ", currentWord, leftContext, rightContext, startIndex - leftStart);
	wcxComm.PushTextResult(currentWord, startIndex - leftStart, paragraph, leftContext, rightContext);

	WcxLogMessage("OnCapture end");
}


function IsTextContainer(elem)
{
	if (elem && (elem instanceof Components.interfaces.nsIDOMHTMLElement))
	{
		if(((elem.tagName.toLowerCase() == "input") && (elem.type.toLowerCase() == "text")) ||
		   (elem.tagName.toLowerCase() == "textarea"))
	   {
			return true;
	   }
	}
	else
	{
		return false;
	}
}
