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

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

*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50 */
/*global $, Node, DwTextHud, AuxiliaryExtensionObject, liveViewObject, dwObject, liveExtensionsConfigPath,TAGS, dwExtensionController, KEYCODES, DWConstants, DW_EXTENSION_EVENT, DWLE_DIMENSIONS */

var CONSTANTS = {
    ExtensionID: "dwTextHud",
    none: "none",
    undef: "undefined",
    TextAlignRadioGroup: "AlignRadioGroup",
    TextAlignLeftClass  : "text-left",
    TextAlignCenterClass: "text-center",
    TextAlignRightClass: "text-right",
    TextAlignJustifyClass: "text-justify",
    TextTransformRadioGroup: "TransformRadioGroup",
    TextTransformLowercaseClass  : "text-lowercase",
    TextTransformAlignUppercaseClass: "text-uppercase",
    TextTransformAlignCapitalizeClass: "text-capitalize",
    HTMLGroup: "HTML",
    BootstrapGroup: "BOOTSTRAP",
    ColorHudGrey: "#AAAAAA",
    ColorHudBlue: "#2D8CEB",
    DWDiv: 'dw-div',
    ClassSeparator: '.',
    DefaultArrowPositon: 20,
    TextDimensionThreshold: 1.5,
    AlignLeftSelected : "align-left_s",
    AlignCenterSelected: "align-center_s",
    AlignRightSelected : "align-right_s",
    AlignJustifySelected : "align-justify_s",
    TransformLowerCase : "transform-lowercase_s",
    TransformUpperCase : "transform-uppercase_s",
    TransformCapitalize : "transform-captialize_s"
};

var DWLE_TEXT_IDS = {
    format: "dwLeTextFormat",
    link: "dwLeTextLink",
    target: "dwLeTextTarget",
    linkBtn: "dwLeTextLinkBtn",
    boldBtn: "dwLeTextBoldBtn",
    italicBtn: "dwLeTextItalicBtn",
    indentRightBtn: "dwLeTextIndentRtBtn",
    indentLeftBtn: "dwLeTextIndentLtBtn",
    hudEntryID: "dwLeTextHudEntry",
    align: "dwLeTextHudAlign",
    alignleft: "dwLeAlignLeft",
    alignRight: "dwLeAlignRight",
    alignCenter: "dwLeAlignCenter",
    alignJustify: "dwLeAlignJustify",
    transform: "dwLeTextHudTransform",
    transformLowercase: "dwLeTransformLowercase",
    transformUppercase: "dwLeTransformUppercase",
    transformCapitalize: "dwLeTransformCapitalize",
    targetDiv: "dwLeTextTargetDiv"
};

var DWLE_TEXT_CLASS = {
    Separator: " ",
    TextHudEntry: "dw_textPropHud",
    TextSelection: "dw_textSelection",
    TextInput: "dw_liveedit_HTMLPI_inputClass",
    TextLabel: "dw_liveedit_HTMLPI_labelClass",
    TextHudRow: "dw_liveedit_HTMLPI_row",
    TextBtnClass: "dw_liveedit_HTMLPI_inputbtnClass",
    TextContainer: "dw_liveedit_HTMLPIWrapper",
    TextContainerRight: "dw_liveedit_HTMLPIWrapperRight",
    TextContainerLeft: "dw_liveedit_HTMLPIWrapperLeft",
    TextContainerTop: "dw_liveedit_HTMLPIWrapperTop",
    TextContainerBottom: "dw_liveedit_HTMLPIWrapperBottom",
    TextContainerGreyed: "dw_liveedit_grey_border",
    NonEditable: "dw_liveedit_HTMLPINonEditable",
    TextDropDownNone: "dw_liveedit_HTMLPI_donotShow",
    TextDropDown: "dw_liveedit_HTMLPI_dropdownClass",
    TextDropDownSmall: "dw_liveedit_HTMLPI_dropdown_smallClass",
    Unselectable: "dw_liveedit_HTMLPI_unselectable",
    NormalBG: "dw_liveedit_HTMLPI_inputbtnClass2_normal",
    LesserTopMargin: "dwLeLessTopMargin",
    TextHudGroup: "dw_liveedit_HTMLPI_group",
    RadioDiv: "dw_liveedit_HTMLPI_RadioImageButton",
    RadioLabel: "dw_livedit_HTMLPI_radioLabel",
    TextRadioLabel: "dw_liveedit_HTMLPI_RadioImageButtonLabel",
    TextGroupHeaderLabel: "dw_liveedit_HTMLPI_GroupHeaderLabel",
    ImgSelected: "dw_liveedit_HMLPI_ImageSelected",
    RadioImageButton: "dw_liveedit_HTMLPI_RadioImageButton_InputRadioBtn",
    RadioImageButtonOutlineNone: "dw_liveedit_HTMLPI_RadioImageButton_ClickOutlineNone",
	Disabled: "dwLeDisabled",
    InputTypeNoUndo: "CantHandleUndo",
    TextStyleRow: "dw_liveedit_HTMLPI_textstyle_row",
    BeforePseudoSelector: ":before",
    AfterPseudoSelector: ":after",
    BoldSelected : "dw_liveedit_BoldSelected",
    ItalicsSelected : "dw_liveedit_ItalicsSelected"
};

var DWLE_TEXTHUD_HEADLIGHTS = {
    RWD: "RWD",
    RWD_BOOTSTRAP: "RWD_Bootstrap",
    ALIGN_LEFT: "Text_HUD_Align_Left",
    ALIGN_CENTER: "Text_HUD_Align_Center",
    ALIGN_RIGHT: "Text_HUD_Align_Right",
    ALIGN_JUSTIFY: "Text_HUD_Align_Justify",
    TRANSFORM_LOWERCASE: "Text_HUD_Transform_Lowercase",
    TRANSFORM_UPPERCASE: "Text_HUD_Transform_Uppercase",
    TRANSFORM_CAPITALISE: "Text_HUD_Transform_Capitalize",
    ELV_TH: "ELV TH",
    DW_SHORTCUT: "DWShortcuts",
    INVOKE_SHORTCUT: "Text_HUD_Invoke_Shortcut",
    INVOKE: "Text_HUD_Invoke",
    FORMAT: "Text_HUD_Format",
    LINK: "Text_HUD_Link",
    BOLD: "Text_HUD_Bold",
    ITALICS: "Text_HUD_Italics",
    INDENT: "Text_HUD_Blockquote"
};

DwTextHud.prototype = new AuxiliaryExtensionObject();
DwTextHud.prototype.constructor = DwTextHud;
DwTextHud.prototype.baseClass = AuxiliaryExtensionObject.prototype.constructor;

function DwTextHud() {
    'use strict';
    
    //initialize variables
    this.callback_counter = 0;  // this is to assign ID's for newly created nodes / modified nodes
    this.callback_map = {};     // map holds the mapping between temporary id's and nodes
    this.formatting_states = {
        boldState: false,
        italicState: false,
        indents: 0
    };
    this.tagChangeInitiated = false;
    this.handledTagChangeSelectionChangeMsg = false;
    dwExtensionController.addListener(DW_EXTENSION_EVENT.ELEMENT_DIMENSION_CHANGED, this.repositionHUD, this);
    // before we disable the extensions we should be committing the HUDS
	dwExtensionController.addListener(parent.DW_EVENTS.DisableExtensions, this.commit, this);
}

DwTextHud.prototype.getHudTag = function () {
    'use strict';
    return "p h1 h2 h3 h4 h5 h6 pre";
};

DwTextHud.prototype.fileSave = function () {
    'use strict';
    if (this.getVisibility()) {
        this.fileSavePending = 1;
        this.commit();
    }
};

DwTextHud.prototype.fileSaveAs = function () {
    'use strict';
    if (this.getVisibility()) {
        this.fileSavePending = 0;
        this.commit();
    }
};

/**
 * @private createLabel
 * Create the label
 * @Param toDiv     :to which this created label should be made as child 
 * @Param labelText :actual label value
 * @Param labelClass:css for label
 * @Param forInputId:inputID corresponding to the label
 * @Param tooltip   :tooltip for the label
 */
DwTextHud.prototype.createLabel = function (toDiv, labelText, labelClass, forInputId, tooltip) {
    'use strict';
    var labelElement = document.createElement("label");
    labelElement.innerHTML = labelText;
    labelElement.title = tooltip || "";
    labelElement.htmlFor = forInputId;
    labelClass.push(DWLE_TEXT_CLASS.Reset);
    labelElement.classList.add.apply(labelElement.classList, labelClass);
    toDiv.appendChild(labelElement);
};
/**
 * @private createImgButton
 * Create input component with given values 
 * @Param toDiv     : to which this created label should be made as child
 * @Param idOfInput : id to be assigned
 * @Param inputClass: class to be applied
 * @Param placeholderText: placeholderText
 * @Param readOnlyFlag : whether the element is readOnly or writable
 * @Param attr : attribute value - used in link textfield currently
 * @Return an InputElement created with given arguments as values
 * @Note DWLE_TEXT_CLASS.Reset 
 *         Needs to be applied on all text HUD elements
 *         In order to make sure that we are not hit by globally applied styles
 */
DwTextHud.prototype.createImgButton = function (toDiv, idOfInput, inputClass, readOnlyFlag, attr, isParent) {
    'use strict';
    var inputElement = document.createElement("img");
    inputElement.id = idOfInput;
    inputElement.classList.add.apply(inputElement.classList, inputClass);
    inputElement.tabIndex = "0";
    if (readOnlyFlag === true) {
        inputElement.setAttribute('readonly', 'true');
    }
    inputElement.onmousedown = function (e) {
        e.stopPropagation();
    };
    toDiv.appendChild(inputElement);
    inputElement.style.cursor = "pointer";
    return inputElement;
};

/**
 * @private createInput
 * Create input component with given values 
 * @Param toDiv     : to which this created label should be made as child
 * @Param ofType    : file/text/image
 * @Param idOfInput : id to be assigned
 * @Param inputClass: class to be applied
 * @Param placeholderText: placeholderText
 * @Param readOnlyFlag : whether the element is readOnly or writable
 * @Param attr : attribute value - used in link textfield currently
 * @Param isParent : if isParent is true, get the parent element of this element - used in link textfield currently
 * @Return an InputElement created with given arguments as values
 * @Note DWLE_TEXT_CLASS.Reset 
 *         Needs to be applied on all text HUD elements
 *         In order to make sure that we are not hit by globally applied styles
 */
DwTextHud.prototype.createInput = function (toDiv, ofType, idOfInput, inputClass, placeHolderText, readOnlyFlag, attr, isParent) {
    'use strict';
    var inputElement = document.createElement('input', 'coral-textfield');
    inputElement.id = idOfInput;
    inputElement.placeholder = placeHolderText;
    inputElement.setAttribute("type", ofType);
    inputElement.classList.add.apply(inputElement.classList, inputClass);
    if (readOnlyFlag === true) {
        inputElement.readOnly = true;
    }
    //this is done to make sure that drag is differentiated from text selection inside input box
    inputElement.onmousedown = function (e) {
        e.stopPropagation();
    };
    toDiv.appendChild(inputElement);
    
    var self = this;
    
    inputElement.onfocus = function () {
        if (attr) {
            var element = self.m_currentSelectedElement;
            if (element) {
                if (isParent) {
                    element = element.parent;
                }
                if (element && (element.getAttribute(attr) || inputElement.value) && (element.getAttribute(attr) !== inputElement.value)) {
                    self.commit();
                }
            }
        }
    };
    return inputElement;
};

DwTextHud.prototype.addItem = function (component, valueId, innerHTMLText, is_disabled, is_selected) {
    'use strict';
    var item = component.items.add({
        value : valueId,
        content : {
            innerHTML : innerHTMLText,
            innerText : innerHTMLText
        },
        disabled : is_disabled,
        selected : is_selected
    });

    return item;
};

/**
 * @private createDropDown
 * create DropDown with given values
 * @Param toDiv             : to which this created label should be made as child
 * @Param idApplied         : id to be assigned
 * @Param classToBeApplied  : class to be applied
 * @Param optionsArray      : object array of {option,tooltip} to be populated in dropdown
 * @Param woNoneOption      : If the dropdown should be without the "none" option
 * @Return an Dropdown Element created with given arguments as values
 */
DwTextHud.prototype.createDropDown = function (toDiv, idApplied, classToBeApplied, optionsArray, woNoneOption) {
    'use strict';
    var selectElement = document.createElement("coral-select");
    selectElement.id = idApplied;
    selectElement.classList.add.apply(selectElement.classList, classToBeApplied);
    var index, opt;
    if (!woNoneOption) {
        this.addItem(selectElement, "none", "none", false, false);
    }
    for (index = 0; index < optionsArray.length; ++index) {
        opt = this.addItem(selectElement, optionsArray[index], optionsArray[index], false, false);
        if (optionsArray[index].tooltip) {
            opt.title = optionsArray[index].tooltip;
        }
    }
    toDiv.appendChild(selectElement);
    if (index >= 0 || !woNoneOption) {
        var item = selectElement.items.getAll()[0];
        item.setAttribute('selected', 'true');
        selectElement.selectedIndex = 0;
    }
    return selectElement;
};

