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

This is the generic Hud object.
Any of the new HUDS should derive from this class.

Member functions have three parts:

1. functions that needs to be DEFINITELY OVERRIDEN from base class in derived class 
    commit
    draw
    destroy
    escapekeyPressed
    ONLY THESE FOUR FUNCTIONS NEED TO BE IMPLEMENTED BY EACH OF THE HUD.

2. setter and getter functions for each of the member variables.
3. functions that are common across all HUDS
    
    initialize
    enableHud
    attachEventListeners
    detachEventListeners
    getElementRect
    showOverLayDiv
    hideOverLayDiv

Points to remember:

1.     events must be blocked from the HUDS after handling
2.    you can replicate on of the HUD implementation and replicate according to your needs.
3.     Any content that needs to be fetched from SM layer please attach them during LiveViewBridging.
4.     Better to call the parent functions from overriden derived class functions.(Keep all the tasks common to all HUDS in parent class)
5.    whenever we destroy the HUD content its better to clear the content of the Container than making Display:none to that Hud
6.     If the previous point hits the performance for your HUD. Initialize/cache the contents to m_HUD_html/m_HUD_css/m_HUD_js
*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50 */
/*global window, DW_LIVEEDIT_CONSTANTS, CustomEvent,DWLE_IMG_CLASS,DW_LIVEEDIT_EVENTS,globalController,DW_EDITABLE_REGION_CONSTS,Node*/

function GenericHUDObject(liveEditEvt) {
    'use strict';
    /*
        member variables
    */

    this.m_liveEditEvent = liveEditEvt; // LiveEditEventHandler. all huds must have a handle to it.

    this.m_initialized = false;
    this.m_hudName = "";
    this.m_hudContainer = null; // container for the HUD in which it has to populate its content
    this.m_hudContentDiv = null;
    this.m_overlayDiv = null; // OverlayDiv that needs to be displayed
    this.m_isVisible = false; // tells whether the HUD is Visible
    this.m_curElement = null; // Current Element that is being edited
    this.m_HUD_html = ""; // Individual HUD's html content
    this.m_HUD_css = ""; // Individual HUD's CSS content
    this.m_HUD_js = ""; // Individual HUD's Js content
    this.m_hudLeft = 0; // Hud's current Left position
    this.m_hudTop = 0; // Hud's current Top position
    this.m_editable = true;
    this.m_isInEditableRegion = false;
    this.locStrings = {};
}
//static members across HUDS 
/* 
this needs to be static member because our intension of introducing this variable is to make sure that 
liveedit state will not change irrespective of what hud is being shown when we get a popup which causes onkillfocus  
so this specific to whole liveedit instead of individual huds.
*/
GenericHUDObject.ignoreKillFocus = false; // state of ignore kill focus
/*
    Following member functions needs to be overridden by the derived classes DEFINITELY.
*/

/*
    Commit the changes the are done to the HUD
*/
GenericHUDObject.prototype.commit = function () {
    'use strict';
    window.writeDWLog("function begin : Generic HUD : commit");
};

/*
    draw HUD yourself
*/
GenericHUDObject.prototype.draw = function () {
    'use strict';

    window.writeDWLog("function begin : Generic HUD : draw");
    if (this.m_initialized === false || (this.m_hudContentDiv && this.m_hudContentDiv.innerHTML === "")) {
        window.writeDWLog("function begin : Generic HUD : content are requested for population");
        this.populateContent();
    }

};

GenericHUDObject.prototype.saveDocument = function () {
    'use strict';
    window.writeDWLog("function begin : Generic HUD : saveDocument");

};

GenericHUDObject.prototype.refreshDocument = function () {
    'use strict';
    window.writeDWLog("function begin : Generic HUD : refreshDocument");
};

// function that gets called on browser loosing focus
GenericHUDObject.prototype.OnViewLostFocus = function () {
    'use strict';

    window.writeDWLog("function begin : GenericHUDObject : OnViewLostFocus");

    if (this.getVisibility() === false || GenericHUDObject.ignoreKillFocus) {

        window.writeDWLog("function begin : Generic HUD : OnViewLostFocus is being ignored");
        return false;
    }
    return true;
};

// function for committing and closing the hud
GenericHUDObject.prototype.CommitAndCloseHud = function () {
    'use strict';

    window.writeDWLog("function begin : GenericHUDObject : CommitAndCloseHud");

    if (this.getVisibility() === true) {

        this.commit();
        this.destroy();
    }

};

//suspendEventPropogation - suspends click/dbl click event propogation for the input element.
GenericHUDObject.prototype.suspendEventPropogation = function (element) {
    'use strict';
    window.writeDWLog("function begin : GenericHUDObject : suspendEventPropogation");

    if (element) {
        element.addEventListener("mousedown",  function clickHander(event) {
            event.stopPropagation();
        }, true);
    } else {
        window.writeDWLog("error : GenericHUDObject : suspendEventPropogation failed");
    }
};

