/*	
 Copyright 2013 Adobe Systems Incorporated.  All rights reserved. 

Purpose- 
This file has the implementation of Text editing functionality in Live Edit view

AdobePatentID="4273US01"
*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50 */
/*global globalController, DWTextEditUtility, DW_LIBRARY_ITEM, ENTER_POST_PROCESSING_ACTIONS, CustomEvent, DW_LIVE_TEXT_EDIT, DW_LIVEEDIT_CONSTANTS, DW_LIVEEDIT_EVENTS, DW_EVENTS, DW_ICE_HEADLIGHTS, window, Node, TextEditHud, GenericHUDObject, DW_EDITABLE_REGION_CONSTS, document,dwExtensionController, liveViewExtensionsObject, DW_EXTENSION_EVENT, SVGElement*/

//TextEditHud instantiation
TextEditHud.prototype = new GenericHUDObject();
TextEditHud.prototype.constructor = TextEditHud;
TextEditHud.prototype.baseClass = GenericHUDObject.prototype.constructor;

//TextEditHud class decleration
function TextEditHud(liveEditEvt) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : constructor");
    this.setHudName(DW_LIVEEDIT_CONSTANTS.HudTextEdit);
    this.initialize(liveEditEvt);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.DoubleClick, this.enableHud, this);

    // TextEditHud class member initialization.
    this.m_elementUnderCurrentEditingSession = null;
    this.m_originalInnerHTMLOfElementUnderCurrentEditingSession = "";

	//Store prev and next sibling for giving dwid's for each tempId
    this.dwSpanPrevSibling = null;
    this.dwSpanNextSibling = null;
    this.m_textEditFeedbackCss = DW_LIVEEDIT_CONSTANTS.OverlayCSS_TextEdit;

    // Current selected text node
    this.m_curTextNode = null;

	// This variable will have a reference to anchor tag if double click was on anchor tag or 
	// enter key is pressed when ESH is show on anchor
	this.m_nodeAtClickPoint = null;
	this.m_nodeAtClickPointInnerHTML = null;

    // Context which contains current text grouping info.
    this.m_textGroupContext = null;

    // Identification info to locate the text group in DW context.
    this.m_textIdentifier = null;

    // To track the any asynchronous request sent to DW
    this.m_dwReqCount = 0;

    // Updating text group would cause change in DW DOM tree.
    // Temp Id counter will be used to generate temp dw ids and 
    // to sync back the new data_liveedit_tagid values into live view DOM.
    this.m_dwgTempElementCount = 0;

    // Cache to reduce element search during editing process.
    this.m_nodeCache = null;

    // This signifies that there's a pending save document call to be done post commit and destroy
    this.saveDocumentPending = false;

    this.refreshDocumentPending = false;

    // To remember the encoded entities
    this.m_encodedEntities = {};
    // state of Formatting Hud
    this.m_lastSelection = null;
	// This flag specifies what post processing action to be performed on Enter key press.
	this.m_enterPostProcessingAction = null;

	// This flag holds the orginal tag name for tags who creates nested p tags on enter.
	this.m_blockLevelTagNameBeforeEnter = null;

	// static attributes - set by dwEditableCallback function.
	this.m_staticAttributes = null;

	// We introduce &nbsp; to make some elments editable
	// while entering into editing. Remember and remove them before
	// exiting from text editing session.
	this.m_mapTempNBSPsForEditing = {};

	// Some time we need to redraw the element when we exit from
	// text editing session.
	this.m_forceRedraw = false;
	this.m_elementInRedraw = null;
	this.m_displayStyleOfElementInRedraw = null;

	// To remember clientX and clientY when editing session
	// is not ready. 
	this.m_lastClientX = null;
	this.m_lastClientY = null;

	// Help Id for Text Hud
	this.HelpId = "AH_TEXT_HUD";
	// to track mosueout during selection
    this.isMouseDown = false;

	// Will be true if groping results in putting dw-span directly around text.
	this.m_deleteTagOnDeletingAllContent = false;

    //to track pressing delete and backspace keys
    this.m_backspacePressed = false;
    this.m_deletePressed = false;
	this.m_undoGroupEnabled = false;
    
    // for triple click
    this.m_clickCounter = 0;
    this.m_selectTimer = null;
    this.m_clickTimer = null;
    this.m_preservedClickPosition = {x:null, 
        y:null
    };
    this.m_element = null;
}

TextEditHud.prototype.saveDocument = function () {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : saveDocument");
    this.saveDocumentPending = true;
};

TextEditHud.prototype.refreshDocument = function () {
    'use strict';
    this.logDebugMsg("function begin : TextEditHud : refreshDocument");
    this.refreshDocumentPending = true;
};

/**
 * @private forceRedraw
 * Using hacky way to force redraw and relayouting for element.
 * @param elem : input element
 */
TextEditHud.prototype.forceRedraw = function (element) {
	'use strict';
    if (!element) {
		return;
	}

    var textNode = document.createTextNode(' '),
		disp = element.style.display;

    element.appendChild(textNode);
    element.style.display = 'none';

	this.m_elementInRedraw = element;
	this.m_displayStyleOfElementInRedraw = disp;

    setTimeout(function () {
        element.style.display = disp;
        textNode.parentNode.removeChild(textNode);

		// Just to make sure we don't leave empty style
		var style = element.getAttribute("Style");
		if (style && style.trim() === "") {
			element.removeAttribute("Style");
		}

		// When redraw happens while douple click on
		// some other element, it would have got affected
		// because of style.display = 'none'
		// Show overlay again.
		if (this.getVisibility()) {
			this.showTextEditingFeedback();
		}

		this.m_elementInRedraw = null;
		this.m_displayStyleOfElementInRedraw = null;
    }.bind(this), DW_LIVEEDIT_CONSTANTS.RedrawTimeOut);
};

// Override - commit - commits the changes back to user document.
TextEditHud.prototype.commit = function () {
    'use strict';

	this.logDebugMsg("function begin : TextEditHud : commit");

    if (this.getVisibility() === false) {
        return;
    }
    // Commit the changes back to user document.
    var textToCommitBack = null,
        dwSpanElement = this.m_elementUnderCurrentEditingSession,
		addedTempId = false;

    if (dwSpanElement) {
        if (window.liveViewExtensionsObject) {
            var isTextFormatHudVisible = false;
            isTextFormatHudVisible = window.liveViewExtensionsObject.isExtensionVisible("TextFormattingHud");
            DWTextEditUtility.cleanUpContent(dwSpanElement);
            if (isTextFormatHudVisible) {
                var txtFormatIframe = window.liveViewExtensionsObject.getExtensionIFrameById("TextFormattingHud");
                if (txtFormatIframe) {
                    var contWind = txtFormatIframe.contentWindow;
                    if (contWind && contWind.textFormattingHudObj) {
                        contWind.textFormattingHudObj.checkIfLinkChanged();
                        if (contWind.textFormattingHudObj.m_changedCode) {
                            // moved this function from text formatting hud to text edit. This removes extra span tag tags browser added,
                            // respects DW preference about strog/b tag.
                            // and also remove the span-id's that we added.
                            this.clearUndoRedoOperations();
                            contWind.textFormattingHudObj.m_changedCode = false;
                        }
                    }
                }
            }
        }
        if (dwSpanElement.innerHTML !== this.m_originalInnerHTMLOfElementUnderCurrentEditingSession) {

            //if dwSpanElement contains only an &nbsp; and if the original tag was surrounded by dwSpanElement
            //then that means the tag itself was deleted.
            if (dwSpanElement.innerHTML === DW_LIVEEDIT_CONSTANTS.NonBreakingSpace && this.m_deleteTagOnDeletingAllContent) {
                dwSpanElement.innerHTML = '';
            }
            // If browser has left the any artefacts like &nbsps, duplicate Id attributes -- trim them off.
            DWTextEditUtility.cleanUpArtifactsOfEnterKeyPress(dwSpanElement);

            // When user selects all the text and deletes it,
            // browser inserts <br>. Ignoring it.
            DWTextEditUtility.removeUnwantedBRTag(dwSpanElement);

            // Create temp IDs for all nodes
            addedTempId = this.createTempIdS(dwSpanElement);

			// Clone node to do clean up before commiting.
			// We should not touch original element.
			var dwSpanClone  = dwSpanElement.cloneNode(true);

			// clean up &nbsp; that we added.
			this.cleanNBSPsIntroducedForEditing(dwSpanClone);

			// Remove dynamic attributes
			DWTextEditUtility.removeDynamicAttribute(dwSpanClone,  this.m_staticAttributes);

			// Text encoded with entity.
            textToCommitBack = this.getEntityEncodedInnerHTMLStr(dwSpanClone);

		} else {
            if (this.saveDocumentPending) {
                window.DWSaveDocument();
                this.saveDocumentPending = false;
            }
            if (this.refreshDocumentPending) {
                window.DWLECallJsBridgingFunction(this.getHudName(), "DwRefreshDocument", null, false);
                this.refreshDocumentPending = false;
            }
        }
    }

    if (textToCommitBack !== null) {
        if (!this.m_nodeCache) {
            this.m_nodeCache = {};
        }

        var idInfoObj = {},
            argObj = {},
            parentId = dwSpanElement.parentNode.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);

        this.m_nodeCache[parentId] = dwSpanElement.parentNode;

        // Temp ID information object
        idInfoObj.parentId = parentId;
		// so now here we need to send spider monkey layer previosu element node, next element node, so that 
		// we can just look at this node and get dwid's for that.
        idInfoObj.prevSibId = "";
        idInfoObj.nextSibId = "";

        if (dwSpanElement.previousElementSibling !== null) {
            var pId = dwSpanElement.previousElementSibling.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
            if (pId !== null) {
                idInfoObj.prevSibId = pId;
            }
        }
        if (dwSpanElement.nextElementSibling !== null) {
            var id = dwSpanElement.nextElementSibling.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
            if (id !== null) {
                idInfoObj.nextSibId = id;
            }
        }

        idInfoObj.callback = function (newIdObj) {
            this.updateDWIdForTempIDcallback(newIdObj);
        }.bind(this);

		if (addedTempId) {
			idInfoObj.lastTempId = this.m_dwgTempElementCount;
		}

        // Update temp Id Information object in identifier
        this.m_textIdentifier.tempIdInfoObj = idInfoObj;

        // Call DW Bridge function to update the text.
        argObj.textId = this.m_textIdentifier;
        argObj.textToCommit = textToCommitBack;
        window.DWLECallJsBridgingFunction(this.getHudName(), "DWSMUpdateLiveText", argObj, true);

		// Remember the inner HTML string to avoid recommit if any other commit call comes immediately.
		this.m_originalInnerHTMLOfElementUnderCurrentEditingSession = dwSpanElement.innerHTML;

		// Reposition all ER HUDs again and make them visible
        this.m_liveEditEvent.fire({
            type: DW_LIVEEDIT_EVENTS.ElementChanged
        });
    }

    GenericHUDObject.prototype.commit.call(this);
};

//  Override - draw - shows the editingFeedback UI around the element under edit 
//	and sets up the editing session
TextEditHud.prototype.draw = function (event, enteringTextEditingFromEnter) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : draw ");

    if (this.getVisibility() === false) {
        return;
    }

    if (!this.m_curTextNode) {
        return;
    }

	// Enable editing after asyncronous validation.
	if (!this.ValidateAndEnableEditing(enteringTextEditingFromEnter)) {
		this.handleValidationFailure();
	}

    GenericHUDObject.prototype.draw.call(this);
};


//  Override - destroy - removes the editing feedback UI
TextEditHud.prototype.destroy = function () {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : destroy");

    if (this.getVisibility() === false) {
        return;
    }

	this.exitTextEditingSession(true);

	// Update Edit Ops
	window.dwUpdateEditOps();

	GenericHUDObject.prototype.destroy.call(this);
};

// Functions: storeContextForESHSelection & restoreESHSelectionFromContext
// Inroducing these functions for restoring selection on same element when
// we press escape key in text editing (for bug #4106706)
// TODO: clean FindElementToBringbackESHHud & BringbackESHHud and use these
// functions.
TextEditHud.prototype.storeContextForESHSelection = function () {
	'use strict';
	this.eshContext = null;
    if (this.m_elementUnderCurrentEditingSession) {
		this.eshContext = {};
		this.eshContext.parentNode = this.m_elementUnderCurrentEditingSession.parentNode;
		this.eshContext.previousSibling = this.m_elementUnderCurrentEditingSession.previousSibling;
	}
};