/**
 * @private createGroupContainer
 * create a DIV for a group with given class applied
 * @Param classToBeApplied:class to be applied on dw-div
 * @Param groupLabel:label for the group
 * @Return return dw-div with the class applied
 */
DwTextHud.prototype.createGroupContainer = function (classToBeApplied, groupLabel) {
    'use strict';
    var newContainerDiv = this.createContainer(classToBeApplied);
    var labelDiv = this.createContainer([DWLE_TEXT_CLASS.TextHudRow]);
    var labelClass = [DWLE_TEXT_CLASS.TextLabel, DWLE_TEXT_CLASS.TextGroupHeaderLabel];
    this.createLabel(labelDiv, groupLabel, labelClass, "", "");
    newContainerDiv.appendChild(labelDiv);
    return newContainerDiv;
};

/**
 * @private createContainer
 * create a DW-DIV with given class applied
 * @Param classToBeApplied:class to be applied on dw-div
 * @Return return dw-div with the class applied
 */
DwTextHud.prototype.createContainer = function (classToBeApplied) {
    'use strict';
    var newContainerDiv = document.createElement(TAGS.DwDiv);
    newContainerDiv.classList.add.apply(newContainerDiv.classList, classToBeApplied);
    return newContainerDiv;
};

/**
 * @private updateHudFields
 * Update HUD Fields with respect to the current element under edit
 * @Param elem : Current Element Under Edit.
 */
DwTextHud.prototype.updateHudFields = function (elem, options, target_options) {
    'use strict';
    if (!elem) {
        return;
    }
    var elemType = elem.tagName;
    var bold, italic, indented;
    
    //For tag format
    if (elemType) {
        var sourceElement = document.getElementById(DWLE_TEXT_IDS.format);
        var index = options.indexOf(elemType.toLowerCase());
        if (index !== -1) {
            var item = sourceElement.items.getAll()[index];
            item.setAttribute('selected', 'true');
        }
        sourceElement.value = elemType.toLowerCase();
        sourceElement.title = elemType.toLowerCase();
    }

    // for Link tag and target
    var onlyChildElement = (elem.childElementCount === 1) ? elem.children[0] : false;
    var targetElement = document.getElementById(DWLE_TEXT_IDS.target);
    // Consider the <a> tag if it encapsulates the entire text tag
    // Ignore leading and trailing whitespaces
    if (!this.hasPrintableTextAsFirstOrLastChild(elem) && onlyChildElement && onlyChildElement.tagName === "A") {
        var href = onlyChildElement.getAttribute("href");
        if (href !== null) {

            var linkElement = document.getElementById(DWLE_TEXT_IDS.link);
            linkElement.value = href;
            linkElement.title = href;
            var target = onlyChildElement.getAttribute("target");

            if (this.getEditable() === true) {
                targetElement.removeAttribute("disabled");
                targetElement.classList.remove(DWLE_TEXT_CLASS.TextDropDownNone);
            }
            if (target !== null) {
                var idx = target_options.indexOf(target);
                if (idx !== -1) {
                    var elem_item = targetElement.items.getAll()[idx + 1];
                    elem_item.setAttribute('selected', 'true');
                }
                targetElement.value = target;
            } else {

                targetElement.value = "none";
                var elem_item1 = targetElement.items.getAll()[0];
                elem_item1.setAttribute('selected', 'true');
            }
            document.getElementById(DWLE_TEXT_IDS.targetDiv).style.display = 'block';
        } else {
            document.getElementById(DWLE_TEXT_IDS.targetDiv).style.display = 'none';
            targetElement.setAttribute("disabled", "disabled");
            targetElement.classList.add.apply(targetElement.classList, [DWLE_TEXT_CLASS.TextDropDown, DWLE_TEXT_CLASS.TextDropDownNone]);
            targetElement.value = "none";
            var elem_item2 = targetElement.items.getAll()[0];
            elem_item2.setAttribute('selected', 'true');
        }
    } else {
        targetElement.value = "none";
        var elem_item3 = targetElement.items.getAll()[0];
        elem_item3.setAttribute('selected', 'true');
        document.getElementById(DWLE_TEXT_IDS.targetDiv).style.display = 'none';
    }
    //Update remaining Text Styling Fields- Bold/Italic/Indent/Remove Indent
    this.updateTextStyleHudFields(elem);
    //Update remaining Bootstrap Fields- Alignment and Transform
    this.updateBootstrapClassHudFields(elem);
};

/**
 * @private updateBootstrapClassHudFields
 * Update Bootstap HUD Fields with respect to the current element under edit
 * @Param elem : Current Element Under Edit.
 */
DwTextHud.prototype.updateBootstrapClassHudFields = function (elem) {
    'use strict';
    if (this.showBootstrapComponents) {
        if (!elem) {
            return;
        }
        
        var radioGrps = [];
		//currently we have 2 radio groups - Align and Transform
        radioGrps.push(CONSTANTS.TextAlignRadioGroup);
        radioGrps.push(CONSTANTS.TextTransformRadioGroup);
        var i, j;
        //the first applied class in the class list is the one to be shown selected
        for (j = 0; j < radioGrps.length; ++j) {
            var radioGroup = document.getElementsByName(radioGrps[j]);
            var classValue = elem.getAttribute("class");
            if (classValue) {
                var indexOffirstClassInClassList = classValue.length;
                if (indexOffirstClassInClassList > 0) {
                    var indexToSet = -1;
                    for (i = 0; i < radioGroup.length; ++i) {
                        
                        radioGroup[i].classList.remove(DWLE_TEXT_CLASS.ImgSelected);
                        radioGroup[i].parentElement.classList.remove(radioGroup[i].selectedclass);
                        if (elem.classList.contains(radioGroup[i].value)) {
                            var indexInClasslist = classValue.indexOf(radioGroup[i].value);
                            if (indexInClasslist !== -1 && indexInClasslist < indexOffirstClassInClassList) {
                                indexOffirstClassInClassList = indexInClasslist;
                                indexToSet = i;
                            }
                        }
                    }
                    if (indexToSet !== -1) {
                        radioGroup[indexToSet].classList.add(DWLE_TEXT_CLASS.ImgSelected);
                        radioGroup[indexToSet].parentElement.classList.add(radioGroup[indexToSet].selectedclass);
                    }
                }
            } else { //no classvalue
                for (i = 0; i < radioGroup.length; ++i) {
                    radioGroup[i].classList.remove(DWLE_TEXT_CLASS.ImgSelected);
                    radioGroup[i].parentElement.classList.remove(radioGroup[i].selectedclass);
                }
            }
        }
    }
};

/**
 * @private updateTextStyleHudFields
 * Update B,I,Blockquote Fields with respect to the current element under edit
 * @Param elem : Current Element Under Edit.
 */
DwTextHud.prototype.updateTextStyleHudFields = function (elem) {
    "use strict";
    if (!elem) {
        return;
    }
    var parent = elem.parentNode;
    //Initialize the text states
    this.formatting_states.boldState = false;
    this.formatting_states.italicState = false;
    this.formatting_states.indents = 0;
    
    //Blockquotes will be placed outside the <p> tag.
    while (parent && parent.tagName === "BLOCKQUOTE") {
        //Consider <blockquote> tags enclosing the current text tag exclusively 
        if (parent.childElementCount !== 1) {
            break;
        }
        this.formatting_states.indents++;
        parent = parent.parentNode;
    }
    
    var element = elem;
    // Consider only those <strong> and <em> tags that encapsulate the entire <p> tag's text.
    // Ignore leading and trailing whitespaces
    while (!this.hasPrintableTextAsFirstOrLastChild(element) && element.childElementCount === 1) {
        
        if (element.children[0].tagName === "STRONG") {
            this.formatting_states.boldState = true;
        } else if (element.children[0].tagName === "EM") {
            this.formatting_states.italicState = true;
        }
        element = element.children[0];
    }
    var boldBtn = document.getElementById(DWLE_TEXT_IDS.boldBtn);
    if (this.formatting_states.boldState) {
        boldBtn.parentElement.classList.add(boldBtn.selectedclass);
        boldBtn.title = this.locStrings.boldRemoveStrong;
    } else {
        boldBtn.parentElement.classList.remove(boldBtn.selectedclass);
    }
    
    var italicBtn = document.getElementById(DWLE_TEXT_IDS.italicBtn);
    if (this.formatting_states.italicState) {
        italicBtn.parentElement.classList.add(DWLE_TEXT_CLASS.ItalicsSelected);
        italicBtn.title = this.locStrings.italicRemoveEm;
    } else {
        italicBtn.parentElement.classList.remove(italicBtn.selectedclass);
    }
    //If no <blockquote> tags present, disable the "Indent Left" button
    if (this.formatting_states.indents === 0) {
        var indentLeftBtn = document.getElementById(DWLE_TEXT_IDS.indentLeftBtn);
        indentLeftBtn.setAttribute("disabled", "disabled");
        indentLeftBtn.classList.add(DWLE_TEXT_CLASS.Disabled);
    }
};

/**
 * @private handleArrowKeys
 * handles Right & Left Arrow keys for input elements
 * If left arrow key is pressed and cursor is at the end of text, or
 * Right arrow key is pressed and cursor is at the beginning of text,
 * then nothing should occur
 * @Param e:keypress event
 * @Param elem:the HTML element on which key is pressed
 */
DwTextHud.prototype.handleArrowKeys = function (e, elem) {
    'use strict';
    if (e.keyCode === KEYCODES.Right) {
        var elemLen = elem.value.length;
        //check if cursor is at the end of elem text
        if (elem.selectionStart === elemLen) {
            e.stopPropagation();
            e.preventDefault();
        }
    } else if (e.keyCode ===  KEYCODES.Left) {
        //check if cursor is at the start of src text
        if (elem.selectionEnd === 0) {
            e.stopPropagation();
            e.preventDefault();
        }
    }
};

/**
 * @private handleArrowKeysForButtons
 * handles Right & Left Arrow keys for buttons
 * If left or right arrow key is pressed, nothing should occur
 * @Param e:keypress event
 */
DwTextHud.prototype.handleArrowKeysForButtons = function (e) {
    'use strict';
    if (e.keyCode === KEYCODES.Right || e.keyCode === KEYCODES.Left) {
        e.stopPropagation();
        e.preventDefault();
    }
};

/**
 * @private populateContent
 * populate content inside the Text HUD container div.
 * this is responsible for both editable and non editable cases
 * creation of each of the component and also attaching event listeners and 
 * handling values should be done from this function
 */
