







// content provider
var DICT_ID = "spellcheckerdictionary";
var SP_VALID_WORD   = 0; // valid word
var SP_INVALID_WORD = 1; // Invalid word
var SP_PENDING_WORD = 2; // Not yet checked

var CSS_UNDERLINE = "url('" + location.protocol + "//" + location.host + "/images/spell/underline.gif') repeat-x bottom";

var NO_SUGGESTION = "No Suggestion";
var IGNORE = "Ignore";
var IGNORE_ALL = "Ignore All";
var ADD_TO_DIC = "Add to Dictionary";
var HIDE = "Hide Spell Checker";

var NODE_ID_PREFIX = "spnode";

var TAB_SPACE = '          ';   //10 spaces

window[DICT_ID] = new NLSPDictionary(DICT_ID);

function enWordChecker(dict )
{
    this.dict = dict;
}

// quick test
enWordChecker.prototype.checkWord = function (word)
{
    if (word.toUpperCase()== word)
    {
        // all upper case
        return true;
    }
    else if (word.length >= 65)  
        return true;
    else
    {
        // number
        var num=/(^\d+$)|(^\d+\.\d+$)/ ;
        if (num.test(word))
            return true;
    }
}

function NLSPDictionary (id)
{
    if (typeof id != "undefined")
        this.id = id;
    else
        this.id = DICT_ID;

    window[this.id] = this;

    this.quickChecker = new enWordChecker (this);
    this.words = new Array();
    this.pending = new Object();
}

function getSpellChecker(textArea)
{
    return (textArea != null) ? textArea.spellchecker : null;
}

NLSPDictionary.prototype.hasWord = function (word)
{
    return this.getWordStatus(word) != SP_PENDING_WORD;
}

NLSPDictionary.prototype.getWordStatus = function (word)
{
    if(typeof word == "undefined") return SP_VALID_WORD;

    word = trim(word);

    var wordInfo = this.words[word];
    if( typeof wordInfo == "object")
    {
        return SP_INVALID_WORD;
    }
    else if( wordInfo == true)
    {
        return SP_VALID_WORD;
    }
    else if (this.quickChecker.checkWord(word) == true)
    {
        return SP_VALID_WORD;
    }
    else if(typeof wordInfo=="undefined" || wordInfo == null)
    {
        return SP_PENDING_WORD;
    }

    return SP_VALID_WORD;
}

NLSPDictionary.prototype.checkWord = function (word, wordId, listenerObj, bCheckServer)
{
    if(typeof word == "undefined") return SP_VALID_WORD;

    word = trim(word);

    var status = this.getWordStatus(word);

    if (status == SP_PENDING_WORD && bCheckServer != false)
    {
        var bNewWord = (typeof this.pending[word] == "undefined" || this.pending[word] == null);
        this.addNotofication (word, wordId, listenerObj);
        if (bNewWord)
        {
            var reqId = this.loadWord(word);
            this.pending[word].reqId = reqId;
        }
    }

    return status;
}


NLSPDictionary.prototype.checkWords = function (words, wordIds, listenerObj)
{
    var newWords = new Array();
    for (var i=0; i<words.length; i++)
    {
        var word = words[i];
        if (this.hasWord(word))
            continue;

        var bCheckServer = (typeof this.pending[word] == "undefined" || this.pending[word] == null);
        if (bCheckServer)
            newWords[newWords.length] = word;
        this.addNotofication (word, wordIds[i], listenerObj);
    }

    if (newWords.length > 0)
    {
        var reqId = this.loadWord(newWords.join(" "));
        for (var i=0; i<newWords.length; i++)
            this.pending[newWords[i]].reqId = reqId;
    }
}



NLSPDictionary.prototype.addNotofication = function (word, wordId, listenerObj)
{
    if (typeof this.pending[word] == "undefined" || this.pending[word] == null)
    {
        this.pending[word] = new Object();
        listenerList = new Object();
        this.pending[word].listenerList = listenerList;
    }
    if (typeof listenerList[listenerObj.id] == "undefined")
    {
        listenerList[listenerObj.id] = new Object();
        listenerList[listenerObj.id].wordIdList = new Object();
    }
    listenerList[listenerObj.id].listener = listenerObj;
    listenerList[listenerObj.id].wordIdList[wordId] = "";
}


NLSPDictionary.prototype.loadWord = function (word, spellCheckerObj)
{
    var path = "/app/common/spellcheck.nl";
    var queryString = new Array();
    queryString["word"] = word;
    var  spellCheckerObjId = "";
    if (typeof spellCheckerObj == "object")
        spellCheckerObjId = spellCheckerObj.id;

    var reqId = (new Date()).getTime();
    nlXMLRequestURL( path, queryString, null, new Function ("response", "window." + this.id+ ".handleUpdateSpell(response, '" + spellCheckerObjId + "', " + reqId + ");"), true);
    return reqId;
}

