////////////////////////////////////////////////////////////////////////////////
// ScrollingPanelManager
////////////////////////////////////////////////////////////////////////////////

function ScrollingPanelManager() {
	this.resultsScroller	= $("resultsScroller");
	this.adminFrame			= $("queryFetcher");
	this.sidebar			= $("searchMenu");
	window.addOnresize("D.debug('onresize'); if (scrollingPanelManager){scrollingPanelManager.divAndIframeResize();}");

	/*
	pretty sure this doesn't do anything now... was needed for ff 1.5...
	if (!document.all && this.resultsScroller) {
		//TODO - firefox 2.0 we dont need this anymore, but since it calls preventDefault, it might not cost us anything to just leave it in. 
		//document.body.addEventListener("DOMMouseScroll", mouseWheelScroller, true);
	}
	*/
}

/**
 * Resizes main viewport items to fill screen
 *
 * -- $('resultsScroller')
 * -- $('adminFrame')
 * -- $('searchMenu')
 *
 */
ScrollingPanelManager.prototype.divAndIframeResize = function() {

	// admin state is implemented by exploding the queryFetcher iframe
	if (window.stateManager && window.stateManager.isState("adminState")) {
		this.adminFrame.style.height = util.getWindowHeight() + "px";
	
	// otherwise, resize usual viewport
	} else {
		var windowHeight = util.getWindowHeight();
		var resultsScrollerHeight = windowHeight - util.getTop(this.resultsScroller);
		this.resultsScroller.style.height = resultsScrollerHeight + "px";
		try {
			var sidebarHeight = windowHeight - util.getTop(this.sidebar);
			this.sidebar.style.height = sidebarHeight + "px";
		} catch (e) {
			D.warn("report menu not found");
		}
	}	
}


////////////////////////////////////////////////////////////////////////////////
// SkinManager
////////////////////////////////////////////////////////////////////////////////

function SkinManager() {
	this.activeSkinStyleSheetIndex   = -1;
	this.mouseOverHighlightColor     = false;
	this.mouseOverHighlightTextColor = false;
	this.searchTermHighlightColor    = false;
	this.buildCrossBrowserSkinList();
	this.pullHighlightColors();	
	this.explicitHighlight = false;	
}
SkinManager.prototype.attach = function() {
	
	// Report view state controls
	addEvent($("reportViewToggle"), 'click', this.handleResultsViewClick.bind(this));
	
	// If mouseover and mouseout events fall all the way down to document, they get caught here 
	addEvent(document, 'mouseover', this.handleMouseOver.bind(this));
	addEvent(document, 'mouseout', this.handleMouseOut.bind(this));	

	window.splunkEvents["preferencesLoaded"].subscribe(this.handlePrefsLoad.bind(this));
}

SkinManager.prototype.handlePrefsLoad = function() {
	skin = stateManager.getProp("skin");
	this.setSkin(skin);
}

SkinManager.prototype.buildCrossBrowserSkinList = function() {
	var linkTags = document.getElementsByTagName("LINK");
	this.skinList = [];
	var lim = linkTags.length;
	for (var i=0;i<lim; i++ ) {
		var ss = linkTags[i];
		if (ss.getAttribute("href").indexOf("skins")!=-1) {
			// skinList has the cross browser references returned by getCSSDomReference. 
			// which may be a link tag, or maybe a reference into document.styleSheets 
			this.skinList[this.skinList.length] = this.getCSSDomReference(ss);
		}	
	}
}
SkinManager.prototype.getActiveSkin = function() {
	if (this.activeSkinStyleSheetIndex > -1) {
		return this.skinList[this.activeSkinStyleSheetIndex];
	}
	// this will only run once when the system loads.  its a bit pedantic because we could just default activeSkinStyleSheetIndex to 0, as the first skin in the list is always the selected one
	var lim = this.skinList.length;
	for (var i=0; i < lim; i++) {
		var skin = this.skinList[i];
		if (this._checkIfActive(skin)) {
			this.activeSkinStyleSheetIndex = i;
			return skin;
		}
	}
	if (this.skinList && this.skinList[0]) return this.skinList[0];
	alert("no skins found");
	return false;
}
SkinManager.prototype.getActiveSkinTitle = function() {
	if (is.opera) return "not implemented";
	else if (this.activeSkinTitle) return this.activeSkinTitle;
	else return this.activeSkinTitle = this.getActiveSkin().title;
}
SkinManager.prototype._checkIfActive = function(skin) {
	if (skin.getAttribute && (skin.getAttribute("disabled") != "false"))  return false;
	else if ((skin.disabled==true)) return false;
	else return true;
}
SkinManager.prototype._hasTitle = function(skin, title) {
	if (this._getTitle(skin) == title) return true;
	else return false
}
SkinManager.prototype._getTitle = function(skin) {
	if (skin.title) return skin.title;
	else if (skin.getAttribute && (skin.getAttribute("title"))) return skin.getAttribute("title");
	else return false
}

// code to change the active stylesheet
SkinManager.prototype.setSkin = function(title) {
	try {
		title = title.toLowerCase();
	}
	catch(e) {
		D.error("SkinManager.setSkin - undefined skin (" + skin + "). Exiting.")
		return false;
	}

	this.activeSkinTitle = false
	for (var i=0; i<this.skinList.length; i++) {
		var skin = this.skinList[i];
		if (this._hasTitle(skin,title)) {
			this.activeSkinStyleSheetIndex = i;
			skin.disabled = false;
			this.activeSkinTitle = title;
		}
		else skin.disabled = true;
	}
	// if none of them matched we will have turned them all off. If that happens, turn on the first one.
	if (!this.activeSkinTitle) {
		this.activeSkinStyleSheetIndex = 0;
		this.skinList[0].disabled = false;
		this.activeSkinTitle = this._getTitle(this.getActiveSkin());
	}
	
	this.pullHighlightColors();	
	return true;
}


SkinManager.prototype._setCssRule = function (newSelector, newRule, index) {
	// be aware of browser crashes
	if (is.opera) return false;

	var styleSheet = this.getActiveSkin();
	var rules = (document.all) ? styleSheet.rules : styleSheet.cssRules;

	if (styleSheet.deleteRule) styleSheet.deleteRule(index);
	else if (styleSheet.removeRule) styleSheet.removeRule(index);
	try {
		if (styleSheet.insertRule) styleSheet.insertRule(newSelector +"{"+newRule+"}",  index);
		else if (styleSheet.addRule) styleSheet.addRule(newSelector, newRule, index);	
	}
	catch (e){
		D.error('Please send this error to one of the splunk ui developers. SkinManager tried to push this CSS selector into the CSSDOM:: ' + newSelector +".\nThis error was caused by '" + newSelector + "'");
		return false;
	}
	return true;
}