DwTextHud.prototype.populateContent = function () {
    'use strict';
    this.showBootstrapComponents = liveViewObject.isBootstrapResponsiveFrameworkLoaded();
    
    var container = this.getHudContainer();
    container.innerHTML = "";
    
    var containerClass = [DWLE_TEXT_CLASS.TextContainer, DWLE_TEXT_CLASS.TextContainerRight];
    
    if (this.getEditable() === false) {
        containerClass.push(DWLE_TEXT_CLASS.TextContainerGreyed);
    }
    
    /* create a container div to holds the content*/
    this.m_hudContentDiv = this.createContainer(containerClass);
    window.setCoralThemeForElement(this.m_hudContentDiv, this.m_currentTheme);
    AuxiliaryExtensionObject.prototype.EnableDragOnElement.call(this, this.m_hudContentDiv);

    //Create containers for each of the rows
    var imgHTMLContainerDiv = this.createGroupContainer([DWLE_TEXT_CLASS.TextHudGroup], CONSTANTS.HTMLGroup);
    var textFormatDiv = this.createContainer([DWLE_TEXT_CLASS.TextHudRow]);
    var textLinkDiv = this.createContainer([DWLE_TEXT_CLASS.TextHudRow]);
    var textFormattingDiv = this.createContainer([DWLE_TEXT_CLASS.TextStyleRow]);
    var textTargetDiv = this.createContainer([DWLE_TEXT_CLASS.TextHudRow, DWLE_TEXT_CLASS.LesserTopMargin]);
    textTargetDiv.id = DWLE_TEXT_IDS.targetDiv;

    var classToApplyOnInput = [DWLE_TEXT_CLASS.TextInput];
    var readOnly = false;
    if (this.getEditable() === false) {
        classToApplyOnInput.push(DWLE_TEXT_CLASS.NonEditable);
        readOnly = true;
    }

    //Text Format Div
    this.createLabel(textFormatDiv, this.locStrings.formatLabel, [DWLE_TEXT_CLASS.TextLabel], DWLE_TEXT_IDS.format, this.locStrings.formatTooltip);
    var options_array1 = [
        "p",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "pre"
    ];
    var inputElemFormat = this.createDropDown(textFormatDiv, DWLE_TEXT_IDS.format, [DWLE_TEXT_CLASS.TextDropDownSmall], options_array1, true);
    
    if (this.getEditable() === true) {
        // this is to achieve shift+tab should go back to last field and should not navigate in the page.
        // it must be onkeydown as tab does not get captured using onkeyup
        inputElemFormat.onkeydown = function (e) {

            if (e.keyCode === KEYCODES.Tab) {
                if (e.shiftKey) {
                    //Focus previous input
                    if (this.showBootstrapComponents) {
                        this.focusHudField(DWLE_TEXT_IDS.transformLowercase);
                    } else if (this.formatting_states.indents > 0) {
                        this.focusHudField(DWLE_TEXT_IDS.indentLeftBtn);
                    } else {
                        this.focusHudField(DWLE_TEXT_IDS.indentRightBtn);
                    }
                    e.preventDefault();
                    e.stopPropagation();
                }
            } else if (e.keyCode === KEYCODES.Escape) {
                this.escapeKeyPressed();
            }
        }.bind(this);
        
        inputElemFormat.addEventListener("change", function () {
            this.updateTag();
            dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.ELV_TH, DWLE_TEXTHUD_HEADLIGHTS.FORMAT);
        }.bind(this));

        inputElemFormat.onmousedown = function (e) {
            e.stopPropagation();
        };
    } else {
        inputElemFormat.setAttribute("disabled", "disabled");
    }
    
    // Text Link Div
    this.createLabel(textLinkDiv, this.locStrings.linkLabel, [DWLE_TEXT_CLASS.TextLabel], DWLE_TEXT_IDS.link, this.locStrings.linkTooltip);
    var inputElemLink = this.createInput(textLinkDiv, "text", DWLE_TEXT_IDS.link, classToApplyOnInput, CONSTANTS.undef, readOnly, "href", true);
    
    if (this.getEditable() === true) {
        var inputElemLinkBtn = this.createImgButton(textLinkDiv, DWLE_TEXT_IDS.linkBtn, [DWLE_TEXT_CLASS.TextBtnClass], readOnly);
        inputElemLinkBtn.title = this.locStrings.browseTooltip;
        inputElemLink.onkeydown = function (e) {
            if (e.keyCode === KEYCODES.Tab) {
                this.updateLink();
                e.stopPropagation();
            } else {
                this.handleArrowKeys(e, inputElemLink);
            }
        }.bind(this);
        inputElemLink.onkeyup = function (e) {
            if (e.keyCode === KEYCODES.Enter) {
                this.updateLink();
                e.stopPropagation();
            } else {
                this.updateTargetDropdown();
            }
        }.bind(this);
        inputElemLinkBtn.onclick = function () {
            inputElemLinkBtn.blur();
            // this needs to be done because this element has focus after showing browse dialog and gets all key events(including enter)
            // and infinitely show up browse dialog
            this.launchFileSelection(this.locStrings.linkLabel);
        }.bind(this);
        
        var invokeSelectLinkDialogOnKeyUp = function (e) {
            
            if (e.keyCode === KEYCODES.Space) {
                
                if (this.getVisibility()) {
                    inputElemLinkBtn.blur();
                    // this needs to be done because this element has focus after showing browse dialog and gets all key events(including enter)
                    // and infinitely show up browse dialog
                    this.launchFileSelection(this.locStrings.linkLabel);
                    e.preventDefault();
                    e.stopPropagation();
                }
            }
        };
        
        var self = this;
        var invokeSelectLinkDialog = function (e) {
            if (self.getVisibility()) {
                inputElemLinkBtn.blur();
                // this needs to be done because this element has focus after showing browse dialog and gets all key events(including enter)
                // and infinitely show up browse dialog
                self.launchFileSelection(self.locStrings.linkLabel);
                e.preventDefault();
                e.stopPropagation();
            }
        };
        
        inputElemLinkBtn.onkeydown = function (e) {
            if (e.keyCode === KEYCODES.Enter) {
                invokeSelectLinkDialog(e);
            } else {
                this.handleArrowKeysForButtons(e);
            }
        }.bind(this);
        
        inputElemLinkBtn.addEventListener("keyup", invokeSelectLinkDialogOnKeyUp.bind(this), true);// enter/space bar should bring the file selection dialog
    }
    
    // Text Target div
    this.createLabel(textTargetDiv, this.locStrings.targetLabel, [DWLE_TEXT_CLASS.TextLabel], DWLE_TEXT_IDS.target, this.locStrings.targetTooltip);
    var options_array2 = [
        "_blank",
        "_self",
        "_parent",
        "_top"
    ];
    var inputElemTarget = this.createDropDown(textTargetDiv, DWLE_TEXT_IDS.target, [DWLE_TEXT_CLASS.TextDropDown, DWLE_TEXT_CLASS.TextDropDownNone], options_array2);
    if (this.getEditable() === true) {
        inputElemTarget.onkeydown = function (e) {
            if (e.keyCode === KEYCODES.Tab) {
                if (e.shiftKey) {
                    this.updateLink();
                    this.focusHudField(DWLE_TEXT_IDS.linkBtn);
                    e.preventDefault();
                    e.stopPropagation();
                }
            } else if (e.keyCode === KEYCODES.Escape) {
                this.escapeKeyPressed();
            }
        }.bind(this);

        inputElemTarget.addEventListener("change", function () {
            // On target change we need to update link because if link is modified and not committed we will not even have anchor tag in the page.
            this.updateLink();

        }.bind(this));

        inputElemTarget.onmousedown = function (e) {
            e.stopPropagation();
        };
    }
    
    //Text Formatting Div
    var inputElemBoldBtn = this.createImgButton(textFormattingDiv, DWLE_TEXT_IDS.boldBtn, [DWLE_TEXT_CLASS.NormalBG], readOnly);
    var inputElemItalicBtn = this.createImgButton(textFormattingDiv, DWLE_TEXT_IDS.italicBtn, [DWLE_TEXT_CLASS.NormalBG], readOnly);
    var inputElemIndentRtBtn = this.createImgButton(textFormattingDiv, DWLE_TEXT_IDS.indentRightBtn, [DWLE_TEXT_CLASS.NormalBG], readOnly);
    var inputElemindentLtBtn = this.createImgButton(textFormattingDiv, DWLE_TEXT_IDS.indentLeftBtn, [DWLE_TEXT_CLASS.NormalBG], readOnly);
    
    inputElemBoldBtn.selectedclass = DWLE_TEXT_CLASS.BoldSelected;
    inputElemItalicBtn.selectedclass = DWLE_TEXT_CLASS.ItalicsSelected;
    
    
    inputElemBoldBtn.title      = this.locStrings.boldAddStrong;
    inputElemItalicBtn.title    = this.locStrings.italicAddEm;
    inputElemIndentRtBtn.title  = this.locStrings.addIndent;
    inputElemindentLtBtn.title  = this.locStrings.removeIndent;
    
	inputElemBoldBtn.classList.add(DWLE_TEXT_CLASS.InputTypeNoUndo);
    inputElemItalicBtn.classList.add(DWLE_TEXT_CLASS.InputTypeNoUndo);
    inputElemIndentRtBtn.classList.add(DWLE_TEXT_CLASS.InputTypeNoUndo);
    inputElemindentLtBtn.classList.add(DWLE_TEXT_CLASS.InputTypeNoUndo);
    
    
    if (this.getEditable() === true) {
        var self = this;

        //Bold Button
        var boldButtonPressed = function (e) {
            var elem = document.getElementById(e.target.id);
            self.toggleBoldItalicTag("bold");
            if (self.formatting_states.boldState) {
                elem.title = self.locStrings.boldRemoveStrong;
                elem.parentElement.classList.add(elem.selectedclass);
            } else {
                elem.title = self.locStrings.boldAddStrong;
                elem.parentElement.classList.remove(elem.selectedclass);
            }
            dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.ELV_TH, DWLE_TEXTHUD_HEADLIGHTS.BOLD);
        };
        var handleKeyUp_bold = function (e) {
            if (e.keyCode === KEYCODES.Space || e.keyCode === KEYCODES.Enter) {
                if (self.getVisibility()) {
                    boldButtonPressed(e);
                    e.preventDefault();
                    e.stopPropagation();
                }
            }
        };
        // Handle Space to Toggle Bold
        inputElemBoldBtn.addEventListener("keyup", handleKeyUp_bold, true);
        //Handle Click to Toggle Bold
        inputElemBoldBtn.onclick = function (e) {
            boldButtonPressed(e);
        }.bind(this);


        //Italics Button
        var italicButtonPressed = function (e) {
            var elem = document.getElementById(e.target.id);
            self.toggleBoldItalicTag("italic");
            if (self.formatting_states.italicState) {
                elem.title = self.locStrings.italicRemoveEm;
                elem.parentElement.classList.add(elem.selectedclass);
            } else {
                elem.title = self.locStrings.italicAddEm;
                elem.parentElement.classList.remove(elem.selectedclass);
            }
            dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.ELV_TH, DWLE_TEXTHUD_HEADLIGHTS.ITALICS);
        };
        var handleKeyUp_italic = function (e) {
            if (e.keyCode === KEYCODES.Space || e.keyCode === KEYCODES.Enter) {
                if (self.getVisibility()) {
                    italicButtonPressed(e);
                    e.preventDefault();
                    e.stopPropagation();
                }
            }
        };
        // Handle Space to Toggle Italic
        inputElemItalicBtn.addEventListener("keyup", handleKeyUp_italic, true);
        //Handle Click to Toggle Italic
        inputElemItalicBtn.onclick = function (e) {
            italicButtonPressed(e);
        }.bind(this);


        //Indent Right Button
        var handleKeyUp_indentRight = function (e) {
            if (e.keyCode === KEYCODES.Space || e.keyCode === KEYCODES.Enter) {
                if (self.getVisibility()) {
                    self.addIndent();
                    e.preventDefault();
                    e.stopPropagation();
                }
            }
        };
        // Handle Space to Indent Right
        inputElemIndentRtBtn.addEventListener("keyup", handleKeyUp_indentRight, true);
        //Handle Click to Indent Right
        inputElemIndentRtBtn.onclick = function () {
            // Call method for indenting right- Adding <blockquote> tag
            this.addIndent();
        }.bind(this);

        // this is to make sure that tabbing is inside the hud
        inputElemIndentRtBtn.onkeydown = function (e) {

            if (e.keyCode === KEYCODES.Tab) {
                if (!e.shiftKey) {
                    if (this.formatting_states.indents > 0) {
                        this.focusHudField(DWLE_TEXT_IDS.indentLeftBtn);
                    } else if (this.showBootstrapComponents) {
                        this.focusHudField(DWLE_TEXT_IDS.alignleft);
                    } else {
                        this.focusHudField(DWLE_TEXT_IDS.format);
                    }
                } else {
                    this.focusHudField(DWLE_TEXT_IDS.italicBtn);
                }
                e.stopPropagation();
                e.preventDefault();
            } else {
                this.handleArrowKeysForButtons(e);
            }
        }.bind(this);


        //Indent Left Button
        var handleKeyUp_indentLeft = function (e) {
            if (e.keyCode === KEYCODES.Space || e.keyCode === KEYCODES.Enter) {
                if (self.getVisibility()) {
                    self.removeIndent();
                    e.preventDefault();
                    e.stopPropagation();
                }
            }
        };
        // Handle Space to Indent Left
        inputElemindentLtBtn.addEventListener("keyup", handleKeyUp_indentLeft, true);
        //Handle Click to Indent Left
        inputElemindentLtBtn.onclick = function () {
            // Call method for indenting left- Removing <blockquote> tag
            this.removeIndent();
        }.bind(this);

        // this is to make sure that tabbing is inside the hud
        inputElemindentLtBtn.onkeydown = function (e) {
            if (e.keyCode === KEYCODES.Tab) {
                if (!e.shiftKey) {
                    if (this.showBootstrapComponents) {
                        this.focusHudField(DWLE_TEXT_IDS.alignleft);
                    } else {
                        this.focusHudField(DWLE_TEXT_IDS.format);
                    }
                } else {
                    this.focusHudField(DWLE_TEXT_IDS.indentRightBtn);
                }
                e.stopPropagation();
                e.preventDefault();
            } else {
                this.handleArrowKeysForButtons(e);
            }
        }.bind(this);
    } else {
        inputElemBoldBtn.classList.add(DWLE_TEXT_CLASS.Disabled);
        inputElemItalicBtn.classList.add(DWLE_TEXT_CLASS.Disabled);
        inputElemIndentRtBtn.classList.add(DWLE_TEXT_CLASS.Disabled);
        inputElemindentLtBtn.classList.add(DWLE_TEXT_CLASS.Disabled);
    }
    
    imgHTMLContainerDiv.appendChild(textFormatDiv);
    imgHTMLContainerDiv.appendChild(textLinkDiv);
    imgHTMLContainerDiv.appendChild(textTargetDiv);
    imgHTMLContainerDiv.appendChild(textFormattingDiv);
    this.m_hudContentDiv.appendChild(imgHTMLContainerDiv);
    
    //create the bootstrap components
    this.createBootstrapComponents(readOnly);
    
    //add actual hud content
    container.appendChild(this.m_hudContentDiv);
    this.m_initialized = true;
    this.updateHudFields(this.m_currentSelectedElement, options_array1, options_array2);

};

