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

Purpose- 
This file has the implementation of for Dreamweaver side utility functions which are exposed for Live View Text Editing.
*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, maxerr: 50 */
/*global dw, GenericLiveEditBridger, TextHudLiveViewBridger, DW_LIVEEDIT_CONSTANTS, Node, TextEditHud, GenericHUDObject, DW_EDITABLE_REGION_CONSTS, DW_ICE_HEADLIGHTS,
document, EditableRegion, DW_LIVE_TEXT_EDIT */

TextHudLiveViewBridger.prototype = new GenericLiveEditBridger();
TextHudLiveViewBridger.prototype.constructor = TextHudLiveViewBridger;
TextHudLiveViewBridger.prototype.baseClass = GenericLiveEditBridger.prototype.constructor;

function TextHudLiveViewBridger(documentDOM) {
    'use strict';
    var browser = dw.getBrowser();
    if (browser) {
        this.browserWindow = browser.getWindow();
    }
    this.setDocumentDOM(documentDOM);
}


/*
DWSetSelectedElement
set the selection in DW to the element with the ID as uniqID

*/
TextHudLiveViewBridger.prototype.DWSetSelectedElement = function (uniqId) {
    'use strict';
    if (uniqId && uniqId.length > 0) {
        var userDOMElement = this.getElementByDWId(uniqId);
        var theDOM = this.getDocumentDOM();
        if (theDOM) {
            var offsets = theDOM.nodeToOffsets(userDOMElement);
            theDOM.setSelection(offsets[0], offsets[1]);
            //check if the setSelection has succeeded . If not try to force set the selection
            var curSelection = theDOM.getSelection();
            if (!(curSelection && (offsets[0] === curSelection[0]) && (offsets[1] === curSelection[1]))) {
                var dwSelectNode = theDOM.getSelectedNode();
                if (dwSelectNode && userDOMElement !== dwSelectNode) {
                    theDOM.setSelectedNode(userDOMElement, false, false, true, false);
                }
            }
            dw.syncCurrentLiveDocFromUserDocSelection();
        }
    }
};


/*
setBoldItalicsPreference
check if the property for bold italics in preferences/general section has changed. 
If yes, update it.
*/
TextHudLiveViewBridger.prototype.setBoldItalicsPreference = function () {
    'use strict';
    this.browserWindow.semanticTags = dw.getPreferenceOfUseSematicTags();
};
/*
Find Text node end.
NOTE: Seen in few cases where text is multiple fragment. Indroduced this 
	function for handling that situation. But could not reproduce it now.
*/
TextHudLiveViewBridger.prototype.getTextEnd = function (startTextNode) {
    'use strict';
    if (startTextNode && startTextNode.nodeType === Node.TEXT_NODE) {
        var endTextNode = startTextNode,
            nextTextNode = startTextNode.nextSibling;

        while (nextTextNode && nextTextNode.nodeType === Node.TEXT_NODE) {
            endTextNode = nextTextNode;
            nextTextNode = nextTextNode.nextSibling;
        }

        return endTextNode;
    }

    return null;
};

/*
Find Text node begining.
NOTE: Seen in few cases where text is multiple fragment. Indroduced this 
	function for handling that situation. But could not reproduce it now.
*/
TextHudLiveViewBridger.prototype.getTextBegin = function (endTextNode) {
    'use strict';
    this.logDebugMsg("getTextBegin from TextHudLiveViewBridger");

    if (endTextNode && endTextNode.nodeType === Node.TEXT_NODE) {
        var beginTextNode = endTextNode,
            nextTextNode = beginTextNode.previousSibling;

        while (nextTextNode && nextTextNode.nodeType === Node.TEXT_NODE) {
            beginTextNode = nextTextNode;
            nextTextNode = nextTextNode.previousSibling;
        }

        return beginTextNode;
    }

    return null;
};

/*
Get text node by position in siblings.
Args:
		startNode - sibling node to start with.
		position - n-th the text node that we are searching for.
		forwardSearch - search forward or backword.
*/
TextHudLiveViewBridger.prototype.getNthTextSibling = function (startNode, position, forwardSearch) {
    'use strict';
    this.logDebugMsg("getNthTextSibling from TextHudLiveViewBridger");

    var nextNode = startNode,
        textNodePos = 0;
    while (nextNode) {
        if (nextNode.nodeType === Node.TEXT_NODE) {
            textNodePos += 1;
            if (forwardSearch) {
                if (textNodePos === position) {
                    return nextNode;
                }

                nextNode = this.getTextEnd(nextNode);
            } else {
                nextNode = this.getTextBegin(nextNode);

                if (textNodePos === position) {
                    return nextNode;
                }
            }
        }
        if (forwardSearch) {
            nextNode = nextNode.nextSibling;
        } else {
            nextNode = nextNode.previousSibling;
        }
    }

    return null;
};

