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

Purpose-
This file has the implementation of Selectors Editing functionality in Live Edit view
*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50 */
/*global $, liveViewObject, dwObject, liveExtensionsConfigPath, dwExtensionController, TAGS, KEYCODES,jQuery, MOUSE,DW_EXTENSION_EVENT, Node*/

/* Constants used in this file */
var CONSTANTS = {
    ExtensionID: "editableSelectors",
    OverlayBorderWidth: 1,
    SuggestionsListMaxHeight: 100,
    ExtensionMinHeight: 130,
    hudEntryID: 'dwLeAuxHudEntry',
    ExtensionPath: '/EditableSelectors',
    Assets: '/assets',
    eshSelectorAddedCommitted: 'ESHSelectorCommitted',
    eshShown: "eshShown",
    endActionCategory: "endAction",
    startAction: "startAction",
    textHudShown: "textHudShown",
    eshHudShown: "eshHudShown",
    eshHudPosition: "eshHudPosition",
    HudSandWich: "/sandwich.svg",
    ccLibCloudClass: "ccLibCloudIcon",
    ccLibCloudLinkID: "ccLibCloudLinkIcon",
    ccLibBrokenLinkID: "ccLibBrokenLinkIcon",
    ccLibLoaderIconID: "ccLibLoaderIcon",
    ccLibOutOfSyncLinkID: "ccLibOutOfSyncLinkIcon",
    ccLibLinkedIcon: "/ccLibAssetLinked.svg",
    ccLibBrokenLinkIcon: "/ccLibAssetLinkBroken.svg",
    ccLibOutOfSyncLinkIcon: "/ccLibAssetLinkNotSynced.svg",
    ccLibLoaderIcon: "/cc-spinner.svg",
    ccLibCloudLoaderClass: "ccLibLoaderIcon",
    ccLibLoaderHeightOffset : 32
};

var DWLE_IMG_CLASS = {
    ImgHudEntry: "dw_imgPropHud",
    ImgSelection: "dw_imgSelection",
    Separator: " "
};

var SELECTOR_CONSTANTS = {
    ElementHudClass: "elementHud",
    ElementHudEditableBgColor: "elementHudEditableBackground",
    ElementHudNonEditableBgColor: "elementHudNonEditableBackground",
    TagnameClass: "tagName",
    OverlayClass: "overlay",
    OverlayEditableBorderColor: "overlayEditableBorderColor",
    OverlayNonEditableBorderColor: "overlayNonEditableBorderColor",
    EditableSelectorClass: "selector",
    NonEditableSelectorClass: "selectorNonEditable",
    ExpandedSelectorClass: "selectorExpanded",
    SelectorsContainerClass: "selectorsContainer",
    SelectorNameClass: "selectorName",
    RemoveSelectorButtonClass: "removeSelectorButton",
    RemoveSelectorButtonString: "",
    AddNewSelectorButtonID: "addNewSelectorButton",
    NewSelectorTextInputID: "newSelectorTextInput",
    NewSelectorTextInputJQueryID: "#newSelectorTextInput",
    RenameSelectorTextInputID: "renameSelectorTextInput",
    RenameSelectorTextInputJQueryID: "#renameSelectorTextInput",
    AddNewSelectorButtonString: "+",
    NewSelectorInputPlaceholderText: "Class / ID",
    GrabCursorStyle: "-webkit-grab"
};

var DW_ESH_HEADLIGHTS = {
    /* SUB CATEGORIES */
    ELV_ESH: "ELV ESH",
    OTH_ELV: "OTH ELV",
    ESH_CATEGORY_DWSHORTCUTS: "ELV DWShortcuts",
    /* FEATURES */
    ESH_ADD_NEW: "ESH AddNew",
    ESH_ADD_EXISTING: "ESH AddExisting",
    ESH_DELETE: "ESH Delete",
    ESH_MULTIADD: "ESH MultiAdd",
    ESH_ESCAPE: "ESH Escape",
    ESH_FEATURE_DOM_TRAVERSED: "ESH DOMTraversed",
    ESH_FEATURE_TRAVERSAL_ATTEMPTED: "ESH Traversal Attempted",
    ESH_FEATURE_SELECTOR_TRAVERSED: "ESH SelectorTraversed",
    ESH_FEATURE_SELECTOR_TRAVERSED_REV: "ESH SelectorTraversedReversed",
    ESH_FEATURE_SELECTOR_DELETE: "ESH SelectorDelete",
    ESH_FEATURE_IMAGEHUD_LAUNCH: "IH Launch",
    ESH_FEATURE_CHILD_TRAVERSED: "ESH ChildTraversed",
    ESH_FEATURE_PARENT_TRAVERSED: "ESH ParentTraversed",
    ESH_FG_ADD: "ESH FluidGrid Add",
    ESH_FG_DELETE: "ESH FluidGrid Delete",
    ESH_FG_MULTIADD: "ESH FluidGrid Multi Add",
    CATEGORY_SHORTCUT: "DWShortcuts",
    TEXT_HUD_INVOKE_SHORTCUT: "Text_HUD_Invoke_Shortcut"
};

var CCLIB_ASSET_STATE = {
    LIBRARY_ASSET_STATE_NONE: 0,
    LIBRARY_ASSET_STATE_LINKED: 1,
    LIBRARY_ASSET_STATE_BROKENLINK: 2,
    LIBRARY_ASSET_STATE_FETCHFAILED: 3
};

/* Object definition */

function EditableSelectors() {
    'use strict';
}
// utility function for keyboard navigation to get ESH on give node