//resumeEventPropogation - resumes click/dbl click event propogation for the input element.
GenericHUDObject.prototype.resumeEventPropogation = function (element) {
    'use strict';
    window.writeDWLog("function begin : GenericHUDObject : resumeEventPropogation");

    if (element) {
        element.addEventListener("mousedown",  function clickHander(event) {
            event.startPropagation();
        }, true);

    } else {
        window.writeDWLog("error : GenericHUDObject : resumeEventPropogation failed");
    }
};
/*
    Destroy the hud
*/
GenericHUDObject.prototype.destroy = function () {
    'use strict';

    this.setVisibility(false);
    this.setCurrentElement({
        element: null
    }); //so that next time draw comes element wont be stale
    this.detachEventListeners();
};

/*
    Cancel the hUD. Temporarily hide may be.
*/
GenericHUDObject.prototype.escapeKeyPressed = function () {
    'use strict';
    window.writeDWLog("function begin : Generic HUD : escapeKeyPressed");
};

/*
    populate content for the HUDS
    Instead of loading everytime. Once they can be formed and stored in m_HudHtml m_hudCss m_hudJs
*/
GenericHUDObject.prototype.populateContent = function () {
    'use strict';
    window.writeDWLog("function begin : Generic HUD : populateContent");

};
/*
    Populate localized strings
*/
GenericHUDObject.prototype.populatelocalizedStrings = function () {
    'use strict';
    window.writeDWLog("function begin: Generic HUD : populatelocalizedStrings");
};


/*
        ANY FUNCTIONS THAT ARE COMMON FOR ALL HUDS MUST BE WRITTEN HERE
*/

/*
Setter and getter functions for each of the member variables.
*/
// SETTER GETTER FUNCTION START
GenericHUDObject.prototype.setHudName = function (name) {
    'use strict';
    this.m_hudName = name;
};
GenericHUDObject.prototype.getHudName = function () {
    'use strict';
    return this.m_hudName;
};
GenericHUDObject.prototype.setHudHTML = function (hudHtml) {
    'use strict';
    this.m_HUD_html = hudHtml;
};
GenericHUDObject.prototype.getHudHTML = function () {
    'use strict';
    return this.m_HUD_html;

};
GenericHUDObject.prototype.setHudCSS = function (hudCss) {
    'use strict';
    this.m_hudCss = hudCss;
};
GenericHUDObject.prototype.getHudCSS = function () {
    'use strict';
    return this.m_hudCss;
};
GenericHUDObject.prototype.setHudJS = function (hudJs) {
    'use strict';
    this.m_hudJs = hudJs;
};
GenericHUDObject.prototype.getHudJS = function () {
    'use strict';
    return this.m_hudJs;
};
GenericHUDObject.prototype.setVisibility = function (isVisible) {
    'use strict';
    this.m_isVisible = isVisible;
};
GenericHUDObject.prototype.getVisibility = function () {
    'use strict';
    return this.m_isVisible;
};

GenericHUDObject.prototype.setLiveEditEvent = function (evtTarget) {

    'use strict';
    this.m_liveEditEvent = evtTarget;

};
GenericHUDObject.prototype.getLiveEditEvent = function () {

    'use strict';
    return this.m_liveEditEvent;

};
// Pass the object not directly the element
// If this function is overrriden, then set the element to if its editable or not
GenericHUDObject.prototype.setCurrentElement = function (args) {
    'use strict';
    if (args) {
        this.m_curElement = args.element;
        this.m_isInEditableRegion = args.isInEditableRegion;

        if (this.m_curElement) {
            this.setEditable(this.isElementEditable(this.m_curElement));
        }
    }
};
GenericHUDObject.prototype.getCurrentElement = function () {
    'use strict';

    return this.m_curElement;
};
// setter function to set if element is editable or not
GenericHUDObject.prototype.setEditable = function (isEditable) {
    'use strict';
    this.m_editable = isEditable;
};
GenericHUDObject.prototype.getEditable = function () {
    'use strict';

    return this.m_editable;
};
GenericHUDObject.prototype.setHudContainer = function (element) {
    'use strict';

    this.m_hudContainer = element;
};
GenericHUDObject.prototype.getHudContainer = function () {
    'use strict';

    return this.m_hudContainer;
};

GenericHUDObject.prototype.setOverlayDiv = function (elem) {
    'use strict';

    this.m_overlayDiv = elem;
    //show overlay div with respect to the position
};

// SETTER GETTER FUNCTION END