TextEditHud.prototype.restoreESHSelectionFromContext = function () {
	'use strict';
    if (this.eshContext) {
		var eshElement = null;
		if (this.eshContext.previousSibling) {
			eshElement = this.eshContext.previousSibling.nextSibling;
		} else if (this.eshContext.parentNode) {
			eshElement = this.eshContext.parentNode.firstChild;
		}

		var selObj = window.getSelection();
		if (eshElement && selObj) {

			var selRange = selObj.getRangeAt(0);

			if (selRange && selRange.startContainer !== eshElement) {
				var range = document.createRange();

				range.selectNodeContents(eshElement);

				selObj.removeAllRanges();
				selObj.addRange(range);
				selObj.removeAllRanges();
			}
		}

		this.eshContext = null;
	}
};

// Override - escapeKeyPressed - removes the editing feedback UI and cancels the editing session
TextEditHud.prototype.escapeKeyPressed = function () {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : escapeKeyPressed");

    if (this.getVisibility() === false) {
        return;
    }

	this.storeContextForESHSelection();

    this.exitTextEditingSession(false);

	this.restoreESHSelectionFromContext();

	DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.DWShortcuts, DW_ICE_HEADLIGHTS.Canceled);

    GenericHUDObject.prototype.destroy.call(this);
};

/*override
name: OnViewLostFocus
arguments: none

This function is called when browser view looses focus
*/

TextEditHud.prototype.OnViewLostFocus = function () {
    'use strict';

	this.logDebugMsg("function begin : TextEditHud : OnViewLostFocus");

    if (!GenericHUDObject.prototype.OnViewLostFocus.call(this)) {
        return;
    }

    this.CommitAndCloseHud();
};

TextEditHud.prototype.FindElementToBringbackESHHud = function (element) {
    'use strict';
	if (!element) {
		return;
	}

    this.logDebugMsg("function begin : TextEditHud : BringbackESHHud" + element);
	var elementForESH = null;
	if (element.firstChild && DW_LIVE_TEXT_EDIT.BlockInclusionList.indexOf(element.firstChild.tagName) >= 0) {
		elementForESH = element.firstChild;
	} else {
		elementForESH = element.parentNode;
	}

	return elementForESH;
};


TextEditHud.prototype.BringbackESHHud = function (element, editDisabled) {
    'use strict';
	if (!element) {
		return;
	}

    this.logDebugMsg("function begin : TextEditHud : BringbackESHHud" + element);

	var params = element.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
	params += ",";
	params += "";
	params += ",";
	params += editDisabled;
	window.DWEventToLiveView(DW_EVENTS.SelectionChanged, params);
};


// Checks for the special cases for which editing needs to be disabled.
TextEditHud.prototype.ShouldDisableEditing = function () {
	'use strict';

    var disablEditing = false;

	//Case 1 - If anchor no text and is styled with background image via CSS, disable editing.
	var element = this.m_nodeAtClickPoint;
	if ((element && element.tagName === "A") &&
			(this.m_nodeAtClickPointInnerHTML === "" || (this.m_nodeAtClickPointInnerHTML && this.m_nodeAtClickPointInnerHTML.trim() === ""))) {
		var elementsComputedStyle = window.getComputedStyle(element, null);
		if (elementsComputedStyle.background && elementsComputedStyle.background.indexOf("url") >= 0) {
			disablEditing = true;
		}
	} else if (this.m_lastClientX !== null && this.m_lastClientY !== null) {
        element = document.elementFromPoint(this.m_lastClientX, this.m_lastClientY);
        if (element && (element instanceof SVGElement)) {
            disablEditing = true;
        }
    }

	return disablEditing;
};

// Is element inside the parent element
TextEditHud.prototype.isElementInsideParent = function (parent, searchnode) {
    'use strict';

    if (!parent || !searchnode) {
        return false;
    }

    if (parent === searchnode) {
        return true;
    }

    if (parent.nodeType === Node.ELEMENT_NODE) {
        var chidNodes = parent.childNodes;

        if (chidNodes) {
            var i, tempNode = chidNodes[0], found = false;
            for (i = 0; tempNode; i += 1, tempNode = chidNodes[i]) {
				if (tempNode === searchnode) {
					found = true;
					break;
				}

                found = this.isElementInsideParent(tempNode, searchnode);
                if (found) {
                    break;
                }
            }

			return found;
        }
    }

    return false;
};

// isEventInsideTextEditContext - Checks whether the click event is inside the Text Editing session.
TextEditHud.prototype.isEventInsideTextEditContext = function (event) {
    'use strict';

    this.logDebugMsg("TextEditHud.prototype.isEventInsideTextEditContext");

    if (!event || !this.getVisibility() || !this.m_elementUnderCurrentEditingSession) {
        return false;
    }

	var isInside = this.isElementInsideParent(this.m_elementUnderCurrentEditingSession, event.target);

	if (!isInside) {
		isInside = this.isElementInsideParent(this.m_elementUnderCurrentEditingSession, DWTextEditUtility.getTextNodeFromClick(event));
	}

	// If dw-span is only child for it's parent and event.target is of same, 
	// then we will consider this as inside context.
	if (!isInside && this.m_elementUnderCurrentEditingSession.parentNode === event.target &&
			this.m_elementUnderCurrentEditingSession.parentNode.firstChild === this.m_elementUnderCurrentEditingSession.parentNode.lastChild) {
		this.logDebugMsg("TextEditHud.isEventInsideTextEditContext Inside Context by Parent matching");
		isInside = true;
	}

	this.logDebugMsg("TextEditHud.isEventInsideTextEditContext is Inside? - " + isInside);

	return isInside;
};

// enterTextEditingSession - sets up the editing session for the input element 
TextEditHud.prototype.enterTextEditingSession = function () {
    'use strict';
    this.logDebugMsg("function begin : TextEditHud : enterTextEditingSession");

	// Update renderer that we are in editing.
    // This should be invoked before making any live editing related change in DOM
    // to allow the renderer to cache the Source/Selection and prevent source change 
    // notification while in edting.
    this.disableDWSelectionChanges(true);

    //clear current undo stack in browser
    this.clearUndoRedoOperations();

	// Create dw-span wrapper, show the editing feedback 
	// and setup editing session
    if (this.prepareTextUnderDwSpan()) {
		var ingoreIBeamPositioning = false,
			element = this.getCurrentElement();

		if (this.m_textIdentifier.editingFromEnter) {
			ingoreIBeamPositioning = true;
		}

        window.DWLECallJsBridgingFunction(this.getHudName(), "clearLiveHighlight", null, false);
        window.DWLECallJsBridgingFunction(this.getHudName(), "setBoldItalicsPreference", null, false);
        DWTextEditUtility.addContentEditableAttribute(element);
		// store them for future use where we try to give dwid's for tempids
        if (element.previousSibling) {
            this.dwSpanPrevSibling = element.previousSibling;
        }
        if (element.nextSibling) {
            this.dwSpanNextSibling = element.nextSibling;
        }

        this.suspendEventPropogation(element);
        this.initializeTripleClickHandler(element);
        this.showTextEditingFeedback();
        this.initializePasteHandler(element);
		// when something is selected inside text editing session and mouse up happened outside the text editing hud
		// still we should be able to propagate selection
        // this is mouseup listener to check if selection has changed and also removing the disable selection class in case it is added from mouseout
		window.addEventListener("mouseup", this.mouseupListenerOnWindow.bind(this), false);
        // Hide all ER HUDs
        this.m_liveEditEvent.fire({
            type: DW_LIVEEDIT_EVENTS.EditingText
        });
        // we need to tell formatting Hud about the node we are working on
        var messageDetails = {};
        messageDetails.type = DW_EXTENSION_EVENT.TEXT_EDIT_BEGIN;
        liveViewExtensionsObject.messageToExtensions(messageDetails);
        // reset format hud state
        this.m_lastSelection = null;
        var parent = element.parentNode;
        while (parent && parent.tagName !== "BODY") {
            if (parent.tagName === "TABLE") {
                if (window.liveViewExtensionsObject && window.liveViewExtensionsObject.dwObject) {
                    window.liveViewExtensionsObject.dwObject.logHeadlightsData(DW_ICE_HEADLIGHTS.OTH_ELV, DW_ICE_HEADLIGHTS.lveTableEdited);
                }
                break;
            }
            parent = parent.parentNode;
        }
        if (window.liveEditFGInstance) {
            if (window.liveViewExtensionsObject && window.liveViewExtensionsObject.dwObject) {
                window.liveViewExtensionsObject.dwObject.logHeadlightsData(DW_ICE_HEADLIGHTS.OTH_ELV, DW_ICE_HEADLIGHTS.lveFgEdited);
            }
        }

        // Preserve the element which is under edit
        this.m_elementUnderCurrentEditingSession = element;

        // Preserve the element's original innerHTML, that will be used in case of cancelling the 
        // editing session via Esc key.
        this.m_originalInnerHTMLOfElementUnderCurrentEditingSession = element.innerHTML;

		// Preserve the grouping level, that will be used while handling backspace
		this.m_deleteTagOnDeletingAllContent = DWTextEditUtility.IsTextContainerDirectlyAroundText(element) ? false : true;

		if (!ingoreIBeamPositioning) {
			this.positionIBeam();
		} else {
			var textNode = DWTextEditUtility.findLastTextNode(this.m_curElement);
			if (textNode) {
				if (textNode.parentNode && DWTextEditUtility.hasSingleElementWhoseContentIsWhitespace(textNode.parentNode)) {
					this.setIbeamToGivenCharOffsetInAGivenNode(textNode, 0);
				} else {
					this.setIbeamToGivenCharOffsetInAGivenNode(textNode, textNode.nodeValue.length);
				}
			}
		}
    } else {
        this.logDebugMsg("error : TextEditHud : enterTextEditingSession - failed");

		// Reset text editing status.
		this.disableDWSelectionChanges(false);

		return false;
    }

	return true;
};


// exitTextEditingSession - exits the current editing session for the input element 
TextEditHud.prototype.exitTextEditingSession = function (commitChange) {
    'use strict';
	this.logDebugMsg("function begin : TextEditHud : exitTextEditingSession");

	var elementToBringbackESHHud = null;
    if (this.m_elementUnderCurrentEditingSession) {
		var parentNode = this.m_elementUnderCurrentEditingSession.parentNode;

		elementToBringbackESHHud = this.FindElementToBringbackESHHud(this.m_elementUnderCurrentEditingSession);

		this.clearTextEditingFeedback();
        DWTextEditUtility.removeContentEditableAttribute(this.m_elementUnderCurrentEditingSession);
        this.resumeEventPropogation(this.m_elementUnderCurrentEditingSession);
        this.unInitializeTripleClickHandler(this.m_elementUnderCurrentEditingSession);
        this.uninitializePasteHandler(this.m_elementUnderCurrentEditingSession);
        window.removeEventListener("mouseup", this.mouseupListenerOnWindow.bind(this), false);
        this.m_elementUnderCurrentEditingSession.removeEventListener("mouseout", this.mouseOutListener.bind(this), true);
        this.m_elementUnderCurrentEditingSession.removeEventListener("mousedown", this.mouseDownListenerOnElement.bind(this), false);
        this.m_elementUnderCurrentEditingSession.removeEventListener("input", this.inputListenerOnElement.bind(this), false);
        this.m_elementUnderCurrentEditingSession.removeEventListener("keydown", this.keyDownListenerOnElement.bind(this), false);
        this.m_elementUnderCurrentEditingSession.removeEventListener("keyup", this.keyUpListenerOnElement.bind(this), false);

        var messageDetails = {};
        messageDetails.type = DW_EXTENSION_EVENT.TEXT_EDIT_END;
        liveViewExtensionsObject.messageToExtensions(messageDetails);

		if (commitChange) {
			// Commit the change.
			this.m_elementUnderCurrentEditingSession.outerHTML = this.m_elementUnderCurrentEditingSession.innerHTML;
		} else {
			 // Revert back to the original content - it removes the dw-span tag as well
			this.m_elementUnderCurrentEditingSession.outerHTML = this.m_originalInnerHTMLOfElementUnderCurrentEditingSession;
		}

		this.m_elementUnderCurrentEditingSession = null;
		this.m_originalInnerHTMLOfElementUnderCurrentEditingSession = "";

		// clean up &nbsp; that we added.
		this.cleanNBSPsIntroducedForEditing(parentNode);

		if (this.m_forceRedraw) {
			this.forceRedraw(parentNode);
		}

		// Done with editing, reset the flag to allow Source/Selection sync.
		this.disableDWSelectionChanges(false);

		//clear undo operations caused by our editing
		this.clearUndoRedoOperations();

		// Reset Text Identifying Context after Escape handling.
		this.resetContext();

		// reset format hud state
		this.m_lastSelection = null;

		// reset the grouping level information
		this.m_deleteTagOnDeletingAllContent = false;

		if (elementToBringbackESHHud) {
			var editDisabled = false;
			this.BringbackESHHud(elementToBringbackESHHud, editDisabled);
		}
    } else {
        this.logDebugMsg("error : TextEditHud : exitTextEditingSession - failed");
    }
};