NLSPDictionary.prototype.addWord = function (word)
{
    var path = "/app/common/spellcheck.nl?";
    var queryString = "addword=" + word;

    this.words[word] = true;

    nlXMLRequestURL( path + queryString, null, null, null, true);
}


/**
 * get suggestion list for the word given.
 * should be called only after checkWord()
 */
NLSPDictionary.prototype.getSuggestions = function (word)
{
    return this.words[word];
}

NLSPDictionary.prototype.handleUpdateSpell = function (response, spellCheckerObjId, reqId)
{
    var text = response.getBody();
    text = text.split( String.fromCharCode(5) )[0];

    try
    {
        eval("var pSuggestions = " + text);

        
        if (spellCheckerObjId != "" && typeof window[spellCheckerObjId] != "undefined" && window[spellCheckerObjId] != null)
            window[spellCheckerObjId].updateContentValidity(pSuggestions);

        
        for (word in this.pending)
        {
            if (this.pending[word] == null || this.pending[word].reqId != reqId)
                continue;
            this.updateSpell(word, pSuggestions[word]);
        }
    }
    catch (e) {}
}

NLSPDictionary.prototype.updateSpell = function (word, suggestions)
{
    if(suggestions != null)
        this.words[word] = suggestions;
    else
        this.words[word] = true;

    if (typeof this.pending[word] == "undefined" || this.pending[word] == null)
        return;
    listenerObjs = this.pending[word].listenerList;
    if (typeof listenerObjs == "object")
    {
        for (var listenerId in listenerObjs)
        {
            var listener = listenerObjs[listenerId].listener;
            for (var wordId in listenerObjs[listenerId].wordIdList)
            {
                if (typeof wordId == "string" && typeof listener == "object")
                    listener.setWordValidaty (wordId, this.words[word]==true? true:false );
            }
        }
    }
    delete this.pending[word];
}


NLSPDictionary.prototype.ignoreWord = function(word)
{
    
    if (this.quickChecker.checkWord(word) == true)
        return;
    this.updateSpell(word, null);
}

function addSpellChecker(textAreaId)
{
    if (document.all)
    {
        eval("window.attachEvent(\"onload\",  function(evnt){ new NLSpellChecker('" + textAreaId + "');});");
    }
    else
    {
        eval("window.addEventListener(\"load\",  function(evnt){ new NLSpellChecker('" + textAreaId + "');}, false);");
    }
}


function NLSpellChecker(textAreaName)
{
    var agt, isIe, isGecko, o, markerDiv, self;


    this.textArea = this.getTextAreaByName(textAreaName);
    if (this.textArea == null)
       return;

    this.textArea.spellchecker = this;
    this.textAreaName = textAreaName;
    this.id = (this.textArea.id == "" ? textAreaName : this.textArea.id)  + "_sc";
    window[this.id] = this;
    this.menu = null;
    this.nodeCount = 0;

    this.lastContent = ""; // the content of the text area
    this.startPos = 0;  // the start pos of the cursor in the text
	this.endPos =0;     // the end pos of the cursor in the text

    this.peindingCheck = null;
    this.words = new Array();  // the words that are set to be ignored in the editor

    /* Create markup container */
    this.markerDivId = textAreaName + "_md";
    this.markerDiv = null;
    this.dummyTextArea = null;
    this.setupMarkerDiv ();
    this.focusedNode = null; // the node that shows suggestion menu

    /* setup content provider */
    this.dict = window[DICT_ID];
    if ( typeof this.dict == "undefined" || this.dict == null)
    {
        window[DICT_ID] = new NLSPDictionary();
        this.dict = window[DICT_ID];
    }

	this.attachEventHandlers ();

    this.bContentReady = false;
    
    this.enableSpellChecker(true);
}

NLSpellChecker.prototype.getTextAreaByName = function (textAreaName)
{
    var textArea = null;
    /* Set initial values */
    for (var i=0; i < document.getElementsByName(textAreaName).length; i ++)
    {
        var elem = document.getElementsByName(textAreaName)[i];
        if (elem.tagName == 'TEXTAREA')
        {
            textArea  = elem;
            break;
        }
    }
    return textArea;
}