// dhtml frameworks typically use either LINK elements in a list, or the document.stylesheets collection. 
// we need to use the former to get wider browser support, and the stylesheet switching is easier
// but we need the fancier dom methods to be able to call insertRule/addRule for the dynamic highlighting. 
// this function provides a one way conversion, from linkElements in the DOM, returning the right CSSDom object. 
SkinManager.prototype.getCSSDomReference = function(linkElement) {
	if (!document.styleSheets) return linkElement
	var lim = document.styleSheets.length;
	for (var i=0; i<lim; i++) {
		
		// TODO: this is a semi-hack, done to circumvent the safari bug
		// that doesn't recognize the .title property
		var sheet = document.styleSheets[i];
		if (sheet.href && (sheet.href.replace(/https?...[^\/]+/, '') == linkElement.getAttribute("href"))) return sheet;
	}
	//alert("Can't get a cross-browser reference to a CSSDom object.")
	return false;
}
SkinManager.prototype.reconcileSkin = function(someDocument) {
	var newTitle = this.getActiveSkinTitle();
	var styleElem = someDocument.createElement("link");
	styleElem.setAttribute("type", "text/css");
	styleElem.setAttribute("rel", "stylesheet");
	styleElem.setAttribute("href", util.getStaticPath() + "/css/skins/" + newTitle + ".css");
	someDocument.getElementsByTagName("head")[0].appendChild(styleElem);
}
SkinManager.prototype.handleMouseOut = function(mozEvent) {
	if (this.explicitHighlight) {
		var originator = util.getOrigin(mozEvent);
		this.removeHighlight(mozEvent);
		if (!originator.getAttribute("title")) util.cancelEventBubbling(mozEvent);
	}
	return true;
}
// This is necessary so that the skin CSS is indeed a comprehensive skin. Before this, the highlight color had to be hardcoded somewhere in the js.
SkinManager.prototype.pullHighlightColors = function() {
	if (is.opera) return false;
	var skinStyleSheet = this.getActiveSkin();

	if (skinStyleSheet) {
		var rules = (document.all) ? skinStyleSheet.rules : skinStyleSheet.cssRules;
		try {
			this.mouseOverHighlightColor = rules[0].style.backgroundColor;
			if (rules[0].style.color) {
				this.mouseOverHighlightTextColor = rules[0].style.color;
			}
			this.searchTermHighlightColor = rules[1].style.backgroundColor;
		} catch(e) {
			alert('Caught exception in SkinManager.pullHighlightColors - \n' + e);
		}
	}
	else {
		this.mouseOverHighlightColor = "#ffed99";
		//this.mouseOverHighlightTextColor = "#ffed99";
		this.searchTermHighlightColor = "#ffed99";
	}
	return true;
}

SkinManager.prototype.handleMouseOver = function(mozEvent) {
	var originator = util.getOrigin(mozEvent);
	if (util.isOfClass(originator, "light")) originator = originator.parentNode;

	// suppress highlighting
	if (util.isOfClass(originator, "nolight")) return false;
	
	//D.debug("handleMouseOver for " + util.getTagName(originator) );
	if (((util.getTagName(originator)=="LABEL") || util.getTagName(originator)=="SVG:PATH") && !util.isOfClass(originator,"label")) {
		var parent = originator.parentNode
		// Simplest. Always the only one if format is all (hence no nested segments ever).  
		// and even in format::all, cascading menu highlights and outermost label highlighting always fall in here.
		if ((searchController.getFormat() != 'all') || util.getTagName(parent) != "LABEL") {
			this.highlightLabel(originator);
		}
		// this one is relevant if format::all is used on queries.   You wont understand it unless you change format to format::all in QueryResults and look at the "pyramids view" by putting an extra classname "exploded" into <div id="outerWrapper"> in index.html
		else if (parent.childNodes[parent.childNodes.length-1] == originator) { 
			this.highlightLabel(parent);
			//debugMsg("higlighting parent label " + util.getTextContent(parent));
		}
		else {
			this.highlightLabel(originator);
			//debugMsg("higlighting label " + util.getTextContent(originator));
		}
	}
	
}


SkinManager.prototype.highlightLabel = function(labelReference) {
	// Always clear any timeout if highlightLabel gets called again.
	if (this.highlightAttacher) {
		D.debug("highlightLabel - CLEARING highlightAttacher");
		window.clearTimeout(this.highlightAttacher);
	}
	// cancel the expensive remove, cause the style will soon enough be overwritten by the new highlight rule.
	if (this.highlightRemover) {
		window.clearTimeout(this.highlightRemover);
		this.highlightRemover = null;
	}
	// usually false, cause previous onmouseout will have taken care of it.
	if (this.explicitHighlight) {util.removeClass(this.explicitHighlight, "explicitMouseOver");}

	if (this.explicitOpen) {
		// despite identical markup, mozilla sometimes forces a close on a label tag if it contains a ul element.
		if ((labelReference.parentNode.parentNode.parentNode != this.explicitOpen) && (labelReference.parentNode.parentNode.parentNode.parentNode != this.explicitOpen)) {
			util.removeClass(this.explicitOpen, "open");
			this.explicitOpen = false;
		}
	}
	// set it to the new one.
	this.explicitHighlight = labelReference;
	//var className = this.explicitHighlight.getAttribute('class');
	var className = labelReference.className;
	className = className.split(" ").shift();
	
	if (util.getTagName(labelReference)=="SVG:PATH") {
		className = labelReference.getAttribute("class");
	}
	
	if (util.isOfClass(this.explicitHighlight.parentNode,"secondary")) {
		this.explicitOpen = this.explicitHighlight.parentNode;
		// add the class to our containing <li>, that will make the child <ul> node visible
		util.addClass(this.explicitOpen, "open");
		
		if (this.explicitOpen.getAttribute("id") == "savedSplunksMenuCloneTarget") {
			var ulElements = this.explicitOpen.getElementsByTagName("UL");
			if (ulElements.length) {
				popupLayerManager.constrainLayerToWindow(ulElements[0], 10, 280, true);
			}
		}
	}
	else if ((util.getTagName(this.explicitHighlight.parentNode)!="LI") && (this.explicitHighlight.parentNode.id!="typeAhead") && util.getTagName(this.explicitHighlight.parentNode) !="LI"){
		if ((className != "") && (className != "explicitMouseOver") && (className != "clickableTime")) {
			//D.debug("highlightLabel pushing highlight change for classname + '" + className + "'");
			this.highlightAttacher = window.setTimeout('skinManager.highlightAllLabels("'+className+'");',1000);
		}
	}
	util.addClass(this.explicitHighlight, "explicitMouseOver");
}