/**
 * @private createRadioGroup
 * create the radio group label and the container div for the radio elements
 * @Param:parentDiv - parent div
 * @Param:radioLabel - label of the radio group
 * @Param:ifOfInput - id of label
 * @Param:tooltip - tooltip of the label
 */
DwTextHud.prototype.createRadioGroup = function (parentDiv, radioLabel, idOfInput, tooltip) {
    "use strict";
    var radioLabelClasses = [DWLE_TEXT_CLASS.TextLabel, DWLE_TEXT_CLASS.TextRadioLabel];
    this.createLabel(parentDiv, radioLabel, radioLabelClasses, idOfInput, tooltip);
    var innerDiv = this.createContainer([DWLE_TEXT_CLASS.RadioDiv]);
    parentDiv.appendChild(innerDiv);
    return innerDiv;
};

/**
 * @private createRadioInput
 * Create the radio input element
 * @Param:parentDiv - parent div 
 * @Param:idOfInput - id of radio element
 * @Param:tooltip - tooltip of radio element
 * @Param:classToBeApplied - class to be applied to the html tag when clicked on the element
 * @Param:radioGroup - name of the radioGroup the element belongs to
 * @Param:selectedclassname - class name for the radio image element selectedstate
 * @Param:tabnextelem - tab next element
 * @Param:tabprevelem - tab previous element
 * @Param:readOnly - readonly or not
 */
DwTextHud.prototype.createRadioInput = function (parentDiv, idOfInput, tooltip, classToBeApplied, radioGroup, selectedclassname, tabnextelem, tabprevelem, readOnly) {
    "use strict";
    var radioButton = this.createImgButton(parentDiv, idOfInput, [DWLE_TEXT_CLASS.TextBtnClass, DWLE_TEXT_CLASS.RadioImageButton], readOnly);
    radioButton.title = tooltip;
    radioButton.value = classToBeApplied;
    radioButton.name = radioGroup;
    radioButton.classList.add(DWLE_TEXT_CLASS.InputTypeNoUndo);
    radioButton.selectedclass = selectedclassname;
    if (this.getEditable() === true) {
        var self = this;
        var handleKeyUp = function (e) {

                if (e.keyCode === KEYCODES.Space) {
                    if (self.getVisibility()) {
                        self.updateRadioButtonClass(e);
                        e.preventDefault();
                        e.stopPropagation();
                    }
                }
            };
        radioButton.onmousedown = function (e) {
            e.target.classList.add(DWLE_TEXT_CLASS.RadioImageButtonOutlineNone);
        };
        radioButton.onblur = function (e) {
            e.target.classList.remove(DWLE_TEXT_CLASS.RadioImageButtonOutlineNone);
        };
        radioButton.onclick = function (e) {
            self.updateRadioButtonClass(e);
        };
        radioButton.onkeydown = function (e) {
            self.handleArrowKeysForButtons(e);
            if (e.keyCode === KEYCODES.Tab) {
                if (!e.shiftKey) {
                    self.focusHudField(tabnextelem);
                } else {
                    self.focusHudField(tabprevelem);
                }
                e.preventDefault();
            } else if (e.keyCode === KEYCODES.Enter) {
                self.updateRadioButtonClass(e);
                e.preventDefault();
                e.stopPropagation();
            }
        };

        radioButton.addEventListener("keyup", handleKeyUp, true);
    } else {
        radioButton.classList.add(DWLE_TEXT_CLASS.Disabled);
    }
};

/**
 * @private createBootstrapComponents
 * create all the Bootstrap components for the hud
 * @Param:readOnly - readonly flag
 */
DwTextHud.prototype.createBootstrapComponents = function (readOnly) {
    "use strict";
    //show the bootstrap components only if the TitanDoc is a bootstrap document
    if (this.showBootstrapComponents) {
        var htmlBootstrapDividerDiv = document.createElement("hr");
        var txtBootstrapContainerDiv = this.createGroupContainer([DWLE_TEXT_CLASS.TextHudGroup], CONSTANTS.BootstrapGroup);
        //Text Align div
        var txtAlignDiv = this.createContainer([DWLE_TEXT_CLASS.TextHudRow]);
        var radioAlignDiv = this.createRadioGroup(txtAlignDiv, this.locStrings.AlignLabel, DWLE_TEXT_IDS.align, this.locStrings.alignTooltip);
        var tabPrevElemId;
        if (this.formatting_states.indents > 0) {
            tabPrevElemId = DWLE_TEXT_IDS.indentLeftBtn;
        } else {
            tabPrevElemId = DWLE_TEXT_IDS.indentRightBtn;
        }
        this.createRadioInput(radioAlignDiv, DWLE_TEXT_IDS.alignleft, this.locStrings.alignLeftTooltip, CONSTANTS.TextAlignLeftClass, CONSTANTS.TextAlignRadioGroup, CONSTANTS.AlignLeftSelected, DWLE_TEXT_IDS.alignCenter, tabPrevElemId, readOnly);
        this.createRadioInput(radioAlignDiv, DWLE_TEXT_IDS.alignCenter, this.locStrings.alignCenterTooltip, CONSTANTS.TextAlignCenterClass,
                              CONSTANTS.TextAlignRadioGroup, CONSTANTS.AlignCenterSelected, DWLE_TEXT_IDS.alignRight, DWLE_TEXT_IDS.alignleft, readOnly);
        this.createRadioInput(radioAlignDiv, DWLE_TEXT_IDS.alignRight, this.locStrings.alignRightTooltip, CONSTANTS.TextAlignRightClass,
                              CONSTANTS.TextAlignRadioGroup, CONSTANTS.AlignRightSelected, DWLE_TEXT_IDS.alignJustify, DWLE_TEXT_IDS.alignCenter, readOnly);
        this.createRadioInput(radioAlignDiv, DWLE_TEXT_IDS.alignJustify, this.locStrings.alignJustifyTooltip, CONSTANTS.TextAlignJustifyClass,
                              CONSTANTS.TextAlignRadioGroup, CONSTANTS.AlignJustifySelected, DWLE_TEXT_IDS.transformUppercase, DWLE_TEXT_IDS.alignRight, readOnly);
        
        
        //Text Transform Div
        var txtTransformDiv = this.createContainer([DWLE_TEXT_CLASS.TextHudRow]);
        var radioTransformDiv = this.createRadioGroup(txtTransformDiv, this.locStrings.TransformLabel, DWLE_TEXT_IDS.transform, this.locStrings.transformTooltip);

        this.createRadioInput(radioTransformDiv, DWLE_TEXT_IDS.transformUppercase, this.locStrings.transformUppercaseTooltip, CONSTANTS.TextTransformAlignUppercaseClass,
                              CONSTANTS.TextTransformRadioGroup, CONSTANTS.TransformUpperCase, DWLE_TEXT_IDS.transformCapitalize, DWLE_TEXT_IDS.alignJustify, readOnly);
        this.createRadioInput(radioTransformDiv, DWLE_TEXT_IDS.transformCapitalize, this.locStrings.transformCapitalizeTooltip, CONSTANTS.TextTransformAlignCapitalizeClass,
                              CONSTANTS.TextTransformRadioGroup, CONSTANTS.TransformCapitalize, DWLE_TEXT_IDS.transformLowercase, DWLE_TEXT_IDS.transformUppercase, readOnly);
        this.createRadioInput(radioTransformDiv, DWLE_TEXT_IDS.transformLowercase, this.locStrings.transformLowercaseTooltip, CONSTANTS.TextTransformLowercaseClass,
                              CONSTANTS.TextTransformRadioGroup, CONSTANTS.TransformLowerCase, DWLE_TEXT_IDS.format, DWLE_TEXT_IDS.transformCapitalize, readOnly);
        
        txtBootstrapContainerDiv.appendChild(txtAlignDiv);
        txtBootstrapContainerDiv.appendChild(txtTransformDiv);
        
        //append the divider and the bootstrap container div to the hud
        this.m_hudContentDiv.appendChild(htmlBootstrapDividerDiv);
        this.m_hudContentDiv.appendChild(txtBootstrapContainerDiv);
    }
    
};

DwTextHud.prototype.launchFileSelection = function (type) {
    'use strict';
    var argObj = {};
    argObj.filter = "";
    if (type === this.locStrings.linkLabel) {
        argObj.operation = this.locStrings.fileDialogType;
        argObj.subOp = this.locStrings.fileTitleLink;
        argObj.callback = function (value) {
            //In case the user presses Esc after clicking the launch file selection,
            //Do NOT consider the new link value as "" to go ahead to remove the link.
            if (value === "") {
                this.focusHudField(DWLE_TEXT_IDS.linkBtn);
                this.stopHide = false;
                return;
            }
            var linkElement = document.getElementById(DWLE_TEXT_IDS.link);
            linkElement.value = value;
            this.updateLink();
            this.focusHudField(DWLE_TEXT_IDS.linkBtn);
            this.stopHide = false;
            this.updateTargetDropdown();
        }.bind(this);
    } else {
        return;
    }
    this.stopHide = true;
    dwObject.dwBrowseForFileURL(argObj.operation, argObj.subOp, argObj.callback, argObj.filter, false);
};

/*
function : focusHudField
Arguments: current Text Element
Give focus to hud field
*/
DwTextHud.prototype.focusHudField = function (id) {
    'use strict';

    var focusElement = document.getElementById(id);
    if (focusElement) {
        focusElement.focus();
    }
};

DwTextHud.prototype.toggleAuxHud = function (message) {
    'use strict';
    AuxiliaryExtensionObject.prototype.toggleAuxHud.call(this, message);
    var elem = this.getCurrentElement();
    if (this.getVisibility()) {
        var srcElement = document.getElementById(DWLE_TEXT_IDS.format);
        if (this.getEditable() === true) {
            srcElement.focus();
        }
    }
    if (message.shortcut) {
        if (this.getVisibility()) {
            dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.DW_SHORTCUT, DWLE_TEXTHUD_HEADLIGHTS.INVOKE_SHORTCUT);
        }
    } else {
        if (this.getVisibility()) {
            dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.ELV_TH, DWLE_TEXTHUD_HEADLIGHTS.INVOKE);
        }
    }
};

DwTextHud.prototype.getExtensionId = function () {
    'use strict';
    return CONSTANTS.ExtensionID;
};

/**
 * @override commit
 * Commit the values of each of the attribute in Text Hud. 
 * @Param None
*/
DwTextHud.prototype.commit = function () {
    'use strict';

    if (this.getVisibility() === false) {
        return;
    }
    //Update the tag
    this.updateTag(false);
    
    // Link needs to be done at the end since it leads to creation of new elements. If provided with a value, this will update the target as well.
    this.updateLink();
};

/**
 * @private isHudObstructingTextBox
 * if the Hud postioned at the given point will obstruct the text box or not
 * DWLE_DIMENSIONS.AuxHudOffSet : 10px  --> for the arrow to come before the HUD
 */
DwTextHud.prototype.isHudObstructingTextBox = function (elemRect) {
    'use strict';
    
    if (elemRect) {
        var elemRight = elemRect.left + elemRect.width;
        var elemBottom = elemRect.top + elemRect.height;
        var elemLeft = elemRect.left <= window.parent.pageXOffset ? window.parent.pageXOffset : elemRect.left;
        var elemTop = elemRect.top <= window.parent.scrollY ? window.parent.scrollY : elemRect.top;
        
        elemRight = elemRight > (window.parent.innerWidth + window.parent.pageXOffset) ? (window.parent.innerWidth + window.parent.pageXOffset) : elemRight;
        elemBottom = elemBottom > (window.parent.innerHeight + window.parent.scrollY) ? (window.parent.innerHeight + window.parent.scrollY) : elemBottom;
        
        if (elemRight > elemLeft && elemBottom > elemTop) {
            var elemWidth = elemRight - elemLeft;
            var elemHeight = elemBottom - elemTop;
            
            if (this.m_currentHudWidth > 0 && this.m_currentHudHeight > 0) {
                var widthRatio = elemWidth / this.m_currentHudWidth;
                var heightRatio = elemHeight / this.m_currentHudHeight;

                if (widthRatio < CONSTANTS.ImgDimensionThreshold && heightRatio < CONSTANTS.ImgDimensionThreshold) {
                    return true;
                }
            }
        }
    }
    
    return false;
};

/**
 * @private positionHud
 * Position the HUD according to element's width, height and position
 * @Param elemRect : contains computed width,height,top,left 
 * @Note This positions the HUD with respect to the space available around the Text.
 * DWLE_DIMENSIONS.AuxHudOffSet : 10px  --> for the arrow to come before the HUD
 */