NLSpellChecker.prototype.setupMarkerDiv = function()
{
    var containerNode = this.textArea.parentNode;

    

    
    this.markerDiv = document.getElementById(this.markerDivId);
    if (this.markerDiv == null)
    {
        var table = document.createElement("TABLE");
        table.style.position = "absolute";
        table.cellPadding = 0;
        table.cellSpacing = 0;
        var tbody = document.createElement("TBODY");
        table.appendChild(tbody);
        var tr = document.createElement("TR");
        tbody.appendChild(tr);
        var td = document.createElement("TD");
        tr.appendChild(td);
        containerNode.insertBefore(table, this.textArea);

        this.markerDiv = document.createElement('div');
        this.markerDiv.id = this.markerDivId;
        this.markerDiv.classAlias = 'scrollarea';
        //containerNode.insertBefore(this.markerDiv, this.textArea);
        td.appendChild(this.markerDiv);
    }


    this.markerDiv.style.padding = this.textArea.style.padding;
    this.markerDiv.className = this.textArea.className;
	//this.markerDiv.style.whiteSpace = "pre";
    this.markerDiv.style.lineHeight="1.1em";
    this.markerDiv.style.backgroundColor = getRuntimeStyle(this.textArea.id, "backgroundColor")
   	this.markerDiv.style.color = this.markerDiv.style.backgroundColor;
    this.markerDiv.style.padding = getRuntimeStyle(this.textArea.id, "padding");
    this.markerDiv.style.borderWidth = getRuntimeStyle(this.textArea.id, "borderWidth");

    
    if (this.textArea.style.paddingLeft == "")
        this.markerDiv.style.paddingLeft = "2px";
    if (this.textArea.style.paddingRight == "")
        this.markerDiv.style.paddingRight = "2px";
    this.markerDiv.style.width = this.textArea.offsetWidth + 'px';
    this.markerDiv.style.height = this.textArea.offsetHeight + 'px';
    this.markerDiv.style.wordWrap="break-word";
    this.markerDiv.style.overflow="auto";
    

    
    if (this.dummyTextArea == null)
    {
        this.dummyTextArea = document.createElement("textarea");
        this.dummyTextArea.id = this.textArea.id + "dummy";
        this.dummyTextArea.setAttribute ("NAME", this.textArea.name + "dummy");
        containerNode.insertBefore(this.dummyTextArea, this.textArea.nextSibling);
    }
    this.dummyTextArea.className = this.textArea.className;
    this.dummyTextArea.style.height = this.textArea.style.height;
    this.dummyTextArea.style.width = this.textArea.style.width;
    this.dummyTextArea.height = this.textArea.height;
    this.dummyTextArea.width = this.textArea.width;
    this.dummyTextArea.rows = this.textArea.rows;
    this.dummyTextArea.cols = this.textArea.cols;
    this.dummyTextArea.disabled = true;
    this.dummyTextArea.style.visibility = "hidden";

    
    this.textArea.style.lineHeight="1.1em";
    this.textArea.style.position = "absolute";
    this.textArea.origBackgroundColor = this.textArea.style.backgroundColor;
    this.textArea.style.background = "transparent";
    this.oldTextAreaScrollTop = this.textArea.scrollTop;

    
    this.paddingNode = this.createBRNode();

    //this.renderSpellCheckButton(true);
};

NLSpellChecker.prototype.resize = function()
{
    if(this.markerDiv != null)
    {
        this.markerDiv.style.width = this.textArea.offsetWidth + 'px';
        this.markerDiv.style.height = this.textArea.offsetHeight + 'px';
    }
}

NLSpellChecker.prototype.renderSpellCheckButton = function (bEnable)
{
    if (typeof this.buttonSpan == "undefined" || this.buttonSpan == null)
    {
        this.buttonSpan = document.createElement('span');
        this.textArea.parentNode.insertBefore(this.buttonSpan, this.dummyTextArea.nextSibling);
    }

    var title = "Check Spelling";
    var action = "doSpellCheck( window['" + this.id + "'].textArea, this); return false;";
    var image = "/images/forms/abc.gif";

    this.buttonSpan.innerHTML = "<a class='smalltextul' title='" + title + "' tabindex=-1 href='#' onclick=\"" + action + "\"><img src='/images/forms/abc.gif' alt='" + title + "' border=0 align=absmiddle></a>";

    this.buttonSpan.style.position = "relative";
    
    this.buttonSpan.style.left = "3px";
    return this.buttonSpan;
}


NLSpellChecker.prototype.attachEventHandlers = function()
{
    if (document.all)
    {
        eval("this.textArea.attachEvent(\"onchange\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleChange(evnt);});");
        eval("this.textArea.attachEvent(\"onkeyup\", function(evnt){if (window." + this.id + ") return window." + this.id + ".handleUserInput(evnt);});");
        eval("this.textArea.attachEvent(\"oncut\", function(evnt){if (window." + this.id + ") return window." + this.id + ".handleCutPaste(evnt);});");
        eval("this.textArea.attachEvent(\"onpaste\", function(evnt){if (window." + this.id + ") return window." + this.id + ".handleCutPaste(evnt);});");
        eval("this.textArea.attachEvent(\"onclick\", function(evnt){if (window." + this.id + ") return window." + this.id + ".handleClick(evnt);});");
        eval("this.textArea.attachEvent(\"oncontextmenu\", function(evnt){if (window." + this.id + ") return window." + this.id + ".handleContextMenu(evnt);});");
        eval("this.textArea.attachEvent(\"onselect\", function(evnt){if (window." + this.id + ") return window." + this.id + ".handleSelect(evnt);});");
        eval("this.textArea.attachEvent(\"onscroll\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleScroll(evnt);});");
    }
    else
    {
        eval("this.textArea.addEventListener(\"change\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleChange(evnt); return true;}, false);");
        eval("this.textArea.addEventListener(\"contextmenu\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleContextMenu(evnt); return true;}, false);");
        //this.textArea.addEventListener("keyup", eval("return window." + this.id + ".handleUserInput(evnt)"), false);
        eval("this.textArea.addEventListener(\"input\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleUserInput(evnt); return true;}, false);");
        eval("this.textArea.addEventListener(\"click\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleClick(evnt); return true;}, false);");
        //this.textArea.addEventListener("mouseup", eval("window." + this.id + ".handleMouseUp(evnt)"), false);
        eval("this.textArea.addEventListener(\"select\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleSelect(evnt); return true;}, false);");
        //this.textArea.addEventListener("mousemove", eval("window." + this.id + ".handleScroll(evnt)"), false);
        eval("this.textArea.addEventListener(\"scroll\", function(evnt){if (window." + this.id + ") window." + this.id + ".handleScroll(evnt);}, false);");
    }
}

