// Copyright 2012 Adobe Macromedia, Inc. All rights reserved.

//--------------------------------------------------------------------
// CLASS:
//   JQuery.DesignTime.Widget.Base
//
// DESCRIPTION:
//   This the base class for all design time widgets (i.e., all design
//   time widgets inherit from this class). 
//--------------------------------------------------------------------

var JQuery;
if (!JQuery) JQuery = {};
if (!JQuery.DesignTime) JQuery.DesignTime = {};
if (!JQuery.DesignTime.Widget) JQuery.DesignTime.Widget = {};

JQuery.DesignTime.Widget.Base = function(dom, element)
{
    this.dom = dom;
    this.element = this.getElement(element);
    this.element_id = (typeof element == "string" || !element) ? element : element.id;
};

//--------------------------------------------------------------------
// FUNCTION:
//   addClassName
//
// DESCRIPTION:
//   Adds the specified class as a translated attribute to the specified
//   element, so that the class becomes visible in Design view. Does not
//   change the actual class attribute.
//
// ARGUMENTS:
//   ele - the element on which to change the class
//   className - the name of the class to apply
//
// RETURNS:
//   Nothing.
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.addClassName = function(ele, className)
{
	if (!ele || !className)
		return;
	
	var classValue = JQuery.DesignTime.Widget.Base.getAttributeForNode(ele,'class');
	
	if( classValue )
		return;
	ele.setTranslatedAttribute("class", className);
};

JQuery.DesignTime.Widget.Base.addStyleForWidget = function(ele, styleValue)
{
	if (!ele || !styleValue)
		return;
	
	var existingStyleValue = JQuery.DesignTime.Widget.Base.getAttributeForNode(ele,'style');
	
	if( existingStyleValue )
		return;
	
	ele.setTranslatedAttribute("style", styleValue);
};

//--------------------------------------------------------------------
// FUNCTION:
//   getAttributeForNode
//
// DESCRIPTION:
//   This method fetches the attribute node with name 'attributeName'
//	 Strangely, node.getAttribute does crashes if there are no attributes, which is why we had to create this method
//
// ARGUMENTS:
//   ele - the element for which we fetch the attribute
//   attributeName - the name of the Attribute
//
// RETURNS:
//   Attribute node if found. Null otherwise.
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.getAttributeForNode = function(ele,attributeName)
{
	var attributes = ele.attributes;
	
	for( var i = 0; i < attributes.length; i++ )
		if( attributes[i].name == attributeName)
			return attributes[i];
			
			
	return null;		
}

JQuery.DesignTime.Widget.Base.removeClassName = function(ele, className)
{
	if (!ele || !className)
		return;

	var tclass = ele.getTranslatedAttribute("class");
	if (!tclass || (tclass.search(new RegExp("\\b" + className + "\\b")) == -1))
		return;

	ele.setTranslatedAttribute("class", tclass.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), ""));
};


//--------------------------------------------------------------------
// FUNCTION:
//   getElementChildren
//
// DESCRIPTION:
//   Checks whether the specified class has been applied to the specified
//   element via a translated attribute.
//
// ARGUMENTS:
//   ele - the element on which to check for the class
//   className - the name of the class for which to check
//
// RETURNS:
//   Boolean value indicating whether the class has been applied to
//   the specified element.
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.getElementChildren = function(element)
{	
	// filter out any non-HTML tags between the container element and
	// its first element child
	var elementToUse = element;
	if( elementToUse && 
	   	elementToUse.hasChildNodes() && 
		elementToUse.childNodes.length == 1 &&
		(elementToUse.firstChild.nodeType != Node.ELEMENT_NODE || !elementToUse.firstChild.isHtmlTag()) )
	{
		var foundNode = dwscripts.getFirstHtmlTag(elementToUse.firstChild);
		if( foundNode ) {
			elementToUse = foundNode.parentNode;
		}
	}
	
	//get all the children
	var rawChildren = JQuery.DesignTime.Widget.Base.getElementChildrenRaw(elementToUse);
	
	//go through and filter out any non HTML tags in the arrray
	var filteredChildren = new Array();
	for( var i = 0 ; i < rawChildren.length ; i++ )
	{		
		goodChild = rawChildren[i];
		if(!goodChild.isHtmlTag() ) 
		{
			//wasn't a good child, look down this child node for a better one
			goodChild = dwscripts.getFirstHtmlTag(goodChild.firstChild);
		}
		
		if( goodChild ) {
			filteredChildren.push(goodChild);
		}
	}
	
	return filteredChildren;
};