DwTextHud.prototype.positionHud = function () {
    'use strict';
    var elemRect = liveViewObject.getElementRect(this.getCurrentElement());
    if (elemRect.height <= 0 || elemRect.width <= 0) {
        return;
    }
    //cache position of hud because offsetwidth and offsetheight gives 0 when element display is made to none
    if (this.m_currentHudHeight === 0 && this.m_currentHudWidth === 0) {
        this.m_currentHudHeight = this.m_hudContentDiv.offsetHeight;
        this.m_currentHudWidth = this.m_hudContentDiv.offsetWidth;
    }
    
    //Hud should not obscure the esh Details 
    var widthOffset = (elemRect.width >= this.eshDetails.Width) ? elemRect.width : this.eshDetails.Width;
    
    // By default try to position on right
    var hudTop = -1;
    var hudLeft = -1;
    var styleToApply = "";
    
    var leftOfElem = elemRect.left >= window.parent.pageXOffset ? elemRect.left : window.parent.pageXOffset;
    var topOfElem = elemRect.top >= window.parent.scrollY ? elemRect.top : window.parent.scrollY;
    
    // right Side Check contains max horizontal position needed to place the hud on right side
    var rightSideCheck = (leftOfElem + widthOffset + this.m_currentHudWidth) - window.parent.pageXOffset;
    // height Check contains max vertical position takes to place the hud at same level
    var heightCheck = (topOfElem + this.m_currentHudHeight) - window.parent.scrollY;
    
    //Step 1: Lets try to fit it at the right/ left of the element entirely
    
    if (rightSideCheck < window.parent.innerWidth) {
        //Keeping hud at the right side will fit inside the live view width 
        hudLeft = (leftOfElem + widthOffset) + DWLE_DIMENSIONS.AuxHudOffSet;
        hudTop = topOfElem;
        styleToApply = DWLE_TEXT_CLASS.TextContainerRight;
    } else {
        //no fit on the right side , lets try on the left side
        var leftSideCheck = leftOfElem - (window.parent.pageXOffset + this.m_currentHudWidth + DWLE_DIMENSIONS.AuxHudOffSet);
        
        if (leftSideCheck > 0) {
            //fit on the left side 
            hudLeft = leftSideCheck + window.parent.pageXOffset;
            hudTop = topOfElem;
            styleToApply = DWLE_TEXT_CLASS.TextContainerLeft;
        } else {
            //No space on the left , only option is to allign it with the element itself 
            hudLeft = leftOfElem;
        }
    }
    
    
    //Step 2 : Not possible on the right / left of the element , try at top / bottom of the element
    
    if (hudTop === -1 || heightCheck > window.parent.innerHeight) {
        //we need to re-position the hud either on top of the element or at the bottom 
        
        //Try at the top of the element
        var topCorner = topOfElem - (this.m_currentHudHeight + DWLE_DIMENSIONS.AuxHudOffSet + this.eshDetails.Height);
        
        if (topCorner - window.parent.scrollY > 0) {
            //Hud can be placed at the top of the element , so align the hud vertically same as the element
            hudLeft = leftOfElem;
            hudTop = topCorner;
            styleToApply = DWLE_TEXT_CLASS.TextContainerTop;
        } else {
            //Can't be placed at the top , so lets try at the bottom of the element 
            var bottom = (topOfElem + elemRect.height + this.m_currentHudHeight + DWLE_DIMENSIONS.AuxHudOffSet + this.eshDetails.Position) - window.parent.scrollY;
            
            if (bottom < window.parent.innerHeight) { //bottom case
                //Fitted at the bottom of the element , so align the hud vertically same as the element
                var heightToPlace = topOfElem + elemRect.height + DWLE_DIMENSIONS.AuxHudOffSet;
                //if the editable selectors is on bottom add that height also
                if (this.eshDetails.Position === false) {
                    heightToPlace += this.eshDetails.Height;
                }
                hudLeft = leftOfElem;
                hudTop = heightToPlace;
                styleToApply = DWLE_TEXT_CLASS.TextContainerBottom;
            } else {
                //Step 3 : Not Possible on top / bottom of the element , now try somewhere in between 
                
                if (styleToApply !== DWLE_TEXT_CLASS.TextContainerRight && styleToApply !== DWLE_TEXT_CLASS.TextContainerLeft) {
                    //hud will be placed on top of the element
                    
                    if (!this.isHudObstructingTextBox(elemRect)) {
                        //Place the hud nearer to the ESH 
                        if (this.eshDetails.Position === true) {
                            //ESH on top, extend hud towards bottom of the element
                            hudTop = topOfElem + DWLE_DIMENSIONS.AuxHudOffSet;
                        } else {
                            //ESH on bottom, extend hud towards top of the element
                            hudTop = topOfElem + elemRect.height - (this.m_currentHudHeight + DWLE_DIMENSIONS.AuxHudOffSet);
                        }
                        //lets check if hud will get clipped some how by positoning it nearer to the ESH 
                        if ((hudTop < window.parent.scrollY) || (hudTop + this.m_currentHudHeight) > (window.parent.innerHeight + window.parent.scrollY)) {
                            
                            if ((hudLeft + this.eshDetails.Width + this.m_currentHudWidth + DWLE_DIMENSIONS.AuxHudOffSet) < (window.parent.innerWidth + window.parent.pageXOffset)) {
                                //Lets try to take the hud a little rightwards , so that hud doesn't cover the ESH at all 
                                hudLeft += (this.eshDetails.Width + DWLE_DIMENSIONS.AuxHudOffSet);
                                styleToApply = DWLE_TEXT_CLASS.ImgContainerRight;
                            } else {
                                //Lets try to take the hud a little rightwards , just after the addbtn
                                var left = (window.parent.innerWidth + window.parent.pageXOffset) - (this.m_currentHudWidth + DWLE_DIMENSIONS.AuxHudOffSet);
                                hudLeft = left < 0 ? hudLeft : left;
                                styleToApply = DWLE_TEXT_CLASS.ImgContainerRight;
                            }
                            
                            if (hudTop < window.parent.scrollY) {
                                //hud will get cliped at the top , so start at the top of the screen 
                                hudTop = window.parent.scrollY;
                            } else if ((hudTop + this.m_currentHudHeight) > (window.parent.innerHeight + window.parent.scrollY)) {
                                //Hud will get clipped at the bottom , take the hud a little upwards
                                var top = (window.parent.innerHeight + window.parent.scrollY) - (this.m_currentHudHeight + DWLE_DIMENSIONS.AuxHudOffSet);
                                hudTop = top < window.parent.scrollY ? window.parent.scrollY : top;
                            }
                        }
                    } else {
                        //Hud's left edge will be alligned with the element's left edge , so lets try to obscure the element as less as possible
                        var hudTopWhenHudFlowsUpwards = window.parent.scrollY;
                        var hudTopWhenHudFlowsDownwards = (window.parent.innerHeight - this.m_currentHudHeight) + window.parent.scrollY;

                        var elemHeightObstructedbyHudUpwards = (hudTopWhenHudFlowsUpwards + this.m_currentHudHeight) - topOfElem;
                        var elemHeightObstructedbyHudDownwards = topOfElem - hudTopWhenHudFlowsDownwards;

                        if (elemHeightObstructedbyHudDownwards < elemHeightObstructedbyHudUpwards) {
                            //Pick the one which obstructs the element least
                            hudTop = hudTopWhenHudFlowsDownwards;
                        } else {
                            hudTop = hudTopWhenHudFlowsUpwards;
                        }
                        
                        if ((hudLeft + this.eshDetails.Width + this.m_currentHudWidth + DWLE_DIMENSIONS.AuxHudOffSet) < (window.parent.innerWidth + window.parent.pageXOffset)) {
                            //Lets try to take the hud a little rightwards , so that hud doesn't cover the ESH at all 
                            hudLeft += (this.eshDetails.Width + DWLE_DIMENSIONS.AuxHudOffSet);
                            if ((hudTop + CONSTANTS.DefaultArrowPositon) > topOfElem && (hudTop + CONSTANTS.DefaultArrowPositon) < (topOfElem + elemRect.height)) {
                                styleToApply = DWLE_TEXT_CLASS.ImgContainerRight;
                            }
                        } else {
                            //Lets try to take the hud a little rightwards , just after the addbtn
                            var leftOfHud = (window.parent.innerWidth + window.parent.pageXOffset) - (this.m_currentHudWidth + DWLE_DIMENSIONS.AuxHudOffSet);
                            hudLeft = leftOfHud < 0 ? hudLeft : leftOfHud;
                            if ((hudTop + CONSTANTS.DefaultArrowPositon) > topOfElem && (hudTop + CONSTANTS.DefaultArrowPositon) < (topOfElem + elemRect.height)) {
                                styleToApply = DWLE_TEXT_CLASS.ImgContainerRight;
                            }
                        }
                    }
                    
                    //Check if Esh is totally covered by the hud
                    if (hudTop < this.eshDetails.addBtnTop && (hudTop + this.m_currentHudHeight) > this.eshDetails.addBtnBottom) {
                        //Take the hud a little upwards , so that arrow is clearly visible in this case 
                        if (hudTop > window.parent.scrollY + DWLE_DIMENSIONS.AuxHudOffSet) {
                            hudTop -= DWLE_DIMENSIONS.AuxHudOffSet;
                        } else {
                            hudTop = window.parent.scrollY;
                        }
                    }

                    //Lets check if the hud width will get clipped somehow 
                    if (this.m_currentHudWidth < window.parent.innerWidth) {
                        var wcheck = hudLeft + this.m_currentHudWidth;
                        //Only if the hud can be somehow fitted inside the live view , then try to re-position the same 
                        if (wcheck > (window.parent.scrollX + window.parent.innerWidth)) {
                            //Imagehud will overflow outside the liveview, try to re-position it properly
                            var wDiff = wcheck - (window.parent.scrollX + window.parent.innerWidth);
                            hudLeft -= wDiff;
                        }
                    }
                } else {
                    //Hud will be at the right / left of the element , so it can't obstruct the image 
                    if (this.eshDetails.Position === true) {
                        //ESH on top , place hud on top of element and extend it to the bottom of the element
                        hudTop = topOfElem + DWLE_DIMENSIONS.AuxHudOffSet;
                        if ((hudTop + this.m_currentHudHeight) > (window.parent.scrollY + window.parent.innerHeight)) {
                            // Hud will overflow outside the liveview , try to re-postion it properly                        
                            hudTop = hudTop - ((hudTop + this.m_currentHudHeight + DWLE_DIMENSIONS.AuxHudOffSet) - (window.parent.scrollY + window.parent.innerHeight));
                        }
                    } else {
                        //ESH on bottom, place hud on top of the element and extend it to the top of the element 
                        hudTop = topOfElem + elemRect.height - (this.m_currentHudHeight + DWLE_DIMENSIONS.AuxHudOffSet);

                        if (hudTop < 0) {
                            //hud will get cliped at the top , so start at the botom of the element & extend it towards the bottom 
                            hudTop = topOfElem + elemRect.height + this.eshDetails.Height + DWLE_DIMENSIONS.AuxHudOffSet;
                        }

                        if ((hudTop + this.m_currentHudHeight) > (window.parent.scrollY + window.parent.innerHeight)) {
                            //Hud will overflow outside the liveview , try to re-postion it properly                            
                            hudTop = hudTop - ((hudTop + this.m_currentHudHeight + DWLE_DIMENSIONS.AuxHudOffSet) - (window.parent.scrollY + window.parent.innerHeight));
                        }
                    }
                }
                
                if (styleToApply !== DWLE_TEXT_CLASS.TextContainerRight && styleToApply !== DWLE_TEXT_CLASS.TextContainerLeft) {
                    
                    //Decide where the arrow points to
                    if (hudTop <= this.eshDetails.addBtnTop && (hudTop + this.m_currentHudHeight) >= this.eshDetails.addBtnBottom) {
                        //Esh is totally covered by the hud , so check what part of element is visible
                        if (hudTop >= topOfElem) {
                            //Element is visible at the top of the hud , so make the arrow point towards top
                            styleToApply = DWLE_TEXT_CLASS.TextContainerBottom;
                        } else if ((hudTop + this.m_currentHudHeight) <= (topOfElem + elemRect.height)) {
                            //Element is visible at the bottom of the hud , so let the arrow point at the bottom
                            styleToApply = DWLE_TEXT_CLASS.TextContainerTop;
                        } else if ((hudLeft + this.m_currentHudWidth) <= (leftOfElem + elemRect.width)) {
                            //Element is visible at the right of the hud
                            styleToApply = DWLE_TEXT_CLASS.TextContainerLeft;
                        } else if (hudLeft > leftOfElem) {
                            //Element is visible at the left of the hud
                            styleToApply = DWLE_TEXT_CLASS.TextContainerRight;
                        } /* else {
                            //Case where nothing is visible , lets not have any arrow pointed anywhere
                        }
                        */
                    } else if ((hudTop + this.m_currentHudHeight) <= this.eshDetails.addBtnBottom) {
                        //Esh is visible at the bottom of the hud , so arrow will point towards bottom 
                        styleToApply = DWLE_TEXT_CLASS.TextContainerTop;
                    } else {
                        //Esh is visible at the top of the hud  , so arrow should point towards top 
                        styleToApply = DWLE_TEXT_CLASS.TextContainerBottom;
                    }
                }
            }
        }
    }
    
    this.m_hudContentDiv.style.top = hudTop + 'px';
    this.m_hudContentDiv.style.left = hudLeft + 'px';
    this.m_hudContentDiv.removeAttribute("class");
    
    if (this.getEditable() === false) {
        styleToApply += " " + DWLE_TEXT_CLASS.TextContainerGreyed;
    }
    
    this.m_hudContentDiv.setAttribute("class", DWLE_TEXT_CLASS.TextContainer + " " + styleToApply);
    window.setCoralThemeForElement(this.m_hudContentDiv, this.m_currentTheme);
    var styleSheet = document.styleSheets[1];
    
    var arrowColor = CONSTANTS.ColorHudBlue;
    if (this.getEditable() === false) {
        arrowColor = CONSTANTS.ColorHudGrey;
    }

    if (styleSheet && styleSheet.insertRule) {
        if (styleToApply.indexOf(DWLE_TEXT_CLASS.TextContainerRight) !== -1 || styleToApply.indexOf(DWLE_TEXT_CLASS.TextContainerLeft) !== -1) {
            //Set the posiiton of the arrow in case hud is on right or left of the element 
            var arrowTop = 0;
            if ((topOfElem - hudTop) > CONSTANTS.DefaultArrowPositon && (topOfElem - hudTop) < (this.m_currentHudHeight - CONSTANTS.DefaultArrowPositon)) {
                arrowTop = (topOfElem - hudTop);
            } else if ((topOfElem - hudTop) < CONSTANTS.DefaultArrowPositon) {
                arrowTop = CONSTANTS.DefaultArrowPositon;
            } else {
                arrowTop = (this.m_currentHudHeight - CONSTANTS.DefaultArrowPositon);
            }

            if (styleToApply.indexOf(DWLE_TEXT_CLASS.TextContainerRight) !== -1) {
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerRight + DWLE_TEXT_CLASS.BeforePseudoSelector + "{ top:" + arrowTop + "px; border-right-color: " + arrowColor + ";}", styleSheet.cssRules.length);
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerRight + DWLE_TEXT_CLASS.AfterPseudoSelector + "{ top:" + arrowTop + "px;} ", styleSheet.cssRules.length);
            } else {
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerLeft + DWLE_TEXT_CLASS.BeforePseudoSelector + "{ top:" + arrowTop + "px; border-left-color: " + arrowColor + ";} ", styleSheet.cssRules.length);
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerLeft + DWLE_TEXT_CLASS.AfterPseudoSelector + "{ top:" + arrowTop + "px;} ", styleSheet.cssRules.length);
            }
        } else {
            //For bottom and top case of Hud 
            var arrowLeft = CONSTANTS.DefaultArrowPositon;
            
            if (leftOfElem > hudLeft) {
                //element is at the right side of the hud 
                if ((hudLeft + this.m_currentHudWidth) < leftOfElem) {
                    //hud start croses the end of the element , so keep the arrow at the end of the hud 
                    arrowLeft = this.m_currentHudWidth - CONSTANTS.DefaultArrowPositon;
                } else if ((leftOfElem - hudLeft) > CONSTANTS.DefaultArrowPositon && (leftOfElem - hudLeft) < (this.m_currentHudWidth - CONSTANTS.DefaultArrowPositon)) {
                    arrowLeft = leftOfElem - hudLeft;
                } else if ((leftOfElem - hudLeft) > (this.m_currentHudWidth - CONSTANTS.DefaultArrowPositon)) {
                    arrowLeft = this.m_currentHudWidth - CONSTANTS.DefaultArrowPositon;
                }
            } //No need of adjustment if element is at the left side of the hud
            
            if (styleToApply.indexOf(DWLE_TEXT_CLASS.TextContainerTop) !== -1) {
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerTop + DWLE_TEXT_CLASS.BeforePseudoSelector + "{ left:" + arrowLeft + "px; border-top-color: " + arrowColor + ";} ", styleSheet.cssRules.length);
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerTop + DWLE_TEXT_CLASS.AfterPseudoSelector + "{ left:" + arrowLeft + "px;} ", styleSheet.cssRules.length);
            } else {
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerBottom + DWLE_TEXT_CLASS.BeforePseudoSelector + "{ left:" + arrowLeft + "px; border-bottom-color: " + arrowColor + ";} ", styleSheet.cssRules.length);
                styleSheet.insertRule(CONSTANTS.DWDiv + CONSTANTS.ClassSeparator + DWLE_TEXT_CLASS.TextContainerBottom + DWLE_TEXT_CLASS.AfterPseudoSelector + "{ left:" + arrowLeft + "px;} ", styleSheet.cssRules.length);
            }
        }
    }
     
    
    
    this.m_hudContentDiv.style.visibility = "visible";
	// explicitly set focus to Text hud because there are lot of scenarios of conflict between editable selectors and Text hud.
    var textHudInParent = parent.document.getElementById(CONSTANTS.ExtensionID);
    if (textHudInParent) {
        textHudInParent.focus();
    }
 
};