// showTextEditingFeedback - shows the text editing feedback (orange border) around the 
// input element
TextEditHud.prototype.showTextEditingFeedback = function () {
    'use strict';

	// If the editing needs to be disabled, dont show it here.
	if (this.ShouldDisableEditing()) {
		return;
	}

    this.logDebugMsg("function begin : TextEditHud : showTextEditingFeedback");

    if (this.m_textEditFeedbackCss) {
        var paddingBetweenBorderAndContent = {
            left: DW_LIVEEDIT_CONSTANTS.OverlayPadding_Left,
            right: DW_LIVEEDIT_CONSTANTS.OverlayPadding_Right,
            top: DW_LIVEEDIT_CONSTANTS.OverlayPadding_Top,
            bottom: DW_LIVEEDIT_CONSTANTS.OverlayPadding_Bottom
        };

        this.showOverLayDiv(this.m_textEditFeedbackCss, paddingBetweenBorderAndContent);
    } else {
        this.logDebugMsg("error : TextEditHud : improper m_textEditFeedbackCss value");
    }
};


// clearTextEditingFeedback - removes the text editing feedback (orange border) around the 
// input element
TextEditHud.prototype.clearTextEditingFeedback = function () {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : clearTextEditingFeedback");

    this.hideOverLayDiv();
};

// positionIBeam - positions the IBeam to the event's x, y cordinates
TextEditHud.prototype.positionIBeam = function (event) {
	'use strict';

	this.logDebugMsg("function begin : TextEditHud : positionIBeam");

	if (this.m_lastClientX && this.m_lastClientY && !event) {
		event = {clientX: this.m_lastClientX, clientY: this.m_lastClientY};
		// reset values.
		this.m_lastClientX = null;
		this.m_lastClientY = null;
	}

	if (!this.getCurrentElement()) {
		// Editable element is not yet ready. 
		// Remember clientX and clientY to set the 
		// cursor postion later.
		if (event) {
			this.m_lastClientX = event.clientX;
			this.m_lastClientY = event.clientY;
		}
		return;
	}

	if (document.caretRangeFromPoint && event) {
		var x = event.clientX,
			y = event.clientY;

		if (x === null || y === null) {
			this.logDebugMsg("positionIBeam failed - invalid click position");
			return;
		}

		// IF element redraw is pending. make sure to set the display style as block before calucalating range and 
		// set the previous style after.
		var reDrawElementsStyle = null;
		if (this.m_elementInRedraw) {
			reDrawElementsStyle = this.m_elementInRedraw.style;
			this.m_elementInRedraw.style.display = "block";
		}

		var range = document.caretRangeFromPoint(x, y);

		// Element redraw is pending. set is previous style back.
		if (this.m_elementInRedraw && reDrawElementsStyle !== null) {
			this.m_elementInRedraw.style.display = reDrawElementsStyle;
		}

		if (range === null) {
			this.logDebugMsg("positionIBeam failed - invalid range received from caretRangeFromPoint");
			return;
		}

		if (range.startContainer.parentNode && DWTextEditUtility.hasSingleElementWhoseContentIsWhitespace(range.startContainer.parentNode)) {
			this.setIbeamToGivenCharOffsetInAGivenNode(range.startContainer, 0);
		} else {
			this.setIbeamToGivenCharOffsetInAGivenNode(range.startContainer, range.startOffset);
		}
	} else {
		this.logDebugMsg("positionIBeam failed - caretRangeFromPoint is not supported");
	}
};

// getEntityName - returns entity name for given character code.
//			if there is no name found in map, then it returns encoded '&#nnnn;' string
TextEditHud.prototype.getEntityName = function (code) {
    'use strict';

    if (!code) {
        return null;
    }

    var entName = this.m_encodedEntities[code];

    if (!entName) {
        entName = String.fromCharCode(code);
    }

    return entName;
};

//encodeString - Utility function that encodes all entities of given string and returns encoded string.
TextEditHud.prototype.encodeString = function (strToEncode) {
    'use strict';

    var encodedStr = strToEncode.replace(/[\u00A1-\u9999"']/gim, function (i) {
        return this.getEntityName(i.charCodeAt(0));
    }.bind(this));

    if (!encodedStr) {
        this.logDebugMsg('TextEditHud.encodeString - FAILED');
        return strToEncode;
    }

    return encodedStr;
};

//prepareEncodableEntities -  prepare encodable entities map.
TextEditHud.prototype.prepareEncodableEntities = function (originalText) {
    'use strict';

    // Reset map.
    this.m_encodedEntities = {};

    // Return if text is not valid.
    if (!originalText) {
        return;
    }

    // Find all valid entities and populate the map
    var idBegin = originalText.indexOf("&");
    while (idBegin >= 0) {
        var entityStr,
            entityCode = null,
            idEnd = originalText.indexOf(';', idBegin + 2);

        if (idEnd >= 0) {
            var hashChar = originalText.charAt(idBegin + 1);
            if (hashChar && hashChar === '#') {
                // Entity is encoded in "&#9999;" format.
                entityStr = originalText.substring(idBegin, idEnd + 1);
                entityCode = Number(originalText.substring(idBegin + 2, idEnd));
            } else {
                // Entity is encoded in "&quot;" format.
                entityStr = originalText.substring(idBegin, idEnd + 1);
                entityCode = DW_LIVE_TEXT_EDIT.Entities[entityStr];
            }
        }

        // Add valid entity into map and continue the search.
        if (entityCode) {
            this.m_encodedEntities[entityCode] = entityStr;
            idBegin = originalText.indexOf("&", idEnd + 1);
        } else {
            idBegin = originalText.indexOf("&", idBegin + 1);
        }
    }
};

//getEntityEncodedInnerHTMLStr - Encodes all entities of given node's inner html and returns.
TextEditHud.prototype.getEntityEncodedInnerHTMLStr = function (node) {
    'use strict';

    this.logDebugMsg('TextEditHud.getEntityEncodedInnerHTMLStr');
    if (!node) {
        return null;
    }

    var encodedStr = "";
    if (node.nodeType === Node.TEXT_NODE) {
        // plain text encode all the possible characters.
        encodedStr = this.encodeString(node.nodeValue);
    } else {
        //
        // Encode text element only:
        //		The inline HTML could be something like this
        //			<em>"some text <strong class="ddd">hello world</strong> dkijiuu"</em>
        //		Here, tag attributes (string inside "<" and ">") should not be encoded.
        //
        var orgStr = node.innerHTML,
            startIndex = 0,
            lastIndex = orgStr.length;

        while (startIndex < lastIndex) {
            var tagStartIndex = orgStr.indexOf("<", startIndex);
            if (tagStartIndex === -1) {
                var subStr = orgStr.substring(startIndex);
                if (subStr) {
                    this.logDebugMsg("Encode string (" + startIndex + ") :" + subStr);
                    encodedStr += this.encodeString(subStr);
                }
                break;
            }

            var tagEndIndex = orgStr.indexOf(">", tagStartIndex);
            if (tagEndIndex === -1) {
                // Some error case, we retain the string as it is.
                encodedStr = orgStr;
                break;
            }

            // Encode text string
            var subString = orgStr.substring(startIndex, tagStartIndex);
            if (subString) {
                this.logDebugMsg("Encode string (" + startIndex + " to " + tagStartIndex + ") :" + subString);
                encodedStr += this.encodeString(subString);
            }

            // Add tag string as it is.
            subString = orgStr.substring(tagStartIndex, tagEndIndex + 1);
            if (subString) {
                this.logDebugMsg("Append string :" + subString);
                encodedStr += subString;
            }

            startIndex = tagEndIndex + 1;
        }
    }

    return encodedStr;
};



//resetContext - clears the context information of current Text editing.
TextEditHud.prototype.resetContext = function () {
    'use strict';

    // Reset Text Identifying Context
    this.m_textGroupContext = null;
    this.m_textIdentifier = null;
	this.m_dwgTempElementCount = 0;
	this.m_staticAttributes = null;
	this.m_forceRedraw = false;

	this.m_nodeAtClickPoint = null;
	this.m_nodeAtClickPointInnerHTML = null;

	this.m_lastClientX = null;
	this.m_lastClientY = null;
};


//  Override - setCurrentElement - event Handler call back registerd with Controller. 
TextEditHud.prototype.setCurrentElement = function (args) {
    'use strict';

    // Validate arguments.
    this.logDebugMsg("function begin : TextEditHud : setCurrentElement");

    // Set / reset member's state.
    this.m_curElement = null;
    this.m_curTextNode = null;

    // clicking on the blank area or empty html should not disable PI, Insert Panel and Code View
    if (args && args.event) {
        var targetNode = args.event.target;
        if (targetNode && targetNode.nodeType === Node.ELEMENT_NODE && targetNode.tagName === "HTML") {
            return;
        }
    }

	// Element redraw is pending. make it visible before calculating
	// current element
	if (this.m_elementInRedraw) {
        this.m_elementInRedraw.style.display = this.m_displayStyleOfElementInRedraw;
	}

    if (!(args && args.element && args.event && args.isInEditableRegion)) {
        return;
    }

    var nodeAtClick = null;
	// Preserve the anchor element reference, if click in on the anchor tag.
	if (this.m_nodeAtClickPoint === null) {
		nodeAtClick = document.elementFromPoint(args.event.clientX, args.event.clientY);
		this.m_nodeAtClickPoint = DWTextEditUtility.findAnchorNodeParentForGivenNode(nodeAtClick);
		if (this.m_nodeAtClickPoint) {
			this.m_nodeAtClickPointInnerHTML = this.m_nodeAtClickPoint.innerHTML;
		}
	}

    this.m_curTextNode = DWTextEditUtility.getTextNodeFromClick(args.event);


    //if curr Text node is null, we will try to find it from event target node
    if (!this.m_curTextNode && args.event.target) {
		var element = args.event.target,
			curTextNode = null;

		if (element) {
			if (DWTextEditUtility.hasOnlyWhitespaceAsContent(element) && DW_LIVE_TEXT_EDIT.BlockInclusionList.indexOf(element.tagName) >= 0) {
				curTextNode = element;
			} else {
				curTextNode = DWTextEditUtility.findAnyTextNode(element);

				// If no text node, check whether there is any <br> tag
				if (!curTextNode) {
					curTextNode = DWTextEditUtility.findAnyBRNode(element);
				}

				// Check whether element can be used as text
				if (!curTextNode && this.canElementBeEditedAsText(element)) {
					curTextNode = element;
				}
			}

			this.m_curTextNode = curTextNode;
		}
	}
    if (this.m_curTextNode !== null) {
		DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.ELV_LVE, DW_ICE_HEADLIGHTS.StartEditing);

        if (nodeAtClick && DW_LIVE_TEXT_EDIT.InlineTextElements.indexOf(nodeAtClick.tagName) >= 0) {
			DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.OTH_ELV, DW_ICE_HEADLIGHTS.InlineTagEdited_Pre + nodeAtClick.tagName + DW_ICE_HEADLIGHTS.InlineTagEdited_Post);
        }
    }

	// Element redraw is pending. hide it again as we are done.
	if (this.m_elementInRedraw) {
        this.m_elementInRedraw.style.display = 'none';
	}

	// Remember clientX and clientY to set the 
	// cursor postion (IBeam) later.
	if (args.event) {
		this.m_lastClientX = args.event.clientX;
		this.m_lastClientY = args.event.clientY;
	}
};