JQuery.DesignTime.Widget.Base.getElementChildrenRaw = function(element)
{
	var children = [];
	
	if( element )
	{
  	var child = element.firstChild;
  	while (child)
  	{
  		if (child.nodeType == 1 /* Node.ELEMENT_NODE */)
  			children.push(child);
  		child = child.nextSibling;
  	}
  }
  
	return children;
};

JQuery.DesignTime.Widget.Base.addEventListener = function(element, eventType, handler, capture)
{
	try
	{
		if (element.addEventListener)
			element.addEventListener(eventType, handler, capture);
		else if (element.attachEvent)
			element.attachEvent("on" + eventType, handler);
	}
	catch (e) {}
};

//--------------------------------------------------------------------
// FUNCTION:
//   getShowPanelIcon
//
// DESCRIPTION:
//   Gets the icon to display in Design view to indicate that an element
//   (doesn't necessarily have to be a panel) can be shown/made visible.
//
// ARGUMENTS:
//   None.
//
// RETURNS:
//   The full path to the the icon, expressed as a file:// URL.
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.getShowPanelIcon = function()
{
	return dw.getConfigurationPath() + "/Shared/MM/Images/disclosure_open.png";
};

JQuery.DesignTime.Widget.Base.addShowPanelContextButton = function(buttonContainer)
{
	if( !buttonContainer || !buttonContainer.setTranslatedAttribute )
		return;
		
	buttonContainer.setTranslatedAttribute("dwedit:hascontextbutton", "hascontextbutton");
	buttonContainer.setTranslatedAttribute("dwedit:contextbuttonurl", JQuery.DesignTime.Widget.Base.getShowPanelIcon());
	buttonContainer.setTranslatedAttribute("dwedit:contextbuttontooltip", dw.loadString("jquery/widget/tooltip/show panel"));
};


JQuery.DesignTime.Widget.Base.removeContextButton = function(buttonContainer)
{
	if( !buttonContainer || !buttonContainer.removeTranslatedAttribute )
		return;
	buttonContainer.removeTranslatedAttribute("dwedit:hascontextbutton");
	buttonContainer.removeTranslatedAttribute("dwedit:contextbuttonurl");
	buttonContainer.removeTranslatedAttribute("dwedit:contextbuttontooltip");
};



// For convenience, we'll add these static methods as members of the widget class itself.
JQuery.DesignTime.Widget.Base.prototype.addClassName = JQuery.DesignTime.Widget.Base.addClassName;
JQuery.DesignTime.Widget.Base.prototype.addStyleForWidget = JQuery.DesignTime.Widget.Base.addStyleForWidget;
JQuery.DesignTime.Widget.Base.prototype.removeClassName = JQuery.DesignTime.Widget.Base.removeClassName;
JQuery.DesignTime.Widget.Base.prototype.getElementChildren = JQuery.DesignTime.Widget.Base.getElementChildren;
JQuery.DesignTime.Widget.Base.prototype.addEventListener = JQuery.DesignTime.Widget.Base.addEventListener;
JQuery.DesignTime.Widget.Base.prototype.getShowPanelIcon = JQuery.DesignTime.Widget.Base.getShowPanelIcon;
JQuery.DesignTime.Widget.Base.prototype.addShowPanelContextButton = JQuery.DesignTime.Widget.Base.addShowPanelContextButton;
JQuery.DesignTime.Widget.Base.prototype.removeContextButton = JQuery.DesignTime.Widget.Base.removeContextButton;

JQuery.DesignTime.Widget.Base.prototype.getElement = function(ele)
{
	if (ele && typeof ele == "string")
		return this.dom.getElementById(ele);
	return ele;
}