DwTextHud.prototype.getHudContainer = function () {
    'use strict';
    return this.m_hudContainer;
};

DwTextHud.prototype.initialize = function () {
    'use strict';
    AuxiliaryExtensionObject.prototype.initialize.call(this);
    
    this.globalController = window.parent.globalController;
    dwExtensionController.addListener(parent.DW_EVENTS.SelectionChanged, this.SelectionChangedMessageHandler, this);
    
    this.locStrings = {};

    this.locStrings.browseTooltip       = this.globalController.getLocalizedString('Common_Browse_Tooltip');
    this.locStrings.fileDialogType      = this.globalController.getLocalizedString('Common_DialogType');
	this.locStrings.fileTitleLink       = this.globalController.getLocalizedString('Common_FileTitleLink');
    
    this.locStrings.formatLabel         = this.globalController.getLocalizedString('Text_Hud_Format_Label');
    this.locStrings.linkLabel           = this.globalController.getLocalizedString('Text_Hud_Link_Label');
    this.locStrings.targetLabel         = this.globalController.getLocalizedString('Text_Hud_Target_Label');
    
    this.locStrings.formatTooltip       = this.globalController.getLocalizedString('Text_Hud_Format_Tooltip');
    this.locStrings.linkTooltip         = this.globalController.getLocalizedString('Common_Link_ToolTip');
    this.locStrings.targetTooltip       = this.globalController.getLocalizedString('Text_Hud_Target_Tooltip');
    
    this.locStrings.pTag                = this.globalController.getLocalizedString('Text_Hud_PTag_Tooltip');
    this.locStrings.h1Tag               = this.globalController.getLocalizedString('Text_Hud_H1Tag_Tooltip');
    this.locStrings.h2Tag               = this.globalController.getLocalizedString('Text_Hud_H2Tag_Tooltip');
    this.locStrings.h3Tag               = this.globalController.getLocalizedString('Text_Hud_H3Tag_Tooltip');
    this.locStrings.h4Tag               = this.globalController.getLocalizedString('Text_Hud_H4Tag_Tooltip');
    this.locStrings.h5Tag               = this.globalController.getLocalizedString('Text_Hud_H5Tag_Tooltip');
    this.locStrings.h6Tag               = this.globalController.getLocalizedString('Text_Hud_H6Tag_Tooltip');
    this.locStrings.preTag              = this.globalController.getLocalizedString('Text_Hud_PreTag_Tooltip');
    
    this.locStrings.boldAddStrong       = this.globalController.getLocalizedString('Text_Hud_AddStrongTag_Tooltip');
    this.locStrings.boldRemoveStrong    = this.globalController.getLocalizedString('Text_Hud_RemoveStrongTag_Tooltip');
    this.locStrings.italicAddEm         = this.globalController.getLocalizedString('Text_Hud_AddEmTag_Tooltip');
    this.locStrings.italicRemoveEm      = this.globalController.getLocalizedString('Text_Hud_RemoveEmTag_Tooltip');
    this.locStrings.addIndent           = this.globalController.getLocalizedString('Text_Hud_AddBlockquoteTag_Tooltip');
    this.locStrings.removeIndent        = this.globalController.getLocalizedString('Text_Hud_RemoveBlockquoteTag_Tooltip');

    this.locStrings.AlignLabel = this.globalController.getLocalizedString('Text_Hud_Align_Label');
    this.locStrings.TransformLabel = this.globalController.getLocalizedString('Text_Hud_Transform_Label');
    
    this.locStrings.alignTooltip = this.globalController.getLocalizedString('Text_Hud_Align_Tooltip');
    this.locStrings.alignLeftTooltip = this.globalController.getLocalizedString('Text_Hud_Align_TextLeft_Tooltip');
    this.locStrings.alignCenterTooltip = this.globalController.getLocalizedString('Text_Hud_Align_TextCenter_Tooltip');
    this.locStrings.alignRightTooltip = this.globalController.getLocalizedString('Text_Hud_Align_TextRight_Tooltip');
    this.locStrings.alignJustifyTooltip = this.globalController.getLocalizedString('Text_Hud_Align_TextJustify_Tooltip');
    
    this.locStrings.transformTooltip = this.globalController.getLocalizedString('Text_Hud_Transform_Tooltip');
    this.locStrings.transformLowercaseTooltip = this.globalController.getLocalizedString('Text_Hud_Transform_TextLowercase_Tooltip');
    this.locStrings.transformUppercaseTooltip = this.globalController.getLocalizedString('Text_Hud_Transform_TextUppercase_Tooltip');
    this.locStrings.transformCapitalizeTooltip = this.globalController.getLocalizedString('Text_Hud_Transform_TextCapitalize_Tooltip');
    
    this.stopHide = false;
};

/*
function:SelectionChangedMessageHandler - Handler for selection changed messages from Live View. Currently needed for undo/redo for Bootstrap category
Arguments: none
Return : None
*/
DwTextHud.prototype.SelectionChangedMessageHandler = function () {
    'use strict';
    var curSelElement = liveViewObject.getCurrentSelectedElement();
    if (this.tagChangeInitiated === true && curSelElement) {
        //check whether new selection is the same tag that we changed to
        var changedTag = document.getElementById(DWLE_TEXT_IDS.format).value;
        var curTag = curSelElement.tagName.toLowerCase();
        if (changedTag === curTag) {
            this.updateChangedHud();
            this.handledTagChangeSelectionChangeMsg = true;
        } else {
            //if selection changed in the middle, Text HUD should go away
            this.tagChangeInitiated = false;
            this.handledTagChangeSelectionChangeMsg = false;
            this.hideAuxHud({commit: false});
        }
    } else {
        this.updateBootstrapClassHudFields(this.m_currentSelectedElement);
    }
};

/**
 * @private repositionHUD
 * respotion the hud
 * @Param none
 */
DwTextHud.prototype.repositionHUD = function () {
    'use strict';
    
    // do not reposition if the hud has been dragged
	if (!this.getCurrentElement() || this.m_hudDragged === true || this.getVisibility() === false) {
        return;
    }
    
    liveViewObject.positionExtensionById(CONSTANTS.ExtensionID, 0, 0, window.parent.innerWidth + window.parent.scrollX, window.parent.innerHeight + window.parent.scrollY);
    this.positionHud();
};

/**
 * @private hasPrintableTextAsFirstOrLastChild
 * Detect if an element has non whitespace string as its first or last child 
 * @Param elem-  The element for which this check needs to be done
 */
DwTextHud.prototype.hasPrintableTextAsFirstOrLastChild = function (elem) {
    'use strict';
    // Just check if there's at least one character of non whitespace at the first or last text child node
    // i.e., Just check if there are leading or trailing non-whitespace characters
    if ((elem.firstChild.nodeType === Node.TEXT_NODE) && (/\S/.test(elem.firstChild.nodeValue))) {
        return true;
    }
    if ((elem.lastChild.nodeType === Node.TEXT_NODE) && (/\S/.test(elem.lastChild.nodeValue))) {
        return true;
    }
    return false;
};

/**
 * @private stripInnerRedundantTags
 * Strip redundant tags for an element
 * Example- 
 *      <strong> This   <strong>is</strong> a   <strong>small</strong> paragraph!! </strong>
 * After stripping redundant <strong> tags-> 
 *      <strong> This is a small paragraph!! </strong>
 * This is needed in case when the "Bold"/"Italic" button is pressed on a text hud
 * @Param elem-  The element to be checked for stripping
 * @Param redundantTag-  The redundant tag which needs to be stripped
 */