// returns false and does nothing in rare cases where the className in question isnt supposed to get highlighted.
SkinManager.prototype.highlightAllLabels = function(className) {
	if (endlessPageController.scrollInProgress) return false;
	var selector = "label." + className;
	var rule = ""
	if (this.mouseOverHighlightColor) {
		rule += "background-color:" + this.mouseOverHighlightColor  + " !important;";
	}
	if (this.mouseOverHighlightTextColor) {
		rule += "color:"  + this.mouseOverHighlightTextColor + ";"
	}
	// only for the subhistograms
	if ((className.indexOf("sg")!=0))
		rule += "display:block !important;";
	
	//this is where most of the expense is.  forces the page to re-render.
	this._setCssRule(selector, rule, 0);
	D.debug("SkinManager._setCssRule " + selector + ", " + rule);
	return true;
}
//TODO Optimize the following case.   Mouseovers in format::all,  from say the "2" in an ip address,  to the "." immediately before it,  do a full onmouseout remove from CSSDom, then onmouseover add the same damn label's class back to the CSS Dom.  Mouseover on the highlight could in theory cancel the remove timeout, if it knew that the pending remove was identical to the pending add... -n
SkinManager.prototype.removeHighlight = function(mozEvent) {
	
	if (this.explicitHighlight && !util.isOfClass(this.explicitHighlight, "secondary")) {
		util.removeClass(this.explicitHighlight, "explicitMouseOver");
		this.explicitHighlight = null;
	}
	if (this.highlightRemover) {
		window.clearTimeout(this.highlightRemover);
		this.highlightRemover = null;
	}
	if (this.highlightAttacher) {
		window.clearTimeout(this.highlightAttacher);
		this.highlightAttacher = null;
	}
	
	// handle the kv extraction
	//var target = util.getOrigin(mozEvent);
	//if(target.getAttribute('k')) {
	//	util.addClass(target, 'kv');
	//	util.removeClass(target, 'kvOn');
	//}
	

	// We wrap it in a timeout,  so that if another mouseover comes,  then this cleanup remove doesnt need to be done. 
	this.highlightRemover = window.setTimeout('skinManager._setCssRule(".stubClassName", "background-color:'    + this.mouseOverHighlightColor +'", 0);',1000);
}
SkinManager.prototype.highlightContextMenu = function(rowElement) {
	this.resultRowHighlight = rowElement;
	util.addClass(this.resultRowHighlight, "rowHighlight");
}
SkinManager.prototype.unHighlightContextMenu = function() {
	util.removeClass(this.resultRowHighlight, "rowHighlight");
}



/**
 * Fires custom event when user clicks on result view
 *
 */
SkinManager.prototype.handleResultsViewClick = function(mozEvent) {

	var originator = util.getOrigin(mozEvent);
	var newState =  stateManager.isState("reportView") ? "normalView" : "reportView";
	stateManager.stateChange(newState, true);
	window.splunkEvents["resultsViewChange"].fire({state: newState});
	D.debug("SkinManager.handleResultsViewClick - END got click event: state=" + newState);
}



/**
 * Handles the index dropdown menu
 *
 */
SkinManager.prototype.handleIndexMenuChange = function(targetObject) {
	searchController.setPulldownIndexName(util.getSelectedValue(targetObject), true);
	queryFactory.runIndexedMetaQuery()
}




////////////////////////////////////////////////////////////////////////////////
// PopupLayerManager
////////////////////////////////////////////////////////////////////////////////

function PopupLayerManager() {
	if (window.popupLayerManager) alert('hey, i already exist, what gives?');
	//this.persistentModalStates    = {"setFormat" : 1, "setSkin" : 1, "setClickBehaviour" : 1};
	this.layerIdsActive = {};
	this.draggingLayer = false;
	this.resizingLayer = false;
	this.constrainedDraggingFollower = false;
	// used for both dragging and resizing...
	// TODO make a Point class...
	this.lastMouseX = false;
	this.lastMouseY = false;
	this.minResizingHeight = 200;
	this.minResizingWidth  = 400;
	this.maxResizingHeight = 700;
	this.maxResizingWidth  = 600;
	this._bodyGuardForIe = document.getElementById("popupLayerBodyGuard");

	
	var h5Elements = document.getElementsByTagName("H5");
	var lim = h5Elements.length;
	for (var i=0;i<lim;i++) {
		var elt = h5Elements[i];
		if (util.isOfClass(elt, "popperUpper")) {
			elt.onclick = function(mozEvent) {
				popupLayerManager.handlePopperUpperRequest(this);
				util.cancelEventBubbling(mozEvent);
				return false;
			}
		}
	}

	addEvent(document,"keypress",  this.handleKeyPress.bind(this));
	addEvent(document,"mousemove", this.handleMouseMove.bind(this));
	addEvent(document,"mouseup",   this.handleMouseUp.bind(this));
}

/*
 *
 * NOTE: only once child <ul> is visible, will it have accurate offset properties.  
 * Consequently calling this method on hidden menus just before they are shown will not work.
 */
PopupLayerManager.prototype.constrainLayerToWindow = function(elt, extraPadding, safeWidth, isPreservingFloatedWidth) {
	// NOTE There are some shortcomings in CSS around the width property.
	// Basically floated values for width are magical things that once you perturb by setting 
	// to the prop directly, can never be regained.
	// EG.  take an element with a complex floated width. assign style.width to "250px",  
	// then try and set width back to "auto".  
	// It wont return to the original floated width.  
	// Thus if we see a big floated width, we cache it, and we'll set it back to that 
	// explicit value at the end. 
	if (isPreservingFloatedWidth && !elt._flowedWidth) elt._flowedWidth = elt.offsetWidth;

	var bottomEdgeOfElement   = util.getTop(elt) + elt.offsetHeight;
	var windowHeight = util.getWindowHeight();
	// if we already have active scrollbars,  or if the bottom edge is offscreen. 
	D.debug(bottomEdgeOfElement + " + " + (extraPadding + util.getOverflow(elt)) + " > " + windowHeight);
	if (bottomEdgeOfElement + util.getOverflow(elt) + extraPadding > windowHeight) {
		var newHeight =  windowHeight - util.getTop(elt);				
		elt.style.overflow = "auto";
		elt.style.width = safeWidth + "px";
		newHeight -=extraPadding;
		elt.style.height = newHeight + "px";
	} else {
		elt.style.height = "auto";
		if (isPreservingFloatedWidth && elt._flowedWidth) elt.style.width  = elt._flowedWidth + "px";
		else elt.style.width = "auto";
	}
}