/*
function:initialize - initialize variabes, add event listeners

Arguments: None

Return : None
*/
EditableSelectors.prototype.initialize = function () {
    'use strict';

    //initialize variables
    this.m_documentClassesForElement = null;
    this.m_documentIdForElement = null;
    this.m_liveClassesForElement = null;
    this.m_liveIdForElement = null;
    this.m_currentSelectedElement = null;
    this.m_hudIsOnTop = true;
    this.textEditing = false;
    this.m_initiateTextEditOnKeyUp = false;
    this.m_numberOfClasses = 0; // number of classes 
    this.m_numberOfIds = 0; // number of ids 
    this.traversalKeyChangedEsh = false; // keyboard traversal tracking
    this.m_lastKeyCode = 0; // Used for headlights tracking of scenario where delete key is pressed after tab
    this.ignoreSelectionChange = false; // ignore the selection change event
    this.selectorList = [];
    this.ignoreLostFocus = false; // ignore lost focus event for cases when selector hud is open
    this.selectorText = ""; // cache the value of text box, to send it to selector hud
    this.addSelectorThroughKeyboard = false;
    this.m_oldSelectorName = null;
    this.m_activeInputId = SELECTOR_CONSTANTS.NewSelectorTextInputID;
    this.m_activeInputJqueryId = SELECTOR_CONSTANTS.NewSelectorTextInputJQueryID;
    this.m_selectorsInDocument = null;

    window.addEventListener("message", this.messageHandler.bind(this), false);
    //initialize our container structure
    this.m_hudContainer = document.body;
    this.m_hudContentDiv = document.createElement(TAGS.DwDiv);
    this.m_hudContentDiv.setAttribute('class', SELECTOR_CONSTANTS.ElementHudClass);
    this.m_hudContainer.appendChild(this.m_hudContentDiv);

    //create overlay component
    this.m_overlayDiv = document.createElement(TAGS.DwDiv);
    this.m_overlayDiv.setAttribute('class', SELECTOR_CONSTANTS.OverlayClass);
    this.m_hudContainer.appendChild(this.m_overlayDiv);

    //set click handler
    this.m_hudContentDiv.onmouseup = this.ClickHandler.bind(this);
    var keyPressHandler = function (evt) {
        evt = evt || window.event;
        // key down event does not come for space key on mac. 
        // so we need to trap on keyprss and stop default behavior 
        // so that page does not scroll
        if (evt.keyCode === KEYCODES.Space) {
            evt.preventDefault();
            evt.stopPropagation();
        }
    };
    //add key up handler
    var keyUpHandler = function (evt) {
        evt = evt || window.event;
        var node = this.m_currentSelectedElement;
        if (!liveViewObject.isExtensionVisible(CONSTANTS.ExtensionID) || !node) {
            return;
        }
        if (evt.keyCode === KEYCODES.Enter) {
            if (this.m_initiateTextEditOnKeyUp && this.globalController && this.m_currentSelectedElement) {
                this.globalController.enterIntoTextEditing(this.m_currentSelectedElement);
            }
            this.m_initiateTextEditOnKeyUp = false;
        } else if (evt.keyCode === KEYCODES.Space) {
            // if we are on sandwich icon and we hit space we should toggle imagehud state
            var hudEntryIcon = document.getElementById(CONSTANTS.hudEntryID);
            if (evt.target && evt.target === hudEntryIcon) {
                this.toggleAuxHudState(false);
                if (dwObject) {
                    var tagName = this.m_currentSelectedElement.tagName.toLowerCase();
                    if (tagName === "img") {
                        dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_FEATURE_IMAGEHUD_LAUNCH);
                    } else if (tagName === "p" || tagName === "h1" || tagName === "h2" || tagName === "h3" || tagName === "h4" || tagName === "h5" || tagName === "h6" || tagName === "pre") {
                        dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.CATEGORY_SHORTCUT, DW_ESH_HEADLIGHTS.TEXT_HUD_INVOKE_SHORTCUT);
                    }
                }
                evt.stopPropagation();
                evt.preventDefault();
            }
        } else if (evt.keyCode === KEYCODES.Delete) {
            var eventTarget = evt.target;
            if (eventTarget) {
                var targetClass = eventTarget.getAttribute('class');
                if (targetClass && targetClass === SELECTOR_CONSTANTS.EditableSelectorClass) {
                    var selectorNameElement = eventTarget.firstChild;
                    var i = 0;
                    var child = eventTarget;
                    while ((child = child.previousSibling) !== null) {
                        i++;
                    }
                    if (selectorNameElement) {
                        var attrToRemove = selectorNameElement.innerHTML;
                        this.removeSelector(attrToRemove);
                        if (dwObject) {
                            dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_FEATURE_SELECTOR_DELETE);
                        }
                        liveViewObject.clearBrowserUndoRedos();
                        // clearbrowserundoredos triggers a selection change(refer to its definition). 
                        // We should not recreate the HUD in this case . if we do the following code will set the focus to next selector but 
                        // focus will be lost again as we recreated hud.
                        this.ignoreSelectionChange = true;
                        //clear browser dom modifications we have done, so that undo will go to DW document
                        //and revert this operaion
                        var selectorContainer = document.getElementsByClassName(SELECTOR_CONSTANTS.SelectorsContainerClass);
                        if (selectorContainer) {
                            child = selectorContainer[0].firstChild;
                            while (i && child.nextSibling) {
                                child = child.nextSibling;
                                i--;
                            }
                            child.focus();
                        }
                    }
                }
            }
        } else if (evt.keyCode === KEYCODES.Down || evt.keyCode === KEYCODES.Up) {
            //In new selector input field, cursor moves to the beginning while navigating through suggestions
            //This is the behaviour for the jQuery plugin with contentEditable input field. so we have
            //to put the cursor at the end 
            var inputElement = document.getElementById(this.m_activeInputId);
            //check the target node
            if (inputElement && evt.target === inputElement) {
                var inputStr = inputElement.innerHTML;
                if (inputStr && inputElement.childNodes[0]) {
                    //set the document selection to bring the cursor to the end
                    var range = document.createRange();
                    var sel = window.getSelection();
                    range.setStart(inputElement.childNodes[0], inputStr.length);
                    range.collapse(true);
                    sel.removeAllRanges();
                    sel.addRange(range);
                }
                //on MAC, first keypress is lost for some reason after a up/down press
                //so send a dummy right arrow key press to get rid of this
                //this is a noop as the cursor is already at the right most position in the input
                $(this.m_activeInputJqueryId).focus().trigger({
                    type: "keypress",
                    which: KEYCODES.Right
                });
            }
            if (this.traversalKeyChangedEsh && dwObject) {
                dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_FEATURE_DOM_TRAVERSED);
                this.traversalKeyChangedEsh = false;
            }
        }
        this.m_lastKeyCode = evt.keyCode;
    };

    //add key down handler to handle escape and enter key
    var keyDownHandler = function (evt) {
        evt = evt || window.event;
        var node = this.m_currentSelectedElement;
        if (!liveViewObject.isExtensionVisible(CONSTANTS.ExtensionID) || !node) {
            return;
        }
        if (evt.keyCode === KEYCODES.Escape) {
            this.escapeKeyPressed();
        } else if (evt.keyCode === KEYCODES.Enter) {
            //pressing enter when HUD on, should enter text editing
            evt.stopPropagation();
            if (this.m_currentSelectedElement && liveViewObject.isExtensionVisible(CONSTANTS.ExtensionID)) {
                //we should initiate text editing only on keyup, 
                //otherwise keyup will be handled by text editing session 
                this.m_initiateTextEditOnKeyUp = true;
            }
        } else if (evt.keyCode === KEYCODES.Tab) {
            //if we are on sandwich icon when we hit shift tab we should goto add selector plus button
            var currentActive = evt.target;
            if (evt.shiftKey) {
                // for reverse cycling
                var sandwichIcon = document.getElementById(CONSTANTS.hudEntryID);
                var selectorContainer = document.getElementsByClassName(SELECTOR_CONSTANTS.SelectorsContainerClass);
                // if current active element is sandwich icon or if sandwich icon is not present then first selector, then focus should goto add button.
                if (currentActive && ((sandwichIcon && currentActive === sandwichIcon) ||
                        (!sandwichIcon && selectorContainer && currentActive === selectorContainer[0].firstChild))) {
                    var addButton = document.getElementById(SELECTOR_CONSTANTS.AddNewSelectorButtonID);
                    if (addButton) {
                        addButton.focus();
                        if (this.isSelectorInList() && this.canApplySelector()) {
                            this.addSelectorThroughKeyboard = true;
                            this.AddSelector();
                        }
                    }
                    evt.preventDefault();
                    evt.stopPropagation();
                }
                if (dwObject) {
                    dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_FEATURE_SELECTOR_TRAVERSED_REV);
                }
            } else {
                if (dwObject) {
                    dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_FEATURE_SELECTOR_TRAVERSED);
                }
                //The Tab order will take care of tabbing, jus need to log data here
            }
        } else if (evt.keyCode === KEYCODES.Down || evt.keyCode === KEYCODES.Up) {
            // dont let the page be scrolled
            evt.stopPropagation();
            evt.preventDefault();
            var inputElement = document.getElementById(this.m_activeInputId);
            //check the target node
            if (inputElement && evt.target === inputElement) {
                return;
            }
            if (this.isAnyAuxHudVisible()) {
                return;
            }
            var isEshChanged = false;
            if (evt.keyCode === KEYCODES.Up) {
                // Up arrow goes to previous sibling's last child and if previous sibling is not present it goes to parent node
                while (isEshChanged !== true && node && node.tagName !== "BODY") {
                    if (node.previousElementSibling) {
                        node = node.previousElementSibling;
                        while (node.lastElementChild) {
                            node = node.lastElementChild;
                        }
                    } else {
                        node = node.parentNode;
                    }
                    isEshChanged = liveViewObject.setSelectionOnElement(node);
                }
            } else if (evt.keyCode === KEYCODES.Down) {
                // down array goes to first child of the node if first child is not present it goes to parents sibling
                while (isEshChanged !== true && node && node.nodeType !== Node.TEXT_NODE) {
                    if (node.tagName.toLowerCase() === parent.DW_LIVEEDIT_CONSTANTS.GlobalContainerDiv) {
                        break;
                    }
                    // Objects or iframes should not be traversed inside 
                    if (node.tagName !== "OBJECT" && node.firstElementChild) {
                        node = node.firstElementChild;
                    } else {
                        while (node && node.tagName !== "BODY") {
                            if (node.nextElementSibling) {
                                node = node.nextElementSibling;
                                break;
                            }
                            node = node.parentNode;
                        }
                    }
                    isEshChanged = liveViewObject.setSelectionOnElement(node);
                }
            }
            this.traversalKeyChangedEsh = isEshChanged || this.traversalKeyChangedEsh;

        } else if (evt.keyCode === KEYCODES.Left || evt.keyCode === KEYCODES.Right) {
			// Do nothing if any Aux HUD is visible.
			if (this.isAnyAuxHudVisible()) {
                return;
            }
			// This is to handling navigation inside Table.
			// Check whether current selected node is TD or inside TD.
			while (node && node.tagName !== "TD" && node.tagName !== "TH" && node.tagName !== "BODY") {
				node = node.parentNode;
			}
			if (!node || (node.tagName !== "TD" && node.tagName !== "TH")) {
				return;
			}

            var selectionChanged = false;
			var currentSibling = null;
			var tableCellnode = null;
            if (evt.keyCode === KEYCODES.Left) {
                // Left arrow goes to previous table cell or last cell of previous table row.
				if (node.previousElementSibling) {
					selectionChanged = liveViewObject.setSelectionOnElement(node.previousElementSibling);
				} else if (node.parentNode && node.parentNode.previousElementSibling) {
					currentSibling = node.parentNode.previousElementSibling;
					tableCellnode = null;

					// if empty row try to find table cell in near by row.
					while (!tableCellnode && currentSibling) {
						tableCellnode = currentSibling.lastElementChild;
						currentSibling = currentSibling.previousElementSibling;
					}

					selectionChanged = liveViewObject.setSelectionOnElement(tableCellnode);
				} else {
					return;
				}
            } else if (evt.keyCode === KEYCODES.Right) {
                // Right arrow goes to next table cell or first cell of next table row.
                if (node.nextElementSibling) {
					selectionChanged = liveViewObject.setSelectionOnElement(node.nextElementSibling);
				} else if (node.parentNode && node.parentNode.nextElementSibling) {
					currentSibling = node.parentNode.nextElementSibling;
					tableCellnode = null;

					// if empty row try to find table cell in near by row.
					while (!tableCellnode && currentSibling) {
						tableCellnode = currentSibling.firstElementChild;
						currentSibling = currentSibling.nextElementSibling;
					}

					selectionChanged = liveViewObject.setSelectionOnElement(tableCellnode);
				} else {
					return;
				}
            }

			// dont let the page be scrolled
            evt.stopPropagation();
            evt.preventDefault();

            this.traversalKeyChangedEsh = selectionChanged || this.traversalKeyChangedEsh;
        }
    };

    window.addEventListener("keyup", keyUpHandler.bind(this), false);
    window.addEventListener("keydown", keyDownHandler.bind(this), false);
    window.addEventListener("keypress", keyPressHandler.bind(this), false);
    //we should handle escape on parent doc also
    if (parent) {
        parent.addEventListener("keydown", keyDownHandler.bind(this), false);
        parent.addEventListener("keyup", keyUpHandler.bind(this), false);
        //add resize handler too
        parent.addEventListener("resize", this.windowResized.bind(this), false);
    }
    dwExtensionController.addListener(DW_EXTENSION_EVENT.RELOAD_ESH, this.reload, this);
    dwExtensionController.addListener(DW_EXTENSION_EVENT.ELEMENT_DIMENSION_CHANGED, this.repositionHUD, this);
    dwExtensionController.addListener(DW_EXTENSION_EVENT.TEXT_EDIT_BEGIN, this.textEditBegin, this);
    dwExtensionController.addListener(DW_EXTENSION_EVENT.TEXT_EDIT_END, this.textEditEnd, this);
    dwExtensionController.addListener(DW_EXTENSION_EVENT.SET_SELECTION_ON_ESH, this.setSelectionOnFirstHudElement, this);
    dwExtensionController.addListener(DW_EXTENSION_EVENT.SELECT_PARENT, this.setSelectionOnParent, this);
    dwExtensionController.addListener(DW_EXTENSION_EVENT.SELECT_CHILD, this.setSelectionOnFirstChild, this);
    dwExtensionController.addListener(DW_EXTENSION_EVENT.IGNORE_SELECTION_CHANGE, this.setIgnoreSelectionChange, this);
    dwExtensionController.addListener(window.parent.DW_EVENTS.ShowHud, this.showHudForIAM, this);

    this.globalController = window.parent.globalController;
    this.locStrings = {};
    this.locStrings.auxHudTooltip = this.globalController.getLocalizedString('ESH_SWIcon_ToolTip');
    this.locStrings.addTooltip = this.globalController.getLocalizedString('ESH_AddButton_Tooltip');
    this.locStrings.removeTooltip = this.globalController.getLocalizedString('ESH_RemoveButton_Tooltip');
    this.locStrings.tableLayoutTooltip = this.globalController.getLocalizedString('ESH_TableLayoutIcon_ToolTip');
    this.locStrings.ccLibCloudIconTooltip = this.globalController.getLocalizedString('ESH_CCLib_CloudIcon_Tooltip');
    this.locStrings.ccLibBrokenCloudIconTooltip = this.globalController.getLocalizedString('ESH_CCLib_BrokenCloudIcon_Tooltip');
    this.locStrings.ccLibNotSyncedCloudIconTooltip = this.globalController.getLocalizedString('ESH_CCLib_NotSyncedCloudIcon_Tooltip');

    this.m_shouldScroll = true;

    //send a selection changed event for the initial selection
    var dwSelectionEvent = {};
    dwSelectionEvent.type = DW_EXTENSION_EVENT.SELECTION_CHANGE;
    window.postMessage(dwSelectionEvent, "*");
};

// this even comes from parent window. (in case tab key is pressed on main document it navigates to next anchor
EditableSelectors.prototype.setSelectionOnFirstHudElement = function () {
    'use strict';

    var sandwichIcon = document.getElementById(CONSTANTS.hudEntryID);
    if (sandwichIcon) {
        sandwichIcon.focus();
        return;
    }
    if (this.m_numberOfClasses > 0 || this.m_numberOfIds > 0) {
        var selectorContainer = document.getElementsByClassName(SELECTOR_CONSTANTS.SelectorsContainerClass);
        if (selectorContainer && selectorContainer[0] && selectorContainer[0].firstChild) {
            selectorContainer[0].firstChild.focus();
            return;
        }
    }

    var addButton = document.getElementById(SELECTOR_CONSTANTS.AddNewSelectorButtonID);
    if (addButton) {
        addButton.focus();
    }

};

// select parent element on ctrl+[ key. (This call is trapped in c++ layer)
EditableSelectors.prototype.setSelectionOnParent = function () {
    'use strict';
    if (this.m_currentSelectedElement && this.m_currentSelectedElement.tagName !== "BODY") {
        liveViewObject.setSelectionOnElement(this.m_currentSelectedElement.parentNode);
    }
};

// select current element
EditableSelectors.prototype.setSelectionOnCurrentElement = function () {
    'use strict';
    if (this.m_currentSelectedElement) {
        liveViewObject.setSelectionOnElement(this.m_currentSelectedElement);
    }
};