NLSpellChecker.prototype.initContent = function()
{
    if (!this.bEnabled)
         return;
    this.bContentReady = false;

    if (trim(this.textArea.value).length > 0)
        this.dict.loadWord(this.textArea.value, this);

    while (this.markerDiv.firstChild)
        this.markerDiv.removeChild(this.markerDiv.firstChild);

    this.lastContent = this.textArea.value;

    this.generateNodes(null, this.textArea.value);

    if (this.lastContent.charAt(this.lastContent.length - 1) == "\n")
    {
        if (this.markerDiv.lastChild != this.paddingNode)
            this.markerDiv.appendChild(this.paddingNode);
    }

    this.adjustScrollBar();
    this.bContentReady = true;
}


NLSpellChecker.prototype.enableSpellChecker = function (bEnable)
{
    this.markerDiv.style.visibility = bEnable ? "visible" : "hidden";
    //this.renderSpellCheckButton(!bEnable);
    this.textArea.style.backgroundColor = bEnable ? "transparent" : "#ffffff";

    
    this.bEnabled = bEnable;

    if (bEnable)
    {
        this.textArea = this.getTextAreaByName(this.textAreaName);
        if (this.textArea == null)
            return;
        this.initContent();
    }
    else
    {
        if (this.menu !== null)
        {
            this.menu.close();
            this.menu = null;
        }
    }
}

/**
 * break content into words(to be checked) and generate nodes for each of the words
 * @param lastNode the node to insert the new nodes in front of
 * @param content the string to parse into words
 */
NLSpellChecker.prototype.generateNodes = function(lastNode, content)
{
    var node = null;

    node = this.parseString(lastNode, content);
    return node;
};


NLSpellChecker.prototype.createTextNode = function(textStr, elem)
{
    var textNode;
    textStrArray = textStr.split("\r\n");
    for(i = 0; i < textStrArray.length; i++)
    {
        if (i>0)
        {
            textNode = this.createBRNode();
            if (elem)
                this.markerDiv.insertBefore(textNode, elem);
            else
                this.markerDiv.appendChild(textNode);
        }

        var text = textStrArray[i];
        if (text != "")
        {
            textNode = document.createElement('pre');// document.createTextNode(textStr);
            textNode.style.display="inline";
            textNode.style.fontFamily = this.textArea.currentStyle['fontFamily'];
            textNode.style.fontStyle = this.textArea.currentStyle['fontStyle'];
            textNode.style.fontWeight = this.textArea.currentStyle['fontWeight'];
            textNode.style.fontSize = this.textArea.currentStyle['fontSize'];
            textNode.style.background = "none";
            textNode.appendChild(document.createTextNode(textStrArray[i]));
            if (elem)
                this.markerDiv.insertBefore(textNode, elem);
            else
                this.markerDiv.appendChild(textNode);
        }
    }
    return textNode;
}

NLSpellChecker.prototype.createBRNode = function()
{
    var textNode = document.createElement("br");
    textNode.style.fontFamily = this.textArea.currentStyle['fontFamily'];
    textNode.style.fontStyle = this.textArea.currentStyle['fontStyle'];
    textNode.style.fontWeight = this.textArea.currentStyle['fontWeight'];
    textNode.style.fontSize = this.textArea.currentStyle['fontSize'];
    textNode.style.background = "none";
    textNode.rawValue = "\r\n";
    return textNode;
}