//
//getTextNodeIdentifier - returns text node identifier. Inline text like (strong, ...) will be 
//	identified by it's own "data_liveedit_tagid" attribute. Text node is identified by using following information.
//		parentID - parent node's data_liveedit_tagid
//		preNodeID - any immediate previous sibling element's data_liveedit_tagid
//		succNodeID - any immediate next sibling element's data_liveedit_tagid
//		noOfBeforeTexts - how many other text node exists in between preNodeID and current text Node.
//		noOfAfterTexts - how many other text node exists in between current text Node and succNodeID.
//		editableRegion - editable region name if it is template instance
//	
TextEditHud.prototype.getTextNodeIdentifier = function (nodeobj, ignoreDWSpanGrouping) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : getTextNodeIdentifier");

    var textIdentifier = null;

    if (!nodeobj) {
        return textIdentifier;
    }

    if (nodeobj.nodeType === Node.ELEMENT_NODE && nodeobj.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId)) {
        textIdentifier = {};

        textIdentifier.nodeType = Node.ELEMENT_NODE;
        textIdentifier.dwId = nodeobj.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);

        return textIdentifier;
    }

    if (nodeobj.nodeType === Node.TEXT_NODE) {
        var parentNode, parentNodeID;

        // Get parent node info
        parentNode = nodeobj.parentNode;
        parentNodeID = parentNode.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);

		// dw_span tag is not real parent, in this case, take dw_span's parentNode.
		if (!parentNodeID && ignoreDWSpanGrouping && parentNode.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer) {
			parentNodeID = parentNode.parentNode.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
		}

        if (!parentNodeID) {
            return null;
        }

        //
        // Identify Sibling element information
        //
        var preNodeID = "",
            succNodeID = "",
            noOfBeforeTexts = 0,
            noOfAfterTexts = 0,
            editableRegion = null,
			parentPrevSiblingSearchDone = false,
			parentNextSiblingSearchDone = false,
            tempNode;

        // Get previous sibling element info
        tempNode = nodeobj.previousSibling;
        while (tempNode) {
            if (tempNode.nodeType === Node.TEXT_NODE && !DWTextEditUtility.isIgnorableText(tempNode)) {
                noOfBeforeTexts += 1;
            }
            if (tempNode.nodeType === Node.ELEMENT_NODE) {
                preNodeID = tempNode.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
                break;
            }
            if (window.liveEditTemplateInstance && tempNode.nodeType === Node.COMMENT_NODE && tempNode.nodeValue.indexOf(DW_EDITABLE_REGION_CONSTS.EditableRegionBegin) === 0) {
                editableRegion = this.getEditableRegionAttributeVal(tempNode, DW_LIVEEDIT_CONSTANTS.DWUniqueId);
                break;
            }
            tempNode = tempNode.previousSibling;

			// When text node is directly under dw_span tag, all the previous sibling of dw_span tag needs to be considered.
			if (ignoreDWSpanGrouping && !tempNode && !parentPrevSiblingSearchDone && parentNode.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer) {
				tempNode = parentNode.previousSibling;
				parentPrevSiblingSearchDone =  true;
			}
        }

        // Get next sibling element info
        tempNode = nodeobj.nextSibling;
        while (tempNode) {
            if (tempNode.nodeType === Node.TEXT_NODE && !DWTextEditUtility.isIgnorableText(tempNode)) {
                noOfAfterTexts += 1;
            }
            if (tempNode.nodeType === Node.ELEMENT_NODE) {
                succNodeID = tempNode.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
                if (!succNodeID && tempNode.tagName.toLowerCase() === DW_LIVEEDIT_CONSTANTS.GlobalContainerDiv) {
                    succNodeID = "";
                }
                break;
            }
            if (window.liveEditTemplateInstance && tempNode.nodeType === Node.COMMENT_NODE && tempNode.nodeValue.indexOf(DW_EDITABLE_REGION_CONSTS.EditableRegionEnd) === 0) {
                break;
            }
            tempNode = tempNode.nextSibling;

			// When text node is directly under dw_span tag, all the next sibling of dw_span tag needs to be considered.
			if (ignoreDWSpanGrouping && !tempNode && !parentNextSiblingSearchDone && parentNode.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer) {
				tempNode = parentNode.nextSibling;
				parentNextSiblingSearchDone =  true;
			}
        }

        // If any one of preNodeID or succNodeID is available, then we can identify the text relatively.
        if (preNodeID !== null || succNodeID !== null) {
            // Create textIdentifier object and populate information.
            textIdentifier = {};

            textIdentifier.nodeType = Node.TEXT_NODE;
            textIdentifier.parentNodeID = parentNodeID;
            textIdentifier.preNodeID = preNodeID;
            textIdentifier.succNodeID = succNodeID;
            textIdentifier.noOfBeforeTexts = noOfBeforeTexts;
            textIdentifier.noOfAfterTexts = noOfAfterTexts;
            if (editableRegion) {
                textIdentifier.editableRegion = editableRegion;
            }
        }
    }

    return textIdentifier;
};

//isTextGroup - utility function to check whether given node
//		is text node or text group ( block contains only inline text)
TextEditHud.prototype.isTextGroup = function (node) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : isTextGroup");

    if (!node) {
        return false;
    }

    if (node.nodeType === Node.TEXT_NODE) {
        return true;
    }

    if (node.nodeType === Node.ELEMENT_NODE) {
        if (!DWTextEditUtility.isEditableElement(node)) {
            return false;
        }

        if (DWTextEditUtility.isInlineTextTag(node.tagName)) {
            return true;
        }
    }

    return false;
};



//groupNearbyTextSiblings - identifies near by sibling text nodes and returs 
//		group object which contains boundry information.
TextEditHud.prototype.groupNearbyTextSiblings = function (node) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : groupNearbyTextSiblings");

    if (!node || !this.isTextGroup(node)) {
        return null;
    }

    //
    // Check previous siblings for grouping
    //
    var allPrevSiblingIsText = true,
        startingPrevSiblingText = node,
        previousSibling = node.previousSibling;
    while (previousSibling) {

        // break, if it is not text or text group
        if (!this.isTextGroup(previousSibling)) {
            allPrevSiblingIsText = false;
            break;
        }

        // Some time, if there is new line between to tag node, it is
        // comming as text node. Ignore that.
        if (DWTextEditUtility.isIgnorableText(previousSibling)) {
            // Ignore new line text node
            previousSibling = previousSibling.previousSibling;
        } else {
            // Remember the text sibling.
            startingPrevSiblingText = previousSibling;
            previousSibling = previousSibling.previousSibling;
        }
    }

    // check all next siblings
    var allNextSiblingIsText = true,
        endNextSiblingText = node,
        nextSibling = node.nextSibling;
    while (nextSibling) {

        // break, if it is not text or text group
        if (!this.isTextGroup(nextSibling)) {
            allNextSiblingIsText = false;
            break;
        }

        // Some time, if there is new line between to tag node, it is
        // comming as text node. Ignore that.
        if (DWTextEditUtility.isIgnorableText(nextSibling)) {
            // Ignore new line text node
            nextSibling = nextSibling.nextSibling;
        } else {
            // Remember the text sibling.
            endNextSiblingText = nextSibling;
            nextSibling = nextSibling.nextSibling;
        }
    }

    // create Group info object
    var textGroup = {};
    textGroup.originalNode = node;
    textGroup.startingSiblingText = startingPrevSiblingText;
    textGroup.endingSiblingText = endNextSiblingText;
    textGroup.allSiblingsAreText = allPrevSiblingIsText && allNextSiblingIsText;

    return textGroup;
};

// getBlockLevelGroup - checks whether block element can be included or not and
// returns group identification to include the block
TextEditHud.prototype.getBlockLevelGroup = function (nodeobj) {
	'use strict';
	this.logDebugMsg("function begin : TextEditHud : getBlockLevelGroup");

	// Need to consider only element node here.
	if (!nodeobj || nodeobj.nodeType !== Node.ELEMENT_NODE) {
		return null;
    }

	// Check whether we can include the block or not.
	if (nodeobj.innerHTML !== DW_LIVEEDIT_CONSTANTS.NonBreakingSpace &&
			DW_LIVE_TEXT_EDIT.BlockInclusionList.indexOf(nodeobj.tagName) < 0) {
		return null;
	}

	// create Group info object
    var textGroup = {};
    textGroup.originalNode = nodeobj;
    textGroup.startingSiblingText = nodeobj;
    textGroup.endingSiblingText = nodeobj;
    textGroup.allSiblingsAreText = false;
	textGroup.blockLevelGrouping = true;

	return textGroup;
};

// findTextGroupRange - find editable text group around given text node object
TextEditHud.prototype.findTextGroupRange = function (nodeobj) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : findTextGroupRange");

    // Identify relative text Group
    var textGroup = this.groupNearbyTextSiblings(nodeobj);

    //
    // Group  at parent level if all siblings are text
    //
    if (textGroup && textGroup.allSiblingsAreText) {
        var parentTextGroup = this.findTextGroupRange(nodeobj.parentNode);
        if (parentTextGroup) {
            textGroup = parentTextGroup;
        } else {
			// if we are here, parentNode is block level element.
			// check whether we can include that in text group.
			parentTextGroup = this.getBlockLevelGroup(nodeobj.parentNode);
			if (parentTextGroup) {
				textGroup = parentTextGroup;
			}
		}
    }

    return textGroup;
};



// prepareTextIdentifier - prepare text group context and Id for the given text node.
TextEditHud.prototype.prepareTextIdentifier = function (nodeobj, ignoreDWSpanGrouping) {
	'use strict';

    this.logDebugMsg("function begin : TextEditHud : prepareTextIdentifier");

    if (!nodeobj) {
        return false;
    }

    var nodeId = this.getTextNodeIdentifier(nodeobj, ignoreDWSpanGrouping);

    if (!nodeId) {
        return false;
    }

    //
    // Create and Initialize Text Group context
    //
    this.m_textGroupContext = {};
    this.m_textGroupContext.textNode = nodeobj;

	if (ignoreDWSpanGrouping) {
		this.m_textGroupContext.group = null;
	} else {
		this.m_textGroupContext.group = this.findTextGroupRange(nodeobj);
	}

    //
    // Prepare DW text identifier.
    //
    // Request Identity
    this.m_dwReqCount += 1;

    // Prepare async request to validate text selection with DW
    this.m_textIdentifier = nodeId;
    this.m_textIdentifier.id = this.m_dwReqCount;
    this.m_textIdentifier.callback = function (result, contextid, originalText, attrMap) {
        this.dwEditableCallback(result, contextid, originalText, attrMap);
    }.bind(this);
    this.m_textIdentifier.grouped = false;

	if (!this.m_textGroupContext.group && nodeobj.nodeType === Node.ELEMENT_NODE) {
		this.m_textGroupContext.group =  this.getBlockLevelGroup(nodeobj);
	}

	this.logDebugMsg("Text Node Id: " + DWTextEditUtility.getTextIdentifierString(this.m_textIdentifier));
    // Add group data to request;
    if (this.m_textGroupContext.group) {
        this.m_textIdentifier.grouped = true;
        this.m_textIdentifier.groupStarting = this.getTextNodeIdentifier(this.m_textGroupContext.group.startingSiblingText);
		this.logDebugMsg("Text Group Starting: " + DWTextEditUtility.getTextIdentifierString(this.m_textIdentifier.groupStarting));

        if (this.m_textGroupContext.group.startingSiblingText !== this.m_textGroupContext.group.endingSiblingText) {
            this.m_textIdentifier.groupEnding = this.getTextNodeIdentifier(this.m_textGroupContext.group.endingSiblingText);
			this.logDebugMsg("Text Group Ending: " + DWTextEditUtility.getTextIdentifierString(this.m_textIdentifier.groupEnding));
        }
    }

	return true;
};


// dwEditableCallback - call back function to be called from DW after validating
// editable range.
TextEditHud.prototype.dwEditableCallback = function (result, contextid, originalText, staticAttr) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : dwEditableCallback - result: " + result);

	// If the editing is disabled explicitly, show the non-editable UI and bail.
	if (this.ShouldDisableEditing()) {
		this.handleValidationFailure();
        return;
	}

    if (contextid !== this.m_dwReqCount) {
        return;
    }

    if (result === true && this.EnableEditingForIdentifiedText()) {
        this.prepareEncodableEntities(originalText);
        if (staticAttr) {
			this.m_staticAttributes = staticAttr;
        }
        var strIdx, arrayLength = DW_LIVE_TEXT_EDIT.DynamicTypes.length;

        if (window.doctype) {
            for (strIdx = 0; strIdx < arrayLength; ++strIdx) {
                if (window.doctype.toUpperCase() === DW_LIVE_TEXT_EDIT.DynamicTypes[strIdx].toUpperCase()) {
					DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.OTH_ELV, DW_ICE_HEADLIGHTS.lvePreString + window.doctype + DW_ICE_HEADLIGHTS.lvePostStringStatic);
                    return true;
                }
            }
        }
    } else {
		this.handleValidationFailure();
    }
};

// Utility method for handling the validation. It clears the state and brings up ESH. 
TextEditHud.prototype.handleValidationFailure = function () {
    'use strict';

	// if text node range is not editable, alert user about dynamic changes
	// cancel the editing and show the feedback
	var elementUnderEdit = null;
	if (this.m_elementUnderCurrentEditingSession) {
		elementUnderEdit = this.FindElementToBringbackESHHud(this.m_elementUnderCurrentEditingSession);

		this.escapeKeyPressed();
	} else {
		var curElement = this.getCurrentElement();
		if (curElement) {
			curElement.outerHTML = curElement.innerHTML;
			this.m_curElement = null;
		}
	}

    if (!elementUnderEdit && this.m_lastClientX !== null && this.m_lastClientY !== null) {
        var element = document.elementFromPoint(this.m_lastClientX, this.m_lastClientY);
        if (element) {
            elementUnderEdit = this.FindElementToBringbackESHHud(element);
            this.escapeKeyPressed();
	    }
    }

    //show non-editable UI and bail.
	this.ShowEditingDisabledUI(elementUnderEdit);

	this.m_liveEditEvent.fire({
		type: DW_LIVEEDIT_EVENTS.EditOpFailed
	});
};