//just a basic check for making sure this.element is still usable
JQuery.DesignTime.Widget.Base.prototype.ensureValidElements = function()
{
	if( this.element && 
	   this.element.ownerDocument && 
	   this.element.id == this.element_id )
	return; //node is still good
	
	//refind our element in the document
	this.element = this.getElement(this.element_id);
};



//--------------------------------------------------------------------
// FUNCTION:
//   updateId
//
// DESCRIPTION:
//   Called when the user changes the id attribute of the widget's
//   top-level element from the PI. Calls changeConstructorId(),
//   which searches through all script tags in the document for 
//   constructor calls that use the old id and modifies them to
//   use the new id.
//
// ARGUMENTS:
//   newId - string - The new element ID to use for the widget.
//
// RETURNS:
//   N/A
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.prototype.updateWidgetId = function(newId)
{
	this.ensureValidElements();
	
	if (!this.element)
		return;
		
  // Set our element's id first.
  this.element.id = newId;
  var oldId = this.element_id;
  this.element_id = newId;

  // Update constructor
  JQuery.DesignTime.Widget.Base.changeConstructorId(this.dom, oldId, newId);
  
  // Make sure we have a handle to the correct element.
	this.ensureValidElements();
};

//--------------------------------------------------------------------
// FUNCTION:
//   changeConstructorId
//
// DESCRIPTION:
//   Searches through all script tags in the document and updates any JQuery
//   widget constructors that use curId so that they use newId instead.
//
// ARGUMENTS:
//   dom - object - The DOM for the current page.
//   curId - string - The element ID to search for.
//   newId - string - New ID for element.
//
// RETURNS:
//   N/A
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.changeConstructorId = function(dom, curId, newId)
{
  var updateConstructorRegEx = new RegExp("\\$\\((\\s)*[\"']#" + dwscripts.escRegExpChars(curId) + "[\"']");
  var scriptTags = dom.getElementsByTagName("script");
  for ( var i = 0; i < scriptTags.length; i++ )
  {
    if ( scriptTags[i].innerHTML.match(updateConstructorRegEx) ){
      scriptTags[i].innerHTML = scriptTags[i].innerHTML.replace(updateConstructorRegEx, "$( \"#" + newId + "\"");
	  }
  }
};



//--------------------------------------------------------------------
// FUNCTION:
//   setWidgetType
//
// DESCRIPTION:
//   Called by the translator to store the type of widget so that those 
//   functions that need the widget type as a string value can get
//   it as an argument rather than having to be repeated in each 
//   widget designtime file.
//
// ARGUMENTS:
//   widgetType - string - The name of the widget as it appears in
//   the JQuery constructor in the user's document (e.g., 'accordion',
//   'tabs')
//
// RETURNS:
//   N/A
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.prototype.setWidgetType = function(widgetType)
{
  this.widgetType = widgetType;
};

//--------------------------------------------------------------------
// FUNCTION:
//   getWidgetType
//
// DESCRIPTION:
//   Gets the widget type as a string.
//
// ARGUMENTS:
//   None.
//
// RETURNS:
//   A string such as "accordion" or "tabs".
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.prototype.getWidgetType = function()
{
  return this.widgetType;
};

//--------------------------------------------------------------------
// FUNCTION:
//  getConstructorRegExp
//
// DESCRIPTION:
//  Gets the regular expression that can be used to parse the file and attain the
//	widget string
//	
//	Example strings:
//	
//	$(function() {
//		$( "#Accordion1" ).accordion({
//			active:2,
//			collapsible:true
//		}); 
//	});
//	
//	$(function() {
//		$( "#Tabs1" ).tabs(); 
//	});
//
//
// ARGUMENTS:
//   None.
//
// RETURNS:
//   A regular expression for jQuery widget strings
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.prototype.getConstructorRegExp = function()
{
	var preString = "[\"']\\s*\\)\\.(?:";
	var postString = ")\\((?:\\s|.)*?(?:\\)?\\s*;)(?:[\\S\\s]*?)(?:\\}\\s*\\)\\s*;\\s*)?\\}\\s*\\)\\s*;";
	var widgetString = "";
	
	for( var i =0; i < widgetArray.length; i++ )
	{
		if (i!=0)
			widgetString += "|";
			
		widgetString += widgetArray[i];
	}
	
	var regExpString = preString + widgetString + postString;
	return new RegExp(this.element_id + regExpString, "g");
}