DwTextHud.prototype.stripInnerRedundantTags = function (element, redundantTag) {
    'use strict';
    var curElem,
        parent,
        node,
        nextNode,
        i;

    for (i = 0; i < element.childElementCount; i++) {
        curElem = element.children[i];
        if (curElem.tagName.toLowerCase() === redundantTag) {
            parent = curElem.parentNode;
            for (node = curElem.firstChild; node; node = nextNode) {
                nextNode = node.nextSibling;
                parent.insertBefore(node, curElem);
            }
            parent.removeChild(curElem);
            i--;
        } else {
            this.stripInnerRedundantTags(curElem, redundantTag);
        }
    }

};

/**
 * @private assignTempId
 * Assign a temp Id to the element and store in callback maps
 * @Param elem- The element to which a temp_id needs to be assigned
 */
DwTextHud.prototype.assignTempId = function (elem) {
    'use strict';
    // attach a temp ID and store them so that once we get correct dwid's we replace the values.
    elem.setAttribute(DWConstants.DWTempId, this.callback_counter);
    this.callback_map[this.callback_counter] = elem;
    this.callback_counter++;
};

/**
 * @private pushElementUpdatesToSMLayer
 * Push changes to SM layer and get TempIds replaced with DW IDs
 * @Param text_callback_id
 * @Param parentId- DW Id of the element's Parent
 * @Param textId- DW Id of the element
 * @Param newText- Target html of the element
 */
DwTextHud.prototype.pushElementUpdatesToSMLayer = function (text_callback_id, parentId, textId, newText, ignoreParentMatching, setSelectionOnParent) {
    'use strict';
    var newCallbackObj = {};
    
    newCallbackObj.node_callback_id = text_callback_id;
    newCallbackObj.parentId = parentId;
    newCallbackObj.nodeId = textId;
    newCallbackObj.newText = newText;
    newCallbackObj.ignoreParentMatching = ignoreParentMatching;
    newCallbackObj.setSelectionOnParent = setSelectionOnParent;
    newCallbackObj.counter = this.callback_counter;
    newCallbackObj.callback = function (newObj) {
        this.updateDWIdsForElements(newObj);
    }.bind(this);

    if (this.fileSavePending >= 1) {
        this.fileSavePending++;
    }
    dwObject.DWSMGetDWIdsForUpdatedTags(newCallbackObj);
};

DwTextHud.prototype.updateChangedHud = function () {
    'use strict';
    //set current HUD essentials
    this.setCurrentHudTag(document.getElementById(DWLE_TEXT_IDS.format).value);
    this.setVisibility(true);

    this.m_currentSelectedElement = liveViewObject.getCurrentSelectedElement();
    this.setEditable(liveViewObject.isElementEditable(this.m_currentSelectedElement));

    //show the content
    this.populateContent();
    this.positionHud();
    document.getElementById(DWLE_TEXT_IDS.format).focus();
};

/**
 * @private updateTag
 * if we pass a string other than "" it will take that value otherwise it will read the value of the field from hud.
 * @Param srcValue: "" if it is from file dialog and value in the input box if is from HUD Input Boxes
 */
DwTextHud.prototype.updateTag = function (updateUI) {
    'use strict';
    var selectElem  = document.getElementById(DWLE_TEXT_IDS.format),
        newTagName  = selectElem.value,
        element     = this.getCurrentElement();
    // Return if current tag is same as newTag
    if (element.tagName.toLowerCase() === newTagName) {
        return;
    }
    
    var parentNode = element.parentNode;
    if (!parentNode) {
        return;
    }
    var textId          = element.getAttribute(DWConstants.DWUniqueId),
        semanticElement = window.parent.document.createElement(newTagName),
        attrs           = element.attributes,
        newCallbackObj  = {};
    var text_callback_id, target, i;
    
    if (attrs) {
        for (i = 0; i < attrs.length; i++) {
            semanticElement.setAttribute(attrs[i].nodeName, attrs[i].nodeValue);
        }
    }
    while (element.firstChild) {
        var curElem = element.firstChild;
        semanticElement.appendChild(curElem);
    }
    
    parentNode.replaceChild(semanticElement, element);
    text_callback_id = this.callback_counter;
    this.assignTempIdsRecursively(semanticElement);
    target = semanticElement;
    
    // get details of parent and push it to SM layer.
    var parentId = target.parentNode.getAttribute(DWConstants.DWUniqueId);
    var newText = target.outerHTML;
    this.pushElementUpdatesToSMLayer(text_callback_id, parentId, textId, newText);

    // After the source has changed we need to redraw the whole stuff again
    if (updateUI === true) {
        AuxiliaryExtensionObject.prototype.elementDimensionChanged.call(this);
    }
    this.tagChangeInitiated = true;
};


/**
 * @private assignTempIdsRecursively
 * Add a temp id to all the nodes in the subtree represented by elem.
 * @Param elem: A DOM node which represents a subtree whose node elements need to be assigned with temp ids
 */
DwTextHud.prototype.assignTempIdsRecursively = function (elem) {
	'use strict';
    if (elem && elem.nodeType === Node.ELEMENT_NODE) {
        this.assignTempId(elem);
        var curElem = elem.firstChild;
        while (curElem) {
            this.assignTempIdsRecursively(curElem);
            curElem = curElem.nextSibling;
        }
    }
};

/**
 * @private toggleBoldItalicTag
 * if we pass a string other than "" it will take that value otherwise it will read the value of the field from hud.
 * @Param srcValue: "" if it is from file dialog and value in the input box if is from HUD Input Boxes
 */
DwTextHud.prototype.toggleBoldItalicTag = function (tag, updateUI) {
    'use strict';
    // For toggling Bold (<strong>)/Italic (<em>) tags
    var elem, newElem, parent, child, actualTag, toEnable;
    
    if (tag === "bold") {
        actualTag = "strong";
        toEnable = !this.formatting_states.boldState;
    } else if (tag === "italic") {
        actualTag = "em";
        toEnable = !this.formatting_states.italicState;
    } else {
        return;
    }
    
    elem = this.getCurrentElement();
    var textId = elem.getAttribute(DWConstants.DWUniqueId),
        target,
        text_callback_id;

    if (elem.parentNode === null) {
        return;
    }
    
    if (toEnable) {
        newElem = document.createElement(actualTag);
        // See if the contents of text tag is entirely enclosed with some tag
        // Ignore leading and trailing whitespaces
        if (!this.hasPrintableTextAsFirstOrLastChild(elem) && elem.childElementCount === 1) {
            // If text contents are enclosed within <a> tag, add the <strong/em> tag like 
            // <textTag> <a href="#id1"> <strong/em> TEXT CONTENTS </strong/em> </a> </textTag>
            if (elem.children[0].tagName !== "A") {
                while (elem.firstChild) {
                    newElem.appendChild(elem.firstChild);
                }
                elem.appendChild(newElem);
            } else {
                var element = elem.children[0];
                while (element.firstChild) {
                    newElem.appendChild(element.firstChild);
                }
                element.appendChild(newElem);
            }
        } else {
            // Otherwise just wrap the child Nodes within the <strong/em> tag inside <text tag>
            while (elem.firstChild) {
                newElem.appendChild(elem.firstChild);
            }
            elem.appendChild(newElem);
        }
        // Check if there is already some <strong/em> tag(s) on parts of the text
        if (newElem.innerHTML.indexOf("<" + actualTag) !== -1) {
            // If present, strip those tags and put their content to its parent
            this.stripInnerRedundantTags(newElem, actualTag);
        }
        
        // attach a temp ID and store them so that once we get correct dwid's we replace the values.
        text_callback_id = this.callback_counter;
        this.assignTempIdsRecursively(elem);
        target = elem;
        
    } else {
        var firstChild;
        parent = elem;
        var onlyChildElement = (elem.childElementCount === 1) ? elem.children[0] : false;
        // Go to the wrapping <strong/em> tag and undo the wrapping
        while (onlyChildElement) {
            if (onlyChildElement.tagName.toLowerCase() === actualTag) {
                firstChild = onlyChildElement.firstChild;
                while (firstChild) {
                    parent.appendChild(onlyChildElement.firstChild);
                    firstChild = onlyChildElement.firstChild;
                }
                parent.removeChild(onlyChildElement);
                break;
            }
            parent = onlyChildElement;
            onlyChildElement = (parent.childElementCount === 1) ? parent.children[0] : false;
        }
        text_callback_id = this.callback_counter;
        this.assignTempIdsRecursively(elem);
        target = elem;
    }
    
    // get details of parent and push it to SM layer.
    var parentId = target.parentNode.getAttribute(DWConstants.DWUniqueId);
    var newText = target.outerHTML;
    this.pushElementUpdatesToSMLayer(text_callback_id, parentId, textId, newText);
    
    //Toggle the bold/italic state
    if (tag === "bold") {
        this.formatting_states.boldState = !this.formatting_states.boldState;
    } else {
        this.formatting_states.italicState = !this.formatting_states.italicState;
    }
};

/**
 * @private addIndent
 * Add an enclosing <blockquote> tag around the text tag.
 * @Param srcValue: "" if it is from file dialog and value in the input box if is from HUD Input Boxes
 */
DwTextHud.prototype.addIndent = function (updateUI) {
    'use strict';
    var elem = this.getCurrentElement(),
        parent = elem.parentNode,
        target = null,
        text_callback_id,
        bqNode;
    
    // add <blockquote> tag in between TEXT and its parent
    bqNode = document.createElement("blockquote");
    
    // Add <blockquote> as the parent for the text tag
    elem.parentNode.replaceChild(bqNode, elem);
    bqNode.appendChild(elem);
    this.assignTempId(bqNode);

    text_callback_id = this.callback_counter;
    this.assignTempIdsRecursively(elem);
    target = bqNode;
    
    // get details of parent and push it to SM layer.
    var parentId = target.parentNode.getAttribute(DWConstants.DWUniqueId),
        textId = elem.getAttribute(DWConstants.DWUniqueId),
        newText = target.outerHTML;
    this.pushElementUpdatesToSMLayer(text_callback_id, parentId, textId, newText);
    
    this.formatting_states.indents++;
    if (this.formatting_states.indents === 1) {
        //Enable the Indent Left Button as it was disabled earlier
        var indentLeftBtn = document.getElementById(DWLE_TEXT_IDS.indentLeftBtn);
        indentLeftBtn.removeAttribute("disabled");
        indentLeftBtn.classList.remove("class", DWLE_TEXT_CLASS.Disabled);
    }
    dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.ELV_TH, DWLE_TEXTHUD_HEADLIGHTS.INDENT);
};

/**
 * @private removeIndent
 * Remove an enclosing <blockquote> tag around the text tag.
 * @Param srcValue: "" if it is from file dialog and value in the input box if is from HUD Input Boxes
 */
DwTextHud.prototype.removeIndent = function (updateUI) {
    'use strict';
    var elem = this.getCurrentElement(),
        parent = elem.parentNode,
        target = null,
        text_callback_id,
        textId,
        bqNode;

    // there is no blockquote tag associated with this TEXT --> just return
    if (parent.tagName !== "BLOCKQUOTE") {
        return;
    }

    bqNode = elem.parentNode;
    textId = bqNode.getAttribute(DWConstants.DWUniqueId);
    // there is a blockquote tag associated we need to delete it.
    var parentNode = bqNode.parentNode;
    var childNodes = bqNode.childNodes,
        length = childNodes.length,
        i = length - 1, // length will atleast be 1 as we know there is a child text tag (p/span/h1-6)
        prevElem = null;
        
    for (i; i >= 0; i--) {
        var curElem = childNodes[i];
        if (i === length - 1) {
            parentNode.replaceChild(curElem, bqNode);
        } else {
            parentNode.insertBefore(curElem, prevElem);
        }
        if (curElem.nodeType === Node.ELEMENT_NODE) {
            // this is an element
            text_callback_id = this.callback_counter;
            this.assignTempIdsRecursively(curElem);
        }
        prevElem = curElem;
    }
    target = elem;
    
    // get details of parent and push it to SM layer.
    var parentId = parentNode.getAttribute(DWConstants.DWUniqueId),
        newText = target.outerHTML;
    
    this.pushElementUpdatesToSMLayer(text_callback_id, parentId, textId, newText);
    
    this.formatting_states.indents--;
    if (this.formatting_states.indents === 0) {
        //Disable the Indent Left Button as there are no more Blockquotes.
        var indentLeftBtn = document.getElementById(DWLE_TEXT_IDS.indentLeftBtn);
        indentLeftBtn.classList.add("class", DWLE_TEXT_CLASS.Disabled);
        indentLeftBtn.setAttribute("disabled", "disabled");
    }
    dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.ELV_TH, DWLE_TEXTHUD_HEADLIGHTS.INDENT);
};