/*
    This is the initialization function for all the HUDS.

*/
GenericHUDObject.prototype.initialize = function (liveEditEvt) {
    'use strict';

    this.setLiveEditEvent(liveEditEvt);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.Destroy, this.destroy, this);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.Escape, this.escapeKeyPressed, this);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.ViewLostFocus, this.OnViewLostFocus, this);
};

/*
    This function enables HUD by
    setting the visibility to true and attaches event listeners.
*/
GenericHUDObject.prototype.enableHud = function () {
    'use strict';

    window.writeDWLog("function begin : enableHud :" + this.getHudName());

    if (this.getVisibility() === false) {
        this.setVisibility(true);
        this.attachEventListeners();
    }
};
/*
    Attach all the  REQUIRED* event listeners to HUD
    Please visit LiveEditEvent.js for 
*/
GenericHUDObject.prototype.attachEventListeners = function () {
    'use strict';

    window.writeDWLog("function begin : attachEventListeners :" + this.getHudName());

    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.SetElement, this.setCurrentElement, this);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.Commit, this.commit, this);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.Draw, this.draw, this);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.SaveDocument, this.saveDocument, this);
    this.m_liveEditEvent.addListener(DW_LIVEEDIT_EVENTS.RefreshDocument, this.refreshDocument, this);
};
/*
    remove all the  REQUIRED* event listeners to HUD
*/

GenericHUDObject.prototype.detachEventListeners = function () {
    'use strict';

    window.writeDWLog("function begin : detachEventListeners :" + this.getHudName());

    this.m_liveEditEvent.removeListener(DW_LIVEEDIT_EVENTS.SetElement, this.setCurrentElement, this);
    this.m_liveEditEvent.removeListener(DW_LIVEEDIT_EVENTS.Commit, this.commit, this);
    this.m_liveEditEvent.removeListener(DW_LIVEEDIT_EVENTS.Draw, this.draw, this);
    this.m_liveEditEvent.removeListener(DW_LIVEEDIT_EVENTS.SaveDocument, this.saveDocument, this);
    this.m_liveEditEvent.removeListener(DW_LIVEEDIT_EVENTS.RefreshDocument, this.refreshDocument, this);
};

/*
Calculate the exact position of the element on screen
*/
GenericHUDObject.prototype.getElementRect = function (inElem) {
    'use strict';

    window.writeDWLog("function begin : getElementRect :" + this.getHudName());

    var result = {
        top: 0,
        left: 0,
        width: 0,
        height: 0
    };

	if (!inElem) {
		return result;
	}

	//
	// Calculate rect
	//
	var elem = inElem;

	// When we wrap block elements like "P" tag with "DW_SPAN" tag, the getboundingClientRect() gives 
	// very bigger rectangle. So calculating rectangle based on its children.
	// This change fixes #3687118 (Text editing feedback is not comming in proper position when the 
	// text is wrapped with <a> tag and CSS transition is applied).
	var rect = null;
	if (elem.nodeType === Node.ELEMENT_NODE && elem.tagName === DW_LIVEEDIT_CONSTANTS.TextContainer) {
		var range = document.createRange();
		if (elem.firstChild === elem.lastChild) {
			range.selectNode(elem.firstChild);
		} else {
			range.setStart(elem.firstChild, 0);
			range.setEndAfter(elem.lastChild);
		}
		rect = range.getBoundingClientRect();

		// if all cordinates are zero we will try to calculate with parent element
		if (rect && rect.top === 0 && rect.left === 0 && rect.right === 0 && rect.bottom === 0) {
			rect = null;
		}

		// Use parent element for rect only when dw-span is the only child
		if (!rect && inElem.parentNode.firstChild === inElem.parentNode.lastChild) {
			elem = inElem.parentNode;
		}
	}

	if (!rect) {
		rect = elem.getBoundingClientRect();
	}
    result.top = rect.top + window.scrollY;
    result.left = rect.left + window.scrollX;
    result.width = rect.right - rect.left;
    result.height = rect.bottom - rect.top;

    return result;
};

/*
    Function to check whether an element is editable or not
*/
GenericHUDObject.prototype.isElementEditable = function (curElement) {
    'use strict';

    var editable = false;
    if (this.m_isInEditableRegion && curElement) {
        //If the element has dw unique ID attribute
        //then we can edit the element
        var uniqId = curElement.getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId);
        if (uniqId && uniqId.length > 0) {
            editable = true;
        }
    }
    return editable;
};

/*
    Show Overlay Div for any hud.
    argumetns: Class to be applied 
               paddingBetweenBorderAndContent, default parameter. If passed, this will specifies the padding values around the
               overlay. Only text hud specifies this value.
*/