//--------------------------------------------------------------------
// FUNCTION:
//  getConstructorArgs
//
// DESCRIPTION:
//  Parses the jQuery widget constructor and creates an object of all the arguments it contains
//	This object can be used to display these value in the PI
//
// ARGUMENTS:
//   String: Widget Type of widget like "accordion", "tabs" etc
//
// RETURNS:
//   An object containing all constructor arguments
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.prototype.getConstructorArgs = function(widgetType)
{
	var consRegExp = this.getConstructorRegExp();
	var scriptTags = this.dom.getElementsByTagName("script");
	for( var i = 0; i < scriptTags.length; i++ )
	{
		var sTag = scriptTags[i];
		var src = sTag.innerHTML;
		if(!src)
			continue;
			
		var matchStrings = src.match(consRegExp);
		if( matchStrings && matchStrings.length )
		{
			var args = JQuery.DesignTime.Editing.Utils.parseForJsOptions(matchStrings[0], widgetType);
			return args;
		}
	}
	return null;
};

//--------------------------------------------------------------------
// FUNCTION:
//  updateOptions
//
// DESCRIPTION:
//  Updates the widget constructor in code to reflect the new values in the opts object
//	We go through code to locate the constructor string and replace this with a new string
//	created from the updated opts object
//
// ARGUMENTS:
//  String: Widget Type of widget like "accordion", "tabs" etc
//	Object: The options object containing all constructor arguments
//
// RETURNS:
//  Nothing
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.prototype.updateOptions = function(widgetType, opts)
{
	var consRegExp = this.getConstructorRegExp();
	var scriptTags = this.dom.getElementsByTagName("script");
	for( var i = 0; i < scriptTags.length; i++ )
	{
		var sTag = scriptTags[i];
		var src = sTag.innerHTML;
		if(!src)
			continue;

		var htmlSplitStrings = src.split(consRegExp);
		if( htmlSplitStrings.length >= 2 )
		{
			var constructorString = consRegExp.exec(src)[0];
			var preHtml = htmlSplitStrings[0];
			var postHtml = htmlSplitStrings[1];
			
			if ( htmlSplitStrings.length > 2)
			{
			//We want to join the rest of the html string as postHtml
				var locateString = htmlSplitStrings[1];
				var locateIndex = src.indexOf(htmlSplitStrings[1]);
				postHtml = src.slice(locateIndex);
			}
		
			if(  constructorString && constructorString.length )
			{
				var newObjectString 			= ""
				var newConstructorString 	= "";
				var preConstructorString 	= "";
				var postConstructorString 	= "";
				
				newObjectString = JQuery.DesignTime.Widget.Base.replaceWithUpdatedOption(opts);
				if( newObjectString == null )
					return;
				
				constructorParseArray = JQuery.DesignTime.Editing.Utils.getContainedString (constructorString, '(', ')', true);
				
				if(!constructorParseArray)
				{
					return;
				}
				
				preConstructorString = constructorString.slice(0,constructorParseArray[1]);
				postConstructorString = constructorString.slice(constructorParseArray[2], constructorString.length);
				if( newObjectString == "" )
						newConstructorString = preConstructorString + "()" + postConstructorString
				else
						newConstructorString = preConstructorString + "({\n" + newObjectString + "\t})" + postConstructorString
				
				this.canRefresh = false;
				sTag.innerHTML = preHtml + newConstructorString + postHtml;
				this.canRefresh = true;		
			}
			else
			{
				if (dw.isDebugBuild())
				{
					alert("Regular expression fail!");
				}
			}

			return;
		}
	}
	
	return;
}