// select parent element on ctrl+[ key. (This call is trapped in c++ layer)
EditableSelectors.prototype.setSelectionOnFirstChild = function () {
    'use strict';

    if (this.m_currentSelectedElement && this.m_currentSelectedElement.hasChildNodes()) {
        var node = this.m_currentSelectedElement.firstElementChild;
        while (node) {
            if (liveViewObject.isElementHidden(node)) {
                node = node.nextElementSibling;
            } else {
                break;
            }
        }
        if (node) {
            liveViewObject.setSelectionOnElement(node);
        }
    }
};

EditableSelectors.prototype.textEditBegin = function () {
    'use strict';
    this.destroy();
    this.textEditing = true;
};

EditableSelectors.prototype.textEditEnd = function () {
    'use strict';
    this.textEditing = false;
};

/*
function:escapeKeyPressed - handler for escape key press

Arguments: None

Return : None
*/
EditableSelectors.prototype.escapeKeyPressed = function () {
    'use strict';
    //if user is adding a new selector, that should be dismissed on escape key
    var inputElem = document.getElementById(this.m_activeInputId);
    if (inputElem && inputElem.offsetWidth > 0) {
        this.focusLostFromNewSelectorInput(null, null);
        if (this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID) {
            this.createElementHud();
        }
        if (dwObject) {
            dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.OTH_ELV, DW_ESH_HEADLIGHTS.ESH_ESCAPE);
        }
    } else {
        this.destroy(true);
    }
};

EditableSelectors.prototype.isAnyAuxHudVisible = function () {
    'use strict';
    if (this.m_currentSelectedElement) {
        return window.parent.liveViewExtensionsObject.getAuxHudVisibility(this.m_currentSelectedElement.tagName.toLowerCase());
    }
    return false;
};
/*
function:documentOrWindowResized - handler for document or window resize. If there is a selected element, bring the element and the hud into view

Arguments: None

Return : None
*/
EditableSelectors.prototype.windowResized = function () {
    'use strict';
    if (liveViewObject.isExtensionVisible(CONSTANTS.ExtensionID) && this.m_currentSelectedElement) {
        var elemRect = liveViewObject.getElementRect(this.m_currentSelectedElement);
        this.positionHud(elemRect);
        this.scrollHUDIntoViewIfNeeded();
    }
};
/*
function:destroy - destroy ourself

Arguments: None

Return : None
*/
EditableSelectors.prototype.destroy = function (isEscaped) {
    'use strict';
    //hide ourself

    if (this.isAnyAuxHudVisible()) {
        if (isEscaped) {
            this.hideAuxHud(false, true, false);
        } else {
            this.hideAuxHud(true, true, false);
        }
        return;
    }

    liveViewObject.hideExtensionById(CONSTANTS.ExtensionID);
    this.m_currentSelectedElement = null;
    parent.focus();

    //log headlights
    if (isEscaped) {
        dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.OTH_ELV, DW_ESH_HEADLIGHTS.ESH_ESCAPE);
    }
};


EditableSelectors.prototype.reload = function (e) {
    'use strict';

    //do not show ESH if there is a marquee selection
    if (!this.m_currentSelectedElement || parent.IsMarqueeSelected()) {
        this.destroy();
        return;
    }

    if (e && typeof e.shouldScroll !== "undefined") {
        this.m_shouldScroll = e.shouldScroll;
    } else {
        this.m_shouldScroll = true;
    }

    //get the current class and id attributes from document
    //this will be returned as a callback
    var callbackFunction = function (resultObj) {
        this.getDocumentElementAttributesCallback(resultObj);
    }.bind(this);
    dwObject.getDWDocumentElementClassAndID(this.m_currentSelectedElement, callbackFunction);
};

/*
function:messageHandler - Handler for messages from Live View

Arguments: None

Return : None
*/
EditableSelectors.prototype.messageHandler = function (e) {
    'use strict';
    if (e.data && e.data.type) {
        if (e.data.type === DW_EXTENSION_EVENT.SELECTION_CHANGE || e.data.type === DW_EXTENSION_EVENT.ESH_RESYNC_SELECTION) {
            if (this.textEditing) {
                // don't do anything and return
                return;
            }
            if (this.ignoreSelectionChange) {
                this.ignoreSelectionChange = false;
                return;
            }
            var new_selection = liveViewObject.getCurrentSelectedElement();
            //removing the following check, since it will not update the changes
            //done to the current element (watson bug: 3701306)
            var old_selection = this.m_currentSelectedElement;
            this.m_currentSelectedElement = new_selection;
            if (!this.m_currentSelectedElement || this.m_currentSelectedElement.tagName === "HTML") {
                this.destroy();
                return;
            }
            if (old_selection !== new_selection) {
                // hide the aux huds but dont ask the hud to post a message to reload esh
                this.hideAuxHud(true, false, false); // hide before updating the element
                this.hilightEditableSelectors = false;
            } else {
                //if it is the same element and we are in the middle of adding a selector, do not update
                //as there would not have been any changes done
                var inputElem = document.getElementById(SELECTOR_CONSTANTS.NewSelectorTextInputID);
                if (inputElem && inputElem.offsetWidth > 0) {
                    return;
                }
                this.reload();
            }
        } else if (e.data.type === DW_EXTENSION_EVENT.STYLES_RELOADED) {
            //recreate the HUD if styles changed
            if (liveViewObject.isExtensionVisible(CONSTANTS.ExtensionID)) {
                this.createElementHud();
            }
        } else if (e.data.type === DW_EXTENSION_EVENT.SHOW_AUX_HUD) {
            //this is triggered by cmd+e for now. Logging headlight accordingly
            var sandwichIcon = document.getElementById(CONSTANTS.hudEntryID);
            if (sandwichIcon) {
                this.toggleAuxHudState(true);
            }
        } else if (e.data.type === DW_EXTENSION_EVENT.VIEW_LOST_FOCUS) {
            // if selector hud is open and we have to ignore focus lost, then do nothing.
            if (!(this.ignoreLostFocus && this.isSelectorHudOpen()) && !this.isTableHudOpen()) {
                this.hideAuxHud(true, true, false);
            }
            this.ignoreLostFocus = false;
        } else if (e.data.type === DW_EXTENSION_EVENT.SET_IGNORE_LOSTFOCUS) {
            this.ignoreLostFocus = true;
        } else if (e.data.type === DW_EXTENSION_EVENT.THEME_CHANGED) {
            console.log(e.data.theme)
        }
    }
};

/*
function:getDocumentElementAttributesCallback - Callback function from DW with element attributes , class & id
                                                HUD creation starts from here

Arguments: callbackObj.classStr - class attribute value
           callbackObj.idStr - id attribute value

Return : None
*/
EditableSelectors.prototype.getDocumentElementAttributesCallback = function (callbackObj) {
    'use strict';
    //alert('this is callback');
    if (parent && parent.liveViewExtensionsObject && callbackObj) {
        //cleanup cached members
        this.m_documentClassesForElement = null;
        this.m_documentIdForElement = null;

        //set the values for document attributes. These values are used to check whether 
        //the curretn applied classes and id for the element in live view is created
        //dynamically or not. If the values are dynamic (not present in document), then 
        //they will not be editable through the HUD
        if (callbackObj.classStr) {
            this.m_documentClassesForElement = callbackObj.classStr.split(' ');
        }

        if (callbackObj.idStr) {
            this.m_documentIdForElement = callbackObj.idStr.split(' ');
        }

        //now create the HUD
        this.createElementHud();
    }
};

/**
 *  Tells whether an auxiliary HUD exists which can possibly be shown for the tag selected
 *  @param tagName - html tag for which an auxiliary hud is registered
 *  @return BOOLEAN true if an aux hud exist, false if no aux hud is registered
 */
EditableSelectors.prototype.isAuxHudRegisteredForTag = function (tagName) {
    'use strict';
    if (!tagName || typeof tagName !== "string") {
        return false;
    }
    // Need to create a mechanism for extensions to register for invocation

    if (liveViewObject.auxiliaryIframeExists[tagName]) {
        return true;
    }
    return false;
};

EditableSelectors.prototype.attachHandlers = function () {
    'use strict';
    var addButton = document.getElementById(SELECTOR_CONSTANTS.AddNewSelectorButtonID);
    if (addButton) {
        addButton.onfocus = function () {
            this.addSelectorThroughKeyboard = true;
            this.AddSelector();
        }.bind(this);
        addButton.onmousedown = function (evt) {
            //this is to make sure that onfocus does not get called. ( useful for right click workflow)
            evt.stopPropagation();
            evt.preventDefault();
        };
    }
};
/**
 *  Generates the HTML to display the sandwich icon that initiates the Auxiliary HUD
 *  @param none
 *  @return STRING outerHTML for the icon to invoke Auxiliary HUD
 */
EditableSelectors.prototype.generateAuxHudIcon = function () {
    'use strict';

    var auxHudToggler = document.createElement(TAGS.Img), verticalLine = document.createElement(TAGS.DwDiv);
    auxHudToggler.id = CONSTANTS.hudEntryID;
    auxHudToggler.tabIndex = "0";
    auxHudToggler.setAttribute('class', DWLE_IMG_CLASS.ImgHudEntry + DWLE_IMG_CLASS.Separator + DWLE_IMG_CLASS.ImgSelection);
    auxHudToggler.setAttribute('src', liveExtensionsConfigPath + CONSTANTS.ExtensionPath + CONSTANTS.Assets + CONSTANTS.HudSandWich);
    verticalLine.classList.add('verticalLine');

    if (this.m_currentSelectedElement.tagName === "TABLE") {
        auxHudToggler.title = this.locStrings.tableLayoutTooltip;
    } else {
        auxHudToggler.title = this.locStrings.auxHudTooltip;
    }
    
    
    return auxHudToggler.outerHTML + verticalLine.outerHTML;
};

EditableSelectors.prototype.hideAuxHud = function (commit, keepESHOpen, shouldScroll) {
    'use strict';
    if (this.ignoreSelectionChange) {
        return;
    }
    if (typeof commit === "undefined") {
        commit = false;
    }

    if (typeof keepESHOpen === "undefined") {
        keepESHOpen = false;
    }

    if (typeof shouldScroll === "undefined") {
        shouldScroll = true;
    }


    var messageDetails = {};
    messageDetails.selectedTag = this.m_currentSelectedElement ? this.m_currentSelectedElement.tagName.toLowerCase() : '*';
    messageDetails.type = DW_EXTENSION_EVENT.HIDE_AUX_HUD;
    messageDetails.commit = commit;
    messageDetails.shouldScroll = shouldScroll;
    if (!this.m_currentSelectedElement) {
        keepESHOpen = true;
    }
    messageDetails.keepESHOpen = keepESHOpen;

    dwExtensionController.sendMessage(messageDetails);
};
/**
 *  Toggles the state of the Auxiliary HUD associated with the tag by sending a message to the parent window
 *  Message contains the selected tagName, and the click count for this instance of the sandwich icon
 *  @param none
 *  @param none
 *  @return none
 */