PopupLayerManager.prototype.handleMouseMove = function(mozEvent) {
	
	// dump out if nothing to do
	if (!this.draggingLayer && !this.resizingLayer) return true;
	
	var ourEvent = util.getEvent(mozEvent);
	
	// first mousemove after a dragStart or resizeStart will fall in here. 
	if (!this.lastMouseX) {
		this.lastMouseX = ourEvent.clientX;
		this.lastMouseY = ourEvent.clientY;
	}

	// TODO - make a Point class
	var dx = ourEvent.clientX - this.lastMouseX;
	var dy = ourEvent.clientY - this.lastMouseY;

	//D.debug("received mouse move: " + dx + ", " + dy);

	// handling mouse dragging events
	if (this.draggingLayer) {
		util.cancelEventBubbling(mozEvent);
		if (dx) util.setLeft(this.draggingLayer, this.draggingLayer.offsetLeft + dx);
		if (dy) util.setTop(this.draggingLayer, this.draggingLayer.offsetTop + dy);
		/*
		if (stateManager) {
			stateManager.stateChange("suppressSelection", true);
			if (stateManager.isState('adminState')) {
				util.addClass(util.getIframeDocument().getElementById("outerWrapper"), "suppressSelection")
			}
		}
		*/
		
		if (is.ie && is.ieVersion <7) {
			popupLayerManager.deployBodyGuard(this.draggingLayer);
			//document.onselectstart = function(){return false;}
		}
	}

	// handle window resize events
	// NOTE: currently this section is really only for the help assistant,
	// as it's the only window that is resizable
	if (this.resizingLayer) {
		var newWidth  = this.resizingLayer.offsetWidth + dx;
		var newHeight = this.resizingLayer.offsetHeight + dy;
		
		newWidth  = Math.max(newWidth,this.minResizingWidth);
		newWidth  = Math.min(newWidth,this.maxResizingWidth);

		newHeight = Math.max(newHeight,this.minResizingHeight);
		newHeight = Math.min(newHeight,this.maxResizingHeight);
		
		
		if (dx) util.setWidth(this.resizingLayer, newWidth - 14);	   // TODO: the 14px is to compensate for the padding style on #popupContent, i.e. the resize layer
		if (dy) util.setHeight(this.resizingLayer, newHeight);
		if (dx) util.setWidth(this.resizingLayer.offsetParent, newWidth);

		// TODO: 
		// because the help viewer
		// has 2 viewing windows, we want to make sure to scale both viewers
		// whenever dragging the resize handle
		// the interger adjustment values are to compensate for CSS padding
		// on the viewer window
		if (this.resizingLayer.id == 'helpContent') {
			if (dx) {
				util.setWidth($('helpFrameViewer'), newWidth - 14);
			}
			if (dy) {
				util.setHeight($('helpFrameViewer'), newHeight - 30);
				util.setHeight($('helpBodyPage'), newHeight - 40);
			}
		}

		D.debug("resizing layer dimensions to: " + newWidth + "x" + newHeight + '; actual is: ' + this.resizingLayer.offsetWidth + "x" + this.resizingLayer.offsetHeight + " [alt: " + this.resizingLayer.style.width + "x" + this.resizingLayer.style.height + "]");
		
		if (is.ie && is.ieVersion <7) {
			popupLayerManager.deployBodyGuard(this.resizingLayer.offsetParent);
		}
	}

	this.lastMouseX = ourEvent.clientX;
	this.lastMouseY = ourEvent.clientY;
	return true;

}
PopupLayerManager.prototype.handleMouseUp = function( mozEvent) {
	util.cancelEventBubbling(mozEvent);
	return popupLayerManager.stopAllDragging(mozEvent);
}
PopupLayerManager.prototype.dragStart = function(dragger) {
	//D.debug("dragStart");
	this.draggingLayer = dragger.offsetParent;
}

PopupLayerManager.prototype.stopAllDragging = function(mozEvent) {
   
	//D.debug("stopAllDragging");
	
	/*
	if (stateManager) {
		stateManager.stateChange("suppressSelection", false);
		if (stateManager.isState('adminState')) {
			util.removeClass(util.getIframeDocument().getElementById("outerWrapper"), "suppressSelection")
		}
	}
	*/
	//if (document.all && document.onselectstart && !document.onselectstart()) document.onselectstart = selectionHandler

	if (this.draggingLayer && this.resizingLayer) {
		var resetLeft = this.resizingLayer.offsetLeft + this.resizingLayer.offsetWidth - this.draggingLayer.offsetWidth ;
		var resetTop = this.resizingLayer.offsetTop + this.resizingLayer.offsetHeight - this.draggingLayer.offsetHeight + 17;
		util.setLeft(this.draggingLayer,resetLeft);
		util.setTop(this.draggingLayer,resetTop);
	}
	//this.constrainedDraggingFollower = this.draggingLayer = this.lastMouseX = this.lastMouseY = false;
	//D.debug("dragging stopped");
	this.draggingLayer = this.resizingLayer = this.lastMouseX = this.lastMouseY = false;
}

// NOTE:
// -- there's an invisible layer that stays with the mouse.  however we just hook this up to conventional dragging functionality,  and put a reference to him in draggingLayer.  
// we Also put a refernce to the content element, in resizingLayer,  and if resizingLayer is defined, we set height and width to values based on constrained dx and dy values on the dragging layer. 
// finally, onmouseup, invisible layer's position is always reset to the bottom right corner of the resizing layer again
PopupLayerManager.prototype.resizeStart = function(resizer) {
	this.draggingLayer = resizer;
	this.resizingLayer = document.getElementById("helpContent");
	
	//D.debug("init resize on dragging: " + this.draggingLayer.id + " resize: " + this.resizingLayer.id);
	
}


// takes all the input Fields in the popupLayer, and if the popperUpper has any attributes of same name,
// it'll clone those values into the fields.
// Also, and this is needed for the cascading menu part,  it'll create raw properties of same name on the
// popuplayer element.
PopupLayerManager.prototype.populatePopupFields = function(popperUpper, popupLayer) {

	popupLayer.opener = popperUpper;
	
	var walkingNode = popperUpper;
	// in a couple places, a popperUpper can open another popperUpper.
	// this will look to see if it's in a popupLayer, and steal the opener reference from it's parent.
	while (walkingNode.parentNode) {
		if (util.isOfClass(walkingNode, "popupLayer")) {
			popupLayer.opener = walkingNode.opener;
			break;
		}
		walkingNode = walkingNode.parentNode;
	}
	// try and focus the first text input.
	if (!util.focusFirstField(popupLayer, "SELECT")) {
		util.focusFirstField(popupLayer, "INPUT");
	}
	if (popupLayer.id == "tagType") {
		var typeId = popperUpper.getAttribute("typecode");
		if (typeId.indexOf("SP") ==-1) {
			typeId = "?" + typeId
		}
		$("displayedTypeId").innerHTML = typeId;
	}
	else if (popupLayer.id == "tagHost") {
		$("displayedHost").innerHTML = popperUpper.getAttribute("hostNameValue");
	}
}

PopupLayerManager.prototype.handleKeyPress = function(mozEvent) {
	var ourEvent = util.getEvent(mozEvent);
	if (ourEvent.keyCode == KEY_ESCAPE) {
		this.closeAll();
		if (stateManager) stateManager.stateChange('adminPopupLayerOpen', false);
	}
	return true;
}


/**
 * Handles requests to popup a window/menu/layer, etc.
 *
 * @param {object} popperUpper The object that fired the event to pop something
 * 					up.
 * @param {object} popupLayer The DOM node to pop up.
 *
 * @return {boolean}true if it was already popped up. False if it wasn't. 
 *
 */
PopupLayerManager.prototype.handlePopperUpperRequest = function(popperUpper, popupLayer) {
	
	//D.debugj("PopupLayerManager.handlePopperUpperRequest - START popperUpper=" + popperUpper.id);
	
	// if no popup layer object passed, then look at the requesting object to
	// see if there is one define there in 'layerid' attribute
	if (!popupLayer) {
		popupLayer  = document.getElementById(popperUpper.getAttribute("layerid"));
	}
	
	// stop if it's already popped up
	if (this.layerIdsActive[popupLayer.getAttribute("id")]) {	
		return true;
	}
	
	
	// close all other open popups
	if (popupLayer.getAttribute("id") != "histogramToolTipLayer") { this.closeAll();}
	if (window.typeAheadManager) window.typeAheadManager.closeAll();
	
	// render the popup
	this.popupLayer(popperUpper, popupLayer);
	
	return false;
	
}
/**
 * Returns boolean based on whether given layer is currently open. 
 *
 */
PopupLayerManager.prototype.isOpen = function(popupLayer) {
	return this.layerIdsActive.hasOwnProperty(popupLayer.getAttribute("id"));
}
/**
 * Renders a popup layer
 *
 * @param {object} popperUpper The object that fired the event to pop something
 * 					up.
 * @param {object} popupLayer The DOM node to pop up.
 *
 */
 // TODO: Optimize significantly