// Identifies whether to do any post processing after browser handles enter key.
// This is called from onKeyDown - i.e., before browser handles enter key. By looking
// the ibeam position, we identify different actions to perform in on keyup.
TextEditHud.prototype.FigureOutPostProcessingAfterEnterKeyPress = function (evt) {
	'use strict';

	this.logDebugMsg("function begin : TextEditHud : FigureOutPostProcessingAfterEnterKeyPress");
	try {
		// Enable document edits into single UNDO group.
		document.execCommand("EnableUndoGrouping");

		var node = document.getSelection().anchorNode;
		if (!node) {
			return;
		}

		var nodeActual =  (node.nodeType === Node.TEXT_NODE ? node.parentNode : node);
		if (!nodeActual) {
			return;
		}

        if (nodeActual.tagName === "MAIN" && nodeActual.parentElement && nodeActual.parentElement.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer) {
            if (!evt) {
                evt = window.event;
            }
            evt.stopPropagation();
            evt.preventDefault();
            this.m_enterPostProcessingAction = null;
			return;
        }
		var inTagThatCreatesNestedPTagsOnEnter = false;
		if (DW_LIVE_TEXT_EDIT.TagsThatCreatesPTagsOnEnter.indexOf(nodeActual.tagName) >= 0) {
			inTagThatCreatesNestedPTagsOnEnter = true;
			this.m_blockLevelTagNameBeforeEnter = nodeActual.tagName;
		}

		nodeActual = DWTextEditUtility.getFirstBlockLevelParent(nodeActual);

		if (DW_LIVE_TEXT_EDIT.LeaveEnterKeyPressBehaviourToBrowser.indexOf(nodeActual.tagName) >= 0) {
			this.m_enterPostProcessingAction = null;
			return;
		}

		var lookForEnterProcessing = false;
		if (nodeActual.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer && DW_LIVE_TEXT_EDIT.TagsThatCreatesPTagsOnEnter.indexOf(nodeActual.parentNode.tagName) >= 0) {
			document.execCommand('formatBlock', false, 'p');
			lookForEnterProcessing = true;
		}

		var inListItemTag = false;
		if (nodeActual.tagName === "LI") {
			inListItemTag = true;
		}

		if (DW_LIVE_TEXT_EDIT.BlockInclusionList.indexOf(nodeActual.tagName) >= 0) {
			lookForEnterProcessing = true;
		}

		var tagIsInline = false;
		if (DW_LIVE_TEXT_EDIT.InlineTextElements.indexOf(nodeActual.tagName) >= 0) {
			tagIsInline = true;
			lookForEnterProcessing = true;
		}

		if (lookForEnterProcessing === true) {
			if (inTagThatCreatesNestedPTagsOnEnter === true) {
				this.m_enterPostProcessingAction = ENTER_POST_PROCESSING_ACTIONS.CreateNestedPTagsAndInsertThemToOrignalParent;
			} else if (DWTextEditUtility.isCaretAtBeginningOfTextNode(nodeActual)) {
				this.m_enterPostProcessingAction = ENTER_POST_PROCESSING_ACTIONS.ChangePreviousSiblingContentToNBSP;
			} else if (DWTextEditUtility.isCaretAtEndOfTextNode(nodeActual)) {
				if (inListItemTag) {
					this.m_enterPostProcessingAction = ENTER_POST_PROCESSING_ACTIONS.ChangeCurrentNodeContentToNBSP;
				} else {
					var needsPostProcessing = true;
					// if ibeam is currently at the end of in line tag, do post processing ONLY if it happens to be last child of its parent.
					if (tagIsInline === true) {
						if (nodeActual.parentNode && nodeActual.parentNode.lastChild &&
								nodeActual !== nodeActual.parentNode.lastChild) {
							needsPostProcessing = false;
						}
					}

					if (needsPostProcessing === true) {
						this.m_enterPostProcessingAction = ENTER_POST_PROCESSING_ACTIONS.ChangeCurrentNodeToParaAndChangeContentToNBSP;
					}
				}
			}
		}
	} catch (e) {
        this.logDebugMsg("FigureOutPostProcessingAfterEnterKeyPress - exception caught:" + e.message);
    }

};

// Performs post processing action identified by m_enterPostProcessingAction.
// This is called from onKeyUp i.e., after browsers enter key handling.
TextEditHud.prototype.DoPostProcessingAfterEnterKeyPress = function () {
	'use strict';

	try {
		if (this.m_enterPostProcessingAction === null) {
			return;
		}
		this.logDebugMsg("function begin : TextEditHud : DoPostProcessingAfterEnterKeyPress" + this.m_enterPostProcessingAction);

		var node = document.getSelection().anchorNode;
		if (!node) {
			return;
		}

		var nodeActual =  (node.nodeType === Node.TEXT_NODE ? node.parentNode : node);
		if (!nodeActual) {
			return;
		}

		var innerHTML;
		if (this.m_enterPostProcessingAction === ENTER_POST_PROCESSING_ACTIONS.ChangeCurrentNodeContentToNBSP) {
			var currentNode = nodeActual;
			if (currentNode && !DWTextEditUtility.elementContainTheTag(currentNode, "IMG") && DWTextEditUtility.hasOnlyWhitespaceAsContent(currentNode)) {
				currentNode.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;

				this.setIbeamToGivenCharOffsetInAGivenNode(currentNode, 0);
			}
		} else if (this.m_enterPostProcessingAction === ENTER_POST_PROCESSING_ACTIONS.ChangePreviousSiblingContentToNBSP) {
			var previousSiblingNode = null,
				nodeActualsFirstBlockLevelParent = DWTextEditUtility.getFirstBlockLevelParent(nodeActual);
			if (nodeActualsFirstBlockLevelParent) {
				previousSiblingNode = nodeActualsFirstBlockLevelParent.previousSibling;
			}

			if (previousSiblingNode && !DWTextEditUtility.elementContainTheTag(previousSiblingNode, "IMG") && DWTextEditUtility.hasOnlyWhitespaceAsContent(previousSiblingNode)) {
				previousSiblingNode.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
			}

			if (previousSiblingNode && previousSiblingNode.nextSibling && DWTextEditUtility.elementContainTheTag(previousSiblingNode, "IMG")) {
				innerHTML = previousSiblingNode.nextSibling.innerHTML.trim();
				if (innerHTML === "" || innerHTML === "<br>") {
					previousSiblingNode.nextSibling.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
				}
			}
		} else if (this.m_enterPostProcessingAction === ENTER_POST_PROCESSING_ACTIONS.ChangeCurrentNodeToParaAndChangeContentToNBSP) {
			var targetElementToReplace = nodeActual;
			if (DW_LIVE_TEXT_EDIT.InlineTextElements.indexOf(nodeActual.tagName) >= 0) {
				targetElementToReplace = DWTextEditUtility.getFirstBlockLevelParent(nodeActual);

				// if targetElementToReplace is dw-span, then our actual node to replace is the div which got added browser.
				// start from nodeActual and go upwards till dw-span and break when you find a div which doesnot have data_liveedit_tagid
				if (targetElementToReplace && targetElementToReplace.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer) {
					var nodeIter = nodeActual;
					while (nodeActual !== targetElementToReplace) {
						if (nodeIter.tagName === "DIV" && !nodeIter.hasAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId)) {
							targetElementToReplace = nodeIter;
							break;
						}
						nodeIter = nodeIter.parentNode;
					}
				}
			}

			if (targetElementToReplace) {
				if (!DWTextEditUtility.elementContainTheTag(targetElementToReplace, "IMG") && DWTextEditUtility.hasOnlyWhitespaceAsContent(targetElementToReplace)) {
					targetElementToReplace.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
				}

				// Unlike h1-h5, if enter is pressed at the end of H6 tag, browser doesn't create div, instead it creates H5. 
				if (targetElementToReplace.tagName === "DIV" || targetElementToReplace.tagName === "H6") {
					document.execCommand('formatBlock', false, 'p');
				} else {
					this.setIbeamToGivenCharOffsetInAGivenNode(targetElementToReplace, 0);
				}

				if (targetElementToReplace && targetElementToReplace.previousSibling && DWTextEditUtility.elementContainTheTag(targetElementToReplace, "IMG")) {
					innerHTML = targetElementToReplace.previousSibling.innerHTML.trim();
					if (innerHTML === "" || innerHTML === "<br>") {
						targetElementToReplace.previousSibling.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
					}
				}
			}
		} else if (this.m_enterPostProcessingAction === ENTER_POST_PROCESSING_ACTIONS.CreateNestedPTagsAndInsertThemToOrignalParent) {
			if (this.m_blockLevelTagNameBeforeEnter) {
				var firstBlockLevelNode = DWTextEditUtility.getFirstBlockLevelParent(nodeActual);
				if (!firstBlockLevelNode || !firstBlockLevelNode.previousSibling) {
					return;
				}

				var origNode = document.createElement(this.m_blockLevelTagNameBeforeEnter);
				var range = document.createRange();
				range.setStartBefore(firstBlockLevelNode.previousSibling);
				range.setEndAfter(firstBlockLevelNode);
				range.surroundContents(origNode);

				if (firstBlockLevelNode.previousSibling.innerHTML === "<br>") {
					firstBlockLevelNode.previousSibling.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
				}

				if (firstBlockLevelNode.innerHTML === "<br>") {
					firstBlockLevelNode.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
				}

				this.setIbeamToGivenCharOffsetInAGivenNode(firstBlockLevelNode, 0);
				this.m_blockLevelTagNameBeforeEnter = null;
			}
		}

		var selectionChangeEvent = new CustomEvent("selectionchange");
		document.dispatchEvent(selectionChangeEvent);
		DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.ELV_LVE, DW_ICE_HEADLIGHTS.NewTagCreatedViaEnter);
	} finally {
		// Disable UNDO grouping.
		document.execCommand("DisableUndoGrouping");
	}

};


// prepareTextUnderDwSpan - move text group under dw-span and insert it
//		live view DOM replacing the original text group.
TextEditHud.prototype.prepareTextUnderDwSpan = function () {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : prepareTextUnderDwSpan");
    if (!this.m_textGroupContext) {
        return false;
    }

    var dwSpanNode = document.createElement(DW_LIVEEDIT_CONSTANTS.TextContainer);

    // Text Group or individual text node? 
    if (this.m_textGroupContext.group && this.m_textGroupContext.group.startingSiblingText && this.m_textGroupContext.group.endingSiblingText) {

        var currNode = this.m_textGroupContext.group.startingSiblingText,
            parentNode = currNode.parentNode,
            endNode = this.m_textGroupContext.group.endingSiblingText;

        // insert dw-span into DOM tree.
        parentNode.insertBefore(dwSpanNode, currNode);

        // move all text nodes as child into dw-span
        var clonedNode;
        while (currNode) {
            var nextNode = currNode.nextSibling,
                foundEndNode = false;

            if (currNode === endNode) {
                foundEndNode = true;
            }

            clonedNode = parentNode.removeChild(currNode);
            dwSpanNode.appendChild(clonedNode);

            currNode = nextNode;

            if (foundEndNode) {
                break;
			}
        }
    } else {
		// Individual text node just wrap it with dw-span.
		// Sometime, text node value is null when tag is empty.
		var nodeobj = this.m_textGroupContext.textNode;
		if (!nodeobj || !nodeobj.nodeValue) {
			return false;
		}

		dwSpanNode.appendChild(document.createTextNode(nodeobj.nodeValue));
        nodeobj.parentNode.replaceChild(dwSpanNode, nodeobj);
    }

    // Update the current element to dw-span node.
    this.m_curElement = dwSpanNode;

	// We need to redraw parent element some times when we 
	// exit from text editing session
	this.m_forceRedraw = this.m_curElement &&
						this.m_curElement.firstChild &&
						this.m_curElement.firstChild.nodeType === Node.ELEMENT_NODE &&
						DW_LIVE_TEXT_EDIT.TagsNeedsRedraw.indexOf(this.m_curElement.firstChild.tagName) >= 0;

	this.setUpHandlersForKeyboardAndMouseClickWorkflows(this.m_curElement);

    return true;
};

// This is the single point method for registering all event hooks for dw-span node.
TextEditHud.prototype.setUpHandlersForKeyboardAndMouseClickWorkflows = function (element) {
    'use strict';

	if (!element) {
		return;
	}

	// handle input event to make sure that the tag itself is not deleted
	element.addEventListener("input", this.inputListenerOnElement.bind(this), false);

	// Intercept keyDown - refer the harder implementation for the purpose.
    element.addEventListener("keydown", this.keyDownListenerOnElement.bind(this), false);

	// Intercept keyUp - refer the harder implementation for the purpose.
	element.addEventListener("keyup", this.keyUpListenerOnElement.bind(this), false);

	// Intercept mouseDown -  for tracking selection on mouse out
    element.addEventListener("mousedown", this.mouseDownListenerOnElement.bind(this), false);

    // Intercept mouseOut - mouse out should disable selection outside and let the selection be there for our element
	element.addEventListener("mouseout", this.mouseOutListener.bind(this), true);
};