EditableSelectors.prototype.toggleAuxHudState = function (shortcut, isSelectorHud) {
    'use strict';

    var messageDetails = {},
        eshDetails = {},
        addButton,
        addButtonRect;

    messageDetails.shortcut = shortcut;
    messageDetails.type = DW_EXTENSION_EVENT.TOGGLE_AUX_HUD;

    if (isSelectorHud) {
        eshDetails.Tag = "selectorIcon";
    } else {
        eshDetails.Tag = this.m_currentSelectedElement.tagName.toLowerCase();
    }
    eshDetails.Position = this.m_hudIsOnTop;
    eshDetails.Height = this.m_hudContentDiv.offsetHeight;
    eshDetails.Width = this.m_hudContentDiv.offsetWidth;
    eshDetails.InputText = this.selectorText;

    addButton = document.getElementById(SELECTOR_CONSTANTS.AddNewSelectorButtonID);
    if (addButton) {
        addButtonRect = addButton.getBoundingClientRect();
        if (addButtonRect) {
            eshDetails.addBtnTop = addButtonRect.top;
            eshDetails.addBtnBottom = addButtonRect.bottom;
            eshDetails.addBtnRight = addButtonRect.right;
            eshDetails.addBtnLeft = addButtonRect.left;
        }
    }
    messageDetails.eshDetails = eshDetails;
    dwExtensionController.sendMessage(messageDetails);
};


/*
function:createElementHud - Create and position the HUD

Arguments: None

Return : None
*/
EditableSelectors.prototype.createElementHud = function () {
    'use strict';
    if (this.textEditing) {
        return;
    }
    if (this.m_hudContentDiv && this.m_currentSelectedElement) {
        //get the rect for current element
        var elemRect = liveViewObject.getElementRect(this.m_currentSelectedElement);
        if (elemRect.width <= 0 || elemRect.height <= 0) {
            this.destroy();
            return;
        }

        //cleanup cached members
        this.m_liveClassesForElement = null;
        this.m_liveIdForElement = null;
        this.m_numberOfClasses = 0;
        this.m_numberOfIds = 0;

        //get currently applied classes in live view
        var appliedClasses = this.m_currentSelectedElement.getAttribute('class');
        if (appliedClasses) {
            this.m_liveClassesForElement = appliedClasses.split(' ');
        }

        var appliedId = this.m_currentSelectedElement.getAttribute('id');
        if (appliedId) {
            this.m_liveIdForElement = appliedId.split(' ');
        }

        //reset current HUD Size so that it will not affect dcumetn size calculations
        liveViewObject.positionExtensionById(CONSTANTS.ExtensionID, 0, 0, 0, 0);

        //generate the HUD contents and position
        this.generateHudContents();
        this.attachHandlers();
        this.positionHud(elemRect);
        
        //we need to show grab icon if element is draggable
        var tagNameElems = document.getElementsByClassName(SELECTOR_CONSTANTS.TagnameClass);
        if (tagNameElems.length > 0) {
            if (liveViewObject.isElementEditable(this.m_currentSelectedElement) && !parent.liveEditFGInstance && this.m_currentSelectedElement.tagName !== "BODY") {
                tagNameElems[0].style.cursor = SELECTOR_CONSTANTS.GrabCursorStyle;
            }
        }

        //remove any text selections
        if (window.getSelection()) {
            window.getSelection().removeAllRanges();
        }

        //Set focus to sandwich icon if present
        //if selector HUD is open then do not shift the focus back to sandwich icon.
        //selector hud should have the focus. 
        //isAnyAuxHudVisible doent handle selectorHUD. So we need to specially add selector HUD case
        if (!this.isAnyAuxHudVisible() && !this.isSelectorHudOpen()) {
            var sandwichIcon = document.getElementById(CONSTANTS.hudEntryID);
            if (sandwichIcon) {
                if (this.canScrollOnElementClicked(this.m_currentSelectedElement.tagName.toLowerCase()) ||
                        this.isSandwichIconInVisibleArea()) {
                    sandwichIcon.focus();
                }
            }
        }

        if (this.m_shouldScroll) {
            this.scrollHUDIntoViewIfNeeded();
        }

        // Also Reset the flag
        this.m_shouldScroll = true;
        this.shouldHilightEditableSelectors();
    }
};

/*
function:cloudIconClickHandler - Mouse click handler for CC Lib cloud icon in ESH

Arguments: event

Return : none
*/
EditableSelectors.prototype.cloudIconClickHandler = function (event) {
    'use strict';

    if (!this.m_currentSelectedElement) {
        return;
    }
    if (event.which === MOUSE.RightClick) {
        return;
    }
    var eventTarget = event.target;
    var targetId = eventTarget.getAttribute('id');
    var targetClass = eventTarget.getAttribute('class');

    if (targetClass && targetClass === CONSTANTS.ccLibCloudClass) {
        var argObj = {};
        var imgSrcName = this.m_currentSelectedElement.getAttribute("src");
        if (imgSrcName) {
            var linkedState = CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_NONE;
            if (targetId) {
                if (targetId === CONSTANTS.ccLibCloudLinkID) {
                    linkedState = CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_LINKED;
                } else if (targetId === CONSTANTS.ccLibBrokenLinkID) {
                    linkedState = CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_BROKENLINK;
                } else if (targetId === CONSTANTS.ccLibOutOfSyncLinkID) {
                    linkedState = CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_FETCHFAILED;
                }
                if (linkedState !== CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_NONE) {
                    argObj.linkedState = linkedState;
                    argObj.fileName = imgSrcName;
                    dwObject.handleCloudIconClick(argObj);
                }
            }
        }
    }
};

/*
function:generateHudContents - build the HUD structure, tagName followed by selectors applied and add button

Arguments: None

Return : None
*/
EditableSelectors.prototype.generateHudContents = function () {
    'use strict';
    if (this.m_hudContentDiv && this.m_currentSelectedElement) {
        //get the tagname and add to our HUD structure
        var tagName = this.m_currentSelectedElement.tagName.toLowerCase();
        var tagNameStr = this.createTagWithClassAndContent(TAGS.DwDiv,
            SELECTOR_CONSTANTS.TagnameClass,
            tagName, null, false);

        //now create the selectors structure
        var contentString = this.buildSelectorElements();
        var auxHudString = '';
        if (this.isAuxHudRegisteredForTag(tagName)) {
            auxHudString = this.generateAuxHudIcon();
        }

        this.m_hudContentDiv.innerHTML = auxHudString + tagNameStr + this.createTagWithClassAndContent(TAGS.DwDiv,
            SELECTOR_CONSTANTS.SelectorsContainerClass, contentString, null, false);

        var existingCCLibIcon = document.getElementsByClassName(CONSTANTS.ccLibCloudClass);
        if (existingCCLibIcon) {
            while (existingCCLibIcon.length > 0) {
                existingCCLibIcon[0].parentNode.removeChild(existingCCLibIcon[0]);
            }
        }
        
        //Remove the older loader if present
        var loaderElements = document.getElementsByClassName(CONSTANTS.ccLibCloudLoaderClass);
        if (loaderElements) {
            while (loaderElements.length > 0) {
                loaderElements[0].parentNode.removeChild(loaderElements[0]);
            }
        }
        
        if (tagName === "img" && this.m_currentSelectedElement.src) {
            // check if its a CCLibraries broken/linked asset. 
            // Then add the corresponding cloud icon at the bottom right in ESH
            var argObj = {};
            var imgSrcName = this.m_currentSelectedElement.getAttribute("src");
			
            var self = this;
            if (imgSrcName) {
                argObj.fileName = imgSrcName;
                argObj.callbackFunc = function (argObj) {
                    var assetState = argObj.assetState;
                    if (assetState > CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_NONE) {
                        var ccLibImgIcon = document.createElement(TAGS.Img);
                        var imgSrc = liveExtensionsConfigPath + CONSTANTS.ExtensionPath + CONSTANTS.Assets;
                        var ccLibIconID = "";
                        var tooltip = "";
                        if (assetState === CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_LINKED) {
                            imgSrc += CONSTANTS.ccLibLinkedIcon;
                            ccLibIconID = CONSTANTS.ccLibCloudLinkID;
                            tooltip = self.locStrings.ccLibCloudIconTooltip;
                        } else if (assetState === CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_BROKENLINK) {
                            imgSrc += CONSTANTS.ccLibBrokenLinkIcon;
                            ccLibIconID = CONSTANTS.ccLibBrokenLinkID;
                            tooltip = self.locStrings.ccLibBrokenCloudIconTooltip;
                        } else if (assetState === CCLIB_ASSET_STATE.LIBRARY_ASSET_STATE_FETCHFAILED) {
                            imgSrc += CONSTANTS.ccLibOutOfSyncLinkIcon;
                            ccLibIconID = CONSTANTS.ccLibOutOfSyncLinkID;
                            tooltip = self.locStrings.ccLibNotSyncedCloudIconTooltip;
                        }
                        ccLibImgIcon.id = ccLibIconID;
                        ccLibImgIcon.setAttribute('class', CONSTANTS.ccLibCloudClass);
                        ccLibImgIcon.setAttribute('src', imgSrc);
                        ccLibImgIcon.setAttribute('style', 'position:absolute; right: 0px; bottom: 0px; background-color:#00BDFE; padding: 3px 2px;');
                        ccLibImgIcon.title = tooltip;
                        ccLibImgIcon.addEventListener("click", self.cloudIconClickHandler.bind(self), true);
                        self.m_overlayDiv.appendChild(ccLibImgIcon);
                    }
                };
                dwObject.isCCLibLinkedImage(argObj);
            }
                                    
            //Add Loading icon for CCLibraries fetch asset.
            var loadingArgObj = {};
            if (imgSrcName) {
                
                //Check if height attribute was provided in the img tag.
                var height = this.m_currentSelectedElement.getAttribute("height");

                if (height === null || height === undefined) {
                    //No height attribute scenario.
                    height = this.m_currentSelectedElement.naturalHeight;
                } else {
                    height = parseFloat(height);
                    if (height === 0) {
                        height = this.m_currentSelectedElement.naturalHeight;
                    }
                }

                height = parseFloat(height);
                var leftAdjustFactor = 0;
                
                //Adjust the position of loader.
                //Do not show loader for smaller images.
                if (height !== 0 && height > CONSTANTS.ccLibLoaderHeightOffset) {
                    
                    //Adjust only if the height is twice the offset and above.
                    if (height > 2 * CONSTANTS.ccLibLoaderHeightOffset) {
                        height = height / 2.0;
                        height = height - CONSTANTS.ccLibLoaderHeightOffset;  //64 is the height of the loader.
                    } else {
                        height = height / 2.0;
                    }
                    
                    var elemRect = this.m_currentSelectedElement.getBoundingClientRect();
                    var viewPortSize = liveViewObject.getViewPortSize();
		            
                    //If the loader is not visible, adjust the top such that it is visible in the view port.
                    if (elemRect.bottom > viewPortSize.height) {
                        height = parseFloat(elemRect.top) + parseFloat(viewPortSize.height);
                        height = height / 2.0;
                    }
                    
                    //Adjust the left co-ordinates of the loader.
                    if (elemRect.right > viewPortSize.width) {
                        leftAdjustFactor = parseFloat(elemRect.left) + parseFloat(viewPortSize.width);
                        leftAdjustFactor = leftAdjustFactor / 2.0;
                        leftAdjustFactor = leftAdjustFactor + "px";
                    }
                    
                    loadingArgObj.fileName = imgSrcName;
                    loadingArgObj.callbackFunc = function (argObj) {
                        var isAssetOptInProgress = argObj.assetOptState;
                        
                        if (isAssetOptInProgress && height !== 0) {

                            height = height + "px";

                            var loaderElements = document.getElementsByClassName(CONSTANTS.ccLibCloudLoaderClass);
                            var styleStr = "top: " + height + ";";
                            if (leftAdjustFactor !== 0) {
                                styleStr += "left: " + leftAdjustFactor + ";";
                            } else {
                                styleStr += "left: 0px; right :0px;";
                            }

                            //Re-use the existing loader..
                            if (loaderElements.length !== 0) {
                                loaderElements[0].setAttribute('style', styleStr);
                            } else {

                                var ccLibLoaderIcon = document.createElement(TAGS.Img);
                                var imgSrc = liveExtensionsConfigPath + CONSTANTS.ExtensionPath + CONSTANTS.Assets + CONSTANTS.ccLibLoaderIcon;
                                var ccLibIconID = CONSTANTS.ccLibLoaderIconID;

                                ccLibLoaderIcon.id = ccLibIconID;
                                ccLibLoaderIcon.setAttribute('class', CONSTANTS.ccLibCloudLoaderClass);
                                ccLibLoaderIcon.setAttribute('src', imgSrc);

                                ccLibLoaderIcon.setAttribute('style', styleStr);
                                self.m_overlayDiv.appendChild(ccLibLoaderIcon);
                            }
                        }
                    };
                    dwObject.isAssetOptInProgress(loadingArgObj);
                }

            }

        }

        //Set the background color and overlay border color based on editability
        var classesForElementHUD = SELECTOR_CONSTANTS.ElementHudClass + " ";
        var classesForOverlay = SELECTOR_CONSTANTS.OverlayClass + " ";
        if (liveViewObject.isElementEditable(this.m_currentSelectedElement)) {
            classesForElementHUD += SELECTOR_CONSTANTS.ElementHudEditableBgColor;
            classesForOverlay += SELECTOR_CONSTANTS.OverlayEditableBorderColor;
        } else {
            classesForElementHUD += SELECTOR_CONSTANTS.ElementHudNonEditableBgColor;
            classesForOverlay += SELECTOR_CONSTANTS.OverlayNonEditableBorderColor;
        }
        this.m_hudContentDiv.setAttribute('class', classesForElementHUD);
        this.m_overlayDiv.setAttribute('class', classesForOverlay);
    }
};