/*
Find the text node in Titan DOM for the given searching context.
Args:
		context - it has information about the parent and sibling of the 
			text node that we are searching for.
				context.parentNodeID - data_liveedit_tagid of Parent node
				context.preNodeID - data_liveedit_tagid of previous Sibling node;
				context.succNodeID - data_liveedit_tagid of successor Sibling node;
				context.noOfBeforeTexts - no of text node exists from preNodeID to text node under search. 
				context.noOfAfterTexts - no of text node exists till succNodeID from text node under search
				context.editableRegion - editable region name if doc is template instance
*/
TextHudLiveViewBridger.prototype.findTextNodeForContext = function (context) {
    'use strict';
    this.logDebugMsg("findTextNodeForContext from TextHudLiveViewBridger");
    if (!context) {
        return null;
    }

    if (context.nodeType === Node.ELEMENT_NODE) {
        return this.getElementByDWId(context.dwId);
    }

    var theDOM,
		startTextNode,
        endTextNode,
        parentNode = this.getElementByDWId(context.parentNodeID);

    if (parentNode) {
        var prevTagNode;
        if (context.preNodeID !== "") {
            prevTagNode = this.getElementByDWId(context.preNodeID);
        } else {
            if (context.editableRegion) {
				theDOM = this.getDocumentDOM();

                var regions = theDOM.getEditableRegionList(true),
                    regionIndex = theDOM.getSelectedEditableRegion(true);

                if (regions && regionIndex < regions.length && regionIndex >= 0 && regions[regionIndex].getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId) === context.editableRegion) {
                    prevTagNode = regions[regionIndex].firstChild;
                }
                // Sometimes, current selected editable region is not set due to 
                // selection sync issue between Live View and DW. In this case we have to 
                // search through all the editable regions to find the one that needs 
                // to be updated.
                if (!prevTagNode && regions && context.editableRegion) {
                    var i;
                    for (i = 0; i < regions.length; i += 1) {
                        if (regions[i].getAttribute(DW_LIVEEDIT_CONSTANTS.DWUniqueId) === context.editableRegion) {
                            prevTagNode = regions[i].firstChild;
                            break;
                        }
                    }
                }
                if (!prevTagNode) {
                    this.logDebugMsg("Error in finding ER " + context.editableRegion);
                    return null;
                }
            } else {
                prevTagNode = parentNode.firstChild;
            }
        }

        startTextNode = this.getNthTextSibling(prevTagNode, context.noOfBeforeTexts + 1, true);
        endTextNode = this.getTextEnd(startTextNode);

        if (context.succNodeID !== "") {
            prevTagNode = this.getElementByDWId(context.succNodeID);
            var textNode = this.getNthTextSibling(prevTagNode, context.noOfAfterTexts + 1, false);

            if (!textNode) {
                return startTextNode;
            }

            if (startTextNode) {
				theDOM = this.getDocumentDOM();

                var startNodeOffsets = theDOM.nodeToOffsets(startTextNode),
                    foundNodeOffsets = theDOM.nodeToOffsets(textNode);

                if (startNodeOffsets[0] === foundNodeOffsets[0] && startNodeOffsets[1] === foundNodeOffsets[1]) {
                    return startTextNode;
                }
            } else {
                return textNode;
            }
        }

        if (context.succNodeID === "") {
            return startTextNode;
        }
    }

    return null;
};

/*
Checks if any run has any dynamic tags like PHP/JSP/ASP/CFM.
returns true if present.
Args:
		sourceStr - source string to be validated for dynamic tags.
*/
TextHudLiveViewBridger.prototype.validateDyamicTagsForTextEdit = function (sourceStr, startNode) {
    'use strict';
    var strIdx,
		arrayLength = DW_LIVE_TEXT_EDIT.DynamicTags.length;

    for (strIdx = 0; strIdx < arrayLength; ++strIdx) {
        if (sourceStr.indexOf(DW_LIVE_TEXT_EDIT.DynamicTags[strIdx]) >= 0) {
            if (this.browserWindow && this.browserWindow.doctype) {
                this.DWLogHeadlightsData({
                    subCategory: DW_ICE_HEADLIGHTS.OTH_ELV,
                    eventString: DW_ICE_HEADLIGHTS.lvePreString + this.browserWindow.doctype + DW_ICE_HEADLIGHTS.lvePostStringDynamic
                });
            }
            return true;
		}
    }

    // Looks like no dynamic tags are found from the above statement.
    // Let us check if there are any sibling runs have server side tags.
    
    if (startNode && startNode.nodeType === Node.TEXT_NODE) {
        
        var theParent = startNode.parentNode;

        if (theParent) {

            var childNodes = theParent.childNodes;
            
            if (childNodes) {
                
                var i = 0;
                for (i = 0; i < childNodes.length; i += 1) {
                    var childNode = childNodes[i];
                    if (childNode && childNode.isServerSideNode()) {
                        
                        this.logDebugMsg('validateDyamicTagsForTextEdit from TextHudLiveViewBridge: Server side node found');
                        return true;
                    }
                }
            }
        }
    }

    return false;
    
};