PopupLayerManager.prototype.popupLayer = function(popperUpper, popupLayer)
{
	var id = popupLayer.getAttribute('id'); 
  
	// notify stateManager of new popup state
	stateManager.stateChange(id + "Open", true);
	
	// mark popup layer as 'active'
	this.layerIdsActive[id] = true;

	D.debug("PopupLayerManager.popupLayer - START popupLayer=" + popupLayer.id);
	
	// set initial origin
	var top,left;

	var resultsScroller = $('resultsScroller');
	var isOpenerWithinResultsScroller = (resultsScroller && popperUpper && util.firstIsParentOf(resultsScroller,popperUpper));
	
	// if there is an originating element, draw the popup layer relative to it
	if (popperUpper) {

		var offset = util.cumulativeOffset(popperUpper);
		left = offset.left;
		top = offset.top;
		
		// fix for WebKit's miscalculation of popperUpper's offsetTop
		if (navigator.appVersion.indexOf('AppleWebKit') != -1)
		{
			if (popperUpper.tagName == 'TD')
			{
				top -= popperUpper.offsetTop;
			}
		}
		
		if (isOpenerWithinResultsScroller)
		{
			var walker = popperUpper;
			while (walker && walker.offsetParent && !util.isOfClass(walker , "endlessScroller") && !util.isOfClass(walker , "timestamp") && walker.id!="resultsScroller") {
				walker = walker.offsetParent;
			}
			
			var isLayerWithinResultsScroller = (resultsScroller && popupLayer && util.firstIsParentOf(resultsScroller,popupLayer));
			if (isLayerWithinResultsScroller) {
				if (util.isOfClass(walker, "endlessScroller") || util.isOfClass(walker , "timestamp")) {
					// if we're inside a deck of the endlessPageController, tell him he has 
					// to bring the containing card to a higher z-index.  SPL-7965	
					endlessPageController.bringCardToFront(walker);
					top -= util.cumulativeOffset(walker).top;
				}
				else
				{
					top -= util.cumulativeScrollOffset(popperUpper).top;
				}
			}
		}

		// add the height of the popper upper 
		var popperUpperHeight = popperUpper.offsetHeight;
		if (!popperUpperHeight) {
			// floated or inlined elements such as the ones in the timeFields,
			// will report an offsetHeight of 0, which isnt really helpful to us.
			// What we do is look inside and see if the popperUpper wraps an
			// image, and get the image's height.
			var firstChild = popperUpper.childNodes[0];
			if (firstChild && (util.getTagName(firstChild)=="IMG")) {
				popperUpperHeight = parseInt(firstChild.getAttribute("height"),10) ;
			}
			// still strange side effects of our searchResults css;  mozilla 
			// calculates wrong offsetLeft positions for the popperUppers in the
			// search results.
			if (!document.all && ( popupLayer.id=="tagType" || popupLayer.id=="sourceTypeRenameForm")) {
				popperUpperHeight = 0;
			}	
		}
		top += popperUpperHeight;

		// Adjust left based on alignment attribute 
		if (popupLayer.getAttribute("alignment") == "right") {
			left += popperUpper.offsetWidth - popupLayer.offsetWidth;
		}
		
		// fill in the popup layer with its options
		this.populatePopupFields(popperUpper, popupLayer);
		
	}

	var windowHeight = util.getWindowHeight();
	var windowWidth  = util.getWindowWidth();
	
	// if we have a centering attrib, center the width of the layer
	if (popupLayer.getAttribute("centerHoriz")) {
		left = (windowWidth - popupLayer.offsetWidth) / 2;
	}

	// if any absolute atts are used, they override default offset-based values.
	var t = popupLayer.getAttribute('t');
	if (t)
	{
		top = parseInt(t, 10);
		D.debug('t is ' + t);
	}
	
	var l = popupLayer.getAttribute('l');
	if (l) left = parseInt(l, 10);
	else
	{
		var r = popupLayer.getAttribute('r');
		if (r) left = windowWidth - parseInt(r, 10);
	}
	
	// then check for offsets
	var xoffset = popupLayer.getAttribute('xoffset');
	if (xoffset) left += parseInt(xoffset,10);

	var yoffset = popupLayer.getAttribute('yoffset');
	if (yoffset) top += parseInt(yoffset,10);
	
	// correcting for boundary conditions.
	if (!isOpenerWithinResultsScroller && (top + popupLayer.offsetHeight > windowHeight)) {
		top = windowHeight - popupLayer.offsetHeight;
	}
	if (left < 0) left = 0;
	
	D.debug("PopupLayerManager.popupLayer left=" + left + ", top=" + top + ", yoffset=" + yoffset);
	
	// assign the new coordinates to the popup layer
	util.setLeft(popupLayer, left);
	util.setTop(popupLayer,top);

	// reveal the popup layer
	popupLayer.style.visibility = 'visible';
	popupLayer.style.display = 'block';

	

	if (is.ie && is.ieVersion <7 && !isOpenerWithinResultsScroller) this.deployBodyGuard(popupLayer);
}



// the bodyguard iframe is an iframe that prevents selects from burning through superimposed positioned layers.
// the bodyguard trumps the select, and the layer trumps the bodyguard. 
// function specifically called from handlePopperUpperRequest, but someday might need to nudge it from outside.
// eg (if popupLayers ever start resizing on window resize)
PopupLayerManager.prototype.deployBodyGuard = function(popupLayer) {
	//debugMsg("deploying bodyGuard", "startTiming")
	util.setLeft(this._bodyGuardForIe,popupLayer.offsetLeft);
	util.setTop(this._bodyGuardForIe, popupLayer.offsetTop);
	util.setWidth(this._bodyGuardForIe,popupLayer.offsetWidth);
	util.setHeight(this._bodyGuardForIe,popupLayer.offsetHeight);
	// only show it after changing it's dimensions.  Might save us some extra render cycles
	this._bodyGuardForIe.style.display = "block"
	//debugMsg("deploying bodyGuard", "endTiming")
}

PopupLayerManager.prototype.hideBodyGuard = function(left, top, width, height) {
	//debugMsg("hiding bodyGuard", "startTiming")
	// hide it before changing it's dimensions.  Might save us some extra render cycles
	this._bodyGuardForIe.style.display = "none"
	util.setLeft(this._bodyGuardForIe,-200);
	util.setTop(this._bodyGuardForIe,0);
	util.setWidth(this._bodyGuardForIe,100);
	util.setHeight(this._bodyGuardForIe,100);
	//debugMsg("hiding bodyGuard", "endTiming")
}
// returns false if it couldnt find popupLayer containing formReference. true if it can. 
PopupLayerManager.prototype.closeContainingPopupLayer = function(formReference) { 
	var walkingElement = formReference;
	while (walkingElement.parentNode && walkingElement.className.indexOf("popupLayer")==-1 ) {
		walkingElement = walkingElement.parentNode;
		if (!walkingElement.parentNode) return false;
	}
	// if we're still here, then walkingElement is of className popupLayer
	var ourPopupLayer = walkingElement;
	ourPopupLayer.style.visibility = "hidden"
	//ourPopupLayer.style.left = "-1000px";

	this.layerIdsActive[ourPopupLayer.getAttribute("id")] = false;
	if (is.ie && is.ieVersion <7) this.hideBodyGuard();
	return true;
}