/**
 * @private updateLink
 * Wraps the text tag with an anchor tag.
 * @Param linkValue: "" if it is from file dialog and value in the input box if is from HUD Input Boxes
 */
DwTextHud.prototype.updateLink = function () {
    'use strict';
    
    var linkElement = document.getElementById(DWLE_TEXT_IDS.link),
        linkValue = linkElement.value;
    linkElement.title = linkValue;
    
    var elem = this.getCurrentElement(),
        parent = elem.parentNode,
        onlyChildElement = (elem.childElementCount === 1) ? elem.children[0] : false,
        target = null,
        linkNode,
        i;
        
    if (parent === null) {
        return;
    }
    var textId = elem.getAttribute(DWConstants.DWUniqueId),
        text_callback_id;
    
    // if LinkValue is empty --> it is deleted or not there at all
    // else linkValue is non empty --> it is associated newly or existing link is modified.
    if (linkValue !== "") {
        // if link exists previously and we updated it -->get parent DWId and push the changes
        // Check if there is already a wrapping anchor tag inside the text tag
        // Ignore leading and trailing whitespace characters
        if (!this.hasPrintableTextAsFirstOrLastChild(elem) && onlyChildElement && onlyChildElement.tagName === "A") {
            var existingHref = onlyChildElement.getAttribute("href");
            //if the value is not changed dont do anything
            if (existingHref && existingHref === linkValue) {
                this.updateTarget();
                return;
            }
            onlyChildElement.setAttribute("href", linkValue);
            dwObject.updateDWDocumentElementAttr(onlyChildElement, 'href', linkValue, null, textId);
            this.updateTarget();
            return;
        }
        // we are associating links newly.
        // add <a> tag in between TEXT and its content
        linkNode = document.createElement("a");
        linkNode.setAttribute("href", linkValue);
        while (elem.firstChild) {
            linkNode.appendChild(elem.firstChild);
        }
        elem.appendChild(linkNode);
        // Check if there is already some anchor <a> tag(s) on parts of the text
        if (linkNode.innerHTML.indexOf("<a href=") !== -1) {
            // If present, strip those tags and put their content to its parent
            this.stripInnerRedundantTags(linkNode, "a");
        }
        
        text_callback_id = this.callback_counter;
        this.assignTempIdsRecursively(elem);

        target = elem;
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.ELV_TH, DWLE_TEXTHUD_HEADLIGHTS.LINK);
        
    } else {
        linkNode = onlyChildElement;
        // there is no anchor tag associated with the entire TEXT --> just return
        if (this.hasPrintableTextAsFirstOrLastChild(elem) || !linkNode || linkNode.tagName !== "A") {
            return;
        }

        text_callback_id = this.callback_counter;
        // there is an anchor tag associated we need to delete it.
        var parentNode = linkNode.parentNode;
        var childNodesOfA = linkNode.childNodes,
            length = childNodesOfA.length,
            prevElem = null;
        i = length - 1; // length will atleast be 1 as we know there is a child text tag (p/span/h1-6)
            
        for (i; i >= 0; i--) {
            var curElem = childNodesOfA[i];
            if (i === length - 1) {
                parentNode.replaceChild(curElem, linkNode);
            } else {
                parentNode.insertBefore(curElem, prevElem);
            }
            prevElem = curElem;
        }
        
        this.assignTempIdsRecursively(elem);
        target = elem;
    }
    
    // get details of parent and push it to SM layer.
    var parentId = target.parentNode.getAttribute(DWConstants.DWUniqueId);
    var newText = target.outerHTML;

    this.pushElementUpdatesToSMLayer(text_callback_id, parentId, textId, newText);
    this.updateTargetDropdown();
};
    
/**
 * @private updateRadioButtonClass
 * update class attribute with the clicked radio image button value
 * @Param event 
 */
DwTextHud.prototype.updateRadioButtonClass = function (event) {
    "use strict";
    var classNamesToSet = [],
        classNamesToRemove = [];
    
    if (!event || !event.target || !event.target.name) {
        return;
    }
    
    var clickedElem = document.getElementById(event.target.id);
    var containerDiv = clickedElem.parentElement;
    if (!clickedElem) {
        return;
    }
   
    clickedElem.classList.toggle(DWLE_TEXT_CLASS.ImgSelected);
    containerDiv.classList.toggle(clickedElem.selectedclass);
    if (clickedElem.classList.contains(DWLE_TEXT_CLASS.ImgSelected)) {
        classNamesToSet.push(clickedElem.value);
        this.LogHeadlights(clickedElem.id);
    }
    var radioGrpElements = document.getElementsByName(event.target.name);
    if (!radioGrpElements || radioGrpElements.length === 0) {
        return;
    }
    var i;
    for (i = 0; i < radioGrpElements.length; ++i) {
        if (classNamesToSet.length === 0) {
            classNamesToRemove.push(radioGrpElements[i].value);
            radioGrpElements[i].classList.remove(DWLE_TEXT_CLASS.ImgSelected);
            containerDiv.classList.remove(radioGrpElements[i].selectedclass);
        } else {
            if (radioGrpElements[i].id !== clickedElem.id) {
                classNamesToRemove.push(radioGrpElements[i].value);
                radioGrpElements[i].classList.remove(DWLE_TEXT_CLASS.ImgSelected);
                containerDiv.classList.remove(radioGrpElements[i].selectedclass);
            }
        }
    }
    
    
    this.updateClass(classNamesToSet, classNamesToRemove);
    AuxiliaryExtensionObject.prototype.elementDimensionChanged.call(this);
};

DwTextHud.prototype.LogHeadlights = function (itemId) {
    "use strict";
    if (DWLE_TEXT_IDS.alignleft === itemId) {
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.RWD_BOOTSTRAP, DWLE_TEXTHUD_HEADLIGHTS.ALIGN_LEFT, DWLE_TEXTHUD_HEADLIGHTS.RWD);
    } else if (DWLE_TEXT_IDS.alignCenter === itemId) {
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.RWD_BOOTSTRAP, DWLE_TEXTHUD_HEADLIGHTS.ALIGN_CENTER, DWLE_TEXTHUD_HEADLIGHTS.RWD);
    } else if (DWLE_TEXT_IDS.alignRight === itemId) {
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.RWD_BOOTSTRAP, DWLE_TEXTHUD_HEADLIGHTS.ALIGN_RIGHT, DWLE_TEXTHUD_HEADLIGHTS.RWD);
    } else if (DWLE_TEXT_IDS.alignJustify === itemId) {
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.RWD_BOOTSTRAP, DWLE_TEXTHUD_HEADLIGHTS.ALIGN_JUSTIFY, DWLE_TEXTHUD_HEADLIGHTS.RWD);
    } else if (DWLE_TEXT_IDS.transformLowercase === itemId) {
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.RWD_BOOTSTRAP, DWLE_TEXTHUD_HEADLIGHTS.TRANSFORM_LOWERCASE, DWLE_TEXTHUD_HEADLIGHTS.RWD);
    } else if (DWLE_TEXT_IDS.transformUppercase === itemId) {
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.RWD_BOOTSTRAP, DWLE_TEXTHUD_HEADLIGHTS.TRANSFORM_UPPERCASE, DWLE_TEXTHUD_HEADLIGHTS.RWD);
    } else if (DWLE_TEXT_IDS.transformCapitalize === itemId) {
        dwObject.logHeadlightsData(DWLE_TEXTHUD_HEADLIGHTS.RWD_BOOTSTRAP, DWLE_TEXTHUD_HEADLIGHTS.TRANSFORM_CAPITALISE, DWLE_TEXTHUD_HEADLIGHTS.RWD);
    }
};

/**
 * @private updateClass
 * update class attribute with adding classes in classNamesToSet and removing classes in classNamesToRemove
 * @Param classNamesToSet class names to be added
 * @Param classNamesToRemove class names to be removed
 */
DwTextHud.prototype.updateClass = function (classNamesToSet, classNamesToRemove) {
    'use strict';
    var elem = this.getCurrentElement();
    
    var classListHasChanged  = false, hasClass = false;
    var i;
    for (i = 0; i < classNamesToSet.length; ++i) {
        hasClass = elem.classList.contains(classNamesToSet[i]);
        if (!hasClass) {
            elem.classList.add(classNamesToSet[i]);
            classListHasChanged = true;
        }
    }
    for (i = 0; i < classNamesToRemove.length; ++i) {
        hasClass = elem.classList.contains(classNamesToRemove[i]);
        if (hasClass) {
            elem.classList.remove(classNamesToRemove[i]);
            classListHasChanged = true;
        }
    }
    var updatedClassList = elem.getAttribute("class");
    
    if (classListHasChanged) {
        var self = this;
        var funcCallback = function () {
            if (self.fileSavePending > 1) {
                self.fileSavePending--;
                if (self.fileSavePending === 1) {
                    window.parent.DWSaveDocument();
                    self.fileSavePending--;
                }
            }
        };
        
        if (this.fileSavePending >= 1) {
            this.fileSavePending++;
        }
        dwObject.updateDWDocumentElementAttr(this.m_currentSelectedElement, "class", updatedClassList, funcCallback);
        
		this.attributeChanged  = true;
    }
};

/**
 * @private updateTarget
 * update target attribute of the text
 * @Param none
 */
DwTextHud.prototype.updateTarget = function () {
    'use strict';

    var elem = this.getCurrentElement();
    if (!elem) {
        return;
    }
    var onlyChildElement = (elem.childElementCount === 1) ? elem.children[0] : false;
    if (onlyChildElement && onlyChildElement.tagName === "A") {
        var value = document.getElementById(DWLE_TEXT_IDS.target).value,
            oldValue = onlyChildElement.getAttribute("target");
        //Return if NO CHANGE in target
        if ((value === oldValue) || (value === "none" && oldValue === null)) {
            return;
        }
        if (value !== "none") {
            onlyChildElement.setAttribute("target", value);
        } else {
            value = "";
            onlyChildElement.removeAttribute("target");
        }

        var self = this;
        var funcCallback = function () {
            if (self.fileSavePending > 1) {
                self.fileSavePending--;
                if (self.fileSavePending === 1) {
                    window.parent.DWSaveDocument();
                    self.fileSavePending--;
                }
            }
        };
        if (this.fileSavePending >= 1) {
            this.fileSavePending++;
        }
        var textTagId = elem.getAttribute(DWConstants.DWUniqueId);
        if (textTagId) {
            dwObject.updateDWDocumentElementAttr(onlyChildElement, 'target', value, funcCallback, textTagId);
        }
    } else {
        return;
    }
};

 /* @private updateTargetDropdown
 * update the target dropdown depending on the value in link. 
 * target does not make sense when we dont have a link associated.
 */
DwTextHud.prototype.updateTargetDropdown = function () {
    'use strict';
    var value = document.getElementById(DWLE_TEXT_IDS.link).value;
    var targetElement = document.getElementById(DWLE_TEXT_IDS.target);
    if (value === "" || value === "undefined") {
        document.getElementById(DWLE_TEXT_IDS.targetDiv).style.display = 'none';
        targetElement.setAttribute("disabled", "disabled");
        targetElement.classList.add.apply(targetElement.classList, [DWLE_TEXT_CLASS.TextDropDown, DWLE_TEXT_CLASS.TextDropDownNone]);
        targetElement.value = "none";
        var item = targetElement.items.getAll()[0];
        item.setAttribute('selected', 'true');
    } else {
        document.getElementById(DWLE_TEXT_IDS.targetDiv).style.display = 'block';
        targetElement.removeAttribute("disabled");
        targetElement.classList.remove(DWLE_TEXT_CLASS.TextDropDownNone);
    }
};

DwTextHud.prototype.shouldHide = function () {
    'use strict';
    if ((this.fileSavePending > 1)  || this.stopHide) {
        return false;
    }
    if (this.tagChangeInitiated === true) {
        if (this.handledTagChangeSelectionChangeMsg === true) {
            this.handledTagChangeSelectionChangeMsg = false;
            this.tagChangeInitiated = false;
        }
        return false;
    }
    return true;
};

/**
 * @private updateDWIdsForElements
 * will be called from sm layer with newObj containing tempId's and their corresponding dwid's
 * @Param newObj: contains a map of tempId,dwid
 */
DwTextHud.prototype.updateDWIdsForElements = function (newObj) {
    'use strict';

    var i, elem;
    for (i = 0; i < this.callback_counter; i++) {
        elem = this.callback_map[i];
        elem.setAttribute(DWConstants.DWUniqueId, newObj[i]);
        elem.removeAttribute(DWConstants.DWTempId);
    }
    this.callback_counter = 0;
    
    if (this.fileSavePending > 1) {
        this.fileSavePending--;
        if (this.fileSavePending === 1) {
            window.parent.DWSaveDocument();
            this.fileSavePending--;
        }
    }
};

var initDwTextHud = function () {
    'use strict';
    window.DwTextHudObj = new DwTextHud();
    window.DwTextHudObj.initialize();
};