NLSpellChecker.prototype.createWordNode = function(wordStr, elem, bCheckSpell)
{
    var wordNode = null;
    
    if (elem && elem.tagName == "SPAN" && !bCheckSpell)
    {
        wordNode = elem;
        wordNode.innerHTML = wordStr;
    }
    else
    {
        var wordNode = document.createElement('span');
        wordNode.id = this.generateNodeId();
        wordNode.style.background = 'none';
        wordNode.style.paddingBottom = '1px';
        wordNode.appendChild(document.createTextNode(wordStr));

        if (elem)
            this.markerDiv.insertBefore(wordNode, elem);
        else
            this.markerDiv.appendChild(wordNode);
    }

    if (bCheckSpell)
    {
        if (this.checkWord(wordStr, wordNode.id, this, this.bContentReady) == SP_INVALID_WORD)
            this.setWordValidaty(wordNode.id, false);
    }
    else
    {
        if (wordNode != this.pendingCheck)
        {
            
            if (typeof this.pendingCheck != "undefined" && this.pendingCheck != null)
            {
                var status = this.checkWord(this.pendingCheck.firstChild.nodeValue, this.pendingCheck.id, this, this.bContentReady);
                if (status == SP_INVALID_WORD)
                    this.setWordValidaty(this.pendingCheck.id, false);
            }
            this.pendingCheck = wordNode;
            
            this.setWordValidaty(this.pendingCheck.id, true);
        }
    }

    return wordNode;
};


NLSpellChecker.prototype.checkWord = function (word, wordId, listenerObj, bCheckServer)
{
    if (this.words[word] == true)
        return SP_VALID_WORD;

    return this.dict.checkWord(word, wordId, listenerObj, bCheckServer);
}

NLSpellChecker.prototype.updateContentValidity = function (suggestions)
{
    var node = this.markerDiv.firstChild;
    while ( node!= null)
    {
        if (node.nodeType == 1 && node.tagName != "PRE" && node.tagName != "BR")
        {
            if (typeof suggestions[node.firstChild.nodeValue] != "undefined")
                this.setWordValidaty(node.id, false );
            else if (!this.dict.hasWord(node.firstChild.nodeValue))
                this.dict.ignoreWord(node.firstChild.nodeValue)
        }
        node = node.nextSibling;
    }
}

NLSpellChecker.prototype.setWordValidaty = function (wordId, bValid)
{
    //aNodes = this.markerDiv.getElementsByTagName('span');
    wordNode = document.getElementById(wordId);
    if (wordNode == null)
        return;

    if (!bValid)
    {
        wordNode.style.background = CSS_UNDERLINE;
        wordNode.onclick = new Function("window." + this.id + ".showSuggestions(this, '" + escapeJSChars(wordNode.innerHTML) + "');");
    }
    else
    {
        wordNode.style.background = "";
        wordNode.onclick = null;

    }
}


NLSpellChecker.prototype.generateNodeId = function ()
{
    return NODE_ID_PREFIX + this.id + this.nodeCount++;
}

NLSpellChecker.prototype.handleChange = function (evnt)
{
    if (this.menu != null)
        return;
    this.handleUserInput(evnt);
}

NLSpellChecker.prototype.handleKeyUp = function (evnt)
{

}

NLSpellChecker.prototype.handleScroll = function (evnt)
{
    this.adjustScrollBar();
}

NLSpellChecker.prototype.handleCutPaste = function (evnt)
{
    setTimeout(this.handleUserInput.bind(this), 0);
}
/**
 * handle user mouse and keybaord input
 */
NLSpellChecker.prototype.handleUserInput = function (evnt)
{
    if (!this.bEnabled)
        return true;

    var lastStartPos = this.startPos;
    var lastEndPos   = this.endPos;
    var content = this.textArea.value;
    this.getActiveNodes();

    if (this.bContentReady)
    {
        if ( this.lastContent.length != content.length || this.lastContent != content)
        {

            // The user selected something, remove deleted/replaced text
            if (lastEndPos > lastStartPos)
                this.remove(lastStartPos, lastEndPos);

            // Remove text erased by backspace
            else if (lastEndPos > this.startPos )
                this.remove(this.startPos, lastEndPos);

            // Append/insert new text
            else if (this.startPos > lastStartPos)
                this.insert(lastStartPos, this.startPos);
            else
                this.initContent();
        }
    }
    else
        this.initContent();

    if (content.charAt(content.length - 1) == "\n")
    {
        if (this.markerDiv.lastChild != this.paddingNode)
            this.markerDiv.appendChild(this.paddingNode);
    }
    else
    {
        if (this.paddingNode.parentNode == this.markerDiv)
            this.paddingNode.parentNode.removeChild(this.paddingNode);
    }

    this.lastContent  = content;

    if (this.menu != null)
    {
        this.menu.close();
        this.menu = null;
    }

    this.handleScroll(evnt);

    return true;
}


NLSpellChecker.prototype.handleContextMenu = function (evnt)
{
    if (this.textArea.readOnly || this.textArea.disabled || !this.bEnabled)
        return true;

    this.getActiveNodes();

    var evnt = getEvent(evnt);

    if (this.endNode && this.endNode.firstChild != null &&
         this.checkWord(this.endNode.firstChild.nodeValue, this.endNode.id, this) == SP_INVALID_WORD &&
         this.focusedNode != this.endNode )
    {
        setTimeout(new Function("window." + this.id + ".showSuggestions(\"" + this.endNode.firstChild.nodeValue + "\");"), 0);
        this.focusedNode = this.endNode;
        evnt.returnValue = false;
        return false;
    }
    else
    {
        this.focusedNode = null;
        this.closeMenu();
    }


    return true;
}