/*
populate static attributes map for element and its sub tree.
Args:
		element - element to start search.
		attrMap - map object to store the attributes for each element.
*/
TextHudLiveViewBridger.prototype.populateAttribtesMap = function (elem, attrMap) {
    'use strict';

	this.logDebugMsg("populateAttribtesMap from TextHudLiveViewBridger");

	if (!elem || !attrMap) {
		return false;
	}
	if (elem.nodeType !== Node.ELEMENT_NODE) {
		return true;
	}
	var i,
		dwID = elem[DW_LIVEEDIT_CONSTANTS.DWUniqueId],
		map = null;
	// Find all attributes in DW Dom and pupulate the map
	for (i = 0; i < elem.attributes.length; i++) {
		var a = elem.attributes[i];
		if (a.name !== DW_LIVEEDIT_CONSTANTS.DWUniqueId) {
            if (!map) {
                map = {};
            }
			map[a.name] = a.value;
		}
	}
	// Sett map object
	attrMap[dwID] = map;

    // Search and do the same for child nodes as well.
	var childNodes = elem.childNodes;
	if (childNodes) {
        for (i = 0; i < childNodes.length; i += 1) {
            this.populateAttribtesMap(childNodes[i], attrMap);
        }
    }
    return true;
};
/*
Validate the text group context to know the text node or text group is identifiable.
Args:
		context - contexts of text node, starting and ending node of the text group.
*/
TextHudLiveViewBridger.prototype.validateTextGroupForEdit = function (context) {
    'use strict';
    this.logDebugMsg("validateTextGroupForEdit from TextHudLiveViewBridger");

    var offsets,
		sourceStr,
		textNode = this.findTextNodeForContext(context),
        theDOM = this.getDocumentDOM();
        

    if (textNode && theDOM) {

        var startNode, endNode, attrMap = {};
        if (context.grouped) {
            startNode = this.findTextNodeForContext(context.groupStarting);
            if (startNode) {
                offsets = theDOM.nodeToOffsets(startNode);

				// Get Attributes for startNode
				this.populateAttribtesMap(startNode, attrMap);

                if (context.groupEnding) {
                    endNode = this.findTextNodeForContext(context.groupEnding);
                    if (!endNode) {
                         return context.callback(false, context.id);
                    }
                    var endoffset = theDOM.nodeToOffsets(endNode);
                    offsets[1] = endoffset[1];

					// Get Attributes for all elements that under edit
					var currNode = startNode.nextSibling;
					while (currNode) {
						this.populateAttribtesMap(currNode, attrMap);
						if (currNode === endNode) {
							break;
						}
						currNode = currNode.nextSibling;
					}
                }

                // Check for any php/asp/jsp/cfm tags inside the run
                // if found disable the editing for that tag.
                sourceStr = theDOM.getDocumentFragment(offsets[0], offsets[1]);
                
                if (this.validateDyamicTagsForTextEdit(sourceStr, startNode)) {
                     return context.callback(false, context.id);
				}
				// if object is just {} ,bridging throws next property asserts. So making it null
                if (this.isEmpty(attrMap)) {
                    attrMap = null;   
                }
                return context.callback(true, context.id, sourceStr, attrMap);
            }
             return context.callback(false, context.id);
        }

        offsets = theDOM.nodeToOffsets(textNode);
        sourceStr = theDOM.getDocumentFragment(offsets[0], offsets[1]);
        
        if (this.validateDyamicTagsForTextEdit(sourceStr, startNode)) {
             return context.callback(false, context.id);
		}

		// Get Attributes for all element
		this.populateAttribtesMap(textNode, attrMap);


		// if object is just {} ,bridging throws next property asserts. So making it null
        if (this.isEmpty(attrMap)) {
                attrMap = null;   
        }
        
        return context.callback(true, context.id, sourceStr, attrMap);
    }

    return context.callback(false, context.id);
};
/*
Update the text group with new inner HTML AND GET NEW IDS
Args:
		context - contexts of text node, starting and ending node of the text group.
		newText - new text of text node or new inner HTML text of text group.
*/