//	KeyDown handler attached to DWSpanNode
//  Used in :
//	1. EnterHandling
//	2. Making Tab key as no-op inside text editing session.
TextEditHud.prototype.keyDownListenerOnElement = function (e) {
	'use strict';
	var shiftDown = event.shiftKey;
	if (event.keyCode === DW_LIVEEDIT_CONSTANTS.EnterKeyCode && !shiftDown) {
		this.FigureOutPostProcessingAfterEnterKeyPress(event);
	} else if (event.keyCode === DW_LIVEEDIT_CONSTANTS.TabKeyCode) {
		event.preventDefault();
		event.stopPropagation();
		return false;
	} else if (event.keyCode === DW_LIVEEDIT_CONSTANTS.Up || event.keyCode === DW_LIVEEDIT_CONSTANTS.Down
                || event.keyCode === DW_LIVEEDIT_CONSTANTS.Left || event.keyCode === DW_LIVEEDIT_CONSTANTS.Right) {
        //do not propogate arrow keys
        event.stopPropagation();
    } else if (event.keyCode === DW_LIVEEDIT_CONSTANTS.BackSpace) {
        this.m_backspacePressed = true;
		// Enable document edits into single UNDO group.
		this.m_undoGroupEnabled = true;
		document.execCommand("EnableUndoGrouping");
    } else if (event.keyCode === DW_LIVEEDIT_CONSTANTS.DeleteKey) {
        this.m_deletePressed = true;
		// Enable document edits into single UNDO group.
		this.m_undoGroupEnabled = true;
		document.execCommand("EnableUndoGrouping");
    }
	this.selectHandler();
};


//	KeyUp handler attached to DWSpanNode
//  Used in :
//	1. Handling of Enter/Return key
TextEditHud.prototype.keyUpListenerOnElement = function (e) {
	'use strict';

	// Enter / Return key handling
	if (event.keyCode === DW_LIVEEDIT_CONSTANTS.EnterKeyCode  && event.shiftKey) {
		DWTextEditUtility.insertHtmlAtCaretPosition('<br>');
		event.preventDefault();
		event.stopPropagation();
	} else if (event.keyCode === DW_LIVEEDIT_CONSTANTS.EnterKeyCode) {
		this.DoPostProcessingAfterEnterKeyPress();
		this.m_enterPostProcessingAction = null;
	}
	this.showTextEditingFeedback();
	this.selectHandler();
};


//	input event handler attached to DWSpanNode
//  Used in :
//	1. Handling of Backspace key
//	2. Handling of Delete Key	
TextEditHud.prototype.inputListenerOnElement = function (e) {
	'use strict';

	try {
		// Backspace key handling
		if (this.m_backspacePressed) {
			this.m_backspacePressed = false;
			if (this.m_curElement) {
				var firstCh = this.m_curElement.firstChild;
				if (firstCh && firstCh.nodeType === Node.ELEMENT_NODE && (firstCh.tagName === DW_LIVEEDIT_CONSTANTS.TableDataTagName || firstCh.tagName === DW_LIVEEDIT_CONSTANTS.TableHeaderTagName)) {
					var innerHtm = firstCh.innerHTML.trim();
					if (innerHtm.length === 0 || innerHtm === "<br>") {
						firstCh.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
					}
					return;
				}
			}
			if (DWTextEditUtility.hasSingleElementWhoseContentIsWhitespace(this.m_curElement) && DWTextEditUtility.isCaretAtBeginningOfTextNode(this.m_curElement)) {
				var collaspeTag = true;
				// we should not delete the tag if the element has only one <br> tag. When user deletes all the tag content, browser adds <br> tag when last character is deleted. Hence we should not delete the tag when last char is deleted.
				if (DWTextEditUtility.isFragmentHasOnlyOneBRTagAsItsContent(this.m_curElement)) {
					collaspeTag = false;
				}
				if (collaspeTag) {
					this.m_curElement.innerHTML = "";
				}
			}

			if (this.m_curElement.innerHTML.length === 0) {
				// From CEF version 1650 onwards, when text container (dwspan) directly around the text, webkit doesn't add <br>/&nbsp; when all of the text
				//	is selected and deleted. 

				// Hence we should we should only delete the tag and close the hud when dwspan is outside of the tags.
				if (this.m_deleteTagOnDeletingAllContent === true) {
					window.CommitAndCloseHud();
				} else {
					// Insert non breaking space
					document.execCommand("insertHTML", false, DW_LIVEEDIT_CONSTANTS.NonBreakingSpace);
				}
			}
			this.showTextEditingFeedback();
			this.selectHandler();

		// Delete key handling	
		} else if (this.m_deletePressed) {
			this.m_deletePressed = false;
			// After delete operation, if browser adds adds <br> tag, change it to nbsp;
			if (DWTextEditUtility.isFragmentHasOnlyOneBRTagAsItsContent(this.m_curElement)) {
				var elementUnderIP = DWTextEditUtility.getElementUnderIP();
				if (elementUnderIP) {
					elementUnderIP.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
					this.setIbeamToGivenCharOffsetInAGivenNode(elementUnderIP, 0);
				}
			}

			if (this.m_curElement.innerHTML.length === 0) {
				// Insert non breaking space
				document.execCommand("insertHTML", false, DW_LIVEEDIT_CONSTANTS.NonBreakingSpace);
			}
			this.showTextEditingFeedback();
			this.selectHandler();
		}
	} finally {
		// Disable UNDO group.
		if (this.m_undoGroupEnabled) {
			this.m_undoGroupEnabled = false;
			document.execCommand("DisableUndoGrouping");
		}
	}
};


//	MouseUp handler attached to DWSpanNode
//  This will propagate selection to text-formatting and also removes the disable selection class we attached to body
TextEditHud.prototype.mouseupListenerOnWindow = function (e) {
    'use strict';

	this.isMouseDown = false;
    if (document.body.classList.contains(DW_LIVEEDIT_CONSTANTS.DisableSelectionOnBody_TextEdit)) {
        document.body.classList.remove(DW_LIVEEDIT_CONSTANTS.DisableSelectionOnBody_TextEdit);
        var classes = document.body.getAttribute("class");
        if (classes &&  classes.trim() === "") {
            document.body.removeAttribute("class");
        }
    }
    this.selectHandler();
};


//	MouseDown handler attached to DWSpanNode
//  For tracking the mouse down for selection on mouse-out handling purposes
TextEditHud.prototype.mouseDownListenerOnElement = function (e) {

    'use strict';

    this.isMouseDown = true;
};

// when we are going out of the element disable selection for everybody else and let the selection be there for our element
TextEditHud.prototype.mouseOutListener = function (e) {
    'use strict';
    if (this.getVisibility() && this.isMouseDown) {
        if (!document.body.classList.contains(DW_LIVEEDIT_CONSTANTS.DisableSelectionOnBody_TextEdit)) {
            document.body.classList.add(DW_LIVEEDIT_CONSTANTS.DisableSelectionOnBody_TextEdit);
        }
    }
};

// createTempIdS - associates temp IDs for affected elements.
//		These temp IDs will be used to map back with new 
//		data_liveedit_tagid values from DW
TextEditHud.prototype.createTempIdS = function (node) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : createTempIdS");
    if (!node) {
        return false;
    }

    if (node.nodeType === Node.TEXT_NODE) {
        return false;
    }

	// if node has temp ID already, we should not be adding new temp ID
	// because there may be commit process is going on and changing temp ID
	// would disturb the commit process.
	var lastTempId = this.m_dwgTempElementCount;
    if (node.nodeType === Node.ELEMENT_NODE
			&& node.tagName !== DW_LIVEEDIT_CONSTANTS.TextContainer
			&& !node.hasAttribute(DW_LIVEEDIT_CONSTANTS.DWTempId)) {
        this.m_dwgTempElementCount += 1;
        node.setAttribute(DW_LIVEEDIT_CONSTANTS.DWTempId, this.m_dwgTempElementCount);
    }

    var i, children = node.children;
    for (i = 0; i < children.length; i += 1) {
        this.createTempIdS(children[i]);
    }

	if (lastTempId !== this.m_dwgTempElementCount) {
		// tempId created
		return true;
	}
};

// updateNewIdS - updates new "data_liveedit_tagid" values 
//		using associated temp IDs.
TextEditHud.prototype.updateNewIdS = function (node, newIdInfo) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : updateNewIdS");

    if (!node) {
        this.logDebugMsg("function begin : TextEditHud : node not present");
        return;
    }
    if (node.nodeType === Node.TEXT_NODE) {
        this.logDebugMsg("function begin : TextEditHud : node text node");

        return;
    }

    if (node.nodeType === Node.ELEMENT_NODE) {
        var dwTempId = node.getAttribute(DW_LIVEEDIT_CONSTANTS.DWTempId);
        node.removeAttribute(DW_LIVEEDIT_CONSTANTS.DWTempId);
        if (dwTempId && newIdInfo[dwTempId]) {
			this.logDebugMsg("TextEditHud : updateNewIdS - node element tempId: " + dwTempId + " new ID: " + newIdInfo[dwTempId]);
            node.setAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId, newIdInfo[dwTempId]);
        } else if (dwTempId) {
			this.logDebugMsg("Failed TextEditHud : updateNewIdS - node element tempId: " + dwTempId + " node tagName " + node.tagName);
		}
    }

    var i, children = node.children;
    for (i = 0; i < children.length; i += 1) {
        this.updateNewIdS(children[i], newIdInfo);
    }
};

// updateNewIdSForRange - updates new "data_liveedit_tagid" values 
//		using associated temp IDs for range of nodes.
TextEditHud.prototype.updateNewIdSForRange = function (startNode, endNode, newIdInfo) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : updateNewIdSForRange");

    if (!startNode && !endNode) {
        this.logDebugMsg("function begin : updateNewIdSForRange : startNode & endNode are not present");
        return;
    }

	if (!startNode) {
		if (endNode.parentNode) {
			startNode = endNode.parentNode.firstChild;
		}

		if (!startNode) {
			this.logDebugMsg("function begin : updateNewIdSForRange : startNode not present");
			return;
		}
	}

	if (!endNode) {
		if (startNode.parentNode) {
			endNode = startNode.parentNode.lastChild;
		}

		if (!endNode) {
			this.logDebugMsg("function begin : updateNewIdSForRange : endNode not present");
			return;
		}
	}

	var curNode = startNode;
	while (curNode) {
		this.updateNewIdS(curNode, newIdInfo);
		if (curNode === endNode) {
			break;
		}
		curNode = curNode.nextSibling;
	}
};

// cleanNBSPsIntroducedForEditing - We introduce &nbsp; to some element to 
// make it editable, search and remove them before quiting editable session. 
TextEditHud.prototype.cleanNBSPsIntroducedForEditing = function (element) {
	'use strict';
	if (!element || element.nodeType !== Node.ELEMENT_NODE) {
		return;
	}

	var orgHtml, dwID = element.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
	if (!dwID) {
		if (element.tagName !== DW_LIVEEDIT_CONSTANTS.TextContainer) {
			return;
		}
	} else {
		orgHtml =  this.m_mapTempNBSPsForEditing[dwID];
	}

	if (typeof orgHtml === 'string') {
		// If we are here, then we have changed the innerHTML of this element.
		// If innerHTML is remains &nbsp; we will put back original html.
		if (element.innerHTML === DW_LIVEEDIT_CONSTANTS.NonBreakingSpace) {
			this.logDebugMsg("Reverting $nbsp; from element tag:" + element.tagName + " DWID:" + dwID);
			element.innerHTML = orgHtml;
		}
	} else if (element.childNodes) {
		var i,
			chidNodes = element.childNodes,
			node = chidNodes[0],
			curTextNode = null;

		// validate nodes
		for (i = 0; node; i += 1, node = chidNodes[i]) {
			this.cleanNBSPsIntroducedForEditing(node);
		}
	}
};