/*
function:buildSelectorElements - function to generate the elements showing the selectors in HUD

Arguments: None

Return : String: selector elements html structure
*/
EditableSelectors.prototype.buildSelectorElements = function () {
    'use strict';
    //create current selectors structure
    var selectorsStr = '';
    this.m_numberOfClasses = this.m_liveClassesForElement ? this.m_liveClassesForElement.length : 0;
    this.m_numberOfIds = this.m_liveIdForElement ? this.m_liveIdForElement.length : 0;
    this.m_numberOfEditableSelectors = 0;
    selectorsStr += this.buildSelectorElementsCore(this.m_documentClassesForElement, this.m_liveClassesForElement, '.');
    selectorsStr += this.buildSelectorElementsCore(this.m_documentIdForElement, this.m_liveIdForElement, '#');

    var newSelectorInputStr = '';
    var addButtonStr = '';
    //add newSelector button and input if the element is editable
    if (liveViewObject.isElementEditable(this.m_currentSelectedElement)) {

        addButtonStr = '<' + TAGS.DwDiv + ' id="' + SELECTOR_CONSTANTS.AddNewSelectorButtonID + '" title="' + this.locStrings.addTooltip + '" tabIndex = "0">' + SELECTOR_CONSTANTS.AddNewSelectorButtonString + '</' + TAGS.DwDiv + '>';
        newSelectorInputStr = '<' + TAGS.DwDiv + ' id="' + SELECTOR_CONSTANTS.NewSelectorTextInputID + '" contenteditable="true" tabIndex="0" data-placeholder="' + SELECTOR_CONSTANTS.NewSelectorInputPlaceholderText + '">' + '</' + TAGS.DwDiv + '>';
    }

    return selectorsStr + newSelectorInputStr + addButtonStr;
};

/*
function:buildSelectorElementsCore - Utility function for building the selector elements

Arguments: originalArrayArg - selectors in document (editable set)
           generatedArrayArg - selectors in live dom
           prefix - prefix to be added ('#' for id and '.' for classes)

Return : String:selectors list properly set with editable/non-editable state
*/
EditableSelectors.prototype.buildSelectorElementsCore = function (originalArrayArg, generatedArrayArg, prefix) {
    'use strict';

    var selectorsStr = '';
    var selectorTagStr;
    var selectorElementStr;
    var i;
    //make a copy of the passed arrays so that we do not modify the passed arrays
    var originalArray = originalArrayArg ? originalArrayArg.slice(0) : null;
    var generatedArray = generatedArrayArg ? generatedArrayArg.slice(0) : null;


    //first go through the document selector list and show them as editable with remove button
    if (generatedArray && originalArray && originalArray.length > 0) {
        var removeButtonStr = '';
        if (liveViewObject.isElementEditable(this.m_currentSelectedElement)) {
            removeButtonStr = this.createTagWithClassAndContent(TAGS.DwDiv,
                SELECTOR_CONSTANTS.RemoveSelectorButtonClass,
                SELECTOR_CONSTANTS.RemoveSelectorButtonString, this.locStrings.removeTooltip, false);
        }
        for (i = 0; i < originalArray.length; ++i) {
            if (originalArray[i] && originalArray[i].length > 0) {
                var curIndex = generatedArray.indexOf(originalArray[i]);
                if (curIndex !== -1) {
                    selectorTagStr = this.createTagWithClassAndContent(TAGS.DwDiv,
                        SELECTOR_CONSTANTS.SelectorNameClass,
                        prefix + originalArray[i], null, false);
                    this.m_numberOfEditableSelectors++;

                    selectorElementStr = this.createTagWithClassAndContent(TAGS.DwDiv,
                        SELECTOR_CONSTANTS.EditableSelectorClass,
                        selectorTagStr + removeButtonStr, null, true);

                    selectorsStr = selectorsStr + selectorElementStr;

                    //remove it from generated array so that it wont be processed again
                    generatedArray.splice(curIndex, 1);
                }
            }
        }
    }

    //now add the remaining in generated array as non-editable
    if (generatedArray && generatedArray.length > 0) {
        for (i = 0; i < generatedArray.length; ++i) {
            if (generatedArray[i] && generatedArray[i].length > 0) {
                selectorTagStr = this.createTagWithClassAndContent(TAGS.DwDiv,
                    SELECTOR_CONSTANTS.SelectorNameClass,
                    prefix + generatedArray[i], null, false);

                selectorElementStr = this.createTagWithClassAndContent(TAGS.DwDiv,
                    SELECTOR_CONSTANTS.NonEditableSelectorClass,
                    selectorTagStr, null, true);

                selectorsStr = selectorsStr + selectorElementStr;
            }
        }
    }

    return selectorsStr;
};

EditableSelectors.prototype.shouldHilightEditableSelectors = function () {
    'use strict';
    if (this.m_hudContentDiv) {
        if (this.hilightEditableSelectors) {
            jQuery(".selector").addClass("animateSelector");
        } else {
            jQuery(".selector").removeClass("animateSelector");
        }
    }
};


/*
function:buildSelectorElementsCore - Utility function for building a tag string based on input attributes.

Arguments: tagNameStr - name of tag
           tagClassName - class attribute for tag
           tagContentString - contents of the tag
           title    - title to display tooltip for an element
           tabInd - true if the element should be listed in the tabbing order.
Return : String : properly constructed tag string
*/
EditableSelectors.prototype.createTagWithClassAndContent = function (tagNameStr, tagClassName, tagContentString, title, tabInd) {
    'use strict';
    var tagString = '<' + tagNameStr;

    if (tagClassName && tagClassName.length > 0) {
        tagString += ' class="' + tagClassName + '"';
    }

    if (title && title.length > 0) {
        tagString += ' title="' + title + '"';
    }
    if (tabInd) {
        tagString += ' tabIndex="0"';
    }
    tagString += '>';

    if (tagContentString && tagContentString.length > 0) {
        tagString += tagContentString;
    }

    tagString += '</' + tagNameStr + '>';

    return tagString;
};

/*
function:positionOverLayDiv - Position our overlay div at the specified rect

Arguments: elemRect - rect to be used

Return : none
*/
EditableSelectors.prototype.positionOverLayDiv = function (elemRect) {
    'use strict';

    if (this.m_overlayDiv && elemRect) {
        if (elemRect.width <= 0 || elemRect.height <= 0) {
            this.m_overlayDiv.style.display = 'none';
            return;
        }

        this.m_overlayDiv.style.top = elemRect.top + 'px';
        this.m_overlayDiv.style.left = elemRect.left + 'px';
        //we should reduce the width and height to accommodate the border
        //of the overlay div. so reduce 2*border_width    
        this.m_overlayDiv.style.width = (elemRect.width - 2 * CONSTANTS.OverlayBorderWidth) + 'px';
        this.m_overlayDiv.style.height = (elemRect.height - 2 * CONSTANTS.OverlayBorderWidth) + 'px';

        this.m_overlayDiv.style.display = 'block';
    }
};

/*
function:positionHud - positions the HUD based on the rect provided

Arguments: elemRect - rect to be used

Return : none
*/
EditableSelectors.prototype.positionHud = function (elemRect) {
    'use strict';

    if (this.m_hudContentDiv && elemRect) {
        if (elemRect.width <= 0 || elemRect.height <= 0) {
            this.destroy();
            return;
        }

        if (!parent.document.documentElement) {
            this.destroy();
            return;
        }

        //set our extension to use the entire document space
        var documentWidth = parent.document.documentElement.offsetWidth;
        var documentHeight = parent.document.documentElement.offsetHeight;


        //in some cases, element stretches beyond the size provided to us by document element
        //so increase the height and width if needed
        if (elemRect.left + elemRect.width > documentWidth) {
            documentWidth = elemRect.left + elemRect.width;
        }

        if (elemRect.top + elemRect.height > documentHeight) {
            documentHeight = elemRect.top + elemRect.height;
        }

        if (documentHeight < CONSTANTS.ExtensionMinHeight) {
            documentHeight = CONSTANTS.ExtensionMinHeight;
        }

        liveViewObject.positionExtensionById(CONSTANTS.ExtensionID, 0, 0, documentWidth, documentHeight);

        //position border overlay
        this.positionOverLayDiv(elemRect);

        //display HUD and then position it
        this.m_hudContentDiv.style.display = 'block';

        //if there is no space at top, keep the HUD at bottom
        var hudTop = elemRect.top - this.m_hudContentDiv.offsetHeight;
        if (hudTop < 0) {
            this.m_hudIsOnTop = false;
            hudTop = elemRect.top + elemRect.height;
            //if the hud exceeds the document height, position it just at top
            if (hudTop + this.m_hudContentDiv.offsetHeight > documentHeight) {
                hudTop = elemRect.top;
            }
        } else {
            this.m_hudIsOnTop = true;
        }

        //set our dimensions same as parent document
        document.body.width = documentWidth;
        document.body.height = documentHeight;

        //now calculate the right based on the parent document rect
        //if there is no space on right, move left
        var hudLeft = elemRect.left;
        if (hudLeft + this.m_hudContentDiv.offsetWidth > documentWidth) {
            hudLeft = elemRect.left + elemRect.width - this.m_hudContentDiv.offsetWidth;
        }

        hudLeft = (hudLeft < 0) ? 0 : hudLeft;
        this.m_hudContentDiv.style.top = hudTop + 'px';
        this.m_hudContentDiv.style.left = hudLeft + 'px';
    }

};

