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

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

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50, continue: true */
/*global $, liveViewObject, dwObject, liveExtensionsConfigPath, dwExtensionController, DW_EXTENSION_EVENT, CssGrids:true, gFluidGridUrl:true, gFluidGridData, getFGUtil, layoutDivManipulator */

if (typeof CssGrids === 'undefined') {
    CssGrids = {}; // Create our namespace
}
var fgUtil = getFGUtil();

CssGrids.LayoutDivManipulator = function (dw, dom, dwscripts, styleSheetManager) {
    "use strict";
    var self = this;

    self.refs = {
        window: window,
        document: document,
        resizeEdgeDiv: null,
        shiftEdgeDiv: null,
        marginDiv: null,
        startsRowDiv: null,
        draggingDiv: null,
        deleteDiv: null,
        duplicateDiv: null,
        moveUpDiv: null,
        moveDownDiv: null,
        hideDiv: null,
        showDiv: null,
        overlayDivs: [],
        cssRuleBeingModified: null,
        columnDivs: [],
        internalStyleSheetDom: null,
        knobMiddle: {}, // props: resize, shift
        dynamicTooltipDiv: null,
        fluidElementRegExp: /(div|ul|ol|li|header|hgroup|nav|aside|article|section|footer|h1|h2|h3|h4|h5|h6|p|figure)/gi,
        gridColRects: []
    };

    self.consts = {
        selectedBorderWidth: 0,
        draggingBorderWidth: 1,
        tagName: '*',   // Used in 'getElementsByTagName()' to find layout divs.
        gridContainer: 'gridContainer',     // The class name that signifies the master container for the layouts
        gridContainerBgColor: 'rgba(200,200,200,0.35)',
        pixelFixedPoint: 1,     // The deviation that we can have in widths.
        hudButtonWidth: 26,
        hudButtonHeight: 22,
        moveUpDownButtonWidth: 20,
        startsNewRowButtonGapSize: 3,   // Distance between right edge of knob and this button.
        knobSize: 12,
        overlayDivBgColor: 'transparent',
        overlayDivBgColorHovered: 'rgba(172, 230, 238, 0.4)', // #ACE6EE, not used anywhere
        hiddenDivBgImage: "url('assets/images/Checks.png')",
        hudBgColor: '#2D8CEB',
        dynamicTooltipOffsetX: 15,
        dynamicTooltipOffsetY: 15,
        extensionID: "FluidGrid",
        CSS_STYLE_RULE: 1,
        CSS_MEDIA_RULE: 4,
        MOBILE: 'mobile',
        TABLET: 'tablet',
        DESKTOP: 'desktop',
        ExtensionPath: '/FluidGrid/',
        AssetsPath: '/assets/',
        VerticalPaddingToAvoidESHOverlap: 150,
        HorizontalPaddingToAvoidESHOverlap: 300,
        FGCornerCaseHorizontalPadding: 9,
        DWUniqueId: 'data_liveedit_tagid'
    };
    
    self.headlights = {
		UT_FLUID_GRID:                      'Fluid Grid',
        UT_FLUID_GRID_TOGGLE_START_ROW:     'FluidGrid ToggleStartRow',
        UT_FLUID_GRID_RESIZE_ELEMENT:       'FluidGrid ResizeElement',
        UT_FLUID_GRID_SHIFT_ELEMENT:        'FluidGrid ShiftElement',
        UT_FLUID_GRID_DUPLICATE_ELEMENT:    'FluidGrid DuplicateElement',
        UT_FLUID_GRID_DELETE_ELEMENT:       'FluidGrid DeleteElement',
        UT_FLUID_GRID_MOVE_UP_ELEMENT:      'FluidGrid MoveUpElement',
        UT_FLUID_GRID_MOVE_DOWN_ELEMENT:    'FluidGrid MoveDownElement',
        UT_FLUID_GRID_HIDE_ELEMENT:         'FluidGrid HideElement',
        UT_FLUID_GRID_SHOW_ELEMENT:         'FluidGrid ShowElement'
	};

    self.data = {
        mouseStartX: -1,
        mouseCurX: -1,
        mouseStartY: -1,
        scrollStartX: -1,
        gridContainerWidthStart: -1,
        selectedUnderlyingDivCurrentColSpan: -1,
        selectedUnderlyingDivStartingColSpan: -1,
        marginsOffsetLeft: -1,
        startingSelectedOverlayDivOffsetWidth: -1,
        startingSelectedUnderlyingDivOffsetLeft: -1,
        startingMarginDivWidth: -1,
        selectedUnderlyingDivIndex: -1,
        mouseOverDivIndex: -1,
        lastColRight: -1,
        gridColRects: [],
        editsArray: [],
        selectorWidthEdited: [],
        subsheet: [],
        maxColumns: 12
    };

    self.keyCodes = {
        ArrowLeftKey: 37,
        ArrowRightKey: 39,
        BackspaceKey: 8,
        altKey: 18
    };

    self.flags = {
        debug: false,
        showProtos: false,
        isInited: false,
        isDragging: false,
        isResizing: false,
        isShifting: false,
        isRefreshing: false,
        wasDomShowingDivBoxModel: false,
        logEvents: false,
        showDebugDumpButton: false,
        inUnhideMode: false
    };


    self.consts.overlayBorder = self.consts.draggingBorderWidth + 'px dotted #0083E8';	// light blue, not used
    self.consts.selectedBorder = self.consts.selectedBorderWidth + 'px solid ' + self.consts.hudBgColor;    // grey
    self.consts.hoverBorder = self.consts.selectedBorderWidth + 'px solid #ACE6EE';		// blue, not used
    self.consts.misalignedDivBorder = self.consts.selectedBorderWidth + 'px solid ' + self.consts.hudBgColor;   // same as selected border
    self.consts.draggingBorder = self.consts.draggingBorderWidth + 'px dashed #2D8CEB'; // light blue

    self.getGridContainerStyle = function () {
        var gridContainerDiv = window.parent.document.getElementsByClassName('gridContainer');
        if (gridContainerDiv.length > 0) {
            gridContainerDiv = gridContainerDiv[0];
        } else {
            gridContainerDiv = document.body;
        }
        var gridContainerStyle = window.parent.getComputedStyle(gridContainerDiv);
        if (!gridContainerStyle) {
            return null;
        }
        var finalStyle = {
            width: parseInt(gridContainerStyle.width, 10),
            paddingLeft: parseInt(gridContainerStyle.paddingLeft, 10),
            borderLeftWidth: parseInt(gridContainerStyle.borderLeftWidth, 10),
            marginLeft: parseInt(gridContainerStyle.marginLeft, 10)
        };
        if (!finalStyle) {
            return null;
        }
        if (isNaN(finalStyle.width) || (finalStyle.width <= 0)) {
            return null;
        }
        if (isNaN(finalStyle.paddingLeft)) {
            finalStyle.paddingLeft = 0;
        }
        if (isNaN(finalStyle.borderLeftWidth)) {
            finalStyle.borderLeftWidth = 0;
        }
        if (isNaN(finalStyle.marginLeft)) {
            finalStyle.marginLeft = 0;
        }
        return finalStyle;
    };

    self.getGridPropsRec = function (device) {
        return {
            allColsWidth: gFluidGridData.allColsWidth[device],
            numCols: gFluidGridData.maxNumCols[device],
            colWidth: gFluidGridData.colWidth,
            gutterWidth: gFluidGridData.gutterWidth
        };
    };

    self.getDeviceFromWindowWidth = function () {
        var screenWidth = parseInt(window.parent.innerWidth, 10);
        if (screenWidth < 481) {
            return self.consts.MOBILE;
        }
        if (screenWidth < 769) {
            return self.consts.TABLET;
        }
        return self.consts.DESKTOP;
    };
	
	self.getGridContainerWidth = function () {
		var gridContainerStyle = self.getGridContainerStyle();
		return (gridContainerStyle ? gridContainerStyle.width : -1);
	};
    
    self.getGridColRects = function () {
        var gridContainerStyle = self.getGridContainerStyle();
        if (!gridContainerStyle) {
            return [];
        }
        var gridPropsRec = self.getGridPropsRec(self.getDeviceFromWindowWidth());
        var rects = [], i;
        var curX = 0;
        for (i = 0; i < gridPropsRec.numCols; i++) {
            var rect = {};
            rect.left = curX;
            curX += gridPropsRec.colWidth;
            rect.right = curX;
            curX += gridPropsRec.gutterWidth;
            rects.push(rect);
        }
        var stretchFactor = gridContainerStyle.width / gridPropsRec.allColsWidth;
        var activeViewScale = 1;
        var bodyLeft = gridContainerStyle.paddingLeft + gridContainerStyle.borderLeftWidth + gridContainerStyle.marginLeft;
        rects = rects.map(function (rect) {
            return {
                left: (Math.floor(rect.left * stretchFactor) / activeViewScale) + Math.floor(bodyLeft / activeViewScale),
                right: (Math.floor(rect.right * stretchFactor) / activeViewScale) + Math.floor(bodyLeft / activeViewScale)
            };
        });
        self.data.gridColRects = rects;
        return rects;
    };

    self.getOrCreateColumns = function () {
        var maxNumCols = self.data.maxColumns, i;
        if (self.refs.columnDivs.length === 0) {
            for (i = 0; i < maxNumCols; i++) {
                var div = document.createElement('div');
                div.setAttribute('class', 'gridColumn');
                document.body.appendChild(div);
                self.refs.columnDivs.push(div);
            }
        }
        return self.refs.columnDivs;
    };

    self.updateColumns = function () {
        var columnDivs = self.getOrCreateColumns();
        var gridColRects = self.getGridColRects();
        var numVisibleCols = gridColRects.length;
        var i;
        for (i = 0; i < columnDivs.length; i++) {
            var div = columnDivs[i];
            if (i < numVisibleCols) {
                var rect = gridColRects[i];
                var left = 'left:' + rect.left + 'px;';
                var width = 'width:' + (rect.right - rect.left) + 'px;';
                var display = 'display:block;';
                div.style.cssText = left + width + display;
            } else {
                div.style.display = 'none';
            }
        }
    };

    self.destroyOverlayDivs = function () {
        self.refs.overlayDivs.forEach(function (div) {
            document.body.removeChild(div);
        });
        self.refs.overlayDivs = [];
    };

    self.nodeListToArray = function (nodeList) {
        var list = [], i;
        for (i = 0; i < nodeList.length; i++) {
            list.push(nodeList[i]);
        }
        return list;
    };

    self.startsNewRow = function (underlyingDiv) {
        return self.getCssPropValFromElem(underlyingDiv, 'clear') === 'both';
    };

    self.getPositionAttributeOfElement = function (device, selector) {
        var cssRules = self.data.subsheet[0]; // self.consts.TABLET
        if (device === self.consts.TABLET) {
            cssRules = self.data.subsheet[1];
        } else if (device === self.consts.DESKTOP) {
            cssRules = self.data.subsheet[2];
        }

        var i;
        for (i = 0; i < cssRules.length; i++) {
            if (cssRules[i].type === self.consts.CSS_STYLE_RULE) {
                if (cssRules[i].selectorText === selector) {
                    return cssRules[i].style.position;
                }
            }
        }

        return '';
    };

    self.getCssPropValFromElem = function (elemNode, prop, expectedReturnType) {
        var returnType = 'string';
        if (expectedReturnType === 'int') {
            returnType = 'int';
        }
        var strVal = '';
        var computedStyle = window.parent.getComputedStyle(elemNode);
        if (computedStyle) {
            strVal = computedStyle.getPropertyValue(prop);
        }
        if (returnType === 'string') {
            return strVal;
        }
        var intVal = parseInt(strVal, 10);
        if (isNaN(intVal)) {
            intVal = 0;
        }
        intVal = Math.floor(intVal / fgUtil.getActiveViewScale());
        return intVal;
    };
    
    self.isLeftCloseEnough = function (colNum, left1, left2) {
        var marginOfError = colNum * 2; //Worst case error grows left to right at 1px per gutter + 1 px per col
        return Math.abs(left1 - left2) <= marginOfError;
    };

    self.widthMatch = function (lWidth, rWidth) {
        if (lWidth === rWidth) {
            return true;
        }

        var lInt = parseInt(lWidth, 10);
        var rInt = parseInt(rWidth, 10);
        var diff = 0;

        if (lInt > rInt) {
            diff = lInt - rInt;
        } else {
            diff = rInt - lInt;
        }

        if (diff <= 1) {//self.consts.pixelFixedPoint)
            return true;
        }

        return false;
    };

    self.getWidthMatchingFxn = function (widthToFind) {
        return function (widthToTest) {
            return self.widthMatch(widthToTest, widthToFind);
        };
    };

    self.getIsInUnhideMode = function () {
        return self.flags.inUnhideMode;
    };
    
    self.setIsInUnhideMode = function (bInUnhideMode) {
        self.flags.inUnhideMode = bInUnhideMode;
    };

    self.getSelectedUnderlyingElems = function (selElem, includeLockedElement, includeHiddenElement, onlySupportedFluidElements) {
        if (typeof includeLockedElement === 'undefined') {
            includeLockedElement = true;
        }
        if (typeof includeHiddenElement === 'undefined') {
            includeHiddenElement = false;
        }
        if (typeof onlySupportedFluidElements === 'undefined') {
            onlySupportedFluidElements = false;
        }

        var divs = self.nodeListToArray(selElem.getElementsByTagName(self.consts.tagName));
        var outDivs = [];
        var validWidths = fgUtil.getValidLayoutDivWidths(self.data.gridColRects);
        divs.forEach(function (div) {
            if (!div) {
                return;
            }

            if (onlySupportedFluidElements) {
                if (!div.tagName.match(self.refs.fluidElementRegExp)) {
                    return;
                }
            }

            if (!fgUtil.validFluidElement(div, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)) {
                return;
            }

            //we should not include elements inside a hidden element
            if (self.isDescendantOfHiddenElement(div)) {
                return;
            }

            if (self.getCssPropValFromElem(div, 'display') === 'none') {
                if (includeHiddenElement) {
                    outDivs.push(div);
                }
                //if in unhide mode, show the hidden divs too
                //and mark them with an attribute
                if (self.getIsInUnhideMode()) {
                    //filter elements with inline display property
                    if (div.style.display && div.style.display !== '') {
                        return;
                    }
                    //filter elements hidden due to non-FG selectors
                    var validFGSelectorApplied = false;
                    var selector = fgUtil.getValidFluidGridSelector(div, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
                    var curDevice = self.getDeviceFromWindowWidth();
                    if (selector && selector.charAt(0) === '.') {
                        //if it is class based, then there should be specific 'hide_' class applied
                        var liveCodeHiddenId = div.getAttribute(self.consts.DWUniqueId);
                        if (div) {
                            var classesApplied = div.getAttribute('class');
                            if (classesApplied) {
                                var hideClass = 'hide_' + curDevice;
                                var index = classesApplied.indexOf(hideClass);
                                //if we find this class, then this was hidden by us
                                if (index !== -1) {
                                    validFGSelectorApplied = true;
                                }
                            }
                        }
                    } else {
                        //if it is ID based, make sure that the display property is none in the selector.
                        var displayVal = window.parent.window.getComputedStyle(div).display;
                        if (displayVal === 'none') {
                            validFGSelectorApplied = true;
                        }
                    }

                    if (validFGSelectorApplied === false) {
                        return;
                    }
                    //display the hidden element
                    div.style.display = 'block';
                    //check if its a broken FG element
                    var divwidth = self.getCssPropValFromElem(div, 'width', 'int');
                    if (validWidths.some(self.getWidthMatchingFxn(divwidth))) {
                        div.setAttribute('data-isHidden', 'true');
                        if (!includeHiddenElement) {
                            outDivs.push(div);
                        }
                    } else {
                        div.style.display = '';
                    }
                }
                return;
            }

            var width = self.getCssPropValFromElem(div, 'width', 'int');

            if ((validWidths.some(self.getWidthMatchingFxn(width)))) {
                outDivs.push(div);
            }
        });
        return outDivs;
    };
    
    self.getPreviousSibling = function (fgElement, onlyVisible, includeLockedElement) {
        if (typeof onlyVisible === "undefined") {
            onlyVisible = false;
        }
        if (typeof includeLockedElement === 'undefined') {
            includeLockedElement = true;
        }
        while (fgElement !== null) {
            fgElement = fgElement.previousSibling;
            if (fgElement !== null && (fgElement.nodeType === 1)) {
                if (!onlyVisible) {
                    return fgElement;
                } else if (onlyVisible && self.getCssPropValFromElem(fgElement, 'display') !== 'none') {
                    return fgElement;
                }
            }
        }
        return null;
    };

    self.getNextSibling = function (fgElement, onlyVisible, includeLockedElement) {
        if (typeof onlyVisible === "undefined") {
            onlyVisible = false;
        }
        if (typeof includeLockedElement === 'undefined') {
            includeLockedElement = true;
        }
        while (fgElement !== null) {
            fgElement = fgElement.nextSibling;
            if (fgElement !== null && (fgElement.nodeType === 1)) {
                if (!onlyVisible) {
                    return fgElement;
                } else if (onlyVisible && self.getCssPropValFromElem(fgElement, 'display') !== 'none') {
                    return fgElement;
                }
            }
        }
        return null;
    };

    self.getPreviousVisibleSibling = function (fgElement, includeLockedElement) {
        return self.getPreviousSibling(fgElement, true, includeLockedElement);
    };

    self.getNextVisibleSibling = function (fgElement, includeLockedElement) {
        return self.getNextSibling(fgElement, true, includeLockedElement);
    };

    self.getUnderlyingDivs = function () {

        return self.getSelectedUnderlyingElems(window.parent.document.body, true, false, true);
    };

    self.getUnderlyingDivIndex = function (element) {
        var i, overlayDiv;
        for (i = 0; i < self.refs.overlayDivs.length; i++) {
            overlayDiv = self.refs.overlayDivs[i];
            if (overlayDiv.underlyingDiv === element) {
                return i;
            }
        }
        return -1;
    };

    self.getUnderlyingDivByIndex = function (index) {
        if (index >= 0 && index < self.refs.overlayDivs.length) {
            return self.refs.overlayDivs[index].underlyingDiv;
        }
        return null;
    };

    self.onMouseOver_OverlayDiv = function (evt) {
        if (self.flags.isDragging) {
            return;
        }
        var oldMouseOverDiv = self.getMouseOverOverlayDiv();
        self.data.mouseOverDivIndex = self.getUnderlyingDivIndex(evt.target.underlyingDiv);
        if (oldMouseOverDiv !== evt.target) {
            if (oldMouseOverDiv) {
                self.setBackgroundForDiv(oldMouseOverDiv);
            }
        }
        if (!self.isUnderlyingDivHidden(evt.target)) {
            evt.target.style.backgroundColor = self.consts.overlayDivBgColorHovered;
        }
    };

    self.onMouseOut_OverlayDiv = function (evt) {
        if (self.flags.isDragging) {
            return;
        }
        var oldMouseOverDiv = self.getMouseOverOverlayDiv();
        self.data.mouseOverDivIndex = -1;
        if (evt.target) {
            self.setBackgroundForDiv(evt.target);
        }
    };

    self.inflateDiv = function (div, amt) {
        div.style.width = parseInt(div.style.width, 10) + (amt * 2) + 'px';
        div.style.height = parseInt(div.style.height, 10) + (amt * 2) + 'px';
    };

    self.deflateDiv = function (div, amt) {
        div.style.width = parseInt(div.style.width, 10) - (amt * 2) + 'px';
        div.style.height = parseInt(div.style.height, 10) - (amt * 2) + 'px';
    };

    self.isDivMisaligned = function (div) {
        var isFirstDivInRow = false, i, underlyingDiv, firstChild, parentElement, childArray;
        for (i = 0; i < self.refs.overlayDivs.length; i++) {
            if (self.refs.overlayDivs[i] === div) {
                underlyingDiv = div.underlyingDiv;
                firstChild = false;
                if (underlyingDiv) {
                    parentElement = underlyingDiv.parentNode;
                    if (parentElement) {
                        childArray = fgUtil.childElementNodesToArray(parentElement.childNodes);
                        if (childArray.length > 0 && self.isEqual(childArray[0], underlyingDiv)) {
                            firstChild = true;
                        }
                    }
                }
                if (i === 0 || firstChild || self.getPosition(div).top !== self.getPosition(self.refs.overlayDivs[i - 1]).top) {
                    isFirstDivInRow = true;
                    break;
                }
            }
        }
        if (!isFirstDivInRow) {
            return false;
        }
        var left = self.getPosition(div).left;
        var rects = self.data.gridColRects;
        for (i = 0; i < rects.length; i++) {
            var validLeft = rects[i].left;
            var colNum = i + 1;
            if (self.isLeftCloseEnough(colNum, validLeft, left)) {
                return false;
            } else if (validLeft > left) {
                return true; //none of the other divs to the right will match either
            }
        }
        return true;
    };

    self.getPosition = function (ele) {
        var result = { top: 0, left: 0 };

        while (ele) {
            result.top += ele.offsetTop;
            result.left += ele.offsetLeft;
            ele = ele.offsetParent;
        }

        return result;
    };

    self.isDescendantOfHiddenElement = function (elem) {
        if (!self.getIsInUnhideMode()) {
			return false;
        }
		
		if (!elem || !elem.parentNode) {
			return false;
        }
			
		var bodyElement = window.parent.document.body;
		if (!bodyElement) {
			return false;
        }
			
		var parent = elem.parentNode;
		while (parent && parent !== bodyElement) {
			if (parent.getAttribute('data-isHidden') === 'true') {
				return true; //we're a descendant of a hidden element
            }
			parent = parent.parentNode;
		}
		
		return false;
    };
    
    self.getHiddenAncestor = function (elem) {
        //returns the highest parent of elem which is hidden
        var bodyElement = window.parent.document.body;
        
        if (!bodyElement || !elem || !elem.parentNode) {
			return false;
        }
        
        var parent = elem.parentNode;
        var hParent = parent;
        
        while (parent && parent !== bodyElement) {
            if (parent.getAttribute('data-isHidden') === 'true') {
                hParent = parent;
            }
            parent = parent.parentNode;
        }
        
        return hParent;
    };

    self.getSelectedUnderlyingDiv = function () {
        return self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
    };

    self.getMouseOverOverlayDiv = function () {
        if (self.data.mouseOverDivIndex < 0) {
            return null;
        }
        return self.refs.overlayDivs[self.data.mouseOverDivIndex];
    };

    self.getOverlayDiv = function (div) {
        var i, overlayDiv;
        for (i = 0; i < self.refs.overlayDivs.length; i++) {
            overlayDiv = self.refs.overlayDivs[i];
            if (overlayDiv.underlyingDiv === div) {
                return overlayDiv;
            }
        }
        return null;
    };

    self.getBackgroundStringForDiv = function (div) {
        if (self.isUnderlyingDivHidden(div)) {
            return 'background-image:' + self.consts.hiddenDivBgImage + ';';
        } else {
            return 'background-color:' + self.consts.overlayDivBgColor + ';';
        }
    };

    self.setBackgroundForDiv = function (div) {
        if (!div) {
            return;
        }
        if (self.isUnderlyingDivHidden(div)) {
            div.style.backgroundImage = self.consts.hiddenDivBgImage;
        }
    };

    self.isUnderlyingDivHidden = function (div) {
        var i, overlayDiv;
        for (i = 0; i < self.refs.overlayDivs.length; i++) {
			overlayDiv = self.refs.overlayDivs[i];
			if (overlayDiv === div) {
				if (overlayDiv.underlyingDiv && overlayDiv.underlyingDiv.getAttribute('data-isHidden') === 'true') {
					return true;
                } else {
					return false;
                }
			}
		}
		return false;
    };

    self.createOverlayDiv = function (underlyingDiv) {
        var overlayDiv = document.createElement('div');
        overlayDiv.underlyingDiv = underlyingDiv;
        overlayDiv.setAttribute('class', 'overlayDiv');
        overlayDiv.addEventListener('mouseover', self.onMouseOver_OverlayDiv);
        overlayDiv.addEventListener('mouseout', self.onMouseOut_OverlayDiv);
        return overlayDiv;
    };

    self.createOverlayDivs = function () {
        if (self.refs.overlayDivs.length > 0) {
            self.destroyOverlayDivs();
        }
        var underlyingDivs = self.getUnderlyingDivs();
        underlyingDivs.forEach(function (underlyingDiv) {
            var div = self.createOverlayDiv(underlyingDiv);
            self.refs.overlayDivs.push(div);
            document.body.appendChild(div);
        });
    };

    self.getPosition = function (ele) {
        var result = { top: 0, left: 0 };

        while (ele) {
            result.top += ele.offsetTop;
            result.left += ele.offsetLeft;
            ele = ele.offsetParent;
        }

        return result;
    };

    self.isEqual = function (fgElement1, fgElement2) {
        if (!fgElement1 && !fgElement2) {
            return true;
        }
        /* First check for IDs  to be equal*/
        if (fgElement1 && fgElement2) {
            var id1 = fgElement1.getAttribute('id');
            var id2 = fgElement2.getAttribute('id');
            if ((id1 || id2) && (id1 === id2)) {
                return true;
            } else if (fgElement1 === fgElement2) {
                /* Check for DOM objects to be equal */
                return true;
            }
        }
        return false;
    };

    self.syncOverlayDivs = function () {
        self.syncSelectedDiv();
        var overlayDivs = self.refs.overlayDivs;
        var i;
        for (i = 0; i < overlayDivs.length; i++) {
            var overlayDiv = overlayDivs[i];
            var underlyingDiv = overlayDiv.underlyingDiv;
            if (!underlyingDiv) {
                return;
            }
            var top = 'top:' + self.getPosition(underlyingDiv).top + 'px;';
            var left = 'left:' + self.getPosition(underlyingDiv).left + 'px;';
            var width = parseInt(underlyingDiv.offsetWidth, 10);
            var height = parseInt(underlyingDiv.offsetHeight, 10);
            width = width - (2 * self.consts.draggingBorderWidth); // deflate the width
            height = height - (2 * self.consts.draggingBorderWidth); // deflate the height
            var offsetWidth = 'width:' + width + 'px;';
            var offsetHeight = 'height:' + height + 'px;';
            var background = self.getBackgroundStringForDiv(overlayDiv);
            var border = "border:" + '0px dotted #2D8CEB' + ';';
            var pointerEvents = "pointer-events:none;";
            overlayDiv.style.cssText = top + left + offsetWidth + offsetHeight + background + border + pointerEvents;
        }
    };
    
    self.positionExtension = function () {
        //set our extension to use the entire document space
        var documentWidth = window.parent.document.documentElement.offsetWidth;
        var getDocHeight = function () {
            var D = window.parent.document;
            return Math.max(
                D.documentElement.scrollHeight,
                D.documentElement.clientHeight,
                window.parent.window.innerHeight
            );
        };
        var documentHeight = getDocHeight();
        liveViewObject.positionExtensionById(self.consts.extensionID, 0, 0, documentWidth, documentHeight);
        liveViewObject.showExtensionById(self.consts.extensionID); // ensure it is shown
        self.positionControls();
    };

    self.parseFluidCSS = function () {
        var i = 0,
            maxComponentMatchCount = 0,
            curMaxComponentMatchCount = 0,
            curFileUrlComponents,
            curIndex = 0,
            fgIndex = 0,
            fluidGridStyleSheet,
            elem,
            imported = false,
            subFluidGridStyleSheet,
            subElem;
        if (!window.gFluidGridUrl) {
            return; //No Fluid Grid URL was specified.
        }
        gFluidGridUrl = gFluidGridUrl.replace("|", ":"); //Dw sends data in the form file|//. We need file://.
        var fluidgridComponents = gFluidGridUrl.split("/");
        for (elem in window.parent.document.styleSheets) {
            if (window.parent.document.styleSheets.hasOwnProperty(elem)) {
                if (window.parent.document.styleSheets[elem].href) {
                    curFileUrlComponents = window.parent.document.styleSheets[elem].href.split("/");
                    curMaxComponentMatchCount = 0;
                    for (curIndex = curFileUrlComponents.length - 1, fgIndex = fluidgridComponents.length - 1; curIndex >= 0 && fgIndex >= 0; --curIndex, --fgIndex) {
                        if (curFileUrlComponents[curIndex] === fluidgridComponents[fgIndex]) {
                            ++curMaxComponentMatchCount;
                        } else {
                            break;
                        }
                    }
                    if (curMaxComponentMatchCount > maxComponentMatchCount) {
                        maxComponentMatchCount = curMaxComponentMatchCount;
                        fluidGridStyleSheet = elem;
                    }
                } else {
                    for (subElem in window.parent.document.styleSheets[elem].cssRules) {
                        if (window.parent.document.styleSheets[elem].cssRules.hasOwnProperty(subElem)) {
                            if (window.parent.document.styleSheets[elem].cssRules[subElem].href) {
                                imported = true;
                                curFileUrlComponents = window.parent.document.styleSheets[elem].cssRules[subElem].href.split("/");
                                curMaxComponentMatchCount = 0;
                                for (curIndex = curFileUrlComponents.length - 1, fgIndex = fluidgridComponents.length - 1; curIndex >= 0 && fgIndex >= 0; --curIndex, --fgIndex) {
                                    if (curFileUrlComponents[curIndex] === fluidgridComponents[fgIndex]) {
                                        ++curMaxComponentMatchCount;
                                    } else {
                                        break;
                                    }
                                }
                                if (curMaxComponentMatchCount > maxComponentMatchCount) {
                                    maxComponentMatchCount = curMaxComponentMatchCount;
                                    fluidGridStyleSheet = elem;
                                    subFluidGridStyleSheet = subElem;
                                }
                            }
                        }
                    }
                }
            }
        }
        var styleSheet;
        if (!imported) {
            styleSheet = window.parent.document.styleSheets[fluidGridStyleSheet];
        } else {
            styleSheet = window.parent.document.styleSheets[fluidGridStyleSheet].cssRules[subFluidGridStyleSheet].styleSheet;
        }
        
        self.rulesInTheFile = styleSheet ? styleSheet.cssRules : null;
        if (self.rulesInTheFile) {
            var cssRules = self.rulesInTheFile;
            self.data.subsheet[0] = cssRules;
            for (i = 0; i < cssRules.length; i++) {
                if (cssRules[i].type === self.consts.CSS_MEDIA_RULE) {
                    self.data.subsheet.push(cssRules[i].cssRules);
                    if (self.data.subsheet.length === 3) {
                        break;
                    }
                }
            }
        }
    };

    self.onResize_OverlayWindow = function () {
        if (self.flags.logEvents) {
            self.log('onResize_OverlayWindow');
        }
        self.refresh('gridAndRediscoverUnderlyingDivs', false);
    };

    self.log = function (str) {
        if (self.debug) {
            console.log(str);
        }
    };
    
    self.getParentColsOfSelectedDiv = function () {
        var selectedDiv = self.getSelectedUnderlyingDiv();
        if (selectedDiv) {
            var parentElement = selectedDiv.parentNode;
            if (parentElement) {
                return fgUtil.getColSpanFromOffsetWidth(parentElement.offsetWidth, self.data.gridColRects);
            }
        }
        return -1;
    };
    
    self.toggleStartsRow = function () {
        dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_TOGGLE_START_ROW);
        var element = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div
        if (!element) {
            self.hideDiv(self.refs.startsRowDiv);
            return;
        }
        var selector = fgUtil.getValidFluidGridSelector(element, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
        var curDevice = self.getDeviceFromWindowWidth();
        var changeMarginProp = true;
        var clearVal = self.getCssPropValFromElem(element, "clear", "string");
        var startNewRow = clearVal !== 'both';
        var overlayDiv = self.getSelectedOverlayDiv();
        var htmlEdited = false;
        var liveCodeHiddenId;
        if (selector.charAt(0) === '.' && self.isDivMisaligned(overlayDiv) && startNewRow) {
            // only do this when the "align to grid is being shown"            
            // since it a class selector apply the zeroMargin class
            changeMarginProp = false;
            liveCodeHiddenId = element.getAttribute(self.consts.DWUniqueId);
        }

        var argObj = {
            curDevice: curDevice,
            selector: selector,
            startNewRow: startNewRow,
            parentCols: self.getParentColsOfSelectedDiv(),
            changeMarginProp: changeMarginProp,
            liveCodeHiddenId: liveCodeHiddenId
        };

        window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "toggleStartsNewRow", argObj, false);

        self.ensureSelectedDivStillVisible();
    };
    
    self.getNestedSelectorsForCSSDeletion = function (parentElement) {
		var childNodes = fgUtil.childElementNodesToArray(parentElement.childNodes);
		if (childNodes && childNodes.length && childNodes.length > 0) {
			childNodes.forEach(function (childNode) {
				if (fgUtil.validFluidElement(childNode, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)) {
                    
				    var validIDSelector = fgUtil.getValidFluidGridIDSelector(childNode, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
					if (validIDSelector) {
                        self.data.editsArray.push(validIDSelector);
                    }
				}
		
				self.getNestedSelectorsForCSSDeletion(childNode);
			});
		}
	};
    
    self.attemptDeletion = function () {
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_DELETE_ELEMENT);
		var div = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
        if (!div) {
            self.hideDiv(self.refs.deleteDiv);
            self.hideDiv(self.refs.duplicateDiv);
            return;
        }
        

		self.data.editsArray.length = 0;
			
        if (div) {
			var userDomElement;
            // figure out which css selectors to delete 
            var idSelector = fgUtil.getValidFluidGridIDSelector(div, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
            if (idSelector) {
                self.data.editsArray.push(idSelector);
            }
           // self.removeEntriesFromLockedArray(div);
            self.getNestedSelectorsForCSSDeletion(div);
			
            var nextNode = self.getNextVisibleSibling(div, false);
			if (!nextNode) {
				nextNode = self.getPreviousVisibleSibling(div, false);
            }
			if (!nextNode) {
				nextNode = div.parentNode;
            }
			
            self.selectDiv(null); // remove any Selection
            var nextNodeLiveCodeHiddenId = nextNode ? nextNode.getAttribute(self.consts.DWUniqueId) : null;
			// always delete the HTML first so as to initiate an EditOp in the HTML Doc Root.
            var liveCodeHiddenId = div.getAttribute(self.consts.DWUniqueId);
            if (liveCodeHiddenId) {
                window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "selectAndDeleteNode",
                    {
                        liveCodeHiddenId: liveCodeHiddenId,
                        nextNodeLiveCodeHiddenId: nextNodeLiveCodeHiddenId,
                        editsArray: self.data.editsArray,
                        callback: function () {
                            self.data.editsArray.length = 0;
                        }
                    }, false);
            }
            window.parent.getSelection().removeAllRanges();
        }

    };
    
    self.attemptNestedDuplication = function (tagID) {
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_DUPLICATE_ELEMENT);
        var fgElement;
        var isContext = false;
        var selectedTagId;
        
        if (typeof tagID === "undefined") {
            fgElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div
        } else {
            isContext = true;
            var curSelectedElement = liveViewObject.getCurrentSelectedElement();
            selectedTagId = curSelectedElement.getAttribute(self.consts.DWUniqueId);
            var bodyElement = window.parent.document.body;
            if (!bodyElement) {
                return;
            }
            var allDivs = self.nodeListToArray(bodyElement.getElementsByTagName(self.consts.tagName));
            allDivs.some(function (underlyingDiv) {
                if (underlyingDiv.getAttribute(self.consts.DWUniqueId) === tagID) {
                    fgElement = underlyingDiv;
                    return true;
                }
            });
        }
        
		if (!fgElement) {
            self.hideDiv(self.refs.duplicateDiv);
            return;
        }
        
		var sourceTargetElemMap  = {}; //Maps source elements to their duplicated counterparts.

		var fgElementParentLeft  = fgElement.parentNode.offsetLeft;
		var fgElementParentWidth = fgElement.parentNode.offsetWidth;
		
		var clearCssValue = self.getCssPropValFromElem(fgElement, "clear", "string");
		
		var heightOfFgElementDuplicated = fgElement.offsetHeight;
		var widthOfFgElementDuplicated  = fgElement.offsetWidth;
		var leftOfFgElementDuplicated   = fgElement.offsetLeft;
		/* Calculate the colspan left between current element's right boundary and its parent right boundary. */
		var remainingRightColSpan = fgUtil.getColSpanFromOffsetWidth(fgElementParentLeft + fgElementParentWidth - leftOfFgElementDuplicated - widthOfFgElementDuplicated, self.data.gridColRects);
		var sourceWidthColSpan    = fgUtil.getColSpanFromOffsetWidth(fgElement.offsetWidth, self.data.gridColRects);
		
		var indexToSelect = self.data.selectedUnderlyingDivIndex + self.getSelectedUnderlyingElems(fgElement, true, false, true).length + 1;
		/* Update all the IDs with new valid values in the outerHTML code for duplicated elements and insert in DOM */
		// Always update the HTML first so as to initiate an EditOP in the HTML Doc Root
		/* In case we do domSource edit in <code>setOuterHtmlWithDupIDs</code> element passed gets corrupted.
		 * Store the offsets to retrieve the updated element later. */
        window.parent.writeDWLog("Attempting Duplication");
        var liveCodeHiddenId = fgElement.getAttribute(self.consts.DWUniqueId);
        if (liveCodeHiddenId) {
            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "setOuterHtmlWithDupIDs",
                {
                    liveCodeHiddenId: liveCodeHiddenId,
                    sourceTargetElemMap: sourceTargetElemMap,
                    isContext: isContext,
                    selectedTagId: selectedTagId,
                    callback: function (devices, dupID) {
                        window.parent.writeDWLog("In duplication callback");
                        var selectedUnderlyingDivs = [];
                        selectedUnderlyingDivs.push(fgElement);
                        var underlyingDivs = self.getSelectedUnderlyingElems(fgElement, true, true);
                        underlyingDivs.forEach(function (underlyingDiv) {
                            selectedUnderlyingDivs.push(underlyingDiv);
                        });

                        self.data.editsArray.length = 0;
                        
                        //selectedUnderlyingDivs array contains all the child fluid grid elements of the element to be duplicated.
                        selectedUnderlyingDivs.forEach(function (fluidElem) {
                            var sourceElement = fluidElem;
                            var dupID =  sourceTargetElemMap[fluidElem.getAttribute('id')];
                            /* Duplicate the CSS */
                            var sourceIDSelector = fgUtil.getValidFluidGridIDSelector(sourceElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
                            if (sourceIDSelector && sourceTargetElemMap[fluidElem.getAttribute('id')]) {
                                var duplicateRuleObject = {};
                                duplicateRuleObject.sourceSelector = sourceIDSelector;
                                duplicateRuleObject.dupSelector = "#" + dupID;
                                self.data.editsArray.push(duplicateRuleObject);
                            }
                        });
                        
                        // Update the CSS
                        if (self.data.editsArray.length > 0) {
                            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "duplicateRules",
                                {
                                    editsArray: self.data.editsArray,
                                    callback: function () {
                                        window.parent.writeDWLog("in CSS duplication callback");
                                        self.data.editsArray.length = 0;
                                        self.data.selectedUnderlyingDivIndex = indexToSelect;
                                    }
                                }, true);
                        }
                    }


                }, false);
        }
	};
    
    self.hideSelectedDiv = function () {
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_HIDE_ELEMENT);
		var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
	    if (!selectedElement) {
            self.hideDiv(self.refs.hideDiv);
            return;
        }
		
		if (!self.getIsInUnhideMode()) {
			self.selectDiv(null);
        }
		var selector = fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
		var curDevice = self.getDeviceFromWindowWidth();
		var changeDisplayProp = true;
        var liveCodeHiddenId;
		if (selector.charAt(0) === '.') {
			// since it a class selector apply the hide class
			changeDisplayProp = false;
            liveCodeHiddenId = selectedElement.getAttribute(self.consts.DWUniqueId);
		}
		
        var argObj = {
            curDevice: curDevice,
            selector: selector,
            changeDisplayProp: changeDisplayProp,
            liveCodeHiddenId: liveCodeHiddenId
        };

        window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "hideElement", argObj, false);

	};
    
    self.showSelectedDiv = function () {
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_SHOW_ELEMENT);
		var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
	    if (!selectedElement) {
            self.hideDiv(self.refs.showDiv);
            return;
        }
		
		var selector = fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
		var curDevice = self.getDeviceFromWindowWidth();
        var liveCodeHiddenId;
        var isClass = false;
		if (selector.charAt(0) === '.') {
            liveCodeHiddenId = selectedElement.getAttribute(self.consts.DWUniqueId);
            isClass = true;
        }

        var argObj = {
            curDevice: curDevice,
            selector: selector,
            isClass: isClass,
            liveCodeHiddenId: liveCodeHiddenId
        };
        window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "showElement", argObj, false);
		//we do not need to remove the unhide mode changes here
		//since it will be removed when the CSS change takes place
		//in the document and grids get rebuilt
	};
    
    self.refreshUnhideMode = function (evt) {
        self.setIsInUnhideMode(evt.params === "true" ? true : false);
		self.refresh('gridAndRediscoverUnderlyingDivs', true);
		self.selectDiv(null);
		self.positionExtension();
		var messageDetails = {type: DW_EXTENSION_EVENT.SELECTION_CHANGE};
		dwExtensionController.sendMessage(messageDetails);
	};
    
    self.callFGFunction = function (evt) {
        var funcName = evt.params;
        if (funcName) {
            if (funcName === "Delete") {
                self.attemptDeletion();
                return;
            } else if (funcName === "Duplicate") {
                self.attemptNestedDuplication();
                return;
            } else if (funcName === "Copy") {
                var liveCodeHiddenIdCur;
                if (self.data.selectedUnderlyingDivIndex >= 0) {
                    var fgElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
                    liveCodeHiddenIdCur = fgElement.getAttribute(self.consts.DWUniqueId);
                }
                var args = {
                    liveCodeHiddenId: liveCodeHiddenIdCur
                };
                window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "handleCopy", args, false);
                return;
            } else if (funcName === "Paste") {
                window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "handlePaste", {
                    callback: function (tagID) {
                        self.attemptNestedDuplication(tagID);
                    }
                },
                    false);
                return;
            }
        }
    };
    
    self.swapWithNext = function () {
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_MOVE_DOWN_ELEMENT);
		var fgElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div
	    if (!fgElement) {
            self.hideDiv(self.refs.moveUpDiv);
            self.hideDiv(self.refs.moveDownDiv);
            return;
        }
		var nextSibling = self.getNextVisibleSibling(fgElement, false);
        var liveCodeHiddenIdCur = fgElement.getAttribute(self.consts.DWUniqueId);
		
		if (nextSibling !== null) {
			var nextElement = nextSibling;
            var liveCodeHiddenIdSib = nextElement.getAttribute(self.consts.DWUniqueId);
			if (nextElement !== null) {
                var addOne = 0;
                if (fgUtil.validFluidElement(nextSibling, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)) {
                    addOne = 1;
                }
				var indexToSelect = self.data.selectedUnderlyingDivIndex + self.getSelectedUnderlyingElems(nextSibling, true, false, true).length + addOne;
				self.selectDiv(null); // remove any Selection before removing outerHTML
				if (liveCodeHiddenIdCur && liveCodeHiddenIdSib) {
					window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "swapWithNext",
                        {
                            liveCodeHiddenId: liveCodeHiddenIdCur,
                            liveCodeHiddenIdSib: liveCodeHiddenIdSib
                        }, false);
                }
			}
		}
	};
	
	self.swapWithPrevious = function () {
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_MOVE_UP_ELEMENT);
		var fgElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div
	    if (!fgElement) {
            self.hideDiv(self.refs.moveUpDiv);
            self.hideDiv(self.refs.moveDownDiv);
            return;
        }
		var prevSibling = self.getPreviousVisibleSibling(fgElement, false);
        var liveCodeHiddenIdCur = fgElement.getAttribute(self.consts.DWUniqueId);
		if (prevSibling !== null) {
			var prevElement = prevSibling;
            var liveCodeHiddenIdSib = prevElement.getAttribute(self.consts.DWUniqueId);
			if (prevElement !== null) {
                var subOne = 0;
				if (fgUtil.validFluidElement(prevSibling, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)) {
                    subOne = 1;
                }
				var indexToSelect = self.data.selectedUnderlyingDivIndex - self.getSelectedUnderlyingElems(prevSibling, true, false, true).length - subOne;
				self.selectDiv(null); // remove any Selection before removing the outerHTML
                if (liveCodeHiddenIdCur && liveCodeHiddenIdSib) {
					window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "swapWithPrevious",
                        {
                            liveCodeHiddenId: liveCodeHiddenIdCur,
                            liveCodeHiddenIdSib: liveCodeHiddenIdSib
                        }, false);
                }
			}
		}
	};
    
    self.findRuleForDiv_LiveView = function (div) {
		var liveViewWin = window,
            rules = liveViewWin.getMatchedCSSRules(div, ''),
            i,
            rule;
		if (!rules) {
			return null;
        }
		// The last matching rule that has a width should be
		// the one we want.
		for (i = rules.length - 1; i >= 0; i--) {
			rule = rules[i];
			if (rule.style.width) {
				return rule;
			}
		}
		return null;
	};
	
	self.findRuleForSelectedUnderlyingDiv_LiveView = function () {
		var div = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
		return self.findRuleForDiv_LiveView(div);
	};
    
    self.positionDraggingDiv = function () {
        if (self.data.selectedUnderlyingDivIndex < 0) {
			self.hideDiv(self.refs.draggingDiv);
			self.hideDiv(self.refs.dynamicTooltipDiv);
			return;
		}
		// Inflate div to make client rect size of actual.
		var selectedOverlayDiv = self.getSelectedOverlayDiv();
		if (!selectedOverlayDiv) {
			return;
		}
		self.inflateDiv(selectedOverlayDiv, self.consts.selectedBorderWidth);
		var draggingDivStyle = self.refs.draggingDiv.style;
		var selectedOverlayDivStyle = selectedOverlayDiv.style;
		draggingDivStyle.left = selectedOverlayDivStyle.left;
		draggingDivStyle.top = selectedOverlayDivStyle.top;
		draggingDivStyle.width = selectedOverlayDivStyle.width;
		draggingDivStyle.height = selectedOverlayDivStyle.height;
		// Now deflate them based on respective border widths.
		self.deflateDiv(self.refs.draggingDiv, self.consts.draggingBorderWidth);
		self.deflateDiv(selectedOverlayDiv, self.consts.selectedBorderWidth);
		self.showDiv(self.refs.draggingDiv);
	};
    
    self.positionDynamicTooltipDiv = function () {
		//  This method should be called after the dragging div has been updated.
		var selectedOverlayDiv = self.getSelectedOverlayDiv();
		if (!self.flags.isDragging || !selectedOverlayDiv) {
			self.hideDiv(self.refs.dynamicTooltipDiv);
			return;
		}
        
		var widthLabel = '';
		var colLabel = '';
		var marginLeftLabel = '';
        if (self.globalController) {
            widthLabel = self.globalController.getLocalizedString('Width_Label');
            colLabel = self.globalController.getLocalizedString('Column_Label');
            marginLeftLabel = self.globalController.getLocalizedString('MarginLeft_Label');
        }
		var left = parseInt(self.refs.draggingDiv.style.left, 10) + self.consts.dynamicTooltipOffsetX;
        var tentativeWidth;
        var argObj;
		if (self.flags.isResizing) {
			tentativeWidth = parseInt(self.refs.draggingDiv.style.width, 10);
			left += tentativeWidth;
			var colSpan = fgUtil.getColSpanFromOffsetWidth(tentativeWidth, self.data.gridColRects);
			
            argObj = {
                isResize: true,
                device: self.getDeviceFromWindowWidth(),
                colSpan: colSpan,
                parentCols: self.getParentColsOfSelectedDiv(),
                callback: function (width) {
                    self.refs.dynamicTooltipDiv.innerHTML = '<nobr>' + widthLabel + ' ' + width + '<br>' + colLabel + ' ' + colSpan + '</nobr>';
                }
            };
            
            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "positionDynamicTooltipDiv", argObj, false);
		} else { // isShifting
			tentativeWidth = self.data.startingMarginDivWidth + self.data.mouseCurX - self.data.mouseStartX;
			var colShift = self.getColShiftFromMarginLeftPx(tentativeWidth);
			
            argObj = {
                isResize: false,
                device: self.getDeviceFromWindowWidth(),
                colShift: colShift,
                startsNewRow: self.startsNewRow(selectedOverlayDiv.underlyingDiv),
                parentCols: self.getParentColsOfSelectedDiv(),
                callback: function (marginLeft) {
                    self.refs.dynamicTooltipDiv.innerHTML = '<nobr>' + marginLeftLabel + ' ' + marginLeft + '<br>' + colLabel + ' ' +  colShift + '</nobr>';
                }
            };
            
            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "positionDynamicTooltipDiv", argObj, false);
		}
		var tipOffsetWidth = self.refs.dynamicTooltipDiv.offsetWidth || 150; // offsetWidth is zero first time around.
		var isOutOfViewRight = (left + tipOffsetWidth) > self.refs.window.innerWidth + self.refs.document.body.scrollLeft;
		if (isOutOfViewRight) {
			left = self.refs.window.innerWidth - tipOffsetWidth + self.refs.document.body.scrollLeft;
		}
		var top = self.data.mouseStartY + self.consts.dynamicTooltipOffsetY + self.refs.document.body.scrollTop;
		var isOutOfViewBottom = (top + self.refs.dynamicTooltipDiv.offsetHeight) > self.refs.window.innerHeight + self.refs.document.body.scrollTop;
		if (isOutOfViewBottom) {
			top = self.refs.window.innerHeight - self.refs.dynamicTooltipDiv.offsetHeight + self.refs.document.body.scrollTop;
		}
		self.refs.dynamicTooltipDiv.style.left = left + 'px';
		self.refs.dynamicTooltipDiv.style.top = top + 'px';
		self.showDiv(self.refs.dynamicTooltipDiv);
	};
    
    self.getSelectedOverlayDiv = function () {
		if (self.data.selectedUnderlyingDivIndex < 0) {
			return null;
		}
		return self.refs.overlayDivs[self.data.selectedUnderlyingDivIndex];
	};
    
    self.getValidMarginLefts = function (startsNewRow) {
		var colRects = self.data.gridColRects;
		if (colRects.length === 0) {
			return [];
		}
		var colWidth = colRects[0].right - colRects[0].left;
		var gutterWidth = colRects[1].left - colRects[0].right;
		var validWidths = fgUtil.getValidLayoutDivWidths(self.data.gridColRects);
		var validMarginLefts = validWidths.map(function (validWidth) {
            var marginLeft = validWidth - colWidth;
            if (!startsNewRow) {
                marginLeft += gutterWidth;
            }
            return marginLeft;
		});
		return validMarginLefts;
	};
    
    self.getColShiftFromMarginLeftPx = function (marginLeftPx) {
		var smallestDiff = 100000;
		var nearestMarginLeftIndex = -1;
		var selectedOverlayDiv = self.getSelectedOverlayDiv();
        var len;
        var i;
        var diff;
        var validMarginLeft;
		if (!selectedOverlayDiv) {
			return;
		}
		var validMarginLefts = self.getValidMarginLefts(self.startsNewRow(selectedOverlayDiv.underlyingDiv));
        len = validMarginLefts.length;
        for (i = 0; i < len; i = i + 1) {
            validMarginLeft = validMarginLefts[i];
            diff = Math.abs(validMarginLeft - marginLeftPx);
            if (diff < smallestDiff) {
                nearestMarginLeftIndex = i;
                smallestDiff = diff;
            }
        }
		return nearestMarginLeftIndex;
	};
    
    self.updateAllDescendents = function (resizedElement, newParentCols, applyUpdates, forceUpdate) {
        var childNodes = fgUtil.childElementNodesToArray(resizedElement.childNodes);
        if (childNodes.length && childNodes.length > 0) {
            childNodes.forEach(function (childNode) {
                 // Do it for only fluid grid elements
                if (fgUtil.validFluidElement(childNode, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)) {
                    if (!childNode) {
                        return;
                    }
                    var childWidth = self.getCssPropValFromElem(childNode, 'width', 'int');
                    var marginPx = self.getCssPropValFromElem(childNode, 'margin-left', 'int');
                    var colSpan = fgUtil.getColSpanFromOffsetWidth(childWidth, self.data.gridColRects); // calculate the existing number of columns. TODO: can this information be stored to optimise performance if required ??
                    var colShifted = self.getColShiftFromMarginLeftPx(marginPx); // calculate the existing number of shifted columns. TODO: can this information be stored to optimise performance if required ??
                    var updateDescendents = false;
                    var recordedChanges = {};
                    recordedChanges.element = childNode;
                    if (newParentCols < colSpan) {
                           // if parent col span has reduced, reduce the colspan to match it
                           // This will result in change in size of the element in pixels => will need to update the children as well.
                        colSpan = newParentCols;
                        updateDescendents = true;
                    }
                    recordedChanges.colSpan = colSpan;
                    recordedChanges.colShifted = colShifted;
                    recordedChanges.parentCols = newParentCols;
                    self.data.editsArray.push(recordedChanges);
                    if (updateDescendents || forceUpdate) {
                        self.updateAllDescendents(childNode, colSpan, false, forceUpdate); // just append to the edit information
                    }
                }
            });
        }
    };
      
    self.onMouseDown_Resizing = function (evt) {
		evt.preventDefault();
		self.flags.isDragging = true;
		self.flags.isResizing = true;

		self.data.mouseStartX = evt.x;
		self.data.mouseStartY = evt.y;
		self.data.scrollStartX = self.refs.window.scrollX;
		self.data.gridContainerWidthStart = self.getGridContainerWidth();
        
        self.refs.cssRuleBeingModified = self.findRuleForSelectedUnderlyingDiv_LiveView();
        
		var selectedUnderlyingDiv = self.getSelectedUnderlyingDiv();
		if (!selectedUnderlyingDiv) {
			return;
		}
		var selectedOverlayDiv = self.getSelectedOverlayDiv();
		if (!selectedOverlayDiv) {
			return;
		}
		self.ensureSelectedDivResizable();
		self.data.selectedUnderlyingDivStartingColSpan = fgUtil.getColSpanFromOffsetWidth(selectedUnderlyingDiv.offsetWidth, self.data.gridColRects);
		self.data.selectedUnderlyingDivCurrentColSpan = self.data.selectedUnderlyingDivStartingColSpan;
		self.data.startingSelectedOverlayDivOffsetWidth = selectedOverlayDiv.offsetWidth;
		self.data.lastColRight = self.data.gridColRects[self.data.gridColRects.length - 1].right;
		self.addDragEditingEventListeners('resizing');
		self.positionDraggingDiv();
		self.positionDynamicTooltipDiv();
		//make sure the whole div is selected when we start to edit it
		self.setUserNodeSelected(true);
	};

    self.snapSelectedDivToNearestValidWidth = function () {
		var newColSpan = fgUtil.getColSpanFromOffsetWidth(self.refs.draggingDiv.offsetWidth, self.data.gridColRects);
		self.data.selectedUnderlyingDivCurrentColSpan = newColSpan;
	};
    
	self.onMouseMove_Resizing = function (evt) {
        
		var selectedDiv = self.getSelectedUnderlyingDiv();
        var diff;
		if (selectedDiv) {
			var selectedDivPosition = self.getPosition(selectedDiv);
			var parentDiv = selectedDiv.parentNode;
			var parentPosition = self.getPosition(parentDiv);
            var parentWidth =  self.getCssPropValFromElem(parentDiv, 'width', 'int');
			var parentRightBoundary =  parentPosition.left + parentWidth;
			var newX;
			if (evt.x < self.data.mouseCurX) {
				newX = Math.max(evt.x,  selectedDivPosition.left);
            } else {
				newX = Math.min(evt.x, parentRightBoundary);
            }
			diff = (newX - self.data.mouseStartX) + (self.refs.window.scrollX - self.data.scrollStartX);
			self.data.mouseCurX = newX;
		}
		var newDraggingDivWidth = self.data.startingSelectedOverlayDivOffsetWidth + diff;
		self.refs.draggingDiv.style.width = newDraggingDivWidth + 'px';
		self.snapSelectedDivToNearestValidWidth(); // Dont Snap to the Grid live - as this changes the widths which the child would need to readjust.
		var selectedUnderlyingDiv = self.getSelectedUnderlyingDiv();
		if (!selectedUnderlyingDiv) {
			return;
		}
		self.positionDynamicTooltipDiv();
		if (self.getPosition(self.refs.draggingDiv).left !== self.getPosition(selectedUnderlyingDiv).left && self.data.gridContainerWidthStart !== self.getGridContainerWidth()) {
			//vdhawal: This case should never happen now as we are not snapping to grid live. But letting it be just in case.
			//The body has shifted while we're editing, we need to refresh
			self.refresh('gridForBodyAdjustment', true);
			self.positionDraggingDiv();
			self.data.gridContainerWidthStart = self.getGridContainerWidth();
		}
		if (self.getPosition(self.refs.draggingDiv).top !== self.getPosition(selectedUnderlyingDiv).top) {
			// If the div being resized jumped up or down, we need to make sure the resizing div 
			// position is synced to the selected div.
			self.positionDraggingDiv();
		}
		self.ensureSelectedDivResizable();
		self.ensureSelectedDivStillVisible();
	};

	self.onMouseUp_Resizing = function (evt) {
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_RESIZE_ELEMENT);
		if (evt) {
			evt.preventDefault();
		}
		self.removeDragEditingEventListeners();
		self.hideDiv(self.refs.draggingDiv);
		self.hideDiv(self.refs.dynamicTooltipDiv);
       
        
        if (self.data.selectedUnderlyingDivCurrentColSpan !== self.data.selectedUnderlyingDivStartingColSpan) {
            
            var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
			var selector = fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
            
            var parentCols = self.getParentColsOfSelectedDiv();
            if (self.data.selectedUnderlyingDivCurrentColSpan > parentCols) {
                self.data.selectedUnderlyingDivCurrentColSpan = parentCols;
            }
            
			self.data.selectorWidthEdited.length = 0;
			self.data.selectorWidthEdited.push(selector);
            
            self.updateAllDescendents(self.getSelectedUnderlyingDiv(), self.data.selectedUnderlyingDivCurrentColSpan, false, false);
            var argObj = {
                device: self.getDeviceFromWindowWidth(),
                selector: selector,
                currentColSpan: self.data.selectedUnderlyingDivCurrentColSpan,
                parentCols: parentCols,
                editsArray: self.data.editsArray,
                cssRules: self.rulesInTheFile,
                selectorWidthEdited: self.data.selectorWidthEdited,
                styleRule: self.consts.CSS_STYLE_RULE,
                mediaRule: self.consts.CSS_MEDIA_RULE,
                callback: function () {
                    if (self.data.editsArray.length > 0) {
                        self.data.editsArray.length = 0;  // this will clear the array and call destructors of existing elements
                    }
                    if (self.data.selectorWidthEdited.length > 0) {
                        self.data.selectorWidthEdited.length = 0;
                    }
                }
            };
            
            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "onMouseUp_Resize", argObj, true);
        }
        
	};
    
    self.onMouseDown_Shifting = function (evt) {
		evt.preventDefault();
		var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
		self.flags.isDragging = true;
		self.flags.isShifting = true;
		self.data.mouseStartX = evt.x;
		self.data.mouseCurX = evt.x;
		self.data.mouseStartY = evt.y;
		self.data.scrollStartX = self.refs.window.scrollX;
		self.data.gridContainerWidthStart = self.getGridContainerWidth();
        self.refs.cssRuleBeingModified = self.findRuleForSelectedUnderlyingDiv_LiveView();
		var selectedUnderlyingDiv = self.getSelectedUnderlyingDiv();
		if (!selectedUnderlyingDiv) {
			return;
		}
		var marginLeft = self.getCssPropValFromElem(selectedElement, 'margin-left', 'int');
		self.data.startingMarginDivWidth = marginLeft;
		self.data.marginsOffsetLeft = self.getPosition(selectedUnderlyingDiv).left - marginLeft;
		self.data.selectedDivStartingColShift = self.getColShiftFromMarginLeftPx(marginLeft);/*, self.data.gridColRects, self.data.selectedUnderlyingDivIndex, self.refs.overlayDivs*/
		self.data.selectedDivCurrentColShift = self.data.selectedDivStartingColShift;
		self.data.startingSelectedUnderlyingDivOffsetLeft = self.getPosition(selectedUnderlyingDiv).left;
		self.addDragEditingEventListeners('shifting');
		self.positionDraggingDiv();
		self.positionDynamicTooltipDiv();
		//make sure the whole div is selected when we start to edit it
		self.setUserNodeSelected(true);
	};
    
    self.snapSelectedDivToNearestValidLeft = function () {
        var marginLeftPx = self.getPosition(self.refs.draggingDiv).left - self.data.marginsOffsetLeft;
        var newColShift = self.getColShiftFromMarginLeftPx(marginLeftPx);/*, self.data.gridColRects, self.data.selectedUnderlyingDivIndex, self.refs.overlayDivs*/
		self.data.selectedDivCurrentColShift = newColShift;
	};

	self.onMouseMove_Shifting = function (evt) {
		var selectedDiv = self.getSelectedUnderlyingDiv();
        var diff;
		if (selectedDiv) {
            var parentDiv = selectedDiv.parentNode;
            var parentPosition = self.getPosition(parentDiv);
            var parentWidth =  self.getCssPropValFromElem(parentDiv, 'width', 'int');
            var parentRightBoundary =  parentPosition.left + parentWidth;
            var newX;
            if (evt.x < self.data.mouseCurX) {
				newX = Math.max(evt.x,  parentPosition.left);
            } else {
				newX = Math.min(evt.x, parentRightBoundary - parseInt(self.refs.draggingDiv.style.width, 10));
            }
		
			self.data.mouseCurX = newX;
			diff = (newX - self.data.mouseStartX) + (self.refs.window.scrollX - self.data.scrollStartX);
		}
		var newDraggingDivLeft = self.data.startingSelectedUnderlyingDivOffsetLeft + diff;
		self.refs.draggingDiv.style.left = newDraggingDivLeft + 'px';
		self.positionDynamicTooltipDiv();
		self.snapSelectedDivToNearestValidLeft();
		self.ensureSelectedDivStillVisible();
	};
	
	self.onMouseUp_Shifting = function (evt) {
		var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
		dwObject.logHeadlightsData(self.headlights.UT_FLUID_GRID, self.headlights.UT_FLUID_GRID_SHIFT_ELEMENT);
		evt.preventDefault();
		self.removeDragEditingEventListeners();
		self.hideDiv(self.refs.draggingDiv);
		self.hideDiv(self.refs.dynamicTooltipDiv);
        

		if (self.data.selectedDivCurrentColShift !== self.data.selectedDivStartingColShift) {
			var selector = fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
            var device = self.getDeviceFromWindowWidth();
            
            var argObj = {
                device: device,
                selector: selector,
                colShift: self.data.selectedDivCurrentColShift,
                parentCols: self.getParentColsOfSelectedDiv(),
                callback: function () {
                    self.setUserNodeSelected(true);
                }
            };
            
            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "onMouseUp_Shift", argObj, true);
		}
        
	};
    
    self.onMouseDown_OverlayDocument = function (evt) {
        if (evt.target === self.refs.resizeEdgeDiv || evt.target === self.refs.shiftEdgeDiv) {
            evt.preventDefault();
            return;
        }
        if (evt.target === self.refs.startsRowDiv) {
            evt.preventDefault();
            self.toggleStartsRow();
            return;
        }
        if (evt.target === self.refs.deleteDiv) {
            evt.preventDefault();
            self.attemptDeletion();
            return;
        }
        if (evt.target === self.refs.duplicateDiv) {
            evt.preventDefault();
            self.attemptNestedDuplication();
            return;
        }
        if (evt.target === self.refs.moveUpDiv) {
            evt.preventDefault();
            self.swapWithPrevious();
            return;
        }
        if (evt.target === self.refs.moveDownDiv) {
            evt.preventDefault();
            self.swapWithNext();
            return;
        }
        if (evt.target === self.refs.hideDiv) {
            evt.preventDefault();
            self.hideSelectedDiv();
            return;
        }
        if (evt.target === self.refs.showDiv) {
            evt.preventDefault();
            self.showSelectedDiv();
            return;
        }
        if (!evt.target.underlyingDiv) {
            // If it doesn't have a underlyingDiv property, it isn't an overlay div.
            self.selectDiv(null);
        } else if (evt.target !== self.getSelectedOverlayDiv()) {
            self.selectDiv(evt.target);
            // Select user node here instead of in selectDiv() because having it there caused
            // the LDM to jump around when live view reloaded after an edit.
            self.setUserNodeSelected();
        }
    };

    //If the div was visible when we started, ensure it's still visible as we edit
    self.ensureSelectedDivStillVisible = function () {
        var divToScroll = self.getSelectedUnderlyingDiv();

        if (!divToScroll) {
            return;
        }

        var clientRect = divToScroll.getBoundingClientRect();
        var windowHeight = divToScroll.ownerDocument.defaultView.innerHeight;

        if (clientRect.top < 0 && clientRect.bottom < 0) {
            //The div is scrolled above, move the bottom into view
            divToScroll.scrollIntoView(false);
        } else if (clientRect.top > windowHeight && clientRect.bottom > windowHeight) {
            //The div is scrolled below, move top into view
            divToScroll.scrollIntoView(true);
        }
    };

    self.syncSelectedDiv = function () {
        if (self.data.selectedUnderlyingDivIndex < 0) {
            return;
        }
        var selectedOverlayDiv = self.getSelectedOverlayDiv();
        if (!selectedOverlayDiv) {
            self.selectDiv(null);
            return;
        }
        var underlyingDiv = self.getSelectedUnderlyingDiv();
        if (!underlyingDiv) {
            self.selectDiv(null);
            return;
        }
        var left = 'left:' + self.getPosition(underlyingDiv).left + 'px;';
        var top = 'top:' + self.getPosition(underlyingDiv).top + 'px;';
        var width = parseInt(underlyingDiv.offsetWidth, 10);
        width = width - (2 * self.consts.selectedBorderWidth); // deflate the width
        var height = parseInt(underlyingDiv.offsetHeight, 10);
        height = height - (2 * self.consts.selectedBorderWidth); // deflate the height
        var offsetWidth = 'width:' + width + 'px;';
        var offsetHeight = 'height:' + height + 'px;';
        var background = self.getBackgroundStringForDiv(selectedOverlayDiv);
        var border = "border:" + self.consts.selectedBorder + ';';
        selectedOverlayDiv.style.cssText = top + left + offsetWidth + offsetHeight + background + border;

        var dragDiv = self.refs.draggingDiv;
        // Let the draggingDiv be whatever width the user indicates with the mouse.
        dragDiv.style.height = underlyingDiv.offsetHeight - (self.consts.draggingBorderWidth * 2) + 'px';
        self.ensureSelectedDivStillVisible();
        self.ensureSelectedDivResizable();
    };

    self.selectDiv = function (overlayDiv) {
        // !! call selectDiv when you know there is a valid underlyingDiv as self.isDivMisaligned() is called which accesses that.!!
        var isSameDiv = (overlayDiv && overlayDiv === self.getSelectedOverlayDiv());
        if (!isSameDiv) {
            //If we're changing selection, ensure we aren't still listening to mouse moves
            //incase the mouseup is somehow not balanced
            self.removeDragEditingEventListeners();
        }
        if (self.data.selectedUnderlyingDivIndex >= 0) {
            // Unselect the currently selected div.
            var selectedOverlayDiv = self.getSelectedOverlayDiv(),
                newBorder;
            if (selectedOverlayDiv) {
                self.setBackgroundForDiv(selectedOverlayDiv);
            }
            // We used to remove the translated attr 'hiliteChildrenOnSelect' here, but that
            // would make it so a user could not pop up the table column context menu, so
            // we don't remove it anymore.		
            self.data.selectedUnderlyingDivIndex = -1;
        }
        if (overlayDiv) {
            self.data.selectedUnderlyingDivIndex = self.getUnderlyingDivIndex(overlayDiv.underlyingDiv);
            self.setBackgroundForDiv(overlayDiv);
        }

        self.ensureSelectedDivResizable();
        self.positionControls();
        if (self.flags.showProtos) {
            self.proto_showPi(overlayDiv !== null);
        }
    };

    self.positionControls = function () {
        self.positionEdge('resize');
        self.positionEdge('shift');
        self.positionMarginDiv();
        self.positionMoveUpDownDiv();
        self.positionRemainingOverlayControls(); // hide, starts row, duplicate and delete
    };

    self.positionEdge = function (whichEdge) {
        // Position edge div.
        var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
        var edgeDiv = whichEdge === 'resize' ? self.refs.resizeEdgeDiv : self.refs.shiftEdgeDiv;
        if (!selectedElement || selectedElement.getAttribute('data-isHidden') === 'true') {
            if (self.flags.isDragging) {
                self.log("Attemping to hide the edges while dragging");
            } else {
                self.hideDiv(edgeDiv);
                self.hideDiv(self.refs.knobMiddle[whichEdge]);
            }
            return;
        }

        var selectedOverlayDiv = self.getSelectedOverlayDiv();
        if (!selectedOverlayDiv) {
            return;
        }
        var edgeDivStyle = edgeDiv.style;
        var selectedOverlayDivStyle = selectedOverlayDiv.style;
        // Left differs based on which edge we are positioning.					
        if (whichEdge === 'resize') {
            edgeDivStyle.left = parseInt(selectedOverlayDivStyle.left, 10) + self.consts.selectedBorderWidth +
						parseInt(selectedOverlayDivStyle.width, 10) + (self.consts.selectedBorderWidth / 2) - (self.consts.knobSize / 2) + 'px';
        } else {
            edgeDivStyle.left = parseInt(selectedOverlayDivStyle.left, 10) + (self.consts.selectedBorderWidth / 2) - (self.consts.knobSize / 2) + 'px';
        }
        // Top and height are the same whether we are positioning the resize or shift edge.
        edgeDivStyle.top = parseInt(selectedOverlayDivStyle.top, 10) - (self.consts.selectedBorderWidth / 2) + 'px';
        edgeDivStyle.height = parseInt(selectedOverlayDivStyle.height, 10) + (self.consts.selectedBorderWidth * 3) + 'px';
        // Position knob divs.
        self.refs.knobMiddle[whichEdge].style.top = parseInt(selectedOverlayDivStyle.top, 10) + (parseInt(selectedOverlayDivStyle.height, 10) / 2) +
								self.consts.selectedBorderWidth - (self.consts.knobSize / 2) + 'px';
        self.refs.knobMiddle[whichEdge].style.left = edgeDivStyle.left;

        var tooltip = '';
        if (self.globalController) { //load tooltip
            tooltip = whichEdge === 'resize' ? self.globalController.getLocalizedString('Resize_Tooltip') : self.globalController.getLocalizedString('Shift_Tooltip');
        }
        tooltip = fgUtil.sprintf(tooltip, selectedElement.tagName + fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)); //format tooltip with tagname and ID
        edgeDiv.setAttribute('title', tooltip);

        self.showDiv(edgeDiv);
        self.showDiv(self.refs.knobMiddle[whichEdge]);
    };

    self.positionMarginDiv = function () {
        var marginDiv = self.refs.marginDiv;
        var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
        if (!selectedElement || selectedElement.getAttribute('data-isHidden') === 'true') {
            self.hideDiv(marginDiv);
            return;
        }
        var marginLeft = self.getCssPropValFromElem(selectedElement, "margin-left", "int");
        if (marginLeft === 0) {
            self.hideDiv(marginDiv);
            return;
        }
        var selectedUnderlyingDiv = self.getSelectedUnderlyingDiv();
        if (!selectedUnderlyingDiv) {
            return;
        }
        marginDiv.style.top = self.getPosition(selectedUnderlyingDiv).top + 'px';
        marginDiv.style.height = selectedUnderlyingDiv.offsetHeight + 'px';
        marginDiv.style.width = marginLeft + 'px';
        marginDiv.style.left = self.getPosition(selectedUnderlyingDiv).left - marginLeft + 'px';
        self.showDiv(marginDiv);
    };

    self.positionMoveUpDownDiv = function () {
        var selectedFGElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
        if ((!selectedFGElement) || selectedFGElement.getAttribute('data-isHidden') === 'true') {
            self.hideDiv(self.refs.moveUpDiv);
            self.hideDiv(self.refs.moveDownDiv);
            return;
        }
        var selectedOverlayDiv = self.getSelectedOverlayDiv();
        if (!selectedOverlayDiv) {
            return;
        }
        var tooltip = '';

        var prevSibling = self.getPreviousVisibleSibling(selectedFGElement, false);
        if (prevSibling !== null) {
            if (self.globalController) { //load tooltip
                tooltip = self.globalController.getLocalizedString('Moveup_Tooltip');
            }
            var prevSiblingInfo = prevSibling.tagName;
            var previousSiblingSelector = fgUtil.getValidFluidGridSelector(prevSibling, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
            if (previousSiblingSelector) {
                prevSiblingInfo += previousSiblingSelector;
            }
            tooltip = fgUtil.sprintf(tooltip, selectedFGElement.tagName + fgUtil.getValidFluidGridSelector(selectedFGElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE), prevSiblingInfo); //format tooltip with tagname and ID of selected and previous element
            self.refs.moveUpDiv.setAttribute('title', tooltip);
            var moveUpDivPos = self.calcMoveUpButtonPos(selectedOverlayDiv);
            self.refs.moveUpDiv.style.left = moveUpDivPos.left + 'px';
            self.refs.moveUpDiv.style.top = moveUpDivPos.top + 'px';
            self.showDiv(self.refs.moveUpDiv);
        } else {
            self.hideDiv(self.refs.moveUpDiv);
        }

        var nextSibling = self.getNextVisibleSibling(selectedFGElement, false);
        if (nextSibling !== null) {
            if (self.globalController) { //load tooltip
                tooltip = self.globalController.getLocalizedString('Movedown_Tooltip');
            }
            var nextSiblingInfo = nextSibling.tagName;
            var nextSiblingSelector = fgUtil.getValidFluidGridSelector(nextSibling, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
            if (nextSiblingSelector) {
                nextSiblingInfo += nextSiblingSelector;
            }
            tooltip = fgUtil.sprintf(tooltip, selectedFGElement.tagName + fgUtil.getValidFluidGridSelector(selectedFGElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE), nextSiblingInfo); //format tooltip with tagname and ID of selected and next element
            self.refs.moveDownDiv.setAttribute('title', tooltip);
            var moveDownDivPos = self.calcMoveDownButtonPos(selectedOverlayDiv);
            self.refs.moveDownDiv.style.left = moveDownDivPos.left + 'px';
            self.refs.moveDownDiv.style.top = moveDownDivPos.top + 'px';
            self.showDiv(self.refs.moveDownDiv);
        } else {
            self.hideDiv(self.refs.moveDownDiv);
        }
    };

    self.positionRemainingOverlayControls = function () {
        // This method will position the following icons :- Hide, Start Row Toggle, Duplicate and Delete.
        // The above buttons will positioned together.
        var pos;
        var buttonGroupSize;
        if (self.data.selectedUnderlyingDivIndex < 0) {
            self.hideDiv(self.refs.startsRowDiv);
            self.hideDiv(self.refs.duplicateDiv);
            self.hideDiv(self.refs.deleteDiv);
            self.hideDiv(self.refs.hideDiv);
            self.hideDiv(self.refs.showDiv);
            return;
        }
        var selectedOverlayDiv = self.getSelectedOverlayDiv();
        if (!selectedOverlayDiv) {
            return;
        }

        //if it is a hidden div, show only 'Show' button
        var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
        if (selectedElement && selectedElement.getAttribute('data-isHidden') === 'true') {
            self.hideDiv(self.refs.startsRowDiv);
            self.hideDiv(self.refs.duplicateDiv);
            self.hideDiv(self.refs.deleteDiv);
            self.hideDiv(self.refs.hideDiv);

            pos = self.calcButtonGroupPos(selectedOverlayDiv, 1);
            self.positionShowDiv({ top: pos.top, left: pos.left });
            return;
        }

        self.hideDiv(self.refs.showDiv);
        var positionStartsRowDiv = self.positionMisalignedDivIcon(selectedOverlayDiv); // this will position the starts row icon if div is misaligned

        if (typeof positionStartsRowDiv === "undefined") {
            positionStartsRowDiv = false; // something is wrong. try other controls
        }

        buttonGroupSize = 3; // Hide, Duplicate and Delete
        if (positionStartsRowDiv) {
            buttonGroupSize = 4; // Position the Starts Row Div as well
        }

        pos = self.calcButtonGroupPos(selectedOverlayDiv, buttonGroupSize);
        // position the entire button group
        self.positionHideDiv({ top: pos.top, left: pos.left });
        var offsetmul = 1;
        if (positionStartsRowDiv) {
            self.positionStartsRowDiv({ top: pos.top, left: pos.left + offsetmul * self.consts.hudButtonWidth });
            offsetmul++;
        }
        self.positionDuplicateDiv({ top: pos.top, left: pos.left + offsetmul * self.consts.hudButtonWidth });
        offsetmul++;
        self.positionDeleteDiv({ top: pos.top, left: pos.left + offsetmul * self.consts.hudButtonWidth });
        
    };

    self.positionDeleteDiv = function (pos) {
        if (self.data.selectedUnderlyingDivIndex < 0) {
            self.hideDiv(self.refs.deleteDiv);
            return;
        }
        var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div
        if (selectedElement) {
            var selector = fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
            var tooltip = '';
            if (selector) {
                if ((selector.charAt(0) === '#') && (self.globalController)) {
                    tooltip = self.globalController.getLocalizedString('DeleteHTMLAndCSS_Tooltip'); //load tooltip
                } else if ((selector.charAt(0) === '.') && (self.globalController)) {
                    tooltip = self.globalController.getLocalizedString('DeleteHTML_Tooltip'); //load tooltip
                }
            }
            tooltip = fgUtil.sprintf(tooltip, selectedElement.tagName + selector); //format tooltip with tagname and ID
            self.refs.deleteDiv.setAttribute('title', tooltip);
            self.refs.deleteDiv.style.left = pos.left + 'px';
            self.refs.deleteDiv.style.top = pos.top + 'px';
            self.showDiv(self.refs.deleteDiv);
        }
    };

    self.positionDuplicateDiv = function (pos) {
        var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div       
        if (!selectedElement) {
            self.hideDiv(self.refs.duplicateDiv);
            return;
        }
        
        var tooltip = '';
        if (self.globalController) { //load tooltip
            tooltip = self.globalController.getLocalizedString('Duplicate_Tooltip');
        }
        tooltip = fgUtil.sprintf(tooltip, selectedElement.tagName + fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE));
        self.refs.duplicateDiv.setAttribute('title', tooltip);
        self.refs.duplicateDiv.style.left = pos.left + 'px';
        self.refs.duplicateDiv.style.top = pos.top + 'px';
        self.showDiv(self.refs.duplicateDiv);
    };

    self.positionMisalignedDivIcon = function (selectedOverlayDiv) {
        if (self.data.selectedUnderlyingDivIndex < 0) {
            self.hideDiv(self.refs.startsRowDiv);
            return;
        }

        var isFirstOverlayDiv = selectedOverlayDiv === self.refs.overlayDivs[0];
        if (isFirstOverlayDiv && !self.isDivMisaligned(selectedOverlayDiv)) {
            // Don't show 'move up a row' button if this is the very first div and it's not misaligned.
            self.hideDiv(self.refs.startsRowDiv);
            return false; // need not be positioned
        }

        if (!self.startsNewRow(selectedOverlayDiv.underlyingDiv)) {
            if (self.isDivMisaligned(selectedOverlayDiv)) {
                self.refs.startsRowDiv.setAttribute('class', 'moveLeft centerImage');
                self.refs.startsRowDiv.style.backgroundColor = 'transparent';
                var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);  // get the underlying div
                if (selectedElement) {
                    var selector = fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE);
                    var tooltip = '';
                    if (selector.charAt(0) === '.') {
                        var curDevice = self.getDeviceFromWindowWidth();
                        var zeroMarginClass = '.zeroMargin_' + curDevice;
                        if (self.globalController) { //load tooltip
                            tooltip = self.globalController.getLocalizedString('ZeroMargin_Tooltip');
                        }
                        tooltip = fgUtil.sprintf(tooltip, zeroMarginClass);
                    } else {
                        if (self.globalController) { //load tooltip
                            tooltip = self.globalController.getLocalizedString('ClickToAlign_Tooltip');
                        }
                        tooltip = fgUtil.sprintf(tooltip, selectedElement.tagName + fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)); //format tooltip with tagname and ID
                    }
                    self.refs.startsRowDiv.setAttribute('title', tooltip);
                    var pos = self.calcButtonPos('top', 'left', selectedOverlayDiv);
                    self.refs.startsRowDiv.style.left = pos.left + 'px';
                    self.refs.startsRowDiv.style.top = pos.top + 'px';
                    self.showDiv(self.refs.startsRowDiv);
                    return false; // ask not to position again
                }
            }
        }
        return true; // position the start row div
    };

    self.positionStartsRowDiv = function (pos) {
        if (self.data.selectedUnderlyingDivIndex < 0) {
            self.hideDiv(self.refs.startsRowDiv);
            return;
        }

        if (self.startsNewRow(self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex))) {
            self.refs.startsRowDiv.setAttribute('class', 'moveUpRow centerImage');
            self.refs.startsRowDiv.setAttribute('title', 'overlay/fluidGridLayout/moveUpRow/tooltip');
        } else { // Does not start new row
            self.refs.startsRowDiv.setAttribute('class', 'startNewRow centerImage');
            self.refs.startsRowDiv.setAttribute('title', 'overlay/fluidGridLayout/startNewRow/tooltip');
        }

        self.refs.startsRowDiv.style.backgroundColor = self.consts.hudBgColor;
        self.refs.startsRowDiv.style.left = pos.left + 'px';
        self.refs.startsRowDiv.style.top = pos.top + 'px';
        self.showDiv(self.refs.startsRowDiv);
    };

    self.positionHideDiv = function (pos) {
        if (self.data.selectedUnderlyingDivIndex < 0) {
            self.hideDiv(self.refs.hideDiv);
            return;
        }

        var tooltip = '';
        var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div
        if (selectedElement) {
            if (self.globalController) { //load tooltip
                tooltip = self.globalController.getLocalizedString('Hide_Tooltip');
            }
            tooltip = fgUtil.sprintf(tooltip, selectedElement.tagName + fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)); //format tooltip with tagname and ID
            self.refs.hideDiv.setAttribute('title', tooltip);
            self.refs.hideDiv.style.left = pos.left + 'px';
            self.refs.hideDiv.style.top = pos.top + 'px';
            self.showDiv(self.refs.hideDiv);
        }
    };

    self.positionShowDiv = function (pos) {
        if (self.data.selectedUnderlyingDivIndex < 0) {
            self.hideDiv(self.refs.showDiv);
            return;
        }

        var tooltip = '';
        var selectedElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex); // get the underlying div
        if (selectedElement) {
            if (self.globalController) { //load tooltip
                tooltip = self.globalController.getLocalizedString('Show_Tooltip');
            }
            tooltip = fgUtil.sprintf(tooltip, selectedElement.tagName + fgUtil.getValidFluidGridSelector(selectedElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)); //format tooltip with tagname and ID
            self.refs.showDiv.setAttribute('title', tooltip);
            self.refs.showDiv.style.left = pos.left + 'px';
            self.refs.showDiv.style.top = pos.top + 'px';
            self.showDiv(self.refs.showDiv);
        }
    };

    self.doRectsCollide = function (rect1, rect2) {
        // rect should be x,y,width,height. x,y should be top left coordinates
        // validate that no corner of rect1 lies in rect2
        if (self.pointContainedInRect({ x: rect1.x, y: rect1.y }, rect2)) {
            return true;
        }
        if (self.pointContainedInRect({ x: rect1.x + rect1.width, y: rect1.y }, rect2)) {
            return true;
        }
        if (self.pointContainedInRect({ x: rect1.x, y: rect1.y + rect1.height }, rect2)) {
            return true;
        }
        if (self.pointContainedInRect({ x: rect1.x + rect1.width, y: rect1.y + rect1.height }, rect2)) {
            return true;
        }
        if (self.pointContainedInRect({ x: rect2.x, y: rect2.y }, rect1)) {
            return true;
        }
        if (self.pointContainedInRect({ x: rect2.x + rect2.width, y: rect2.y }, rect1)) {
            return true;
        }
        if (self.pointContainedInRect({ x: rect2.x, y: rect2.y + rect2.height }, rect1)) {
            return true;
        }
        if (self.pointContainedInRect({ x: rect2.x + rect2.width, y: rect2.y + rect2.height }, rect1)) {
            return true;
        }

        return false;
    };

    self.pointContainedInRect = function (point, rect) {
        if (point.x >= rect.x && point.x <= rect.x + rect.width) {
            if (point.y >= rect.y && point.y <= rect.y + rect.height) {
                return true;
            }
        }

        return false;
    };

    self.calcButtonPos = function (whereVert, whereHorz, selectedOverlayDiv) {
        var selectedOverlayDivStyle = selectedOverlayDiv.style;
        var left, top;
        if (whereHorz === 'left') {
            left = parseInt(selectedOverlayDivStyle.left, 10) - self.consts.hudButtonWidth + self.consts.startsNewRowButtonGapSize;
        } else if (whereHorz === 'right') {
            left = parseInt(selectedOverlayDivStyle.left, 10) + parseInt(selectedOverlayDivStyle.width, 10) - self.consts.hudButtonWidth + self.consts.selectedBorderWidth;
        }
        if (whereVert === 'top') {
            top = parseInt(selectedOverlayDivStyle.top, 10) + (self.consts.selectedBorderWidth / 2) - (self.consts.knobSize / 2);
        } else if (whereVert === 'bottom') {
            top = parseInt(selectedOverlayDivStyle.top, 10) + parseInt(selectedOverlayDivStyle.height, 10) + self.consts.startsNewRowButtonGapSize + 3 * self.consts.selectedBorderWidth / 2; // not sure why
        }
        return { top: top, left: left };
    };

    self.calcButtonGroupPos = function (selectedOverlayDiv, buttonGroupSize) {
        if (typeof buttonGroupSize === "undefined") {// cant calculate valid position without proper button info.
            return { top: 0, left: 0 };            // return default value
        }
        var widthOfGroup = buttonGroupSize * self.consts.hudButtonWidth;
        // determine the basic position which should work most cases
        var selectedOverlayDivStyle = selectedOverlayDiv.style;
        var overlayDivWidth = parseInt(selectedOverlayDivStyle.width, 10);
        var overlayDivLeft = parseInt(selectedOverlayDivStyle.left, 10);
        var overlayDivHeight = parseInt(selectedOverlayDivStyle.height, 10);
        var overlayDivTop = parseInt(selectedOverlayDivStyle.top, 10);
        
        var left = 0;
        var top = 0;
        
        if (widthOfGroup < overlayDivWidth || (overlayDivLeft + widthOfGroup > self.refs.window.innerWidth)) { // If the overlayDiv is wider than the button group,align to the right of the selected div. Else align to the left
            left = overlayDivLeft + overlayDivWidth - widthOfGroup + 2;
        } else {
            left = overlayDivLeft;
        }
        //If there's space at the bottom, and the ESH cannot possibly overlap with us at the bottom, come at the bottom.
        //If there's no space at the bottom and the ESH cannot possibly overlap with us at the top, come at the top.
        //If the ESH overlaps with us and we cannot come at the top or the bottom, if we have space on the right, come on the right.
        //Else come on the left.
        
        if (overlayDivTop + overlayDivHeight + self.consts.hudButtonHeight < self.refs.window.innerHeight) {
            if (overlayDivTop < self.consts.VerticalPaddingToAvoidESHOverlap && (overlayDivWidth < self.consts.HorizontalPaddingToAvoidESHOverlap + widthOfGroup) && (overlayDivLeft + overlayDivWidth + widthOfGroup < self.refs.window.innerWidth)) {
                left = overlayDivLeft + overlayDivWidth + self.consts.FGCornerCaseHorizontalPadding;
                top = overlayDivTop;
            } else if (overlayDivTop < self.consts.VerticalPaddingToAvoidESHOverlap &&  overlayDivWidth < self.consts.HorizontalPaddingToAvoidESHOverlap + widthOfGroup) {
                left = overlayDivLeft - widthOfGroup - self.consts.FGCornerCaseHorizontalPadding;
                top = overlayDivTop;
            } else {
                top = parseInt(selectedOverlayDivStyle.top, 10) + parseInt(selectedOverlayDivStyle.height, 10) + 1;
            }
        } else {
            if ((overlayDivWidth < self.consts.HorizontalPaddingToAvoidESHOverlap + widthOfGroup) && (overlayDivLeft + overlayDivWidth + widthOfGroup < self.refs.window.innerWidth)) {
                left = overlayDivLeft + overlayDivWidth + self.consts.FGCornerCaseHorizontalPadding;
                top = overlayDivTop + overlayDivHeight - self.consts.hudButtonHeight;
            } else if (overlayDivWidth < self.consts.HorizontalPaddingToAvoidESHOverlap + widthOfGroup) {
                left = overlayDivLeft - widthOfGroup - self.consts.FGCornerCaseHorizontalPadding;
                top = overlayDivTop + overlayDivHeight - self.consts.hudButtonHeight;
            } else {
                top = overlayDivTop - self.consts.hudButtonHeight;
            }
        }
        
        //If a div occupies the full height of the viewport, move the FG Hud inside
        if (overlayDivHeight >= self.refs.window.innerHeight - self.consts.hudButtonHeight) {
            left = overlayDivLeft + overlayDivWidth - widthOfGroup + 2;
            top = overlayDivHeight - self.consts.hudButtonHeight;
        }

        // determine the move up down button group pos which runs a chance of overlap
        var moveDownDivStyle = self.refs.moveDownDiv.style; // get the move down button styles
        var moveUpDivStyle = self.refs.moveUpDiv.style;     // get the move up button styles
        var moveGroupPos = { left: 0, right: 0 };
        var moveButtonPosValid = true;
        if (moveDownDivStyle.display !== 'none') { // this will determine the right pos of the move group
            moveGroupPos.right = parseInt(moveDownDivStyle.left, 10) + parseInt(moveDownDivStyle.width, 10);
            if (moveUpDivStyle.display !== 'none') {
                moveGroupPos.left = parseInt(moveUpDivStyle.left, 10);
            } else {
                moveGroupPos.left = parseInt(moveDownDivStyle.left, 10);
            }
        } else if (moveUpDivStyle.display !== 'none') {
            moveGroupPos.right = parseInt(moveUpDivStyle.left, 10) + parseInt(moveUpDivStyle.width, 10);
            moveGroupPos.left = parseInt(moveUpDivStyle.left, 10);
        } else {// both the move up and move down buttons are hidden
            moveButtonPosValid = false;
        }

        if (moveButtonPosValid) { // align according to the left right values here. period.
            if ((left + widthOfGroup) > self.refs.window.screen.availWidth + self.refs.document.body.scrollLeft) { // the right position goes over the window
                left = moveGroupPos.left - widthOfGroup - self.consts.startsNewRowButtonGapSize; // shift to the left of the move button group
            }
            // at this point it cannot be left < 0
        } else { // neither move button is being shown
            // its aligned to the right of the selected div; hence cant go over the right edge
            // check if it goes over the left edge
            if (left < 0) {
                left = parseInt(selectedOverlayDivStyle.left, 10);
            }
        }
        
        if (left < 0) {
            //This is the case when fg hud appears in the non visible area- to the left of the doc
            top = overlayDivTop + overlayDivHeight + 1;
            var gridwidth = self.getGridContainerWidth();
            var newleft = (self.refs.window.innerWidth + gridwidth) / 2 - widthOfGroup;
            left = (newleft > overlayDivLeft + overlayDivWidth) ? overlayDivLeft + overlayDivWidth : newleft;
        }

        return { top: top, left: left };
    };

    self.calcMoveUpButtonPos = function (selectedOverlayDiv) {
        var selectedOverlayDivStyle = selectedOverlayDiv.style;
        var left = (parseInt(selectedOverlayDivStyle.left, 10) + parseInt(selectedOverlayDivStyle.width, 10) / 2) - self.consts.moveUpDownButtonWidth / 2;
        var top = parseInt(selectedOverlayDivStyle.top, 10);
        return { top: top, left: left };
    };

    self.calcMoveDownButtonPos = function (selectedOverlayDiv) {
        var selectedOverlayDivStyle = selectedOverlayDiv.style;
        var divPos = self.calcMoveUpButtonPos(selectedOverlayDiv);
       
        divPos.top = parseInt(selectedOverlayDivStyle.top, 10) + parseInt(selectedOverlayDivStyle.height, 10) - parseInt(self.refs.moveDownDiv.style.height, 10) + 1;

        return divPos;
    };
    
    self.addDragEditingEventListeners = function (editType) {
		if (self.flags.logEvents) {
			self.log('addDragEditingEventListeners');
		}
		if (editType === 'resizing') {
			self.refs.document.addEventListener('mousemove', self.onMouseMove_Resizing);
			self.refs.document.addEventListener('mouseup', self.onMouseUp_Resizing);
            self.refs.window.parent.document.addEventListener('mousemove', self.onMouseMove_Resizing);
			self.refs.window.parent.document.addEventListener('mouseup', self.onMouseUp_Resizing);
		}
		if (editType === 'shifting') {
			self.refs.document.addEventListener('mousemove', self.onMouseMove_Shifting);
			self.refs.document.addEventListener('mouseup', self.onMouseUp_Shifting);
            self.refs.window.parent.document.addEventListener('mousemove', self.onMouseMove_Shifting);
			self.refs.window.parent.document.addEventListener('mouseup', self.onMouseUp_Shifting);
		}
	};
    
	self.removeDragEditingEventListeners = function () {
		if (self.flags.logEvents) {
			self.log('removeDragEditingEventListeners');
		}
		self.refs.document.removeEventListener('mousemove', self.onMouseMove_Resizing);
		self.refs.document.removeEventListener('mouseup', self.onMouseUp_Resizing);
		self.refs.document.removeEventListener('mousemove', self.onMouseMove_Shifting);
		self.refs.document.removeEventListener('mouseup', self.onMouseUp_Shifting);
        self.refs.window.parent.document.removeEventListener('mousemove', self.onMouseMove_Resizing);
		self.refs.window.parent.document.removeEventListener('mouseup', self.onMouseUp_Resizing);
		self.refs.window.parent.document.removeEventListener('mousemove', self.onMouseMove_Shifting);
		self.refs.window.parent.document.removeEventListener('mouseup', self.onMouseUp_Shifting);
        
		self.flags.isDragging = false;
		self.flags.isResizing = false;
		self.flags.isShifting = false;
		self.refs.cssRuleBeingModified = null;
	};
    
    //Makes sure the right edge resizes are always visible
    self.ensureSelectedDivResizable = function () {
        var selectedOverlayDiv = self.getSelectedOverlayDiv();
        if (!selectedOverlayDiv) {
            return;
        }
        //verify that the right edge is alway accessible and not wider than what a user can scroll to
        var minWidthNeeded = self.refs.document.documentElement.scrollWidth - (self.consts.selectedBorderWidth * 2 + self.consts.knobSize / 2);
        var unscrollableSize = (parseInt(selectedOverlayDiv.style.left, 10) + parseInt(selectedOverlayDiv.style.width, 10)) - minWidthNeeded;
        if (unscrollableSize > 0) {
            selectedOverlayDiv.style.width = (parseInt(selectedOverlayDiv.style.width, 10) - unscrollableSize) + "px";
        }
    };

    self.setUserNodeSelected = function (setEvenInDesignView) {
        var userNode = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
        if (!userNode) {
            return;
        }
        
        var liveCodeHiddenId = userNode.getAttribute(self.consts.DWUniqueId);
        if (liveCodeHiddenId) { window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "selectNode",
            {
                liveCodeHiddenId : liveCodeHiddenId
            }, true);
            }

    };
    
    self.onResourceLoadingCompleted_LiveView = function (evt) {
        var onResourceLoadingCompleted_LiveView_Worker = function (evt) {
            if (self.flags.logEvents) {
                self.log('onResourceLoadingCompleted_LiveView');
            }
			if (self.textEditing) {
				return;
			}
            self.refresh('gridAndRediscoverUnderlyingDivs', true);
            var underlyingDiv = self.getSelectedUnderlyingDiv();
            if (underlyingDiv) {
                var range = document.createRange();
                var selectedOverlayDiv = self.getSelectedOverlayDiv();
                self.selectDiv(selectedOverlayDiv);
                range.selectNode(underlyingDiv);
            }
            self.positionExtension();
        };
        setTimeout(onResourceLoadingCompleted_LiveView_Worker.bind(null, evt), 30);
    };

    self.onSelectionChanged = function (evt) {
        if (this.textEditing) {
            return;
        }
        
        //We should not show the controls if some text is selected
        if (parent.IsMarqueeSelected()) {
            self.selectDiv(null);
            return;
        }
        
        liveViewObject.showExtensionById(self.consts.extensionID); // ensure the extension is shown
        var curSelectedElement = liveViewObject.getCurrentSelectedElement();
        if (curSelectedElement !== self.getSelectedUnderlyingDiv()) {
            var selectedOverlayDiv;
            var isHiddenChild = self.isDescendantOfHiddenElement(curSelectedElement);
            if (isHiddenChild) {
                var hiddenParent = self.getHiddenAncestor(curSelectedElement);
                selectedOverlayDiv = self.getOverlayDiv(hiddenParent);
                var liveCodeHiddenId = hiddenParent.getAttribute(self.consts.DWUniqueId);
                if (liveCodeHiddenId) { window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "selectHiddenParent",
                    {
                        liveCodeHiddenId : liveCodeHiddenId
                    }, true);
                    }
            } else {
                selectedOverlayDiv = self.getOverlayDiv(curSelectedElement);
            }
            self.selectDiv(selectedOverlayDiv);
            //if selection is coming from Code View, we should not set the selection back.
            if (parent.document.hasFocus()) {
                self.setUserNodeSelected();
            }
            var isFluid = false;
            if (self.data.selectedUnderlyingDivIndex >= 0) {
                var fgElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
                if (fgUtil.validFluidElement(fgElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)) {
                    isFluid = true;
                }
            }
            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "setElementFluidity", { isFluid: isFluid }, false);
        }
    };
    
    self.textEditBegin = function () {
		window.parent.globalController.reloadOverlayStyles();
        liveViewObject.hideExtensionById(self.consts.extensionID);
        this.textEditing = true;
        var isFluid = false;
        window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "setElementFluidity", { isFluid: isFluid }, false);
    };
    
    self.textEditEnd = function () {
        this.textEditing = false;
        var isFluid = false;
        if (self.data.selectedUnderlyingDivIndex >= 0) {
            var fgElement = self.getUnderlyingDivByIndex(self.data.selectedUnderlyingDivIndex);
            if (fgUtil.validFluidElement(fgElement, self.rulesInTheFile, self.consts.CSS_STYLE_RULE, self.consts.CSS_MEDIA_RULE)) {
                isFluid = true;
            }
        }
        window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "setElementFluidity", { isFluid: isFluid }, false);
        self.refresh('gridAndRediscoverUnderlyingDivs', false);
    };
    
    self.addEventListeners = function () {
        self.refs.window.addEventListener('resize', self.onResize_OverlayWindow);
        self.refs.document.addEventListener('mousedown', self.onMouseDown_OverlayDocument);
        dwExtensionController.addListener(DW_EXTENSION_EVENT.STYLES_RELOADED, self.onResourceLoadingCompleted_LiveView, self);
        dwExtensionController.addListener(DW_EXTENSION_EVENT.PARTIAL_REFRESH_COMPLETE, self.onResourceLoadingCompleted_LiveView, self);
        dwExtensionController.addListener(DW_EXTENSION_EVENT.SELECTION_CHANGE, self.onSelectionChanged, self);
        dwExtensionController.addListener(DW_EXTENSION_EVENT.TOGGLE_UNHIDE_MODE, self.refreshUnhideMode, self);
        dwExtensionController.addListener(DW_EXTENSION_EVENT.CALL_FG_FUNCTION, self.callFGFunction, self);
        dwExtensionController.addListener(DW_EXTENSION_EVENT.TEXT_EDIT_BEGIN, this.textEditBegin, this);
        dwExtensionController.addListener(DW_EXTENSION_EVENT.TEXT_EDIT_END, this.textEditEnd, this);
    };

    self.removeEventListeners = function () {
        self.refs.window.removeEventListener('resize', self.onResize_OverlayWindow);
        self.refs.document.removeEventListener('mousedown', self.onMouseDown_OverlayDocument);
        dwExtensionController.removeListener(DW_EXTENSION_EVENT.STYLES_RELOADED, self.onResourceLoadingCompleted_LiveView);
        dwExtensionController.removeListener(DW_EXTENSION_EVENT.PARTIAL_REFRESH_COMPLETE, self.onResourceLoadingCompleted_LiveView);
        dwExtensionController.removeListener(DW_EXTENSION_EVENT.CALL_FG_FUNCTION, self.callFGFunction);
        dwExtensionController.removeListener(DW_EXTENSION_EVENT.TOGGLE_UNHIDE_MODE, self.refreshUnhideMode);
        dwExtensionController.removeListener(DW_EXTENSION_EVENT.SELECTION_CHANGE, self.onSelectionChanged);
        dwExtensionController.removeListener(DW_EXTENSION_EVENT.TEXT_EDIT_BEGIN, this.textEditBegin);
        dwExtensionController.removeListener(DW_EXTENSION_EVENT.TEXT_EDIT_END, this.textEditEnd);
    };
    
    self.createOverlayControls = function () {
        self.createDraggingDiv();
        self.createEdgeDiv('resize');
        self.createEdgeDiv('shift');
        self.createMarginDiv();
        self.createStartsRowDiv();
        self.createDynamicTooltipDiv();
        self.createDuplicateDiv();
        self.createDeleteDiv();
        self.createHideDiv();
        self.createMoveUpDownDiv();
        self.createShowDiv();
    };

    self.createEdgeDiv = function (whichEdge) {
        // Edge div
        var edgeDiv = self.refs.document.createElement('div');
        edgeDiv.style.position = 'absolute';
        edgeDiv.style.zIndex = 10001; // z-index one higher than the knobs.
        edgeDiv.style.width = self.consts.knobSize + 'px';
        edgeDiv.style.cursor = 'ew-resize';
        edgeDiv.style.webkitUserSelect = "none";
        edgeDiv.style.pointerEvents = "auto";
        edgeDiv.addEventListener('mousedown', whichEdge === 'resize' ? self.onMouseDown_Resizing : self.onMouseDown_Shifting);
        self.refs[whichEdge === 'resize' ? 'resizeEdgeDiv' : 'shiftEdgeDiv'] = edgeDiv;
        self.hideDiv(edgeDiv);
        self.refs.document.body.appendChild(edgeDiv);
        // Knob divs (overlaid by edge div)
        self.refs.knobMiddle[whichEdge] = self.createKnobDiv();
    };

    self.createKnobDiv = function () {
        var knobDiv = self.refs.document.createElement('div');
        knobDiv.style.position = 'absolute';
        knobDiv.style.zIndex = 10000;
        knobDiv.style.width = self.consts.knobSize + 'px';
        knobDiv.style.height = self.consts.knobSize + 'px';
        knobDiv.style.backgroundImage = "url(assets/images/Resize_Handle.svg)";
        knobDiv.style.backgroundSize = "12px 12px";
        knobDiv.style.backgroundRepeat = "no-repeat";
        knobDiv.style.webkitUserSelect = "none";
        knobDiv.style.pointerEvents = "auto";
        self.hideDiv(knobDiv);
        self.refs.document.body.appendChild(knobDiv);
        return knobDiv;
    };

    self.createDraggingDiv = function () {
        var draggingDiv = self.refs.document.createElement('div');
        draggingDiv.style.position = 'absolute';
        draggingDiv.style.border = self.consts.draggingBorder;
        self.hideDiv(draggingDiv);
        self.refs.document.body.appendChild(draggingDiv);
        self.refs.draggingDiv = draggingDiv;
    };

    self.createDynamicTooltipDiv = function () {
        var div = self.refs.document.createElement('div');
        div.style.position = 'absolute';
        div.style.backgroundColor = 'white';
        div.style.borderRadius = '10px';
        div.style.padding = '10px';
        div.style.border = '1px solid #ccc';
        div.style.fontFamily = 'Arial';
        div.style.fontSize = '10pt';
        div.style.zIndex = 20000;
        self.hideDiv(div);
        self.refs.document.body.appendChild(div);
        self.refs.dynamicTooltipDiv = div;
    };

    self.createMarginDiv = function () {
        var marginDiv = self.refs.document.createElement('div');
        marginDiv.style.position = 'absolute';
        marginDiv.style.backgroundColor = self.consts.hudBgColor;
        marginDiv.style.opacity = '.3';
        self.refs.marginDiv = marginDiv;
        self.hideDiv(marginDiv);
        self.refs.document.body.appendChild(marginDiv);
    };

    self.createHUDDiv = function () {
        var newHUDDiv = self.refs.document.createElement('div');
        newHUDDiv.style.position = 'absolute';
        newHUDDiv.style.zIndex = 1000;
        newHUDDiv.style.cursor = 'pointer';
        newHUDDiv.style.width = self.consts.hudButtonWidth + 'px';
        newHUDDiv.style.height = self.consts.hudButtonHeight + 'px';
        newHUDDiv.style.backgroundColor = self.consts.hudBgColor;
        newHUDDiv.style.pointerEvents = "auto";
        self.hideDiv(newHUDDiv);
        self.refs.document.body.appendChild(newHUDDiv);
        return newHUDDiv;
    };

    self.createStartsRowDiv = function () {
        self.refs.startsRowDiv = self.createHUDDiv();
    };

    self.createHideDiv = function () {
        self.refs.hideDiv = self.createHUDDiv();
        self.refs.hideDiv.setAttribute('class', 'hideDiv centerImage');
    };

    self.createShowDiv = function () {
        self.refs.showDiv = self.createHUDDiv();
        self.refs.showDiv.setAttribute('class', 'showDiv centerImage');
    };

    self.createDuplicateDiv = function () {
        self.refs.duplicateDiv = self.createHUDDiv();
        self.refs.duplicateDiv.setAttribute('class', 'duplicateDiv centerImage');
    };

    self.createDeleteDiv = function () {
        self.refs.deleteDiv = self.createHUDDiv();
        self.refs.deleteDiv.setAttribute('class', 'deleteDiv centerImage');
    };

    self.createMoveUpDownDiv = function () {
        var moveUpDiv = self.refs.document.createElement('div');
        var moveDownDiv = self.refs.document.createElement('div');
        moveUpDiv.style.position = moveDownDiv.style.position = 'absolute';
        moveUpDiv.style.zIndex = moveDownDiv.style.zIndex = 1000;
        moveUpDiv.style.cursor = moveDownDiv.style.cursor = 'pointer';
        moveUpDiv.style.width = moveDownDiv.style.width = '20px';
        moveUpDiv.style.height = moveDownDiv.style.height = '12px';
        moveUpDiv.style.backgroundColor = moveDownDiv.style.backgroundColor = self.consts.hudBgColor;
        moveUpDiv.style.pointerEvents = moveDownDiv.style.pointerEvents = "auto";
        self.refs.moveDownDiv = moveDownDiv;
        self.refs.moveUpDiv = moveUpDiv;
        self.refs.moveUpDiv.setAttribute('class', 'moveUp centerImage');
        self.refs.moveDownDiv.setAttribute('class', 'moveDown centerImage');
        self.hideDiv(moveDownDiv);
        self.hideDiv(moveUpDiv);
        self.refs.document.body.appendChild(moveDownDiv);
        self.refs.document.body.appendChild(moveUpDiv);
    };

    self.showDiv = function (div) {
        div.style.display = '';
    };

    self.hideDiv = function (div) {
        div.style.display = 'none';
    };

    self.removeUnhideModeChanges = function () {
        var bodyElement = window.parent.document.body;
        if (!bodyElement) {
			return;
        }
        
		//we need to make all the originally hidden divs to 
		//their original state
		var underlyingDivs = self.nodeListToArray(bodyElement.getElementsByTagName(self.consts.tagName));
		underlyingDivs.forEach(function (underlyingDiv) {
			if (underlyingDiv.getAttribute('data-isHidden') === 'true') {
				underlyingDiv.removeAttribute('data-isHidden');
				underlyingDiv.style.display = '';
			}
		});
    };

    self.refresh = function (inWhatToRefresh, resetStyleSheet) {
        var whatToRefresh = inWhatToRefresh; // Can be changed by zoom logic in this function.
        if (self.flags.isRefreshing) {
            return;
        }
        if (typeof resetStyleSheet === "undefined") {
            resetStyleSheet = true;
        }
        //reset the style sheet
        if (resetStyleSheet) {
            self.parseFluidCSS();
        }
        self.flags.isRefreshing = true;
        // Update the grid.
        if (!self.flags.isDragging || whatToRefresh === "gridForBodyAdjustment") {
            // No need to recalc the grid col rects if we are dragging.
            // In fact, trying to yields peculiar results.
            self.data.gridColRects = self.getGridColRects();
        }
        if (self.data.gridColRects.length === 0) {
            self.flags.isRefreshing = false;
            return; // Can't refresh if we don't know the dimensions.
        }
        self.updateColumns();
        if (whatToRefresh === 'gridAndRediscoverUnderlyingDivs') {
            self.removeUnhideModeChanges();
            self.createOverlayDivs();
        }
        self.syncOverlayDivs();
        if (self.flags.showProtos) {
            self.proto_positionPi();
        }
        self.flags.isRefreshing = false;
    };
    self.focusGridContainer = function () {
        var gridContainerDiv = window.parent.document.getElementsByClassName('gridContainer');
        if (gridContainerDiv.length > 0) {
            var hiddenId = gridContainerDiv[0].getAttribute(self.consts.DWUniqueId);
            window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "selectNode", {liveCodeHiddenId : hiddenId, focusDocument: true }, true);
        }
    };
    self.init = function () {
        window.parent.DWLECallJsBridgingFunction("FluidGridBridger", "isInUnhideMode",
		    {
                callback: function (args) {
                    if (args) {
                        self.setIsInUnhideMode(args.inUnhideMode);
                        self.data.maxColumns = args.maxCol;
                    }
                    if (!self.flags.isInited) {
                        self.createOverlayControls();
                    }
					if (sessionStorage && !sessionStorage.getItem("pageLoadedEarlier")) {
						sessionStorage.setItem("pageLoadedEarlier", "true");
						self.focusGridContainer();
					}
                    self.parseFluidCSS();
                    self.refresh('gridAndRediscoverUnderlyingDivs', true);
                    self.positionExtension();
                    self.addEventListeners();
                    self.flags.isInited = true;
                    self.textEditing = false;
                    self.globalController = window.parent.globalController;
					self.onSelectionChanged();
                }
		    }, false);
    };
};

var timeOut = null;
window.parent.addEventListener("resize",  function () {
    "use strict";
    if (timeOut !== null) {
        clearTimeout(timeOut);
    }
    timeOut = setTimeout(function () {
        if (window.layoutDivManipulator) {
            window.layoutDivManipulator.removeEventListeners();
            window.layoutDivManipulator.init();
        }
    }, 100);
}, true);
