// Copyright 2006-2007 Adobe Systems Incorporated.  All rights reserved.

//A common function for translating widgets in the DOM. They don't have to be spry widgets
//as long as they follow the same coding convetions. The main difference in translators 
//is the regex to find your widgets and your constructors.

function translateWidgetsInDOM( dom, origSourceStr, widgetIdRegExp, widgetConstructors)
{
	if( typeof dom == 'undefined' )
	{
		return;
	}
	
	var widgetMgr = Spry.DesignTime.Widget.Manager.getManagerForDocument(dom);
	var curWidgetIds = new Array();
	
	//prepare to handle error conditions
	var brokenWidgetSearches = new Array();
	var brokenWidgetObject = new Array();
	
	//render widgets in other files
	dw.useTranslatedSource(true);
	
	//Only search in script tags
	var scriptTags = dom.getElementsByTagName("script");
	for( var i = 0; i < scriptTags.length; i++ )
	{
		var scriptTag = scriptTags[i];
		var scriptStr = scriptTag.innerHTML;
		if( !scriptStr || scriptStr.length <= 0 )
			continue;
	
		//Tokenize all the servermarkup in the document so we can find dynamic widgets easier
		var tokenMap = new Object();
		var sourceStr = dwscripts.tokenizeServerMarkup(dom, scriptStr, tokenMap);
		
		//begin searching
		var widgetIdMatch = widgetIdRegExp.exec(sourceStr);
		
		while( widgetIdMatch )
		{
			if( widgetIdMatch.length >= 3 )
			{
				var widgetFullMatch = widgetIdMatch[0];
				var widgetObject = widgetIdMatch[1];
				var widgetType = widgetIdMatch[2];
				var widgetId = widgetIdMatch[3];
				
				//remove any servermarkup tokens
				widgetFullMatch = dwscripts.untokenizeServerMarkup(widgetFullMatch, tokenMap);
				widgetObject = dwscripts.untokenizeServerMarkup(widgetObject, tokenMap);
				widgetType = dwscripts.untokenizeServerMarkup(widgetType, tokenMap);
				widgetId = dwscripts.untokenizeServerMarkup(widgetId, tokenMap);

				//see if the widgets html node is still in the document
				var ele = dom.getElementById(widgetId);
				
				if(typeof ele == 'undefined')
				{
					//check our dom reference to see if we've already warned about this match
					//so we only warn the user once each time the doc is opened
					if( typeof dom.SpryWidgetTranslatorWarnings == 'undefined' )
						dom.SpryWidgetTranslatorWarnings = new Array;
					
					if( typeof dom.SpryWidgetTranslatorWarnings[widgetFullMatch] == 'undefined' )
					{
						var searchObj = new Object;
						searchObj.searchString = widgetFullMatch;
						searchObj.searchSource = true;
						searchObj.matchCase = true;
						searchObj.matchWholeWord = true;
						searchObj.searchWhat = "document";
						
						brokenWidgetSearches.push(searchObj);
						
						searchObj = new Object;
						searchObj.searchString = widgetObject;
						searchObj.searchSource = true;
						searchObj.matchCase = true;
						searchObj.matchWholeWord = true;
						searchObj.searchWhat = "document";
							
						brokenWidgetSearches.push(searchObj);
						
						brokenWidgetObject.push(widgetFullMatch);
						dom.SpryWidgetTranslatorWarnings[widgetFullMatch] = true;
					}
				}
				else
				{
					//wrap the actual widget code in a try/catch since
					//the code may not be in a good state for that actual
					//widget. The widgets PI should be able to tell how to correct this
					
					try
					{
						//create the object for this id		 
						var widget =  widgetMgr.getWidget(widgetType, widgetId);
						if( widget ) {
							if( widget.refresh ) {
								widget.refresh();
							}
						}
						else
						{
							var cons = widgetConstructors[widgetType];
							if( cons )
							{
								var consArgs = Spry.DesignTime.Editing.Utils.getParamsAsStrings(sourceStr, widgetIdMatch.index, '(', ')', true);
								for (var i = 0; i < consArgs.length; i++)
									consArgs[i] = dwscripts.untokenizeServerMarkup(consArgs[i], tokenMap);
								//create the new widget
								widget = new cons(dom, widgetObject, widgetId, consArgs);
								widgetMgr.setWidget(widgetType, widgetId, widget);
							}
						}
					}
					catch(e)
					{
					}
					
					//save the id for this type
					if(typeof curWidgetIds[widgetType] == 'undefined')
						curWidgetIds[widgetType] = new Array();
					curWidgetIds[widgetType].push(widgetId);
					
					setTranslatedAttributes(ele, widgetType, widgetId);
					
					//add a delete handler for this widget to remove the constructor
					var deleteFunction = getDeleteFunction(widgetFullMatch, widgetType, widgetId, widgetConstructors);
					ele.addEventListener( "DOMNodeRemovedFromDocument", deleteFunction , false ); 
					
					var insertFunction = getInsertFunction(widgetFullMatch, widgetType, widgetId, dom, widgetConstructors);
					ele.addEventListener( "DOMNodeInsertedIntoDocument", insertFunction , false ); 
					
					//listen to modifed events on the script tag
					var subtreeModifiedFunction = getSubtreeModifiedFunction(widgetType, scriptTag);
					scriptTag.parentNode.addEventListener( "DOMSubtreeModified",subtreeModifiedFunction , false );
					
					//If this widget is inside an editable region, we should warn the user 
					if( !dom.isTemplateInstance() )
					{
						var editableRegion = dom.getSelectedEditableRegion();
						if( editableRegion != -1 )
						{
							//an editable region is selected, see if it's this node
							var selNode = dom.getSelectedNode();
							if( ele == selNode || dwscripts.nodeIsChildOfNode(ele, selNode) )
							{
							
								if( typeof dom.SpryWidgetTranslatorEditableWidgetWarnings == 'undefined' )
										dom.SpryWidgetTranslatorEditableWidgetWarnings = new Array;
										
								var alertIndex = widgetFullMatch + editableRegion.toString();
							
								if( typeof dom.SpryWidgetTranslatorEditableWidgetWarnings[alertIndex] == 'undefined' )
								{
									alert(dw.loadString("spry/widget/alert/widget found in editable region"));
									dom.SpryWidgetTranslatorEditableWidgetWarnings[alertIndex] = true;
								}
							}
						}
					}
				}
			}
			
			widgetIdMatch = widgetIdRegExp.exec(sourceStr);
		}
	}
	
	//now go through all the widget types and remove and instances that no longer
	//exist on the page
	for( widgetType in curWidgetIds )
	{
		var removedWidgets = widgetMgr.removeUnlistedWidgets(widgetType, curWidgetIds[widgetType]);
		
		//give each widget a chance to cleanup before it's removed
		for( var widgetId in removedWidgets )
		{
			var remWidget = removedWidgets[widgetId];
			if( remWidget.onRemoved )
				remWidget.onRemoved();
			else
			if( remWidget.getId )
			{
				var remEle = dom.getElementById(remWidget.getId());	
				removeTranslatedAttributes(remEle, widgetType, widgetId);
			}
			
		}
		
	}
		
	//prompt to find all the missing objects
	if( brokenWidgetSearches.length > 0 )
	{
	
		//user has a widget constuctor without the corresponding code, prompt to delete
		var message = "<p>" + dw.loadString("spry/widget/alert/constructor missing html") + "</p>";
		
		for( var i = 0 ; i < brokenWidgetObject.length ; i++ ) {
			message += "<p>" + dw.loadString("spry/widget/label") + ": " + brokenWidgetObject[i] + "</p>";
		}
			
		var onYes = function() {
			
			var searches = brokenWidgetSearches;
			var clearPreviousFindResults = true;
			
			for( var i = 0 ; i < searches.length ; i++ )
			{
				dw.setUpFind(searches[i]);
				dw.findAll(true, dom, clearPreviousFindResults);
				clearPreviousFindResults = false;
			}
		}
		dwscripts.askYesNo(message, MM.BTN_Yes, onYes);
	}
}