NLSpellChecker.prototype.closeMenu = function NLSpellChecker_closeMenu (bSetCursurPos)
{
    if (this.menu != null)
    {
        this.menu.close();
        this.menu = null;
    }
    this.textArea.focus();
    if (bSetCursurPos)
        this.setCursorPosition(this.textArea, this.endPos, this.endPos);
}

NLSpellChecker.prototype.handleSelect = function (evnt)
{
    this.getActiveNodes();
}

NLSpellChecker.prototype.handleClick = function (evnt)
{
    if (this.menu !== null)
    {
        this.menu.close();
        this.menu = null;
    }
    this.handleSelect(evnt);
}

NLSpellChecker.prototype.adjustScrollBar = function()
{

    if (this.textArea.scrollHeight > this.textArea.clientHeight)
        this.markerDiv.style.overflowY = 'scroll';
    else
        this.markerDiv.style.overflowY = getRuntimeStyle(this.textArea, "overflowY");

    if (this.textArea.scrollWidth > this.textArea.clientWidth)
        this.markerDiv.style.overflowX = 'scroll';
    else
        this.markerDiv.style.overflowX = getRuntimeStyle(this.textArea, "overflowX");


    this.markerDiv.scrollTop = this.textArea.scrollTop;
    this.markerDiv.scrollLeft = this.textArea.scrollLeft;
}

NLSpellChecker.prototype.insert = function NLSpellChecker_insert(startPos, endPos)
{
    var str;
    var startNode = null;
    var offset = -1;

    // Locate start node and determine offset
    var startNodeInfo = this.getStartNode(startPos);
    if (startNodeInfo != null)
    {
        startNode =  startNodeInfo[0];
        offset = startNodeInfo[1];
    }

    var str = this.textArea.value.substring(startPos, endPos);
    if (startNode != null)
    {
        var nodeValue = this.getNodeValue(startNode);
        var word = nodeValue.substr(0, offset) + str + nodeValue.substr(offset, nodeValue.length);
        this.updateNodeContent(startNode, word);
    }
    else
        this.generateNodes(null, str);
};


NLSpellChecker.prototype.getStartNode = function (startPos)
{
    var startNode = null;
    var len = 0;
    var offset = -1;
    var node = this.markerDiv.firstChild;
    while ( node!= null)
    {
        str = this.getNodeValue(node);
        var n = str.length;
        if (len + str.length >= startPos)
        {
            startNode = node;
            offset = startPos - len;
            break;
        }
        len += n;
        node = node.nextSibling;
    }
    return [startNode, offset];
}


NLSpellChecker.prototype.updateNodeContent = function (updateNode, content)
{
    var lastNode = this.parseString(updateNode, content);

    if (lastNode != updateNode)
    {
        // remove current node
        lastNode = updateNode.previousSibling;
        updateNode.parentNode.removeChild(updateNode);
    }
    return lastNode;
}