/*
function:AddSelectorTypedInTextInputBox - Adds the selector that user has typed in NewSelectorTextInput

Arguments: none

Return : none
*/
EditableSelectors.prototype.AddSelectorTypedInTextInputBox = function () {
    'use strict';

    if (this.canApplySelector()) {
        if (this.isSelectorInList()) {
            this.newSelectorItemSelected(null, null);
        } else {
            var inputElement = document.getElementById(this.m_activeInputId);
            if (inputElement) {
                this.selectorText = inputElement.innerHTML.split('<')[0];
                this.applyNewSelectors(this.selectorText, false);
                this.toggleAuxHudState(false, true);
            }
        }
    } else if (this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID) {
        this.createElementHud();
    }
};

EditableSelectors.prototype.AttachEventsForAddOrRenameESH = function () {
    'use strict';
    //attach the autocomplete widget to input
    //by default, suggestions come below the input field. If there is no space below, then the 
    //suggestions should come above the input            
    var suggestionListPositionMy = "left top";
    var suggestionListPositionAt = "left bottom";
    var hudRect = this.m_hudContentDiv.getBoundingClientRect();
    if (hudRect.bottom + window.scrollY + CONSTANTS.SuggestionsListMaxHeight > document.body.height) {
        suggestionListPositionMy = "left bottom";
        suggestionListPositionAt = "left top";
    }
    $(this.m_activeInputJqueryId).autocomplete({
        select: this.newSelectorItemSelected.bind(this)
    }, {
        focus: this.focusChangedInSuggestions.bind(this)
    }, {
        change: this.focusLostFromNewSelectorInput.bind(this)
    }, {
        position: {
            my: suggestionListPositionMy,
            at: suggestionListPositionAt,
            collision: "fit none"
        }
    });

    //handle Enter key in the input
    $(this.m_activeInputJqueryId).keydown(function (e) {
        if (e.keyCode === KEYCODES.Enter || (e.keyCode === KEYCODES.Tab && !e.shiftKey)) {
            e.stopPropagation();
            e.preventDefault();
            var inputElement = document.getElementById(this.m_activeInputId);
            if (inputElement && inputElement.innerHTML.length > 0) {
                if (this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID && inputElement.innerHTML.length === 1 && (inputElement.innerHTML === '.' || inputElement.innerHTML === '#')) {
                    this.removeSelector(this.m_oldSelectorName);
                } else {
                    this.AddSelectorTypedInTextInputBox();
                }
            } else if (this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID) {
                this.removeSelector(this.m_oldSelectorName);
            }
            if (e.keyCode === KEYCODES.Tab) {
                if (inputElement.innerHTML.length <= 0 && this.m_activeInputId !== SELECTOR_CONSTANTS.RenameSelectorTextInputID) {
                    this.setSelectionOnFirstHudElement();
                    if (dwObject) {
                        dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_FEATURE_SELECTOR_TRAVERSED);
                    }
                }
            }
        } else {
            //see whether typing has increased the width of HUD to cross the boundaries
            //if it exceeds the extension width, then scrollbars will appear.
            //so increase extension width if needed
            var hudRect = this.m_hudContentDiv.getBoundingClientRect();
            if (hudRect.right > document.body.width) {
                liveViewObject.positionExtensionById(CONSTANTS.ExtensionID, 0, 0, hudRect.right, document.body.height);
                document.body.width = hudRect.right;
            }
        }
    }.bind(this));

    //populate the autocomplete with all the selectors available in the document
    var callbackFunction = function (allSelectors, bAppend) {
        if (bAppend) {
            this.selectorList = this.selectorList.concat(allSelectors);
        } else {
            this.selectorList = allSelectors;
        }
        this.attachSelectorHints(allSelectors, bAppend);
    }.bind(this);

    dwObject.getDWDocumentClassesAndIds(callbackFunction);
};

EditableSelectors.prototype.AddSelector = function () {
    'use strict';

    this.m_activeInputId = SELECTOR_CONSTANTS.NewSelectorTextInputID;
    this.m_activeInputJqueryId = SELECTOR_CONSTANTS.NewSelectorTextInputJQueryID;

    //show the input field and focus
    var inputElement = document.getElementById(SELECTOR_CONSTANTS.NewSelectorTextInputID);
    if (inputElement) {
        inputElement.innerHTML = '';
        inputElement.style.display = 'inline-block';
        inputElement.focus();
        this.AttachEventsForAddOrRenameESH();
        //Hud height might have changed
        //do the positioning again
        this.repositionHUD();
        this.hideAuxHud(true, true, false);
    }
};

EditableSelectors.prototype.blurOnRenameSelector = function () {
    'use strict';
    window.editableSelectorsObj.createElementHud();
};

EditableSelectors.prototype.InitializeForRenameSelector = function (eventTarget) {
    'use strict';
    var i;
    if (eventTarget && eventTarget.parentElement) {
        this.m_activeInputId = SELECTOR_CONSTANTS.RenameSelectorTextInputID;
        this.m_activeInputJqueryId = SELECTOR_CONSTANTS.RenameSelectorTextInputJQueryID;
        var parElement = eventTarget.parentElement;

        var allChildNodes = parElement.childNodes;
        for (i = allChildNodes.length - 1; i >= 0; --i) {
            var className = allChildNodes[i].getAttribute('class');
            if (className === SELECTOR_CONSTANTS.RemoveSelectorButtonClass) {
                parElement.removeChild(allChildNodes[i]);
            }
        }
        eventTarget.setAttribute('id', SELECTOR_CONSTANTS.RenameSelectorTextInputID);
        eventTarget.setAttribute('contentEditable', true);
        this.m_oldSelectorName = eventTarget.innerHTML;

        eventTarget.style.display = 'inline-block';
        eventTarget.focus();
        eventTarget.onblur = this.blurOnRenameSelector;
        this.AttachEventsForAddOrRenameESH();
        //Hud height might have changed
        //do the positioning again
        this.repositionHUD();
        this.hideAuxHud(true, true, false);
    }
    return;
};

/*
function:ClickHandler - Mouse click handler for ESH elements

Arguments: event - event passed from browser

Return : none
*/
EditableSelectors.prototype.ClickHandler = function (event) {
    'use strict';

    if (!this.m_currentSelectedElement) {
        return;
    }
    if (event.which === MOUSE.RightClick) {
        return;
    }
    this.m_lastKeyCode = 0; // reset last key code.
    var eventTarget = event.target;
    var targetId = eventTarget.getAttribute('id');
    var targetClass = eventTarget.getAttribute('class');

    if (targetClass && targetClass === SELECTOR_CONSTANTS.SelectorNameClass && eventTarget.parentElement && eventTarget.parentElement.getAttribute('class') === SELECTOR_CONSTANTS.EditableSelectorClass && liveViewObject.isElementEditable(this.m_currentSelectedElement)) {
        this.InitializeForRenameSelector(eventTarget);
        return;
    }
    var background = SELECTOR_CONSTANTS.ElementHudClass + " " + SELECTOR_CONSTANTS.ElementHudEditableBgColor;

    //if the click is on a selector, expand it
    if (eventTarget.id === CONSTANTS.hudEntryID) {
        if (this.isSelectorHudOpen()) {
            this.hideAuxHud(true, true, false);
        }
        this.toggleAuxHudState(false);
        event.stopPropagation();
        event.preventDefault();
    } else if (eventTarget && targetClass && targetClass === SELECTOR_CONSTANTS.SelectorNameClass) {
        var newClass = targetClass + ' ' + SELECTOR_CONSTANTS.ExpandedSelectorClass;
        eventTarget.setAttribute('class', newClass);
        this.repositionHUD();
    } else if (eventTarget && eventTarget.id === SELECTOR_CONSTANTS.AddNewSelectorButtonID) {
        this.addSelectorThroughKeyboard = false;
        this.m_activeInputId = SELECTOR_CONSTANTS.NewSelectorTextInputID;
        this.m_activeInputJqueryId = SELECTOR_CONSTANTS.NewSelectorTextInputJQueryID;

        var inputElement = document.getElementById(this.m_activeInputId);
        if (inputElement && inputElement.innerHTML.length > 0) {
            this.AddSelectorTypedInTextInputBox();
        } else {
            this.AddSelector();
        }
    } else if (eventTarget && targetClass && targetClass === SELECTOR_CONSTANTS.RemoveSelectorButtonClass) {
        var selectorNameElement = eventTarget.previousSibling;

        if (selectorNameElement) {
            var attrToRemove = selectorNameElement.innerHTML;
            this.removeSelector(attrToRemove);
            //clear browser dom modifications we have done, so that undo will go to DW document
            //and revert this operaion
            liveViewObject.clearBrowserUndoRedos();
        }
    } else if (targetClass === SELECTOR_CONSTANTS.TagnameClass || targetClass === background) {
        //  by click on the tagName or click on the blue background color should select the whole element
        this.setSelectionOnCurrentElement();
    }
};

/*
function:attachSelectorHints - callback from DW with all the selectors in the document. Attach it to autocomplete widget

Arguments: allSelectors - list of selectors in the document

Return : none
*/
EditableSelectors.prototype.attachSelectorHints = function (allSelectors, bAppend) {
    'use strict';
    //make sure we have the autocomplete field to attach to
    var inputElem = document.getElementById(this.m_activeInputId);
    if (!inputElem || inputElem.offsetWidth === 0) {
        return;
    }

    if (bAppend) {
        this.m_selectorsInDocument = this.m_selectorsInDocument.concat(allSelectors);
    } else {
        this.m_selectorsInDocument = allSelectors;
    }

    var availableSelectors = this.m_selectorsInDocument;

    //attach to autocomplete
    $(this.m_activeInputJqueryId).autocomplete('option', 'source', function (request, response) {
        var curText = request.term;
        var terms = curText.split(' ');

        //remove selectors which are currently applied on the element and which are already present in the input
        var lastTerm = terms.pop();
        var newSelectors = this.getListOfSelectorsForAutoComplete(availableSelectors, terms);

        //get the last term in the input field, which is the input for suggestions        
        response($.ui.autocomplete.filter(newSelectors, lastTerm));
    }.bind(this));
};

/*
function:getListOfSelectorsForAutoComplete - returns the list of selectors that can be applied to the current element.
    removes the selectors currently applied on the element from the passed list

Arguments: allSelectors - list of selectors in the document
           removeItems - list of items to be removed other than the applied ones

Return : newSelectorsAvailable = allSelectors - currently_Applied_Selectors
*/
EditableSelectors.prototype.getListOfSelectorsForAutoComplete = function (allSelectors, removeItems) {
    'use strict';

    var newSelectorsAvailable = null;
    var i;
    var index;

    //allselectors is the list of all selectors in the document.
    if (allSelectors && allSelectors.length > 0) {
        //assigning array directly will be a reference
        //so make a copy
        newSelectorsAvailable = allSelectors.slice(0);

        //remove the classes currently applied on the element.
        if (this.m_documentClassesForElement) {
            for (i = 0; i < this.m_documentClassesForElement.length; ++i) {
                index = newSelectorsAvailable.indexOf('.' + this.m_documentClassesForElement[i]);
                if (index !== -1) {
                    newSelectorsAvailable.splice(index, 1);
                }
            }
        }

        //similarly remove the id currently applied on the element.
        if (this.m_documentIdForElement) {
            for (i = 0; i < this.m_documentIdForElement.length; ++i) {
                index = newSelectorsAvailable.indexOf('#' + this.m_documentIdForElement[i]);
                if (index !== -1) {
                    newSelectorsAvailable.splice(index, 1);
                }
            }
        }

        //now remove everything from remove items
        if (removeItems) {
            for (i = 0; i < removeItems.length; ++i) {
                var curItem = removeItems[i];
                var itemIndex = -1;
                //if it is specified as class or ID, then use it
                //or take this as a class
                if (curItem.charAt(0) === '#' || curItem.charAt(0) === '.') {
                    itemIndex = newSelectorsAvailable.indexOf(curItem);
                } else {
                    itemIndex = newSelectorsAvailable.indexOf('.' + curItem);
                }

                //if found, remove from list
                if (itemIndex !== -1) {
                    newSelectorsAvailable.splice(itemIndex, 1);
                }
            }
        }
    }

    return newSelectorsAvailable;
};