TextHudLiveViewBridger.prototype.DWSMUpdateLiveText = function (argObj) {
    'use strict';
    var context = argObj.textId,
        newText = argObj.textToCommit;

    // Update text in Titan DOM.
    this.updateTextGroup(context, newText);

    // Find dw data ID for new changes using temp ID 
    // and update LiveView
    this.getDWIDsForTempIDs(context.tempIdInfoObj, true);

};
/*
Update the text group with new inner HTML.
Args:
		context - contexts of text node, starting and ending node of the text group.
		newText - new text of text node or new inner HTML text of text group.
*/
TextHudLiveViewBridger.prototype.updateTextGroup = function (context, newText) {
    'use strict';
    this.logDebugMsg("updateTextGroup from TextHudLiveViewBridger");
    var textNode = this.findTextNodeForContext(context);
    if (textNode) {
        var startNode, endNode;
        if (context.grouped) {
            startNode = this.findTextNodeForContext(context.groupStarting);
            if (startNode) {
                var theDOM = this.getDocumentDOM(),
					offsets = theDOM.nodeToOffsets(startNode),
					actualParent = startNode.parentNode,
					parentNode = this.getElementByDWId(context.tempIdInfoObj.parentId);

				// Check parents are matching. In case of Editable Region, it won't match
				// because of ER comment node is treated as parent in DW side.
				if (parentNode !== actualParent && !this.isElementERComment(actualParent)) {
					this.logDebugMsg("updateTextGroup - ERROR : Parent is not matching ");

					// if parent tag is UL/OL, then there is chance of parent might have swaped in DW.
					// try commiting process with actualPrent.
					if (DW_LIVE_TEXT_EDIT.TagsWhereRunsMaySwap.indexOf(actualParent.tagName) >= 0) {
						context.tempIdInfoObj.oldParentId = context.tempIdInfoObj.parentId;
						context.tempIdInfoObj.parentId = actualParent[DW_LIVEEDIT_CONSTANTS.DWUniqueId];
						context.tempIdInfoObj.parentSwapped = true;
					} else {
						throw DW_LIVEEDIT_CONSTANTS.ParentNotMatching;
					}
				}

                if (context.groupEnding) {
                    endNode = this.findTextNodeForContext(context.groupEnding);
                    if (!endNode) {
                        this.logDebugMsg("ERROR: text edit identification mis match");
                        return;
                    }
                    var endoffset = theDOM.nodeToOffsets(endNode);
                    offsets[1] = endoffset[1];
                }
                theDOM.setSelection(offsets[0], offsets[1]);
                var curSelection = theDOM.getSelection();
                if (curSelection && (offsets[0] === curSelection[0]) && (offsets[1] === curSelection[1])) {
                    theDOM.insertHTML(newText, true, true);
                } else {
                    theDOM.setSelectionAndInsertHTML(offsets[0], offsets[1], newText, false, true, true);
                }
            } else {
                this.logDebugMsg("ERROR: text edit identification mis match");
            }
        } else {
            textNode.outerHTML = newText;
        }
    }

};

/*
function:rollBackFailedChanges
(Internal function on spider monkey side)
Arguments:none
Description: 
    Undo last change and clean up the redo events.
*/
TextHudLiveViewBridger.prototype.rollBackFailedChanges = function () {
	'use strict';
	this.logDebugMsg('TextHudLiveViewBridger.prototype.rollBackFailedChanges');

	var dom = this.getDocumentDOM();
	if (!dom) {
		return;
	}

	try {
		if (this.isDWTempIDLeftInDom(dom)) {
			this.logDebugMsg('isDWTempIDLeftInDom is TRUE - doing UNDO');
			dom.undo();
			dom.clearRedo();
		}
	} catch (e) {
		this.logDebugMsg('ERROR in rollBackFailedChanges : ' + e.message);
	}
};

/*
Function: DwRefreshDocument
Args: none
Desc: calls dom.synchronizeLiveView() which actually does the job of refresing document
*/
TextHudLiveViewBridger.prototype.DwRefreshDocument = function () {
    'use strict';
    var dom = this.getDocumentDOM();
    if (!dom) {
        return;
    }
    
    dom.synchronizeLiveView(true);
};