//--------------------------------------------------------------------
// FUNCTION:
//  replaceWithUpdatedOption
//
// DESCRIPTION:
//	The method that converts the opts object to string.
//	For example, the output would be a string like:
//	'{min: 40, max: 20, appendTo: "#containerDiv"}'	
//
// ARGUMENTS:
//	Object: The options object containing all constructor arguments
//
// RETURNS:
//  String: The string of constructor options
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.replaceWithUpdatedOption = function(opts)
{
	var returnString = "";
	var optsSize = JQuery.DesignTime.Widget.Base.getObjSize(opts);
	var i = 0;
	for( attribute in opts )
	{
		if( opts[attribute] != null )
		{
			if( typeof opts[attribute] == "string" )
			{
				if (opts[attribute].length)
					returnString += "\t\t" + attribute + ":" + '"' + opts[attribute] + '"';
				else
					continue;
			}
			else if ( typeof opts[attribute] == 'object' )
			{
				returnString += "\t\t" + attribute + ":" + JQuery.DesignTime.Widget.Base.convertObjToString(opts[attribute]);
			}
			else
			{
				returnString += "\t\t" + attribute + ":" + opts[attribute];
			}
			
			if ( i < optsSize-1 )
			{
				returnString += ","
			}
			returnString += "\n";
			i++;
		}
	}
	return returnString;
}

//--------------------------------------------------------------------
// FUNCTION:
//  getObjSize
//
// DESCRIPTION:
//	Since there is no way or API to get the number of attributes in an object straight out.
//	Here is a method that does that. Given an object it will return the number of valid attributes	
//	it contains.
//
// ARGUMENTS:
//	Object: An object for which we want to know the size
//
// RETURNS:
//  Number: The number of attributes in the object
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.getObjSize =function (obj)
{
	var size = 0, key;
  for (key in obj) {
    if( obj.hasOwnProperty(key) && obj[key] != null ) 
			size++;
  }
  return size;
}

//--------------------------------------------------------------------
// FUNCTION:
//  convertObjToString
//
// DESCRIPTION:
//	If one of the attribute values received in replaceWithUpdatedOption turns out
//	to be an object or an array itself, we should be able to parse that again and not just strings
//	and numbers. This is still not foolproof. There can still be things like functions that
//	we can't deal with right now. 
//	
// ARGUMENTS:
//	Object: An object for which we want to know the size
//
// RETURNS:
//  Number: The number of attributes in the object
//--------------------------------------------------------------------

JQuery.DesignTime.Widget.Base.convertObjToString = function(obj)
{
	var returnString = "";
	var i;
	
	if (!obj)
		return "";

	if( obj.constructor.toString() == Array.toString() )															//array
	{
		returnString = "[";
		for ( i = 0; i < obj.length; i++)
		{
			if( typeof obj[i] == 'object')
				returnString += JQuery.DesignTime.Widget.Base.convertObjToString(obj[i]);
			else if( typeof obj[i] == 'string' )
				returnString += '"' + obj[i] + '"';
			else
				returnString += obj[i]
			
			if( i != obj.length-1 )
				returnString += ", ";
		}
		returnString += "]";
	}
	else if ( obj.constructor.toString() == Object.toString() )													//object
	{
		var objLength = JQuery.DesignTime.Widget.Base.getObjSize(obj);
		returnString = "{";
		i = 0;

		for ( key in obj)
		{
			if (!obj[key])
				continue;
			
			returnString += key.toString();
			returnString += ": ";
			
			if( typeof obj[key] == 'object')
				returnString += JQuery.DesignTime.Widget.Base.convertObjToString(obj[key]);
			else if( typeof obj[key] == 'string' && obj[key].length){
				returnString += '"' + obj[key] + '"';
				}
			else{
				returnString += obj[key];
				}
			
			if( i < objLength-1 )
				returnString += ", ";
			
			i++;	
		}
		returnString += "}";
	}
	else
	{
		if( dw.isDebugBuild() )
		{
			alert("Can not parse this object type!");
		}
	}
	//alert('obj String'+returnString);
		
	return returnString;
}