/**
 * Close all open popup layers.
 *
 * this is the anchor man in onclick handling. If any popup layers are open, 
 * he will close them and trap the click.  First checking to make sure that
 * the event didnt originate within the layer itself.  Otherwise he'll step
 * off and return true.
 *
 * This method called at: 
 *		end of processTermClick
 *		stateChange into landingPageState
 *		clearStartTime, clearEndTime,  entryClearer
 *		typeAheadManager.closeAll();
 *		check and share splunkEvents
 *		document.onclick anything with an href
 *		anything not of the known set of originator tagNames
 *		label click, originator is of class menu
 *		any escape key anywhere
 *		handlePopperUpperRequest from elements that are not already open
 *
 * @param {DOMElement} originator The DOM element that fired the event that
 * 			called this method.
 */
PopupLayerManager.prototype.closeAll = function(originator) {
	
	// if called anonymously, scope this at document level
	if (!originator) originator = document;

	

	// loop through all the registered popup layers and close them;
	// if was open, supress all subsequent click actions
	for (layerId in this.layerIdsActive) {
		if (this._closeLayer(originator, layerId)) return false;
	}

	// notify stateManager of no popup state
	if (stateManager) stateManager.stateChange("noPopupLayersOpen",true);

	// return true allows click's default actions to take place. Anything from
	// scrollbars, right-click menus, you name it. 
	return true;

}



/**
 * Closes a specific popup layer, by DOM ID.
 *
 * @param {DOMElement} originator The DOM element that fired the event that
 *			called this method.
 * @param {string} layerId The DOM ID of the popup layer to close.
 *
 * @return {boolean} true if the layer was successfully closed; false if the 
 *			layer wasn't open to begin with.
 *
 */
PopupLayerManager.prototype._closeLayer = function(originator, layerId) {
	if (this.layerIdsActive[layerId]) {
		
		// get reference to layer; dump out if it doesn't exist
		var popupLayer = $(layerId);
		if (!popupLayer) return false;
		
		// remove any 'open' state associated with this layer
		if (stateManager) stateManager.stateChange(layerId + "Open", false);

		// if closing the distributed server layer, notify the query manager
		// of any selection changes
		if (queryFactory && (layerId=="searchServerChooserCloneTarget")) {
			queryFactory.checkForServerListChangeOnClose();
		}
		

		// TODO: just make these clients not even call _closeLayer
		// oh, and mozilla or someone has a <scrollbar> element which is annoying
		if ((util.firstIsParentOf(popupLayer, originator) || util.getTagName(originator) == "scrollbar") 
			&& !util.isOfClass(originator,"cancel")
		) {
			return false;
		}
		if (popupLayer.onLayerClose) popupLayer.onLayerClose(originator);
		
		// otherwise, hide the layer and remove from active roster
		popupLayer.style.visibility = "hidden";
		popupLayer.style.display = "none"; // SPL-11700
		//popupLayer.style.left = "-1000px";
		delete this.layerIdsActive[layerId];
		if (is.ie && is.ieVersion <7) this.hideBodyGuard();

		return true;
	} 
	else {
		return false;	
	}
}



PopupLayerManager.prototype.attachModalHandlers = function(rootNode) {
	var ulElements;
	if(rootNode) ulElements = rootNode.getElementsByTagName("UL");
	else ulElements = document.getElementsByTagName("UL");
  
	var lim = ulElements.length;
	for (var i=0;i<lim;i++) {
		var elt = ulElements[i];
		// not all ul elements are selectable modal menus
		if (util.isOfClass(elt,"modalOptions")) {

			elt.onclick = function(mozEvent) {
				var ourEvent = util.getEvent(mozEvent);
				popupLayerManager.handleModalClick(this,ourEvent);
				util.cancelEventBubbling(ourEvent);
				return false;
			}
			// not all selectable modal menus are necessarily persistent
			/*
			if (elt.id in this.persistentModalStates) {
				var ulId = elt.id
				var preferenceValue = stateManager.getProp(ulId);
				if (preferenceValue) { 
					var setterMethod = document.getElementById(ulId).getAttribute("setter");
					window.addOnload(setterMethod + "('" + preferenceValue + "');popupLayerManager.updateModalSelectionState(document.getElementById('" + ulId + "'),'" + preferenceValue + "')");
				}
			}
			*/
		}
	}
}
PopupLayerManager.prototype.handleModalClick = function(ulElement, ourEvent) {
	var originator = util.getOrigin(ourEvent);
	if (util.isOfClass(originator, "light")) originator = originator.parentNode;
	if (!util.isOfClass(originator,"disabled")) {
		var setterMethod = ulElement.getAttribute("setter");
		var newValue = originator.getAttribute("argument")
		if (!newValue) newValue = util.getTextContent(originator);
		
		var returnValue = eval(setterMethod + "('"+ newValue + "');");
		if (returnValue) {
			this.updateModalSelectionState(ulElement, newValue);
		}
	}
	
}
PopupLayerManager.prototype.updateModalSelectionState = function(ulElement, selectedValue) {
	var labelElements = ulElement.getElementsByTagName("LABEL");
	if (util.isOfClass(ulElement, "allowMultiple")) {
		
		clickedOnElement = null
		var lim = labelElements.length;
		for (var i=0;i<lim;i++) {
			var elt = labelElements[i];
			if (util.getTextContent(elt) == selectedValue) clickedOnElement = elt;
		}
		if (!util.isOfClass(clickedOnElement,"modalSelectionOn")) {
			util.addClass(clickedOnElement,"modalSelectionOn");
		}
		else {
			util.removeClass(clickedOnElement,"modalSelectionOn");
		}
	}
	else {
		var lim = labelElements.length;
		for (var i=0;i<lim;i++) {
			var elt = labelElements[i];
			if (util.getTextContent(elt) == selectedValue) {
				util.addClass(elt,"modalSelectionOn");
			}
			else {
				util.removeClass(elt,"modalSelectionOn");
			}
		}
		setTimeout("popupLayerManager.closeAll();",150);
	}
}



////////////////////////////////////////////////////////////////////////////////
// Form-based search model
////////////////////////////////////////////////////////////////////////////////

function TemplateSearch(inputString) {
	this.rawString 			= '';
	this.templateModels 	= {};
	this.userInputs 		= {};
	this.tokenOrder			= [];
	if (inputString) this.parseString(inputString);
}


/**
 * Defines the regex used to extract dynamic fields from search string
 *
 */
TemplateSearch.prototype.tokenMatch = /([^\s\$]*)(\$([^\$]+)\$)/g;
TemplateSearch.prototype.kvSplitter = /\s*=\s*/;
TemplateSearch.prototype.listSplitter = /\s*,\s*/;
TemplateSearch.prototype.customFuncMatch = /\{([^\}]+)\}/;


/**
 * Generates a template model from a search string
 *
 */