GenericHUDObject.prototype.showOverLayDiv = function (classToBeApplied, paddingBetweenBorderAndContent) {
    'use strict';

    window.writeDWLog("function begin : showOverLayDiv :" + this.getHudName());

    var paddingLeft = 0,
        paddingRight = 0,
        paddingTop = 0,
        paddingBottom = 0;
    if (paddingBetweenBorderAndContent) {
        paddingLeft = paddingBetweenBorderAndContent.left;
        paddingRight = paddingBetweenBorderAndContent.right;
        paddingTop = paddingBetweenBorderAndContent.top;
        paddingBottom = paddingBetweenBorderAndContent.bottom;
    }

    var elem = this.getCurrentElement();
    var elemRect = this.getElementRect(elem);
    //if document body or html element is positioned relative
    //then their top left may not be (0,0). We should reduce this
    //in our computations
    var topOffset = 0, leftOffset = 0, documentHtmlStyle = null;
    var documentBodyStyle = window.getComputedStyle(document.body);
    var documentHTMLElement = document.getElementsByTagName("html")[0];
    if (documentHTMLElement) {
        documentHtmlStyle = window.getComputedStyle(documentHTMLElement);
    }
    if (documentBodyStyle.position === "relative") {
        var bodyRect = this.getElementRect(document.body);
        topOffset = bodyRect.top;
        leftOffset = bodyRect.left;
    } else if (documentHtmlStyle && documentHtmlStyle.position === "relative") {
        var htmlRect = this.getElementRect(documentHTMLElement);
        topOffset = htmlRect.top;
        leftOffset = htmlRect.left;
    }
    
    this.m_overlayDiv.style.top = (elemRect.top - topOffset - paddingTop) + 'px';
    this.m_overlayDiv.style.left = (elemRect.left - leftOffset - paddingLeft) + 'px';
    //we should reduce the width and height to accommodate the border
    //of the overlay div. so reduce 2*border_width    
    this.m_overlayDiv.style.width = ((elemRect.width - 2 * DW_LIVEEDIT_CONSTANTS.OverlayBorderWidth) + paddingRight) + 'px';
    this.m_overlayDiv.style.height = ((elemRect.height - 2 * DW_LIVEEDIT_CONSTANTS.OverlayBorderWidth) + paddingBottom) + 'px';

    this.m_overlayDiv.style.display = 'block';
    this.m_overlayDiv.setAttribute('class', classToBeApplied);

};
/*
    Hide overlay div
*/
GenericHUDObject.prototype.hideOverLayDiv = function () {
    'use strict';

    this.m_overlayDiv.style.display = 'none';
    this.m_overlayDiv.removeAttribute('class');
};
/*
    Function to clear the undo stack of live view
*/
GenericHUDObject.prototype.clearUndoRedoOperations = function () {
    'use strict';

    //clear the stack with the new command
    document.execCommand("ClearUndoRedoOperations", false, null);

    //DW updates the Live View Undo/Redo status on selection change
    //so initiate a selection change
    var selectionChangeEvent = new CustomEvent("selectionchange");
    document.dispatchEvent(selectionChangeEvent);
};

/*
    This function for DEBUGGING purpose only.
*/
GenericHUDObject.prototype.logDebugMsg = function (msg) {
    'use strict';
    if (window.writeDWLog) {
        window.writeDWLog(msg);
    }
};

/*
 getEditableRegionAttributeVal - returns value for the given attribute from the ER begin node
        node - ER comment node
        attrName - name of the attribute
*/
GenericHUDObject.prototype.getEditableRegionAttributeVal = function (node, attrName) {
    'use strict';
    if (!node || node.nodeType !== Node.COMMENT_NODE || node.nodeValue.indexOf(DW_EDITABLE_REGION_CONSTS.EditableRegionBegin) !== 0) {
        return null;
    }

    var comment = node.nodeValue;
    var idIndex = comment.indexOf(attrName);
    if (idIndex >= 0) {
        var idBegin = comment.indexOf('"', idIndex);
        var idEnd = comment.indexOf('"', idBegin + 1);
        if (idBegin >= 0 && idEnd >= 0) {
            return comment.substring(idBegin + 1, idEnd);
        }
    }

    return null;
};



GenericHUDObject.prototype.disableDWSelectionChanges = function (disable) {
    'use strict';
    // We will calculate offsets here for caching on start of text editing in LV and pass them while sending live editing in progress notification
    var offsets = window.DW_GetOffsetsForCurrentSelection();
    if (offsets.length === 0) // If the returned offset string is empty, we will set it to dummy values
        offsets = "0,0";
    var offsetArray = offsets.split(",");
    window.DWSetLiveEditingInProgress(disable,Number(offsetArray[0]),Number(offsetArray[1]));
    globalController.m_disableDWSelection = disable;
};