NLSpellChecker.prototype.parseString = function(updateNode, content)
{
    var bWord = false;
    var wordStr = "";
    var textStr = "";
    var node = null;

    var words = new Array();
    var wordIds = new Array();
    for (var i = 0; i < content.length; i++)
    {
        var c = content.substr(i, 1);
        if (c.match(/[\w']/))         // Match alphanumerical character
        {
            if (textStr != "")
            {
                node = this.createTextNode(textStr, updateNode);
                textStr = "";
            }
            wordStr += c;
        }
        else
        {
            if (wordStr != "")
            {
                this.bContentReady = false;
                node = this.createWordNode(wordStr, updateNode, true);
                this.bContentReady = true;
                if(!this.dict.hasWord (wordStr))
                {
                    words[words.length] = wordStr;
                    wordIds[wordIds.length] = node.id;
                }
                wordStr = "";
            }
            textStr += c;
        }
    }

    
    if (words.length > 0)
        this.dict.checkWords(words, wordIds, this);

    if (textStr != "")
        node = this.createTextNode(textStr, updateNode);
    else if (wordStr != "")
        node = this.createWordNode(wordStr, updateNode, false);

    return node;
}

NLSpellChecker.prototype.remove = function NLSpellChecker_remove(startPos, endPos)
{
    if (this.startNode == null)
        return;

    var startNodeStr = "";
    var endNodeStr = "";
    /* Locate start and end node and determine what to keep of first and last node */
    var len = 0;
    startNode = endNode = null;
    var node = this.markerDiv.firstChild;
    if (node == null)
        return;
    while ( node != null)
    {
        str = this.getNodeValue(node);
        var n = str.length;

        if ((startNode == null) && (len + n >= startPos))
        {
            startNode = node;
            word = str.substr(0, startPos - len);
        }
        if (len + n >= endPos)
        {
            endNode = node.nextSibling;
            if (this.startPos > startPos)
                word += this.textArea.value.substring(startPos, this.startPos);
            word +=  str.substr(endPos - len, n - (endPos - len));
            break;
        }

        len += n;
        node = node.nextSibling;
    }

    /* Remove all but first node */
    if (startNode == null)
        return;
    node = startNode.nextSibling;
    while ( node != endNode)
    {
        var nextNode = node.nextSibling;
        this.markerDiv.removeChild(node);
        node = nextNode;
    }
    // if both start and end node are word nodes, combine them
    if (startNode.nodeType == 1 && endNode != null && endNode.nodeType == 1 && endNode.firstChild)
    {
        word += endNode.firstChild.nodeValue;
        this.markerDiv.removeChild(endNode);
    }


    /* update word */
    this.updateNodeContent(startNode, word);
}

NLSpellChecker.prototype.getActiveNodes = function()
{
    this.nodeStartPos = -1;
    this.nodeEndPos = -1;

    if (!this.getSelectedTextRange())
        return;

    node = this.markerDiv.firstChild;
    this.startNode = node;
    this.endNode = node;

    var curPos = 0;
    var text = "";
    while (node != null)
    {
        text = this.getNodeValue(node);

        if (curPos + text.length <= this.startPos)
            this.startNode = node;

        this.endNode = node;
        if (curPos + text.length >= this.endPos)
            break;

        curPos += text.length;
        node = node.nextSibling;
    }
    if (this.pendingCheck != null && this.startNode != this.pendingCheck && this.endNode != this.pendingCheck && this.pendingCheck.firstChild != null)
    {
        var status = this.checkWord(this.pendingCheck.firstChild.nodeValue, this.pendingCheck.id, this);
        if (status == SP_INVALID_WORD)
        {
            this.setWordValidaty(this.pendingCheck.id, false);
            this.pendingCheck = null;
        }

    }
}

// correction  related
NLSpellChecker.prototype.showSuggestions = function (word)
{
    if(this.menu!= null)
    {
        this.menu.close();
        this.menu = null;
    }

    var elem = this.endNode;
    var word = this.endNode.firstChild.nodeValue;

    var suggestions = this.dict.getSuggestions(word).suggestions;

    //create suggestion list
    var values = new Array();
    var value;
    if (suggestions.length == 0)
    {
        value = new Array();
        value[0] = NO_SUGGESTION;
        value[1] = "";
        value[2] = "";
        values[values.length] = value;
    }
    for (var i = 0; i < suggestions.length; i++)
    {
        value = new Array();
        value[0] = suggestions[i];
        value[1] = "javascript:window." + this.id + ".replaceWord('" + elem.id + "', '" + value[0] + "'); window." + this.id + ".closeMenu(true);";
        value[2] = "";
        values[values.length] = value;
    }

    value = new Array("ddmSeperator", "ddmSeperator", "");
    values[values.length] = value;

    // ignore
    value = new Array();
    value[0] = IGNORE;
    value[1] = "javascript:window." + this.id + ".ignore('" + elem.id + "', false); window." + this.id + ".closeMenu(true);";
    value[2] = "";
    values[values.length] = value;

    // ignore all
    value = new Array();
    value[0] = IGNORE_ALL;
    value[1] = "javascript:window." + this.id + ".ignore('" + elem.id + "', true); window." + this.id + ".closeMenu(true);";
    value[2] = "";
    values[values.length] = value;

    // add to dictionary
    value = new Array();
    value[0] = ADD_TO_DIC;
    value[1] = "javascript:window." + this.id + ".addToDictionary('" + elem.id + "', true); window." + this.id + ".closeMenu(true);";
    value[2] = "";
    values[values.length] = value;

    value = new Array("ddmSeperator", "ddmSeperator", "");
    values[values.length] = value;

    // hide
    value = new Array();
    value[0] = HIDE;
    value[1] = "javascript:window." + this.id + ".enableSpellChecker(false); window." + this.id + ".closeMenu(true);";
    value[2] = "";
    values[values.length] = value;

    this.menu = new NLMenu(this.id, values, 0, null);
    window.menus[this.id] = this.menu;
    this.menu.span = elem;
    this.menu.showHide(false);
}


NLSpellChecker.prototype.replaceSpellCheckerWord = function (nodeId, word, replaceWord, bReplaceAll)
{
    this.words[word] = true;
    var node = document.getElementById(nodeId);
    if (node == null)
        node = this.endNode;

    if (node != null)
    {
        this.updateNodeContent(node, replaceWord);
        this.setWordValidaty(node.id, true);
    }

    if (bReplaceAll)
    {
        for (node = this.markerDiv.firstChild; node; node = node.nextSibling)
        {
            if (node.nodeType == 1 && node.firstChild != null && node.firstChild.nodeValue == word)
            {
                this.updateNodeContent(node, replaceWord);
                this.setWordValidaty (node.id, true);
            }
        }
    }
}

NLSpellChecker.prototype.replaceWord = function (nodeId, replaceWord)
{
    this.dict.ignoreWord(replaceWord);
    if (this.endNode)
    {
        this.updateNodeContent(this.endNode, replaceWord);
        this.setWordValidaty(this.endNode.id, true);

        var str = this.textArea.value;
        var len = this.textArea.value.length;
        var offset = this.endPos;
        var c = "";
        for (n = offset-2; n >= 0; n--)
        {
            c = str.substr(n, 1);
            if (!c.match(/[\w]/))
                break;
        }
        var start = n+1;

        for (n = offset; n < len; n++)
        {
            c = str.substr(n, 1);
            if (!c.match(/[\w\']/))
                break;
        }
        var end = n;
        this.startPos = start + replaceWord.length;
        this.endPos = this.startPos;
        this.textArea.value = str.substr(0, start) + replaceWord + str.substr(end, len-end);
    }
}

NLSpellChecker.prototype.ignore = function (nodeId, bIgnoreAll)
{
    var node = document.getElementById(nodeId);
    if (node == null)
        node = this.endNode;

    if (node == null)
       return;

    var word = node.firstChild.nodeValue;

    this.setWordValidaty (node.id, true);

    if (bIgnoreAll)
    {
        this.words[word] = true;

        for (node = this.markerDiv.firstChild; node; node = node.nextSibling)
        {
            if (node.nodeType == 1 && node.firstChild != null && node.firstChild.nodeValue == word)
                this.setWordValidaty (node.id, true);
        }
    }
}


NLSpellChecker.prototype.addToDictionary = function (nodeId)
{
    var node = document.getElementById(nodeId);
    if (node == null)
        node = this.endNode;

    if (node == null)
        return;

    var word = node.firstChild.nodeValue;

    this.dict.addWord(word);
    this.ignore(nodeId, true);
}

NLSpellChecker.prototype.getSelectedTextRange = function()
{
    var bSelected = true;
    if (document.all)
    {
        if (document.selection.type == "none")
            return false;
        try
        {
            var selectedText = document.selection.createRange();
            var range = selectedText.duplicate();
            range.moveToElementText(this.textArea);
            if (!range.inRange(selectedText))
                return false;
            range.setEndPoint('EndToEnd', selectedText);
            var selectedTextLen = this.getTextRangeCharLength (selectedText);
            this.startPos = this.getTextRangeCharLength (range) - selectedTextLen;
            this.endPos   = this.startPos + selectedTextLen;

        } catch (e)
        {
            return false;
        }
    }
    else
    {
        this.startPos = this.textArea.selectionStart;
        this.endPos   = this.textArea.selectionEnd;
    }
    return bSelected;
}


NLSpellChecker.prototype.getTextRangeCharLength = function(range)
{
    var numChars = 0;

    
    rangeTmp = range.duplicate();
    while(true)
    {
        rangeTmp.moveEnd("character", -1);
        if (rangeTmp.text.length != range.text.length || !range.inRange(rangeTmp))
            break;
        else
            numChars++;
    }

    
    
    if (typeof window.event != "undefined" && window.event != null && window.event.type=="click" && numChars >= 2)
        this.bContentReady = false;

    
    return numChars*2 + range.text.length;
}

NLSpellChecker.prototype.setCursorPosition = function (oInput,oStart,oEnd)
{
    if( oInput.setSelectionRange )
        oInput.setSelectionRange(oStart,oEnd);
    else if( oInput.createTextRange )
    {
        var range = oInput.createTextRange();
        range.collapse(true);
        range.moveEnd('character',oEnd);
        range.moveStart('character',oStart);
        range.select();
     }
}


NLSpellChecker.prototype.getNodeValue = function (node)
{
    var str = "";
    if (node.nodeType == 1) //element node such as <BR> or <SPAN>, not text node
    {
        if (typeof node.rawValue == "undefined" || node.rawValue == null)
            str = (node.firstChild) ? node.firstChild.nodeValue : "\n";
        else
            str = node.rawValue;
    }
    else
        str = node.nodeValue;

    return str;
}



function copyRuntimeStyle(fromElemId, toElemId)
{
	var fromElem = document.getElementById(fromElemId);
    var toElem = document.getElementById(toElemId);

    if (fromElem == null || toElem == null)
        return;

    if (fromElem.currentStyle)
    {
        for (var prop in fromElem.currentStyle)
        {
            try {
                toElem.style[prop] = currentStyle[styleProp];
            }
            catch (e) {
                
            }
        }
    }
    else if (window.getComputedStyle)
    {
        var properties = document.defaultView.getComputedStyle(fromElem, null);
        for (var i = 0; i < properties.length; i++)
        {
            try {
                toElem.style[prop] = properties.item(i)
            }
            catch (e) {
                
            }
        }
    }
}

function escapeJSChars (content)
{
    var returnStr = '';
    for (var i = 0; i < content.length; i++)
    {
        var c = content.substr(i, 1);
        if (c == "\'" || c== "\"" || c== "\\")
            returnStr += "\\";

        returnStr += c;
    }
    return returnStr;
}