TemplateSearch.prototype.parseString = function(inputString) {

	this.rawString = inputString;
	this.templateModels = {};
	this.tokenOrder = [];
	
	var tokenName, optionList, matchInfo, funcMatch;
	while(matchInfo = this.tokenMatch.exec(inputString)) {
		
		//D.debugj('TemplateSearch.parseString - found match1=' + matchInfo[1] + ' match3=' + matchInfo[3]);
		parts = matchInfo[3].trim().split(this.kvSplitter);
		tokenName = parts[0];
		optionList = [];
		if (parts.length > 1) {
			if (funcMatch = this.customFuncMatch.exec(parts[1])) {
				uri = funcMatch[1];
				xhr = new XMLHttpRequest();
				xhr.open('GET', uri, false);
				messageController.setStatus('Loading "' + tokenName + '" options');
				xhr.send(false);
				messageController.clearStatus();
				if (xhr.status == 200) {
					response = xhr.responseText;
					optionList = eval('(' + response + ')');
				} else {
					D.error('TemplateSearch.parseString - could not load custom list values for "' + tokenName + '"');
				}
			} else {
				optionList = parts[1].split(this.listSplitter);
			}
		}
		
		this.templateModels[tokenName] = {
			tokenLiteral: matchInfo[2],
			prefix: matchInfo[1],
			comparer: 1,
			optionList: optionList
		}
		
		this.userInputs[tokenName] = {
			value: '',
			modifier: ''
		}
		
		this.tokenOrder.push(tokenName);
		
	}

};


/**
 * Sets the state of specific tokens
 *
 */
TemplateSearch.prototype.setField = function(tokenName, value, modifier) {
	
	if (!this.templateModels.hasOwnProperty(tokenName)) return false;
	
	this.userInputs[tokenName] = {
		value: value.trim(),
		modifier: modifier
	}
	
	return true;
};


/**
 * Retuns a search string with the tokens replaced with user inputs provided
 * via the setField method.
 *
 */
TemplateSearch.prototype.toString = function() {

	var o = '' + this.rawString;

	var v, idx;
	for (token in this.templateModels) {
		if (this.templateModels.hasOwnProperty(token)) {
			D.debugj('TemplateSearch.toString - replacing token=' + this.templateModels[token].tokenLiteral);
			idx = 0;
			while(idx > -1) {
				idx = o.indexOf(this.templateModels[token].tokenLiteral);
				if (idx > -1) {
					v = '' + this.userInputs[token].value;
					o = o.replace(this.templateModels[token].tokenLiteral, v.trim('\\"\\\''));
				}
			}
		}
	}

	return o;

};



////////////////////////////////////////////////////////////////////////////////
// Form-based search controller
////////////////////////////////////////////////////////////////////////////////

function TemplateController() {
	this.searchString	= '';
	this.template		= null;
	this.container		= $('templateSearchContainer');
	this.taControllers	= [];
}


/**
 * The regex for identifying tokens in a search string.  This is redefined here
 * because the same regex defined in TemplateSearch is global, thus cannot be
 * reused here without running into the lastIndex issue.
 *
 */
TemplateController.prototype.tokenTest = /([^\s\$]*)(\$([^\$]+)\$)/;


TemplateController.prototype.attach = function() {
	
   /*
	* IE 6 attachEvent doesnt support more than one event listener per type per element.
	* Since typeahead has to listen to onkeyup, templateController is outta luck here. 
	* See in typeahead.js, there's a conditional in TypeaheadFieldController.handleKeyUp() 
	* that checks for the templateController singleton and calls it explicitly for IE6.
	*/
	if (!document.all || !typeAheadManager) addEvent(searchController.entryField, 'keyup', this.handleKeyUp.bind(this));
	addEvent($('templateSearchToggleOn'), 'click', this.handleToggleClick.bind(this));
	//addEvent($('templateSearchToggleOff'), 'click', this.handleToggleClick.bind(this));

	// dear god no.  Do Not Uncomment. This was causing a race condition between this click, and the onsubmit handler. 
	//addEvent($('templateSearchSubmit'), 'click', this.submitTemplate.bind(this));
	
};


TemplateController.prototype.handleKeyUp = function(evt) {
	var target = util.getOrigin(evt);

	if (this.containsTokens(target.value)) {
		//D.debugj('TemplateController.handleKeyUp - target.tagName=' + target.tagName + ' id=' + target.id + ' q=' + target.value);
		stateManager.stateChange('isTemplatable', true);
	} else {
		stateManager.stateChange('isTemplatable', false);
	}
	
	return true;
};


TemplateController.prototype.handleToggleClick = function(evt) {
	
	if (stateManager.isState('templateSearchMode')) {
		this.clearTemplate();
		if (this.containsTokens(searchController.getPendingSearch())) {
			stateManager.stateChange('isTemplatable', true);
		} else {
			stateManager.stateChange('isTemplatable', false);
		}
		searchController.setPendingSearch(this.template.rawString);
	} else {
		this.registerSearch(searchController.getPendingSearch());
		this.renderTemplate();
	}
	
	scrollingPanelManager.divAndIframeResize();
	
	return false;
};


/**
 * Determines if a search string has valid tokens specified that can be 
 * turned into a template.
 *
 */
TemplateController.prototype.containsTokens = function(searchString) {
	return this.tokenTest.test(searchString);
};


/**
 * Evaluates a search string and generates a local TemplateSearch object
 *
 */
TemplateController.prototype.registerSearch = function(searchString) {
	
	if (!searchString) this.searchString = searchController.getPendingSearch();
	else this.searchString = searchString;
	
	this.template = new TemplateSearch(this.searchString);
	
	return true;
	
};


/**
 * Generates form elements that match the current template
 *
 */