// canElementBeEditedAsText - if element is empty or has only &nbsp; then 
// we can use that as text for editing.
TextEditHud.prototype.canElementBeEditedAsText = function (element) {
	'use strict';
	if (!element) {
		return false;
	}

	var dwID = element.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
	if (!dwID) {
		return false;
	}

	this.m_mapTempNBSPsForEditing = {};

	// if NO text node present inside element, then 
	// check whether element is empty blocks. If it is
	// empty then add &nbsp; and enable text editing.
	var canbe = false;
	if (element.nodeType === Node.ELEMENT_NODE &&
			element.innerHTML === DW_LIVEEDIT_CONSTANTS.NonBreakingSpace) {
		canbe = true;
	} else if (element.nodeType === Node.ELEMENT_NODE &&
				(DW_LIVE_TEXT_EDIT.BlockInclusionList.indexOf(element.tagName) >= 0 ||
				DW_LIVE_TEXT_EDIT.InlineTextElements.indexOf(element.tagName) >= 0) &&
				element.innerHTML.length === 0) {
		this.logDebugMsg("Adding $nbsp; to element tag:" + element.tagName + " DWID:" + dwID);
		this.m_mapTempNBSPsForEditing[dwID] = element.innerHTML;
		element.innerHTML = DW_LIVEEDIT_CONSTANTS.NonBreakingSpace;
		canbe = true;
	}

	return canbe;
};

// enterIntoTextEditing - enter into text editing for the element's text content.
TextEditHud.prototype.enterIntoTextEditing = function (element) {
	'use strict';

	this.logDebugMsg("function begin : TextEditHud : enterIntoTextEditing");

	if (!element) {
		return false;
	}

	var curTextNode = null;
	if (DWTextEditUtility.hasOnlyWhitespaceAsContent(element) && DW_LIVE_TEXT_EDIT.BlockInclusionList.indexOf(element.tagName) >= 0) {
		curTextNode = element;
	}

	if (!curTextNode) {
		curTextNode = DWTextEditUtility.findAnyTextNode(element);
	}
	// If no text node, check whether there is any <br> tag
	if (!curTextNode) {
		curTextNode = DWTextEditUtility.findAnyBRNode(element);
	}

	// Check whether element can be used as text
	var elementCopy = element.cloneNode(true);
	if ((elementCopy && elementCopy.nodeType === Node.ELEMENT_NODE) && elementCopy.tagName !== "A") {
		elementCopy = DWTextEditUtility.findAnchorNodeParentForGivenNode(elementCopy);
	}

	if (!curTextNode && this.canElementBeEditedAsText(element)) {
		curTextNode = element;
	}

	if (curTextNode) {
		this.resetContext();
		this.enableHud();
		this.m_curTextNode = curTextNode;
		this.m_curElement = null;
		this.m_elementUnderCurrentEditingSession = null;

		// If esh is on anchor tag, figure out the the reference from the curTextNode and preserve it. 
		if (this.m_nodeAtClickPoint === null) {
			var nodeIter = (curTextNode.nodeType === Node.TEXT_NODE ? curTextNode.parentNode : curTextNode);
			this.m_nodeAtClickPoint = DWTextEditUtility.findAnchorNodeParentForGivenNode(nodeIter);
			if (this.m_nodeAtClickPoint && elementCopy && elementCopy.tagName === "A") {
				this.m_nodeAtClickPointInnerHTML = elementCopy.innerHTML;
			}
		}

		this.draw({}, true);

        //execute the following code to set the selection in the Dreamweaver side only if there is a 
        //selection mismatch here and in dreamweaver
        if (window.liveViewExtensionsObject) {
            var dreamweaverSelection = window.liveViewExtensionsObject.getCurrentSelectedElement();
            if (this.m_curElement) {
                var elementUnderEdit = this.FindElementToBringbackESHHud(this.m_curElement);
                if (elementUnderEdit !== dreamweaverSelection) {
                    var dwId = elementUnderEdit.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
                    window.DWLECallJsBridgingFunction(this.getHudName(), "DWSetSelectedElement", dwId, false);
                }
            }
        }

		DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.DWShortcuts, DW_ICE_HEADLIGHTS.StartEditing);

        return true;
	}

	return false;
};

// recreateTextEditingContext - called after commit to recreate the dw_span tag and 
//  text group context and identifiers.
TextEditHud.prototype.recreateTextEditingContext = function () {
	'use strict';
	this.logDebugMsg("function begin : TextEditHud : recreateTextEditingContext");

	var dwSpanElement = this.m_elementUnderCurrentEditingSession;
	if (!dwSpanElement || dwSpanElement.innerHTML.length <= 0) {
		return false;
	}

	// No text node, we can't re create context
	if (!DWTextEditUtility.findAnyTextNode(dwSpanElement)) {
		return false;
	}

	// Here we will remove and recreate dw_span and current context.
	// We will find some text element which was under dw_span and 
	// use that as current text node (usually it is identified as part 
	// of setElement) and call this.draw() which will recreate dw_span 
	// and context.
	var prevSibling = dwSpanElement.previousSibling,
		nextSibling = dwSpanElement.nextSibling,
		parentNode = dwSpanElement.parentNode,
		curNode = null,
		curTextNode = null,
		curDwSpanChild =  dwSpanElement.firstChild,
		anchNode = document.getSelection().anchorNode,
		offset = DWTextEditUtility.getCharacterOffsetWithin(anchNode);

	// To recreate entire context, move out all the elemnts from 
	// dw-span tag and remove it.
	while (curDwSpanChild) {
		curDwSpanChild = dwSpanElement.removeChild(curDwSpanChild);
		parentNode.insertBefore(curDwSpanChild, dwSpanElement);
		curDwSpanChild =  dwSpanElement.firstChild;
	}
	parentNode.removeChild(dwSpanElement);

	if (prevSibling) {
		curNode = prevSibling.nextSibling;
	} else {
		curNode = parentNode.firstChild;
	}

	while (curNode) {
		if (nextSibling && curNode === nextSibling) {
			break;
		}

		curTextNode = DWTextEditUtility.findAnyTextNode(curNode);
		if (curTextNode) {
			break;
		}

		curNode = curNode.nextSibling;
	}

	if (curTextNode) {
		this.resetContext();
		this.m_curTextNode = curTextNode;
		this.m_curElement = null;
		this.m_elementUnderCurrentEditingSession = null;
		this.draw({}, true);

		// Try restore selection.
		if (!anchNode) {
			anchNode = DWTextEditUtility.findLastTextNode(this.m_curElement);
			offset = 0;
		}

		if (anchNode.parentNode && DWTextEditUtility.hasSingleElementWhoseContentIsWhitespace(anchNode.parentNode)) {
			this.setIbeamToGivenCharOffsetInAGivenNode(anchNode, 0);
		} else {
			this.setIbeamToGivenCharOffsetInAGivenNode(anchNode, offset);
		}

		return true;
	}

	return false;
};


// refreshTextEditingContext - called after commit to refresh/recalculate the
//  text group context and identifiers.
TextEditHud.prototype.refreshTextEditingContext = function () {
	'use strict';

	this.logDebugMsg("function begin : TextEditHud : refreshTextEditingContext");

	var dwSpanElement = this.m_elementUnderCurrentEditingSession;
	if (!dwSpanElement || dwSpanElement.innerHTML.length <= 0) {
		return false;
	}

	//
	// Check whether this is block level grouping.
	//
	var i,
		node,
		isBlockLevelGrouping = false,
		chidNodes = dwSpanElement.childNodes,
		curTextNode = null;

	if (chidNodes) {
		node = chidNodes[0];
		isBlockLevelGrouping = true;
		// validate nodes
		for (i = 0; node; i += 1, node = chidNodes[i]) {
			if (node.nodeType !== Node.ELEMENT_NODE || DW_LIVE_TEXT_EDIT.BlockInclusionList.indexOf(node.tagName) < 0) {
				isBlockLevelGrouping = false;
				break;
			}
		}
	}

	if (isBlockLevelGrouping) {
		// In case of block level grouping, the current dw_span element will
		// have block level elements only. We will use any of its child text node
		// as current text and refresh the text identifies. 
		// Note: We don't need to recreate dw_span here as there is no hanging 
		// text.
		curTextNode = DWTextEditUtility.findAnyTextNode(dwSpanElement);

		// If no valid text node, consider the element as text if it can be.
		if (!curTextNode && this.canElementBeEditedAsText(dwSpanElement.firstChild)) {
			curTextNode = dwSpanElement.firstChild;
		}

		if (curTextNode) {
			this.resetContext();
			this.m_curTextNode = curTextNode;

			// The below will prepare context for just one block which contains curTextNode.
			if (!this.prepareTextIdentifier(curTextNode)) {
				return false;
			}

			// Include the all other blocks  in grouping.
			this.m_textGroupContext.group.startingSiblingText = dwSpanElement.firstChild;
			this.m_textGroupContext.group.endingSiblingText = dwSpanElement.lastChild;

			this.m_textIdentifier.groupStarting = this.getTextNodeIdentifier(this.m_textGroupContext.group.startingSiblingText);
			if (this.m_textGroupContext.group.startingSiblingText !== this.m_textGroupContext.group.endingSiblingText) {
				this.m_textIdentifier.groupEnding = this.getTextNodeIdentifier(this.m_textGroupContext.group.endingSiblingText);
			}

			// Successfully refreshed the identifiers.
			return true;
		}
	} else {
		// TODO: the same code can be used for block level grouping too. As the above code is already tested code, not changing now.
		// There is hanging text in current editing context. Try refreshing context
		curTextNode = DWTextEditUtility.findAnyTextNode(dwSpanElement);

		// If no valid text node, consider the element as text if it can be.
		if (!curTextNode && this.canElementBeEditedAsText(dwSpanElement.firstChild)) {
			curTextNode = dwSpanElement.firstChild;
		}

		if (curTextNode) {
			this.resetContext();
			this.m_curTextNode = curTextNode;

			// The below will prepare context for curTextNode.
			if (!this.prepareTextIdentifier(curTextNode, true)) {
				return false;
			}

			// create Group info object
			this.m_textGroupContext.group = {};
			this.m_textGroupContext.group.originalNode = curTextNode;
			this.m_textGroupContext.group.allSiblingsAreText = dwSpanElement.parentNode.firstChild === dwSpanElement.parentNode.lastChild;

			// Include the all other blocks  in grouping.
			this.m_textGroupContext.group.startingSiblingText = dwSpanElement.firstChild;
			this.m_textGroupContext.group.endingSiblingText = dwSpanElement.lastChild;

			this.m_textIdentifier.grouped = true;
			this.m_textIdentifier.groupStarting = this.getTextNodeIdentifier(this.m_textGroupContext.group.startingSiblingText, true);
			if (this.m_textGroupContext.group.startingSiblingText !== this.m_textGroupContext.group.endingSiblingText) {
				this.m_textIdentifier.groupEnding = this.getTextNodeIdentifier(this.m_textGroupContext.group.endingSiblingText, true);
			}

			// Successfully refreshed the identifiers.
			return true;
		}
	}

	// failed to refresh the identifiers
	return false;
};

// updateDWIdForTempIDcallback - call back function for DW to refresh 
//		"data_liveedit_tagid" values of affected elements using temp IDs.
TextEditHud.prototype.updateDWIdForTempIDcallback = function (newIdObj) {
    'use strict';

    this.logDebugMsg("function begin : TextEditHud : updateDWIdForTempIDcallback");

    if (!newIdObj) {
        // This should never happen as per protocol.
        this.logDebugMsg("TextEditHud : updateDWIdForTempIDcallback - invalid argument");
        return;
    }
    var startNode = null,
		endNode = null;
    if (this.dwSpanPrevSibling !== null && this.dwSpanNextSibling !== null) {
		startNode = this.dwSpanPrevSibling.nextSibling;
		endNode = this.dwSpanNextSibling.previousSibling;
		this.dwSpanPrevSibling = null;
		this.dwSpanNextSibling = null;
    } else if (this.dwSpanPrevSibling !== null) {
        startNode = this.dwSpanPrevSibling.nextSibling;
        this.dwSpanPrevSibling = null;
    } else if (this.dwSpanNextSibling !== null) {
        endNode = this.dwSpanNextSibling.previousSibling;
        this.dwSpanNextSibling = null;
    }

    if (!startNode && !endNode) {
        var parentNode = this.m_nodeCache[newIdObj.parentId];
        if (!parentNode || newIdObj.parentSwapped) {
            if (newIdObj.parentSwapped && newIdObj.oldParentId) {
                parentNode = this.m_nodeCache[newIdObj.oldParentId];
            }

            if (!parentNode) {
                this.logDebugMsg("TextEditHud : updateDWIdForTempIDcallback - parentNode not found");
                return;
            }

            parentNode.setAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId, newIdObj.parentId);
        }
        startNode = parentNode;
		endNode = parentNode;
    }

	this.updateNewIdSForRange(startNode, endNode, newIdObj);
	DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.OTH_ELV, DW_ICE_HEADLIGHTS.Commit);

	if (!this.m_elementUnderCurrentEditingSession) {
		return;
	}

    this.logDebugMsg("TextEditHud : updateDWIdForTempIDcallback - HTML after tempID update " + this.m_elementUnderCurrentEditingSession.outerHTML);

	// Update the original HTML
	this.m_originalInnerHTMLOfElementUnderCurrentEditingSession = this.m_elementUnderCurrentEditingSession.innerHTML;

	// After saving, we are retaining the editing session. However, commit operation would have caused
	// data-id changes in DW side and also we need to update the text editing context for the new text content.
	var refreshFailed = false;
	if (!this.refreshTextEditingContext()) {
		// If we are here, we can't refresh Text identifiers because of hanging text.
		refreshFailed = true;
	}

    if (this.saveDocumentPending) {
		this.logDebugMsg("TextEditHud : updateDWIdForTempIDcallback - save pending.");

		// We have to recreate whole dw_span editing session in case 
		// refresh context is failed.
		if (refreshFailed && !this.recreateTextEditingContext()) {
			this.destroy();
		}

        // bring up DW save dialog
        window.DWSaveDocument();
        this.saveDocumentPending = false;
    }

    if (this.refreshDocumentPending) {
        window.DWLECallJsBridgingFunction(this.getHudName(), "DwRefreshDocument", null, false);
        this.refreshDocumentPending = false;
    }

};