// This function returns a hash of extra options from the widget definition. The value returned
// will be passed as a constructor argument for the current widget
function prepareWidgetOptions(optionsStr)
{
	var optionsHash = new Object();
	eval("optionsHash = {" + optionsStr + "}");
	return optionsHash;
}


//This function returns the function that gets called whenever a node is deleted. We need
//to create the function in it's own function so the closure is clear.
function getDeleteFunction(removeString, widgetType, widgetId, widgetConstructors)
{
	var deleteFunction = function(e)
	{
		var targetDom = e.target.ownerDocument;					
		var scriptTags = targetDom.getElementsByTagName("script");
		for( var i = 0; i < scriptTags.length; i++ )
		{
			var str = scriptTags[i].innerHTML;
			var index = str.indexOf(removeString);
			if( index >= 0 )
			{
				var strBefore = str.substr(0, index);
				var strAfter = str.substr(index+removeString.length);
				while( strBefore.length > 0 && ( strBefore.charAt(strBefore.length-1) == ' ' ||
												strBefore.charAt(strBefore.length-1) == '\t' ||
												strBefore.charAt(strBefore.length-1) == '\n' ||
												strBefore.charAt(strBefore.length-1) == '\r' ) )
				{
					strBefore = strBefore.substr(0, strBefore.length-1);
				}
				// We'll store the new InnerHTML to newInnerHTML and we'll decide later whether we'll replace the inner value with this
				// one or we'll simply remove the SCRIPT tag.
				var newInnerHTML = strBefore + strAfter;

				// Look if there is any valid JS code remining in the SCRIPT tag; of empty (or only comments were found) => go and remove it
				var tempInnerHTML = newInnerHTML;
				if (tempInnerHTML && tempInnerHTML.replace)
				{
					tempInnerHTML = tempInnerHTML.replace(/[\r\n]*(?:\<\!\-\-|\/\/\-\-\>)[\r\n]*/gi, "");
					tempInnerHTML = tempInnerHTML.replace(/[\r\n\s\t]*/gi, "")
				}
				
				// If the InnerHTML is empty, we'll remove the entire SCRIPT tag.
				if (tempInnerHTML === "")
				{
					scriptTags[i].outerHTML = "";
				}
				else
				{
					scriptTags[i].innerHTML = newInnerHTML;
				}
				
				// Get WidgetManager instance
				var widgetMgr = Spry.DesignTime.Widget.Manager.getManagerForDocument(targetDom);

				// Search for widgets of the same type in the page, but different from the current one (compared by ID)
				var count = 0;
				var allWidgets = widgetMgr.getAllWidgets(widgetType);
				for (var widget in allWidgets) {
					if (widget != widgetId)
					{
						count++;
						break;
					}
				}

				if (count == 0)
				{
					// There are no more widgets remaining in the page of the current type, we'll remove the assets as well
					var cons = widgetConstructors[widgetType];
					var assets = cons.getAssets();
					
					var tags = new Array();
					tags = tags.concat(targetDom.getElementsByTagName("script"));
					tags = tags.concat(targetDom.getElementsByTagName("link"));
					
					for (var j=0; j<tags.length; j++)
					{
						var tag = tags[j];
						if (tag && tag.getAttribute)
						{
							// Get the value to search; the attribute is different according to tag name: script or link
							var valueToSearch = tag.getAttribute("src");
							if (!valueToSearch)
							{
								valueToSearch = tag.getAttribute("href");
							}

							// Once we have a value, we test it against all assets
							if (valueToSearch)
							{
								for (var k=0; k<assets.length; k++)
								{
									if (assets[k].type)
									{
										// If the current tag's value matches an asset we'll remove the tag from page
										if (valueToSearch.match && valueToSearch.match(new RegExp("(?:^|[\\/\\\\])" + dwscripts.escRegExpChars(assets[k].file) + "$", "gi")))
										{
											tag.outerHTML = "";
											break;
										}
									}
								}
							}
						}
					}
				}
			}
		}
	};
	return deleteFunction;
}