TemplateController.prototype.renderTemplate = function(templateObject) {

	if (!templateObject) templateObject = this.template;

	stateManager.stateChange('templateSearchMode', true);
	
	// render headline
	var savedName = searchController.getSearchNameFromString(templateObject.rawString, true);
	var isExistingSaved = true;
	if (!savedName) {
		savedName = 'Form search';
		isExistingSaved = false;
	}
	
	// title
	var title = document.createElement('div');
	title.className = 'title';
	var name = document.createElement('p');
	name.appendChild(document.createTextNode(savedName));
	title.appendChild(name);
	
	// link container
	var bag = document.createElement('span');
	bag.className = 'bag';
	title.appendChild(bag);
	
	// view link
	var link = document.createElement('a');
	link.appendChild(document.createTextNode('show as text'));
	addEvent(link, 'click', this.handleToggleClick.bind(this));
	bag.appendChild(link);
	
	// permalink
	if (isExistingSaved) {
		bag.appendChild(document.createTextNode(' | '));
		link = document.createElement('a');
		link.appendChild(document.createTextNode('permalink'));
		link.href = '/?s=' + encodeURIComponent(savedName);
		link.title = 'Permanent link to this search form';
		link.target = '_top';
		bag.appendChild(link);
	}

	// export link
	bag.appendChild(document.createTextNode(' | '));
	exportLink = document.createElement('a');
	exportLink.appendChild(document.createTextNode('export'));
	exportLink.title = 'Export this search.';
	exportLink.className= 'popperUpper';
	exportLink.setAttribute('layerid','exportQuery');
	bag.appendChild(exportLink);
	
	this.container.appendChild(title);
	
	
	// render main form
	var table = document.createElement('table');
	var tbody = document.createElement('tbody');
	table.appendChild(tbody);
	
	var tr, td, label, field, templateModel, option;
	var tokenNames = this.template.tokenOrder.dedupe();
	var lim = tokenNames.length;
	for (var i=0,l=lim; i<l; i++) {
		
		templateModel = this.template.templateModels[tokenNames[i]];
		
		tr = document.createElement('tr');
		tbody.appendChild(tr);
		td = document.createElement('td');
		tr.appendChild(td);

		label = document.createElement('label');
		label.className = 'label';
		label.appendChild(document.createTextNode(cropString(tokenNames[i], 50)));
		
		if (templateModel.optionList.length) {
			field = document.createElement('select');
			for (var j=0,m=templateModel.optionList.length; j<m; j++) {
				option = document.createElement('option');
				option.value = templateModel.optionList[j];
				option.appendChild(document.createTextNode(templateModel.optionList[j] || '(none)'));
				field.appendChild(option);
			}
		} else {
			field = document.createElement('input');
			field.type = 'text';
			field.size = 50;
			// this is needed to suppress the browsers native typeahead
			field.setAttribute("autocomplete","OFF");			
		}

		field.id = 'templateField' + i;
		field.setAttribute('tokenName', tokenNames[i]);
		field.className = 'templateInput';
		label.setAttribute('for', field.id);

		if (!templateModel.optionList.length) {
			field.setAttribute('typeaheadlayer', 'genericTypeAhead');
			if (templateModel.prefix)
				field.setAttribute('typeaheadprefix', templateModel.prefix);
		}
		
		td.appendChild(label);
		td.className = 'keyLabel';

		td = document.createElement('td');
		td.appendChild(field);
		tr.appendChild(td);

	}

	this.container.appendChild(table);
	
	// adjust the timerange label
	var lbls = $('searchForm').getElementsByTagName('div');
	for (var i=0,l=lbls.length; i<l; i++) {
		if (lbls[i].className == 'templateSearchLabels' && tr)
			lbls[i].style.width = tr.firstChild.offsetWidth + 'px';
	}
	
	// typeahead; done in separate loop because we need the DOM objects to be
	// present before attaching controllers
	var controller;
  var tokenNameLim = tokenNames.length;
	for (var i=0,l=lim; i<l; i++) {
		if ($('templateField' + i).type == 'text') {
			controller = typeAheadManager.addController('templateField' + i);
			this.taControllers.push(controller);
		}
	}	
};

TemplateController.prototype.fillTemplate = function() {

	// bring in values from text boxes
	var inputs = $('searchForm').elements;
	var lim = inputs.length;
	for (var i=0,l=lim; i<l; i++) {
		if (inputs[i].className == 'templateInput') {
			switch (inputs[i].tagName) {
				case 'select':
					this.template.setField(
						inputs[i].getAttribute('tokenName'), 
						inputs[i].options[inputs[i].selectedIndex].value
					);
					break;
				default:
					this.template.setField(
						inputs[i].getAttribute('tokenName'), 
						inputs[i].value
					);
			}
		}
	}
};

TemplateController.prototype.submitTemplate = function(evt) {
	
	var target = util.getOrigin(evt);
	this.fillTemplate();

	var searchString = this.template.toString();
	
	D.debugj('TemplateController.submitTemplate - submitting rendered: ' + searchString);
	searchController.runPendingSearch(true, searchString);
	
};


TemplateController.prototype.clearTemplate = function() {
	
	for (var i=0,l=this.taControllers.length; i<l; i++) {
		// cant delete the controllers directly, because they are managed by typeAheadManager 
		// Instead call removeController and pass it the id. 
		typeAheadManager.removeController(this.taControllers[i].field.id);	
	}
	this.taControllers = [];
	
	$('templateSearchContainer').innerHTML = '';
	
	stateManager.stateChange('templateSearchMode', false);
	
};


////////////////////////////////////////////////////////////////////////////////
// generic popup with overlay mask methdos
////////////////////////////////////////////////////////////////////////////////

/**
 * displays a given node on top of a masking overlay;
 *
 */
function showOverlay(domId) {

	var overlay = $('overlayMask')
	util.show(overlay); 
	util.setHeight(overlay, util.getWindowHeight());

	var content = $(domId);
	var container = $('overlayContainer');
	container.innerHTML = '';
	util.copyIntoDocument(container, content);
	util.show(container);
}

/**
 * hides the overlay and related content
 */
function hideOverlay() {
	util.hide('overlayMask'); 
	
	var container = $('overlayContainer');
	util.hide(container);
	container.innerHTML = '';
}


////////////////////////////////////////////////////////////////////////////////
// Drag and drop handling for home page panels
////////////////////////////////////////////////////////////////////////////////

/**
 * Initialize a home page panel as a draggable object within the context of a
 * sorted group
 *
 * @param {DOMElement} panelObject The home page panel DIV object to init.
 * @param {string} sGroup The group name to add the panel to
 * @param {object} config Additional configuration info to pass to handler
 *
 */
PanelDragHandler = function(panelObject, sGroup, config) {

	// check for valid object
	if(!panelObject) {
		D.warn("PanelDragHandler - null panelObject received; drag init cancelled");
		return false;
	}
	
	// init on DDProxy
	this.init(panelObject, sGroup, config);
	
	// set drag handle to the panel summary bar
	this.setHandleElId('panelSummary' + panelObject.getAttribute('panelId'));
	
	// set panel id on the DOM object
	this.panelId = panelObject.getAttribute('panelId');
	
	// start DDM stuff
	this.initFrame();
	this.setPadding(-4);
	this.goingUp = false;
	this.lastY = 0;
	
}


/**
 * Cast the DragHandler function as DDProxy class, and add our own custom event
 * handlers.
 *
 */
if(window.YAHOO) {
	YAHOO.extend(PanelDragHandler, YAHOO.util.DDProxy, {

		startDrag: function(x, y) {
			//this.logger.log(this.id + " startDrag");

			// setup shortcut references
			var Dom = YAHOO.util.Dom;

			var dragEl = this.getDragEl();
			var clickEl = this.getEl();
			Dom.setStyle(clickEl, "visibility", "hidden");

			dragEl.innerHTML = clickEl.innerHTML;

			// set styles
			Dom.setStyle(dragEl, "color", Dom.getStyle(clickEl, "color"));
			Dom.setStyle(dragEl, "backgroundColor", Dom.getStyle(clickEl, "backgroundColor"));
			Dom.setStyle(dragEl, "border", "2px solid gray");
		},

		endDrag: function(e) {

			var Dom = YAHOO.util.Dom;
			Dom.setStyle(this.getDragEl(), "visibility", "hidden");
			Dom.setStyle(this.getEl(), "visibility", "");

			// persist the new order
			dashboardManager.handlePanelReorder();
		
		},

		onDrag: function(e, id) {

			// figure out which direction we are moving
			var y = YAHOO.util.Event.getPageY(e);

			if (y < this.lastY) {
				this.goingUp = true;
			} else if (y > this.lastY) {
				this.goingUp = false;
			}

			this.lastY = y;

		},

		onDragOver: function(e, id) {
			var Dom = YAHOO.util.Dom;

			var srcEl = this.getEl();
			var destEl = Dom.get(id);

			var p = destEl.parentNode;

			if (this.goingUp) p.insertBefore(srcEl, destEl);
			else p.insertBefore(srcEl, destEl.nextSibling);

			YAHOO.util.DragDropMgr.refreshCache();
		}

	});
}