/*
JQuery AutoComplete widget callback functions
*/

/*
Handles cases when focus changes in autocomplete suggestions
*/
EditableSelectors.prototype.focusChangedInSuggestions = function (event, ui) {
    'use strict';
    //default is to set the focused node content to input box,
    //we do not need to update the input field since it sets the cursor 
    //to the beginning of text in input filed
    if (event) {
        event.preventDefault();
    }

    //auto populate input box only if the focus changed through keyboard
    //original even is actually nested inside jQuery event
    var origEvent = event;
    while (origEvent && origEvent.originalEvent) {
        origEvent = origEvent.originalEvent;
    }
    if (origEvent && origEvent.type !== "keydown") {
        return;
    }

    //let us set the suggestion text in intupt field and set the cursor properly
    var inputElem = document.getElementById(this.m_activeInputId);
    if (ui && ui.item && inputElem) {
        //since we allow multiple selectors, we should append the current suggestion
        //to the current list of selectors in the input field
        var itemString = ui.item.value;

        var curValue = inputElem.innerHTML;
        //we should replace only the last term with the suggestion item
        var lastTermStartindex = curValue.lastIndexOf(" ");
        if (lastTermStartindex !== -1) {
            curValue = curValue.substr(0, lastTermStartindex);
        } else {
            curValue = "";
        }
        //add a space as separator
        if (curValue.length > 0) {
            curValue += " ";
        }
        //now set the valu in the filed
        inputElem.innerHTML = curValue + itemString;

        //set the document selection to set cursor at proper position
        if (itemString && inputElem.childNodes[0]) {
            var range = document.createRange();
            var sel = window.getSelection();
            range.setStart(inputElem.childNodes[0], inputElem.innerHTML.length);
            range.collapse(true);
            sel.removeAllRanges();
            sel.addRange(range);
        }
    }
};

/*
Handles cases when autocomplete box loses focus.
*/
EditableSelectors.prototype.focusLostFromNewSelectorInput = function (event, ui) {
    'use strict';
    // If selector hud visible, do not clean up text field
    if (this.isSelectorHudOpen()) {
        return;
    }
    //remove autocomplete
    $(this.m_activeInputJqueryId).autocomplete('destroy');
    $(this.m_activeInputJqueryId).unbind("keydown");
    //if the focus is not in the input field, hide it
    var inputElem = document.getElementById(this.m_activeInputId);
    if (inputElem && this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID) {
        inputElem.setAttribute('contentEditable', false);
    } else if (inputElem) {
        inputElem.style.display = 'none';
        inputElem.innerHTML = '';
        inputElem.blur();
    }
    //Hud height might have changed
    //do the positioning again
    this.repositionHUD();
    //clear browser dom modifications we have done, so that undo will go to DW document
    liveViewObject.clearBrowserUndoRedos();
};

/*
Handles selection inside the autocomplete popup window.
*/
EditableSelectors.prototype.newSelectorItemSelected = function (event, ui) {
    'use strict';

    //if this event was triggered due to a selection using keyboard, 
    //then the input file d will have the exact value to be applied.
    //if it was trigeered by a mouse click, then we should do additional stuff
    //to get the value to be applied
    var selectedUsingKeyBoard = true;
    //we do not need to update the input field
    if (event) {
        event.preventDefault();
        //original event is included at some depth
        var origEvent = event;
        while (origEvent && origEvent.originalEvent) {
            origEvent = origEvent.originalEvent;
        }
        if (origEvent && origEvent.type !== "keydown") {
            selectedUsingKeyBoard = false;
        }
    }

    var valueSubmitted = "";
    var inputElem = document.getElementById(this.m_activeInputId);
    //if the selection was using mouse click, then we should replace the 
    //last term in the input filed with the suggestion item value
    if (inputElem && !selectedUsingKeyBoard && ui && ui.item && ui.item.value) {
        var itemString = ui.item.value;
        var curValue = inputElem.innerHTML;
        if (curValue && curValue.length > 0) {
            var lastTermStartindex = curValue.lastIndexOf(" ");
            if (lastTermStartindex !== -1) {
                curValue = curValue.substr(0, lastTermStartindex);
            } else {
                curValue = "";
            }
            if (curValue.length > 0) {
                curValue += " ";
            }
        } else {
            curValue = "";
        }
        valueSubmitted = curValue + itemString;
    } else {
        valueSubmitted = inputElem.innerHTML;
    }

    // Ideally this function should be called on selection of a item from the hints. But we are calling this function
    // if the entered text is same as one of the hints. Hence adding class prefix explicitly for lookup to succeed.
    if (valueSubmitted && valueSubmitted[0] !== "." && valueSubmitted[0] !== "#") {
        valueSubmitted = "." + valueSubmitted;
    }

    // Check if value typed is present in the suggested hints. It might be possible that user edited the selected hint.
    if (this.selectorList.indexOf(valueSubmitted) < 0) {
        return;
    }

    //cleaup input field
    this.focusLostFromNewSelectorInput(event, ui);

    //apply new selectors
    this.applyNewSelectors(valueSubmitted.trim());

    //clear browser dom modifications we have done, so that undo will go to DW document
    //and revert this operaion
    liveViewObject.clearBrowserUndoRedos();
};
/*
Handles the renaming of the selector, which are given in classesToApply,
we iterates through the list of selectors we have already try to find the selector index which is being modified,
then we replace the new selector list pointed by the classesToApply element in the above index of the selector being modified
*/
EditableSelectors.prototype.handleRenameOfSelector = function (classesToApply) {
    'use strict';
    var i = 0;
    if (classesToApply.length > 0) {
        var curClasses = this.m_currentSelectedElement.getAttribute('class');
        var shouldOldIdBeRemoved = false;
        var oldSelector = this.m_oldSelectorName;
        if (oldSelector && oldSelector.charAt(0) === '.') {
            oldSelector = oldSelector.slice(1);
        }

        if (oldSelector && oldSelector.charAt(0) === '#') {
            shouldOldIdBeRemoved = true;
        }

        if (shouldOldIdBeRemoved) {
            this.m_currentSelectedElement.removeAttribute('id');
            dwObject.updateDWDocumentElementAttr(this.m_currentSelectedElement, 'id', '');
            curClasses = curClasses ? curClasses + ' ' + classesToApply.join(' ') : classesToApply.join(' ');
        } else if (curClasses && curClasses.length > 0) {
            if (this.m_oldSelectorName) {
                var existingClassArray = curClasses.split(' ');
                var newClasses = '';

                if (existingClassArray.length > 0) {
                    /* if the first selector is being renamed then just replace this with the new one,
                    else attach the previous elements before the classes to apply followed by next elemenst */
                    for (i = 0; i < existingClassArray.length; ++i) {
                        if (existingClassArray[i] === oldSelector) {
                            newClasses = newClasses + ' ' + classesToApply.join(' ');
                        } else {
                            newClasses = newClasses + ' ' + existingClassArray[i];
                        }
                    }
                    if (newClasses.length > 0) {
                        newClasses = newClasses.trim();
                    }
                }
                curClasses = newClasses;
            } else {
                curClasses = curClasses + ' ' + classesToApply.join(' ');
            }
        } else {
            curClasses = classesToApply.join(' ');
        }

        this.m_currentSelectedElement.setAttribute('class', curClasses);

        if (this.m_documentClassesForElement) {
            this.m_documentClassesForElement = curClasses.split(' ');
        } else {
            this.m_documentClassesForElement = classesToApply;
        }
    }


};
/*
function:applyNewSelector - Function to add the set of new selectors to the current element. Adds to both live dom and DW document

Arguments: newSelector - new selectors to be added

Return : none
*/
EditableSelectors.prototype.applyNewSelectors = function (newSelector, refreshHud) {
    'use strict';
    if (this.m_currentSelectedElement && newSelector && newSelector.length > 0) {
        var selectorsArray = newSelector.split(' ');
        var idToApply = "";
        var classesToApply = [];
        var i;
        //get ID to apply and classes to apply
        //if multiple ids are provided, then we will apply the last one.
        //if selector starts with '#' then its an id, else it is a class

        for (i = 0; i < selectorsArray.length; i++) {
            var currentSelector = selectorsArray[i];
            if (currentSelector.charAt(0) === '#') {
                var currentID = currentSelector.slice(1);
                if (currentID.length > 0) {
                    idToApply = currentID;
                }
            } else {
                var currentClass = (currentSelector.charAt(0) === '.' ? currentSelector.slice(1) : currentSelector);
                if (currentClass.length > 0) {
                    classesToApply.push(currentClass);
                }
            }
        }

        //apply the ID
        if (idToApply.length > 0) {
            //apply to current element
            this.m_currentSelectedElement.setAttribute('id', idToApply);
            //update our data
            this.m_documentIdForElement = idToApply.split(' ');
            //propogate to Document
            dwObject.updateDWDocumentElementAttr(this.m_currentSelectedElement, 'id', idToApply);

            if (this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID && this.m_oldSelectorName && this.m_oldSelectorName.charAt(0) === '.') {
                this.removeSelector(this.m_oldSelectorName);
            }
        }

        //apply classes
        if (classesToApply.length > 0) {
            if (this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID) {
                this.handleRenameOfSelector(classesToApply);
            } else {
                var curClasses = this.m_currentSelectedElement.getAttribute('class');
                if (curClasses && curClasses.length > 0) {
                    curClasses = curClasses + ' ' + classesToApply.join(' ');
                } else {
                    curClasses = classesToApply.join(' ');
                }

                //apply to current element
                this.m_currentSelectedElement.setAttribute('class', curClasses);

                if (this.m_documentClassesForElement) {
                    this.m_documentClassesForElement = this.m_documentClassesForElement.concat(classesToApply);
                } else {
                    this.m_documentClassesForElement = classesToApply;
                }
            }

            //propogate to Document
            var newClasses = this.m_documentClassesForElement.join(' ');
            dwObject.updateDWDocumentElementAttr(this.m_currentSelectedElement, 'class', newClasses);
        }

        //add headlights data
        var idApplied = idToApply.length > 0;
        var numClassesApplied = classesToApply.length;
        if (idApplied || numClassesApplied > 0) {
            var addExistingClass = (this.selectorList && this.selectorList.indexOf(newSelector) < 0) ? false : true;
            var addedThroughKeyboard = this.addSelectorThroughKeyboard;
            var argObj = {};
            argObj.callbackFunc = function (argObj) {
                if (argObj.isFluid) {
                    dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.OTH_ELV, DW_ESH_HEADLIGHTS.ESH_FG_ADD);

                    //log if multiple selectors were added
                    if ((idApplied && numClassesApplied > 0) || numClassesApplied > 1) {
                        dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.OTH_ELV, DW_ESH_HEADLIGHTS.ESH_FG_MULTIADD);
                    }
                } else {
                    if (addExistingClass) {
                        if (addedThroughKeyboard) {
                            dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_ADD_EXISTING);
                        } else {
                            dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ELV_ESH, DW_ESH_HEADLIGHTS.ESH_ADD_EXISTING);
                        }
                    } else {
                        if (addedThroughKeyboard) {
                            dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ESH_CATEGORY_DWSHORTCUTS, DW_ESH_HEADLIGHTS.ESH_ADD_NEW);
                        } else {
                            dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ELV_ESH, DW_ESH_HEADLIGHTS.ESH_ADD_NEW);
                        }
                    }

                    //log if multiple selectors were added
                    if ((idApplied && numClassesApplied > 0) || numClassesApplied > 1) {
                        dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ELV_ESH, DW_ESH_HEADLIGHTS.ESH_MULTIADD);
                    }
                }
            };
            dwObject.checkIfFluidElement(argObj);
        }

        //update the HUD and bring the element back into view
        //incase it has moved
        if (typeof (refreshHud) === 'undefined' || this.m_activeInputId === SELECTOR_CONSTANTS.RenameSelectorTextInputID) {
            refreshHud = true;
        }
        if (refreshHud) {
            this.createElementHud();
        }
    }
};