function getInsertFunction(constructorJs, widgetType, widgetId, curDom, widgetConstructors)
{
	var insertFunction = function(e)
	{
		var targetDom = e.target.ownerDocument;	
		var newId = e.target.id;
		var newConJs = constructorJs;

		var cons = widgetConstructors[widgetType];
		var assetList = new Array();

		// Construct the assets list using the array of assets returned by the static getAssets() function
		var assets = cons.getAssets();
		for (var i=0; i<assets.length; i++)
		{
			assetInfo = new AssetInfo(assets[i].fullPath, assets[i].file, assets[i].type);
			assetList.push(assetInfo);
		}
		
		if (assetList && assetList.length)
		{
			targetDom.copyAssets(assetList);
		}

		//id got renamed on paste, update our constructor
		if( newId && newId != widgetId )
		{
			newConJs = newConJs.replace( widgetId, newId, "gm" );
		}
		
		targetDom.addJavaScript(newConJs, false);
	};
	return insertFunction;
}

//This function is for when the last scriptag with our objects is deleted. Our translator
//won't run again so we need to do a little post cleanup.
function getSubtreeModifiedFunction(widgetType, ele)
{
	var subtreeModifiedFunction = function(e)
	{
		if( ele && ele.ownerDocument && ele.innerHTML.indexOf(widgetType) >= 0 )
			return; //at least one widget of this type exists
		
		//no more widgets of this type exist, remove them all
		var dom = e.target.ownerDocument;
		
		//check to see if there's another scripttag we're in now
		var scriptTags = dom.getElementsByTagName("script");
		for( var i = 0; i < scriptTags.length; i++ )
		{
			var scriptTag = scriptTags[i];
			var scriptStr = scriptTag.innerHTML;
			if( !scriptStr || scriptStr.length <= 0 )
				continue;
			if ( scriptStr.indexOf(widgetType) > -1 )
				return; //still exists somewhere in the document
		}
		
		var widgetMgr = Spry.DesignTime.Widget.Manager.getManagerForDocument(dom);
		
		var removedWidgets = widgetMgr.removeUnlistedWidgets(widgetType, {});
		
		//give each widget a chance to cleanup before it's removed
		for( var widgetId in removedWidgets )
		{
			var remWidget = removedWidgets[widgetId];
			if( remWidget.onRemoved )
				remWidget.onRemoved();

			if( remWidget.getId )
			{
				var remEle = dom.getElementById(remWidget.getId());	
				removeTranslatedAttributes(remEle, widgetType, widgetId);
			}
		}
	};
	return subtreeModifiedFunction;
}

function setTranslatedAttributes(ele, widgetType, widgetId)
{
	if( !ele ) return;
	
	// See if there's a localized version of the label
	var outlineLabel = dw.loadString( widgetType + "/outlineLabel" );
	if( !outlineLabel || outlineLabel == "" )
		outlineLabel = widgetType;
	
	// Add attributes to the main element for tabbedoutlines
	ele.setTranslatedAttribute("outline", outlineLabel + ": " + widgetId);
	ele.setTranslatedAttribute("outlineId", "unique");
	ele.setTranslatedAttribute("outlineForSelection", "outlineForSelection");
	ele.setTranslatedAttribute("hiliteChildrenOnSelect", "false");
	// Leave a marker for the PI to know it can inspect this selection
	ele.setTranslatedAttribute(widgetType, widgetId);
}

function removeTranslatedAttributes(ele, widgetType, widgetId)
{
	if( !ele ) return;
	
	ele.removeTranslatedAttribute("outline");
	ele.removeTranslatedAttribute("outlineId");
	ele.removeTranslatedAttribute("outlineForSelection");
	ele.removeTranslatedAttribute(widgetType);
}