TextEditHud.prototype.singleClickHandler = function (event) {
    'use strict';
    event.preventDefault();
    event.stopPropagation();
};

TextEditHud.prototype.doubleClickHandler = function (event) {
    'use strict';
    // On double click, register for thrid single click and leave it.
    if (this.m_clickCounter === 0) {
        this.m_clickCounter = 2;
        this.m_element.addEventListener("click", this.onThirdClick.bind(this), false);

        // Get the current mouse position
        var currentClickPosition = {
            x: event.clientX,
            y: event.clientY
        };

        this.conserveClick(currentClickPosition);
    }
};
// Top level method that setup neccessary infrastructure for selecting the 
// complete text block content on triple clicks on the text content
TextEditHud.prototype.initializeTripleClickHandler = function (element) {
    'use strict';

    if (!element) {
        this.logDebugMsg("initializeTripleClickHandler - invalid element");
        return;
    }

    this.m_preservedClickPosition.x = null;
    this.m_preservedClickPosition.y = null;
    this.m_selectTimer = null;
    this.m_clickTimer = null;
    this.m_clickCounter = 0;
    this.m_element = element;

    // When click happens to be on form label, browser forcibly sets focus to controls which is sitting next to it.
    // To fix the same, we are intercepting single click within the editing block and stopping it here itself.
    element.addEventListener('click', this.singleClickHandler.bind(this));
    // simulate the triple click
    element.addEventListener('dblclick', this.doubleClickHandler.bind(this));
    
};

// Function that actually does the selection
TextEditHud.prototype.selectAll = function () {
    'use strict';
    //Clear the existing selection and select the complete current element.
    var selection = window.getSelection();
    if (selection && this.m_element) {
        var range = document.createRange();
        range.selectNodeContents(this.m_element);
        selection.removeAllRanges();
        selection.addRange(range);
        DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.ELV_LVE, DW_ICE_HEADLIGHTS.SelectAll);
    }

    clearTimeout(this.m_selectTimer);
    this.m_selectTimer = null;
    // on triple click we are updating the selection so we should notify this to textFormattingHud
    this.selectHandler();
};

// Function that sets up select all.
TextEditHud.prototype.initiateSelectAll = function () {
    'use strict';
    clearTimeout(this.m_selectTimer);
    this.m_selectTimer = setTimeout(this.selectAll.bind(this), DW_LIVE_TEXT_EDIT.Triple_Click_Selection_Threshold);
};

// Function to reset the data
TextEditHud.prototype.resetClick = function () {
    'use strict';
    this.m_clickCounter = 0;
    this.m_preservedClickPosition.x = null;
    this.m_preservedClickPosition.y = null;
    this.m_element.removeEventListener("click", this.onThirdClick.bind(this), false);
};

TextEditHud.prototype.onThirdClick = function(event) {
    'use strict';
    this.m_clickCounter += 1;
    var currentClickPosition = {
        x: event.clientX,
        y: event.clientY
    };

    // identify the triple click - if three clicks happened within the Triple_Click_Position_Threshold pixels and within 
    // Triple_Click_Interval_Threshold time period
    if (this.m_clickCounter === 3 && (Math.abs(this.m_preservedClickPosition.x - currentClickPosition.x) < DW_LIVE_TEXT_EDIT.Triple_Click_Position_Threshold) && (Math.abs(this.m_preservedClickPosition.y - currentClickPosition.y) < DW_LIVE_TEXT_EDIT.Triple_Click_Position_Threshold)) {
        this.initiateSelectAll();
    }
    this.resetClick();
};

// Function to wait for the next click
TextEditHud.prototype.conserveClick = function (currentClickPosition) {
    'use strict';
    this.m_preservedClickPosition = currentClickPosition;
    clearTimeout(this.m_clickTimer);
    this.m_clickTimer = setTimeout(this.resetClick.bind(this), DW_LIVE_TEXT_EDIT.Triple_Click_Interval_Threshold);
};

// Unintialize triple click handler.
TextEditHud.prototype.unInitializeTripleClickHandler = function (element) {
    'use strict';

    if (!element) {
        this.logDebugMsg("unInitializeTripleClickHandler - invalid element");
        return;
    }

    // remove the click handlers from the element
    element.removeEventListener('click',this.singleClickHandler.bind(this));
    element.removeEventListener('dblclick', this.doubleClickHandler.bind(this));
    this.m_element = null;
};
// updateselection from text formatting hud
TextEditHud.prototype.updateSelection = function () {
    "use strict";
    var sel = window.getSelection();
	if (sel) {
        this.m_lastSelection = sel.toString();
	}
};
// Select handler which listens to selection on the text and calls text hud
TextEditHud.prototype.selectHandler = function () {

    'use strict';
    var curSelectionString = null;
    var currentSelection = window.getSelection();
	if (currentSelection) {
		curSelectionString = currentSelection.toString();
	}
    var messageDetails = {};
    if (!curSelectionString) {
        if (this.m_lastSelection !== null) {
            messageDetails.type = DW_EXTENSION_EVENT.TEXTEDIT_SELECTION_LOST;
            dwExtensionController.sendMessage(messageDetails);
            this.m_lastSelection = null;
        }
    } else {
        messageDetails.type = DW_EXTENSION_EVENT.TEXTEDIT_SELECTION_CHANGED;
        var queryFailed = false, boldState = false, italicState = false;
        try {
            boldState = document.queryCommandState('bold');
            italicState = document.queryCommandState('italic');
        } catch (e) {
            queryFailed = true;
        }
        messageDetails.bold = boldState;
        messageDetails.italic = italicState;
        messageDetails.failed = queryFailed;
        dwExtensionController.sendMessage(messageDetails);
        this.m_lastSelection = curSelectionString;
	}
};


/**
* @setIbeamToGivenCharOffsetInAGivenNode
* Utility method - sets IBeam to the start of the incoming node.
* @param node : node inside which caret need to be set.
* @param charOffset : character Offset.
*/
TextEditHud.prototype.setIbeamToGivenCharOffsetInAGivenNode = function (node, charOffset) {
    'use strict';

	if (!node) {
		return;
	}

	this.logDebugMsg("function begin TextEditHud:  setIbeamToGivenCharOffsetInAGivenNode:" + node);

	//	Post keyboard navigation implementation in Live view, ESH takes the focus for the feature to work. 
	//	On entering text editing session, we need to give focus to parent window explicitly before I beam is shown.
	window.focus();
	var iBeamPos = document.createRange();
	iBeamPos.setStart(node, charOffset);
	iBeamPos.setEnd(node, charOffset);
	iBeamPos.collapse(false);

	var selectionObject = window.getSelection();
	selectionObject.removeAllRanges();
	selectionObject.addRange(iBeamPos);

	// tell text formatting hud about this selection Change
	this.selectHandler();
};

TextEditHud.prototype.pasteHandler = function (event) {
        try {
            // validate the incoming event.
            if (!event) {
                return;
            }

            //Cancel default paste operation.
            event.preventDefault();

            //Stop the event propagation.
            event.stopPropagation();

            // Get text representation of clipboard
            var text = event.clipboardData.getData("text/plain");

            // Replace new lines to br tags
            text = text.replace(/\r/g, "");
			text = text.replace(/\n/g, " ");

			// If < and > are part of the content, insertHTML tries handle them differently. It tries to add close tag
			// when <tagname> is pasted. Hence we are encoding them here.
			text = text.replace(/</g, DW_LIVEEDIT_CONSTANTS.MarkupForLessThan);
			text = text.replace(/>/g, DW_LIVEEDIT_CONSTANTS.MarkupForGreatorThan);

			// if there are multiple spaces next to each other, trim them to singe one
			text = text.replace(/\s+/g, " ");

			// Insert text manually
            document.execCommand("insertHTML", false, text);

            // Reset the feedback to consider the text change (post paste operation).
            this.showTextEditingFeedback();
        } catch (e) {
            this.logDebugMsg("TextHUD Paste handler - exception caught while pasting :" + e.message);
        }
};
// Initialize cut paste handler for text edit
TextEditHud.prototype.initializePasteHandler = function (element) {
    'use strict';

    if (!element) {
        this.logDebugMsg("initializePasteHandler - invalid element");
        return;
    }

    element.addEventListener("paste", this.pasteHandler.bind(this));
};

// Uninitialize cut paste handler.
TextEditHud.prototype.uninitializePasteHandler = function (element) {
    'use strict';

    if (!element) {
        this.logDebugMsg("uninitializePasteHandler - invalid element");
        return;
    }

    // remove the cut and paste handler from the element
    element.removeEventListener('paste', this.pasteHandler.bind(this));
};

// For showing editing disablement UI, we are firing single via Controller, that
// will in turn show the proper disablement UI.
TextEditHud.prototype.ShowEditingDisabledUI = function (element) {
    'use strict';

	if (!element) {
		return;
	}

    this.logDebugMsg("function begin : ShowEditingDisabledUI : draw" + element);

	var editDisabled = true;
	this.BringbackESHHud(element, editDisabled);
};

//  ValidateAndEnableEditing - Identifies the text range and validates it with DW.
//		The selected text range will become editable when we get confirmation callback.
TextEditHud.prototype.ValidateAndEnableEditing = function (enteringTextEditingFromEnter) {
	'use strict';
    this.logDebugMsg("function begin : TextEditHud : ValidateAndEnableEditing");

	// Return if no current text node selected.
	if (!this.m_curTextNode) {
        return false;
    }

	// Library items are not allowed for editing.
    if (DWTextEditUtility.isInLibraryItem(this.m_curTextNode)) {
        DWTextEditUtility.logHeadlightsData(DW_ICE_HEADLIGHTS.OTH_ELV, DW_ICE_HEADLIGHTS.LibEdited);
        return false;
    }

    // Prepare text group and identity for text node
    if (!this.prepareTextIdentifier(this.m_curTextNode)) {
        return false;
    }

	// Ensure text identifier is created
	if (!this.m_textIdentifier) {
		return false;
	}

	// Rememeber enteringTextEditingFromEnter
	this.m_textIdentifier.editingFromEnter = enteringTextEditingFromEnter;

    //
    // Implemented asynchronous way of validating text selection.
    // The way HUD infrastructure validates the editablity is synchronous,
    // Text selection should be validated in aasynchronous way. The below callback is to 
    // cancel the editing on failure after validating with DW SM.
    //
    window.DWLECallJsBridgingFunction(this.getHudName(), "validateTextGroupForEdit", this.m_textIdentifier, false);

	return true;
};

// EnableEditingForIdentifiedText - Makes selected text range (identified by m_textIdentifier) 
// editable using custom tag dw_span.
TextEditHud.prototype.EnableEditingForIdentifiedText = function () {
	'use strict';
    this.logDebugMsg("function begin : TextEditHud : EnableEditingForIdentifiedText");

	if (!this.m_textIdentifier) {
		return false;
	}

	// Show the editing feedback and setup editing session
    if (!this.enterTextEditingSession()) {

        // Reset Text Identifying Context.
        this.resetContext();

        // Error in editing, reset the flag to allow Source/Selection sync.
        this.disableDWSelectionChanges(false);

		var elementUnderEdit = null;
		if (this.m_curTextNode.parentNode.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer) {
			elementUnderEdit = this.m_curTextNode.parentNode.parentNode;
		} else {
			elementUnderEdit = this.m_curTextNode.parentNode;
		}

		var element = this.getCurrentElement();
		if (element) {
			elementUnderEdit = this.FindElementToBringbackESHHud(element);
		}

		if (elementUnderEdit) {
			this.ShowEditingDisabledUI(elementUnderEdit);
		}

        // Bring back the ER.
        this.m_liveEditEvent.fire({
            type: DW_LIVEEDIT_EVENTS.EditOpFailed
        });
    }

    return true;
};