/*
function:removeSelector - Function to remove a new selector from the current element. removes from both live dom and DW document

Arguments: newSelector - selector to be removed

Return : none
*/
EditableSelectors.prototype.removeSelector = function (attrValToRemove) {

    'use strict';
    var theDocumentAttrValList = null;
    var theLiveAttrValList = null;
    var theAttrName = null;

    if (!(attrValToRemove && attrValToRemove.length > 0)) {
        return;
    }

    //check whether it is a class or id
    if (attrValToRemove.charAt(0) === '.') {
        theDocumentAttrValList = this.m_documentClassesForElement;
        theLiveAttrValList = this.m_liveClassesForElement;
        theAttrName = 'class';
    } else if (attrValToRemove.charAt(0) === '#') {
        theDocumentAttrValList = this.m_documentIdForElement;
        theLiveAttrValList = this.m_liveIdForElement;
        theAttrName = 'id';
    } else {
        return;
    }

    if (theAttrName && theDocumentAttrValList && theDocumentAttrValList.length > 0 && this.m_currentSelectedElement) {
        //remove first char '.' or '#'
        attrValToRemove = attrValToRemove.slice(1);
        var valIndex = theDocumentAttrValList.indexOf(attrValToRemove);
        if (valIndex !== -1) {
            // Update current element
            var liveValIndex = theLiveAttrValList.indexOf(attrValToRemove);
            if (liveValIndex !== -1) {
                theLiveAttrValList.splice(liveValIndex, 1);
                var liveAttrValStr = theLiveAttrValList.join(' ');
                if (liveAttrValStr && liveAttrValStr.length > 0) {
                    this.m_currentSelectedElement.setAttribute(theAttrName, liveAttrValStr);
                } else {
                    this.m_currentSelectedElement.removeAttribute(theAttrName);
                }
            }

            //update document
            theDocumentAttrValList.splice(valIndex, 1);
            var newDocumentAttrValStr = theDocumentAttrValList.join(' ');
            dwObject.updateDWDocumentElementAttr(this.m_currentSelectedElement, theAttrName, newDocumentAttrValStr);

            //log headlights
            var argObj = {};
            argObj.callbackFunc = function (argObj) {
                if (argObj.isFluid) {
                    dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.OTH_ELV, DW_ESH_HEADLIGHTS.ESH_FG_DELETE);
                } else {
                    dwObject.logHeadlightsData(DW_ESH_HEADLIGHTS.ELV_ESH, DW_ESH_HEADLIGHTS.ESH_DELETE);
                }
            };
            dwObject.checkIfFluidElement(argObj);

            //update the HUD and bring the element back into view
            //incase it has moved
            this.createElementHud();
        }
    }
};

EditableSelectors.prototype.setIgnoreSelectionChange = function (evt) {
    'use strict';
    this.ignoreSelectionChange = evt.isIgnore;
};

/*
function:repositionHUD - updates the position of HUD

Arguments: none

Return : none
*/
EditableSelectors.prototype.repositionHUD = function () {
    'use strict';
    if (this.m_currentSelectedElement) {
        var elemRect = liveViewObject.getElementRect(this.m_currentSelectedElement);
        this.positionHud(elemRect);
    }
};

/*
function:scrollHUDIntoViewIfNeeded - Function to bring HUD back into view if it the element
                                     is outside the current viewable area
Arguments: none

Return : none
*/
EditableSelectors.prototype.scrollHUDIntoViewIfNeeded = function () {
    'use strict';
    var resizing = window.parent.liveViewExtensionsObject.getLiveViewResizing();
    /*do not scroll if and only if the not-completely hidden element is selected in the live view.(ie even if some portion 
		of the element is visible then don't scroll) */
    if (!this.m_currentSelectedElement) {
        return;
    }
    //get where our current element is
    var elemRect = liveViewObject.getElementRect(this.m_currentSelectedElement);
    if (elemRect.width <= 0 || elemRect.height <= 0) {
        return;
    }

    //Get the current viewable window cordinates
    var windowTop = parent.window.scrollY;
    var windowBottom = windowTop + parent.window.innerHeight;
    var windowLeft = parent.window.scrollX;
    var windowRight = windowLeft + parent.window.innerWidth;
    
    var shouldSetFocusToSandWichIcon = false;
    //for text hud alone, we don't scroll to top to bring the sandwich icon in focus if the entire content is not in the viewable area.When this function is called
    //we scroll the hud into view and since we didn't have the focus in sandwich icon, we set the focus to it only when no huds are shown and only for text hud tag elements
    var sandwichIcon = document.getElementById(CONSTANTS.hudEntryID);
    if (sandwichIcon && this.isSandwichIconInVisibleArea() && !this.canScrollOnElementClicked(this.m_currentSelectedElement.tagName.toLowerCase()) && !this.isAnyAuxHudVisible() && !this.isSelectorHudOpen()) {
        shouldSetFocusToSandWichIcon = true;
    }
    
    //do not scroll if the focus is with Live View and the element is visible
    if (!resizing && (parent.document.hasFocus() &&
            !(elemRect.left > windowRight || elemRect.left + elemRect.width < windowLeft ||
                elemRect.top + elemRect.height < windowTop || elemRect.top > windowBottom))) {
        if (shouldSetFocusToSandWichIcon) {
            sandwichIcon.focus();
        }
        return;
    }
    //if our HUD is not within the viewable window, lets scroll
    if (this.IsElementNotInViewableWindow(elemRect)) {
        this.m_currentSelectedElement.scrollIntoView();
        parent.window.scrollBy((windowLeft - elemRect.left) * -1, 0);
        //if our HUD is on top of element, then lets scroll that much so that HUD will be visible
        if (this.m_hudIsOnTop) {
            parent.window.scrollBy(0, this.m_hudContentDiv.offsetHeight * -1);
        }
        if (shouldSetFocusToSandWichIcon) {
            sandwichIcon.focus();
        }
    }
};

/*
function:IsElementNotInViewableWindow - Check if an element rect is in viewable window
Arguments: elemRect
Return: Boolean
*/
EditableSelectors.prototype.IsElementNotInViewableWindow = function (elemRect) {
    'use strict';
    if (!elemRect) {
        return false;
    }
    //Get the current viewable window cordinates
    var windowTop = parent.window.scrollY;
    var windowBottom = windowTop + parent.window.innerHeight;
    var windowLeft = parent.window.scrollX;
    var windowRight = windowLeft + parent.window.innerWidth;
    
    //is our elem not within the viewable window
    if (elemRect.left > windowRight ||
            elemRect.left < windowLeft ||
            elemRect.top < windowTop ||
            elemRect.top > windowBottom) {
        return true;
    }
    return false;
};

/*
function:isSandwichIconInVisibleArea - Function to check if sandwichicon is outside the current viewable area
Arguments: none
Return : none
*/
EditableSelectors.prototype.isSandwichIconInVisibleArea = function () {
    'use strict';
    //get where our current element is
    var sandwichIcon = document.getElementById(CONSTANTS.hudEntryID);
    if (sandwichIcon) {
        var elemRect = sandwichIcon.getBoundingClientRect();
    
        if (this.IsElementNotInViewableWindow(elemRect)) {
            return false;
        }
    }
    return true;
};

/*
function:canScrollOnElementClicked - should we focus the sandwich icon on an element click
Arguments: tagname
Return: boolean
*/
EditableSelectors.prototype.canScrollOnElementClicked = function (tag) {
    'use strict';
    if (parent.DW_LIVE_TEXT_EDIT.NoFocusOnSandwichIconClickTags.indexOf(tag) !== -1) {
        return false;
    }
    return true;
};

EditableSelectors.prototype.isSelectorInList = function () {
    'use strict';
    var inputElement = document.getElementById(this.m_activeInputId);
    var inputText = inputElement ? inputElement.innerHTML : null;

    if (inputText && inputText[0] !== "." && inputText[0] !== "#") {
        inputText = "." + inputText;
    }

    return inputText && this.selectorList.indexOf(inputText) >= 0;
};

EditableSelectors.prototype.canApplySelector = function () {
    'use strict';
    var inputElement = document.getElementById(this.m_activeInputId);
    if (!inputElement) {
        return false;
    }
    var selectorText = inputElement.innerHTML;
    if (selectorText === "") {
        return false;
    }
    var isAppliedClass = false;
    var isAppliedID = false;
    if (selectorText[0] === ".") {
        selectorText = selectorText.substr(1);
        isAppliedClass = this.m_documentClassesForElement !== null && this.m_documentClassesForElement.indexOf(selectorText) >= 0;
    } else if (selectorText[0] === "#") {
        selectorText = selectorText.substr(1);
        isAppliedID = this.m_documentIdForElement !== null && this.m_documentIdForElement.indexOf(selectorText) >= 0;
    } else {
        isAppliedClass = this.m_documentClassesForElement !== null && this.m_documentClassesForElement.indexOf(selectorText) >= 0;
    }
    return !isAppliedClass && !isAppliedID;
};

EditableSelectors.prototype.isSelectorHudOpen = function () {
    'use strict';
    return window.parent.liveViewExtensionsObject.getAuxHudVisibility("selectorIcon");
};

EditableSelectors.prototype.isTableHudOpen = function () {
    'use strict';
    return window.parent.liveViewExtensionsObject.getAuxHudVisibility("table");
};

/*
function:initEditableSelectors - Entry point for Editable Selectors. Set on body Onload in HTML

Arguments: none

Return : none
*/
var initEditableSelectors = function () {
    'use strict';
    window.editableSelectorsObj = new EditableSelectors();
    window.editableSelectorsObj.initialize();
};

/*
function:canStartDragAtPoint - Check whether drag can start from the point

Arguments: X and Y coordinates of the point in the document

Return : True if drag is allowed, false otherwise
*/

var canStartDragAtPoint = function (pointX, pointY) {
    'use strict';

    if (!liveViewObject.isExtensionVisible(CONSTANTS.ExtensionID)) {
        return false;
    }


    //we should allow to drag element from the tagname part
    var tagNameElement = document.getElementsByClassName(SELECTOR_CONSTANTS.TagnameClass)[0];
    if (tagNameElement) {
        var tagNameRect = tagNameElement.getBoundingClientRect();
        if (tagNameRect && tagNameRect.left < pointX && tagNameRect.right > pointX && tagNameRect.top < pointY && tagNameRect.bottom > pointY) {
            return true;
        }
    }
    return false;
};

EditableSelectors.prototype.showHudForIAM = function () {
    'use strict';
    this.toggleAuxHudState(true);
};
     
