
/**
 * The tab list
 */
HP.Tabs = Class.create({
	initialize: function(){
		this.element = $('tabs');
		this.setDragDrop();
		this.Tabs = {};
		var t = this.element.select('li:not(.addPage)');
		for(var i = 0; i < t.length; i++){
			var tab = t[i];
			var id = HP.getIdNumber(tab);
			this.Tabs[id] = (new HP.Tab(this, tab));
		}
		this.addBtn = this.element.select('li.addPage').first();
		Event.observe(this.addBtn, 'click', this.addTab.bind(this));
	},
	setDragDrop: function(){
		var sortOptions = {	constraint:null,
							dropZoneCss: HP.CSS.TABHITAREA,
							only: HP.CSS.DRAGTAB,
							onUpdate: this.saveTabOrder.bind(this)
							};
		Sortable.create('tabs',sortOptions);
	},
	getActiveTab: function(){
		for(var id in this.Tabs){
			if(this.Tabs[id].element.className.indexOf(HP.CSS.ACTIVE) >= 0){
				return this.Tabs[id];
			}
		}
		return null;
	},
	addTab: function(){
		HP.doAjaxGet('addNewTab',{}, this.insertTab.bind(this));
	},
	insertTab: function(response){
		var t = response.responseText.evalJSON();
		var link = new Element('a',{href:'javascript:void(0)'}).update(t.tabname);
		var tab = new Element('li', {id: 'tab_' + t.tabid}).insert(link);
		Element.insert(this.addBtn, {before: tab});
		this.Tabs[t.tabid] = new HP.Tab(this, tab);
		this.Tabs[t.tabid].edit();
	},
	deleteTab: function(tab){
		HP.doAjaxGet('deleteTab', {tabid: tab.id}, HP.reload);
	},
	saveTabOrder: function(element){
		var items = element.select('li:not(.addPage)');
		var tabs = '';
		for(var i = 0; i < items.length; i++){
			tabs += HP.getIdNumber(items[i]) + ',';
		}
		HP.doAjaxGet('saveTabOrder', {tabids: tabs});
		return false;
	}
});
	
/**
 * A tab
 */
HP.Tab = Class.create({
	initialize: function(tabs, element){
		this.tabs = tabs;
		this.element = element;
		this.id = HP.getIdNumber(this.element);
		this.title = this.element.firstDescendant();
		this.name = this.title.firstChild.nodeValue;
		this.isActive = this.element.hasClassName(HP.CSS.ACTIVE);
		this.closeBtn = (this.isActive) ? this.element.select('.closeTab').first() : null;
		
		// cache event handlers so we can turn them on and off
		this.switchTabHandler = this.switchTab.bind(this);
		this.deleteTabHandler = this.deleteTab.bind(this);
		this.editTabHandler = this.edit.bind(this);
		this.saveTabNameHandler = this.saveTabName.bindAsEventListener(this);
		
		this.registerClickEvents();
		this.makeDroppable();
		this.extendDraggable();
	},
	registerClickEvents: function(){
		if(this.isActive){
			Event.observe(this.closeBtn, 'click', this.deleteTabHandler);
			Event.observe(this.title, 'click', this.editTabHandler);
		} else {
			Event.observe(this.element, 'click', this.switchTabHandler);
		}
	},
	unregisterClickEvents: function(){
		if(this.isActive){
			Event.stopObserving(this.closeBtn, 'click', this.deleteTabHandler);
			Event.stopObserving(this.title, 'click', this.editTabHandler);
		} else {
			Event.stopObserving(this.element, 'click', this.switchTabHandler);
		}
	},
	// prevents the A default action from firing on a tab drag and drop
	extendDraggable: function(){
		for(var i = 0; i < Draggables.drags.length; i++){
			var draggable = Draggables.drags[i];
			if(draggable.element == this.element){
				Object.extend(draggable.options, {
					onStart: this.setDragged.bind(this)
				});
				return;
			}
		}
	},
	setDragged: function(){
		this.dragged = true;
	},
	executeDefault: function(){
		if(this.dragged){
			this.dragged = null;
			return false;
		}
		return true;
	},
	switchTab: function(){
		if(!this.executeDefault()){
			return false;
		}
		HP.doAjaxGet('setActiveTab', {tabid:this.id}, HP.reload);
	},	
	edit: function(){
		if(!this.executeDefault()){
			return false;
		}
		Event.stopObserving(this.element, 'click', this.switchTabHandler);
		this.input = new Element('input', {type: 'text', name: 'tabName', id: 'tabName', value: this.name});
		Element.hide(this.title);
		if(this.closeBtn){
			Element.hide(this.closeBtn);
		}
		this.element.insert(this.input);
		Event.observe(this.input, 'keypress', this.saveTabNameHandler);
		Event.observe(document, 'click', this.saveTabNameHandler);
		this.element.removeClassName(HP.CSS.ACTIVE);
		this.element.addClassName(HP.CSS.CHANGENAME);
		Field.activate(this.input);
	},
	inactive: function(){
		Event.observe(this.element, 'click', this.switchTabHandler);
		if(this.input){
			this.element.removeChild(this.input);
		}
		Element.show(this.title);
	},
	active: function(){
		if(this.input){
			this.name = this.input.value;
			this.title.update(this.name);
			this.element.removeChild(this.input);
			this.input = null;
			this.element.removeClassName(HP.CSS.CHANGENAME);
			this.element.addClassName(HP.CSS.ACTIVE);
		}
		Element.show(this.title);
		if(this.closeBtn){
			Element.show(this.closeBtn);
		}else{
			this.closeBtn = new Element('a', {href:'javascript:void(0)'});
			this.closeBtn.addClassName('closeTab');
			this.element.insert(this.closeBtn);
			Event.observe(this.closeBtn, 'click', this.tabs.deleteTab.bind(this.tabs));
		}
	},
	saveTabName: function(ev){
		var event = ev || window.event;
		if(event.type == 'click'){
			var target = Event.element(ev);
			if(target == this.element || target == this.input || target == this.title){
				return false;
			}
		} else if(event.type == 'keypress'){
			var keyCode = ev.keyCode || ev.which;
			if(keyCode != Event.KEY_RETURN){
				if(this.input.value.length > 20){
					this.input.size = this.input.value.length;
				}
				return false;
			}
		}else{
			return false;
		}
		Event.stopObserving(this.input, 'keypress', this.saveTabNameHandler);
		Event.stopObserving(document, 'click', this.saveTabNameHandler);
		var cb = (this.isActive) ? this.active.bind(this): HP.reload;
		HP.doAjaxGet('updateTabInfo',{tabid: this.id, tabName: this.input.value}, cb);
	},
	deleteTab: function(){
		this.tabs.deleteTab(this);
	},
	makeDroppable: function(){
		if(!this.isActive){
			var options = {	accept: HP.CSS.MODULE,
							containment:null,
							hoverclass: HP.CSS.TABHOVER,
							onDrop:this.onTabDrop.bind(this)
							};	
			Droppables.add(this.element,options);
		}
	},
	onTabDrop: function(dragEl, dropEl, ev){
		if(Prototype.Browser.IE6){
			Element.hide(dragEl);
		}else{
			dragEl.parentNode.removeChild(dragEl);
		}
		HP.Main.Container.Grid.moveModuleToNewTab(dragEl, dropEl);
	}
});

/**
 * A tab content container
 */
HP.Container = Class.create({
	initialize: function(){
		this.element = $('mainContent');
		this.topBar = this.element.firstDescendant();
		this.Grid = new HP.Grid();
		this.UtitlityMenu = new HP.UtilityMenu($('utilitiesMenu'), this.Grid);
		this.MessageBar = new HP.MessageBar($('messaging'), this.Grid);
		this.refreshCount = 0;
		this.versionCheckRate = 1000 * 60 * 1; // rate at which versionCheck is called in ms
		this.versionCheck();
	},
	resetGrid: function(){
		delete this.Grid;
		this.Grid = new HP.Grid();
	},
	versionCheck: function(){
		HP.doAjaxGet(	"versionCheck", 
				{refreshCount: this.refreshCount}, 
				this.versionCheckHandler.bind(this),
				{rate: this.versionCheckRate});
	},
	versionCheckHandler: function(response){
		var obj = response.responseText.evalJSON()    
		this.refreshCount = obj.refreshCount;
		
		// Update the module content
	    var contentData	= obj.content;
	    for (var i = 0; i < contentData.length; i++) {
	    	var data = contentData[i];
	    	this.Grid.getModuleById(data.moduleid).updateContent(data);
	    }
	    
	    // Avoid leaking by making the versionCheck happen
	    // without a surrounding lexical scope.  The old code simply called 
	    // versionCheck() here.  With the old code, each versionCheck 
	    // is in the lexical scope of the previous, and because our error handling
	    // records the point of initiation of each ajax request so we can report
	    // it if the request fails, the entire chain is never garbage.  
	    setTimeout(this.versionCheck.bind(this), this.versionCheckRate);
	}
});

/**
 * A module layout grid
 */
HP.Grid = Class.create({
	initialize: function(){
		this.element = $$('.tabGrid').first();
		this.collapsed = false;
		this.resetDragDrop();
	},
	getModuleById: function(moduleid){
		for(var i = 0; i < this.Columns.length; i++){
			var column = this.Columns[i];
			if(column.Modules[moduleid]){
				return column.Modules[moduleid];
			}
		}
		return null;
	},
	addModule: function(doReload, response){
		if(doReload){
			HP.reload();
			return;
		}
		if(response.responseText){
			response = response.responseText.evalJSON();
		}
		HP.doAjaxGet("moduleHTML",{moduleid:response.moduleid},this.insertModule.bind(this, response));
	},
	insertModule: function(module, response){		
		response = response.responseText;
		var div = document.createElement('div');
		div.innerHTML = response.stripScripts();
		div = Element.firstDescendant(div);
		this.Columns[module.column].addModule(module.moduleid, div, module.rank);
		setTimeout(response.evalScripts.bind(response), 100);
		this.resetDragDrop();
	},
	deleteModule: function(module){
		var moduleid = module.moduleid;
		HP.Main.Container.MessageBar.show(module.header.innerHTML);
		for(var i = 0; i < this.Columns.length; i++){
			var column = this.Columns[i].deleteModule(moduleid);
		}
	},
	reset: function(){
		var cols = this.element.select('.column');
		this.numOfColumns = cols.length;
		if(this.Columns){
			delete this.Columns;
		}
		this.Columns = [];
		for(var i = 0; i < this.numOfColumns; i++){
			this.Columns.push(new HP.Column(this, cols[i]));
		}
	},
	moveModuleToNewTab: function(module, tab){
		if(!module || !tab ){
			return;
		}
		HP.doAjaxGet(	'moveModuleToNewTab', 
					{	moduleid: HP.getIdNumber(module),
						tabid: HP.getIdNumber(tab)});
	},
	toggleAll: function(ev){
		var element = Event.element(ev);
		var link = element.firstDescendant();
		if(!link){
			link = element;
			element = element.parentNode;
		}
		if(link.hasClassName(HP.CSS.TOGGLEEXPANDED)){
			link.removeClassName(HP.CSS.TOGGLEEXPANDED);
			link.addClassName(HP.CSS.TOGGLECOLLAPSED);
			link.update('Expand All');
			link.writeAttribute('title', 'Expand all modules');
		}else{
			link.addClassName(HP.CSS.TOGGLEEXPANDED);
			link.removeClassName(HP.CSS.TOGGLECOLLAPSED);
			link.update('Collapse All');
			link.writeAttribute('title', 'Collapse all modules');
		}
		if(this.collapsed){
			this.expandAll();
		}else{
			this.collapseAll();
		}
	},
	collapseAll: function(){
		var cols = this.Columns;
		for(var c = 0; c < cols.length; c++){
			var modules = cols[c].Modules;
			for(var m in modules){
				modules[m].collapse();
			}
		}
		this.collapsed = true;
	},
	expandAll: function(){
		var cols = this.Columns;
		for(var c = 0; c < cols.length; c++){
			var modules = cols[c].Modules;
			for(var m in modules){
				modules[m].expand();
			}
		}
		this.collapsed = false;
	},
	refreshHeight: function(){
		var len = this.Columns.length;
		var maxH = 0;
		for(var i = 0; i < len; i++){
			var h = this.Columns[i].getHeight();
			maxH = (h > maxH) ? h : maxH;
		}
		for(var i = 0; i < len; i++){
			this.Columns[i].setHeight(maxH);
		}
	},
	resetDragDrop: function(){
		this.reset();
		if(!this.containment){
			this.containment = [];
			var cols = $$('.column');
			for(var i = 0; i < this.Columns.length; i++){
				this.containment.push(this.Columns[i].element.id);
			}
		}
		if(this.Columns.length > 1){
			this.Columns.each(this.createSortableCol.bind(this));
		}
		this.refreshHeight();
	},
	createSortableCol: function(column){
		var options = {	tag:'div',
						containment: this.containment, 
						constraint:null, 
						handle: HP.CSS.MODULETITLE, 
						dropOnEmpty: true,
						only: HP.CSS.MODULE, 
						dropZoneCss: HP.CSS.MODULEHITAREA,
						scroll:window,
						onChange: this.update.bind(this),
						onUpdate: this.updateMovedModule.bind(this)
						};
		Sortable.create(column.element,options);
		Element.setStyle(column.element, {position: 'static'}); // Fix for IE z-indexing
	},
	update: function(module){
		this.Columns.each(function(col){col.setPadding.bind(col)});
		this.module = module;
	},
	updateMovedModule: function(){
		this.refreshHeight();
		if(this.module == null){
			return;
		}
		var moduleid = HP.getIdNumber(this.module);
		var column = this.module.parentNode;
		if(column == null)
			return;
		var newCol = HP.getIdNumber(column);
		var newRow;
		var children = Element.childElements(column);
		for(var i=0; i< children.length; i++){
			if(children[i] == this.module){
				newRow = i;
				break;
			}
		}
		HP.doAjaxGet('moveModuleWithinTab', {moduleid: moduleid,
											newCol: newCol,
											newRow: newRow}, 
											this.reset.bind(this));
		this.module = null;
	}
});

/**
 * A column in the module layout grid
 */
HP.Column  = Class.create({
	initialize: function(grid, element){
		this.grid = grid;
		this.element = $(element);
		this.Modules = {};
		var mods = this.element.childElements();
		for(var i = 0; i < mods.length; i++){
			var mod = mods[i];
			var mid = HP.getIdNumber(mod);
			this.Modules[mid] = new HP.DragModule(grid, mod, mid);
		}
	},
	addModule: function(moduleid, div, rank){
		if(rank < Element.childElements(this.element).length){
			this.element.insertBefore(div, Element.childElements(this.element)[rank]);
		}else{
			this.element.appendChild(div);
		}
		this.Modules[moduleid] = new HP.DragModule(this.grid, div, moduleid);
	},
	deleteModule: function(moduleid){
		if(this.Modules[moduleid]){
			this.element.removeChild(this.Modules[moduleid].element);
			delete this.Modules[moduleid];
			HP.doAjaxGet("deleteModule", {tabid: HP.Main.Tabs.getActiveTab().id, moduleid: moduleid});
		}
	},
	getHeight: function(){
		var h = 0;
		var children = this.element.childElements();
		for(var i = 0; i < children.length; i++){
			var child = children[i];
			var margin = parseInt(child.getStyle('margin-bottom').replace(/px/,''));
			h += Element.getHeight(children[i]) + margin;
		}
		return h;
	},
	setHeight: function(height){
		Element.setStyle(this.element, {height: height + 'px'});
	},
	setPadding: function(){
		Element.setStyle(this.element,{paddingBottom: '50px'});
	}
});

/**
 * A module drag box (a module in a column)
 */
HP.DragModule = Class.create({
	initialize: function(grid, element, moduleid){
		this.grid = grid;
		this.element = $(element);
		this.moduleid = moduleid;

		this.container = this.element.select('.' + HP.CSS.MODULEMAINCONTENT).first();
		this.settingsPanel = this.container.firstDescendant();
		this.contentPanel = this.settingsPanel.next();

		this.header = this.element.firstDescendant().select('h3').first();
		this.correctTitleWrap();
		Event.observe(window, 'resize', this.correctTitleWrap.bind(this));

		this.toggleBtn = this.element.select('.' + HP.CSS.MODULECOLLAPSEBTN).first();
		this.collapsed = false;

		this.refreshBtn = this.element.select('.' + HP.CSS.MODULEREFRESHBTN).first();
		this.settingsBtn = this.element.select('.' + HP.CSS.MODULESETTINGSBTN).first();
		this.closeBtn = this.element.select('.' + HP.CSS.MODULECLOSEBTN).first();
		
		if(!this.element.readAttribute('registered')){
			this.refreshContent();
		
			Event.observe(this.toggleBtn, 'click', this.toggle.bind(this));
			if(this.refreshBtn){
				Event.observe(this.refreshBtn, 'click', this.refreshContent.bind(this));
			}
			if(this.settingsBtn){
				Event.observe(this.settingsBtn, 'click', this.refreshSettings.bind(this));
			}
			if(this.closeBtn){
				Event.observe(this.closeBtn, 'click', this.deleteModule.bind(this));
			}
			this.element.writeAttribute('registered',true);
		}
	},	refreshContent: function(){
		var adjustedHeight = this.contentPanel.getHeight() - 24;
		adjustedHeight = adjustedHeight < 0 ? 0 : adjustedHeight;
		this.contentPanel.setStyle({height: adjustedHeight + 'px'});
		this.contentPanel.update('<span class="loading"></span>');
		HP.doAjaxGet('refreshModuleContent', {moduleid: this.moduleid}, this.updateContent.bind(this));
	},
	updateContent: function(response){
		// If module doesn't exist on the tab, it's new and we should add it.
		var module;
		if(response.responseText){
			module = response.responseText.evalJSON();
		}else{
			module = response;
		}
		if(this.contentPanel){
			this.contentPanel.update(module.content);
			this.contentPanel.setStyle({height: null});
			
			if(module.title && this.header){
				this.header.update(module.title);
				this.correctTitleWrap();
			}
			this.grid.refreshHeight();
		}else{
			HP.reload();
		}
	},
	hideSettings: function(){
		Element.hide(this.settingsPanel);
		this.grid.refreshHeight();
	},
	refreshSettings: function(){
		if(this.settingsPanel.visible()){
			Element.hide(this.settingsPanel);
			this.refreshContent();
		}else{
			HP.doAjaxGet('refreshModuleSettings',{moduleid:this.moduleid}, this.updateSettings.bind(this));
		}
	},
	updateSettings: function(response){
		var module = response.responseText.evalJSON();
		this.settingsPanel.update(module.settings);
		Element.show(this.settingsPanel);
		this.grid.refreshHeight();
	},
	deleteModule: function(){
		this.grid.deleteModule(this);
	},
	toggle: function(){
		(this.collapsed) ? this.expand() : this.collapse();
	},
	collapse: function(){
		this.collapsed = true;
		this.element.addClassName(HP.CSS.MODULECOLLAPSED);
		this.toggleBtn.firstDescendant().writeAttribute('title','Expand');
		Element.hide(this.container);
		this.grid.refreshHeight();
	},
	expand: function(){
		this.collapsed = false;
		this.element.removeClassName(HP.CSS.MODULECOLLAPSED);
		this.toggleBtn.firstDescendant().writeAttribute('title','Collapse');
		Element.show(this.container);
		this.grid.refreshHeight();
	},
	correctTitleWrap: function(){
		/**
		 * This is not an ideal solution for the module title wrapping problem.
		 * I'd prefer that this be handled with CSS.
		 */
		var buffer = (Prototype.Browser.IE) ? 200 : 145;
		var width = this.header.getWidth();
		if(width > this.element.getWidth() - buffer){
			var limit = (parseFloat(width - 50) / 6.2) - 3;
			this.header.update(this.header.firstChild.nodeValue.substring(0,limit) + '...');
		}
	}
});

/**
 * Header search bar
 */
HP.SearchBar = Class.create({
	initialize: function(){
		this.form = $(HP.ID.SEARCHFORM);
		this.input = $(HP.ID.SEARCHINPUT);
		Event.observe(this.input, 'focus', this.doFocus.bind(this));
		Event.observe(this.input, 'blur', this.doBlur.bindAsEventListener(this));
		this.selectBtn = $$('.' + HP.CSS.SEARCHSELECT).first();
		this.submitBtn = $(HP.ID.SEARCHBUTTON);
		Event.observe(this.submitBtn, 'click', this.submitForm.bind(this));
		this.providers = { google: {cssClass: 'google', formAction: 'http://search.google.com/search'},
					yahoo: {cssClass: 'yahoo', formAction: 'http://search.yahoo.com/search'},
					msn: {cssClass: 'msn', formAction: 'http://search.msn.com/results.aspx'}};
		this.optionList = $(HP.ID.SEARCHOPTIONS);
		this.options = this.optionList.select('li');
		for(var i = 0; i < this.options.length; i++){
			Event.observe(this.options[i], 'click', this.changeProvider.bindAsEventListener(this));
		}
		if(Prototype.Browser.IE6){
			var cb = this.toggleOptions.bindAsEventListener(this);
			Event.observe(this.selectBtn, 'mouseover', cb);
			Event.observe(this.selectBtn, 'mouseout', cb);
		}
	},
	submitForm: function(){
		if(!this.input.value.empty() && 
			this.input.value != this.input.defaultValue){
			var provider = this.form.readAttribute('provider');
			HP.doAjaxGet('logSearch',{provider: this.providers[provider]});
			this.form.submit();
		}
	},
	doFocus: function(){
		if(this.input.value == this.input.defaultValue){
			this.input.value = '';
		}
		Element.setStyle(this.input,{color:'#000'});
	},
	doBlur: function(ev){
		if(!ev || this.input.value == ''){
			this.input.value = this.input.defaultValue;
			Element.setStyle(this.input,{color:''});
		}
	},
	changeProvider: function(ev){
		var element = Event.element(ev);
		var provider = element.readAttribute('id');
		this.form.action = this.providers[provider].formAction;
		this.form.writeAttribute('provider', provider);
		for(var i = 0; i < this.options.length; i++){
			if(this.options[i] == element){
				Element.hide(this.options[i]);
			}else{
				Element.show(this.options[i]);
			}
		}
		HP.doAjaxGet("setSearchProvider",{provider: provider});
		this.selectBtn.className = 'engine ' + provider;
	},
	toggleOptions: function(ev){
		var display = (ev.type == 'mouseover') ? 'block' : 'none';
		this.optionList.setStyle({display: display});
	}
});

/**
 * Tab utility menu (right side menu)
 */
HP.UtilityMenu = Class.create({
	initialize: function(element, grid){
		this.element = $(element);
		this.items = this.element.childElements();
		this.customizeDialog = new HP.Dialog('customizePage', 
											{openTrigger: 'openCustomizeDialog', 
											closeTrigger: 'closeCustomizeDialog', 
											masking:true});
		this.toggleBtn = this.items[3];
		Event.observe(this.toggleBtn, 'click', grid.toggleAll.bindAsEventListener(grid));
	}
});

/**
 * Tab message bar which handles messages like "Undo module deletion"
 */
HP.MessageBar = Class.create({
	initialize: function(element, grid){
		this.element = $(element);
		this.grid = grid;
		var children = this.element.select('a');
		this.undoBtn = children.first();
		this.closeBtn = children.last();
		this.title = this.element.select('span').first();
		Event.observe(this.undoBtn, 'click', this.undo.bind(this));
		Event.observe(this.closeBtn, 'click', this.hide.bind(this));
	},
	show: function(moduleTitle){
		this.cancelEffect();
		this.title.update(moduleTitle);
		this.effect = new Effect.Appear(this.element);
		this.timout = setTimeout(this.hide.bind(this), 10000);
	},
	undo: function(){
		HP.doAjaxGet('deleteModuleUndo', {}, this.addModule.bind(this));
		this.hide();
	},
	addModule: function(response){
		this.grid.addModule(false, response);
	},
	hide: function(){
		this.cancelEffect();
		this.effect = new Effect.Fade(this.element);
		clearTimeout(this.timout);
	},
	cancelEffect: function(){
		if(this.effect){
			this.effect.cancel();
			delete this.effect;
		}
	}
});


/**
 * Tooltips (mainly module tooltips)
 * Usage: add a tooltip attribute with the content as the value to any DOM 
 * element that should have a tooltip
 * Example: <a href="#" tooltip="This is a tooltip">Tooltip</a>
 */
HP.Tooltips = Class.create({
	initialize: function(context){
		// context is the DOM container that owns the tooltip for positioning
		// purposes.  For modules it's the module's rootNode.
		this.context = (context) ? $(context) : document.body;
		// check if there are tooltips
		var tts = this.context.select('[tooltip]');
		if(!tts || tts.length < 1){
			return false;
		}
		
		if(!HP.Tooltips.element){
			HP.Tooltips.element = new Element('div');
			HP.Tooltips.pointerEl = new Element('div');
			HP.Tooltips.pointerEl.addClassName('pointer');
			HP.Tooltips.element.appendChild(HP.Tooltips.pointerEl);
			HP.Tooltips.contentEl = new Element('p');
			HP.Tooltips.contentEl.addClassName('content');
			HP.Tooltips.element.appendChild(HP.Tooltips.contentEl);
			document.body.appendChild(HP.Tooltips.element);
		}

		this.options = {
			className: 'tooltip',
			pointerDim: 45,
			buffer: 10,
			screenBuffer: 20,
			effectDuration: .25,
			displayDuration: 10000
		};		
		Object.extend(this.options, arguments[1] || {});
		
		// register tooltips
		for(var i = 0; i < tts.length; i++){
			var trigger = tts[i];
			var content = trigger.readAttribute('tooltip');
			content = content.replace(/\\'/g,"'");
			if(content && content.length > 0){ // don't show empty tooltips
				Event.observe(trigger, 'mouseover', this.show.bind(this, trigger, content));
				Event.observe(trigger, 'mouseout', this.hide.bind(this, trigger));
			}
			// remove attribute so we won't register this trigger again
			trigger.writeAttribute('tooltip', null);
		}
	},
	show: function(trigger, content){
		content = content.replace(/\\"/g,'"');
		HP.Tooltips.contentEl.innerHTML = content;
		this.setPosition($(trigger));
		this.cancelEffect();
		this.effect = new Effect.Appear(HP.Tooltips.element, {duration: this.options.effectDuration});
		this.timeout = setTimeout(this.hide.bind(this), this.options.displayDuration);
	},
	hide: function(trigger){
		this.cancelEffect();
		this.effect = new Effect.Fade(HP.Tooltips.element, {duration: this.options.effectDuration});
		clearTimeout(this.timeout);
	},
	setPosition: function(trigger){
		var contextPos = this.context.positionedOffset();
		var trPos = this.moduleOffset(trigger);
		trPos = { top: trPos.top + contextPos.top,
				  left: trPos.left + contextPos.left };
		var trDim = trigger.getDimensions();
		var elDim = this.resetElement('right');
		var screenScroll = HP.getScroll();
		var screenDim = {height: HP.getWindowHeight() + screenScroll.y, 
						 width:  HP.getWindowWidth() + screenScroll.x};
		var screenEdge = {top: screenScroll.y + this.options.screenBuffer,
						  right: screenScroll.x + screenDim.width - this.options.screenBuffer,
						  bottom: screenDim.height - screenScroll.y - this.options.screenBuffer,
						  left: screenScroll.x + this.options.screenBuffer};
		var elPos = {top: trPos.top + (trDim.height / 2) - (this.options.pointerDim / 2),
					 left: trPos.left + trDim.width + this.options.buffer};
		
		// switch to left if necessary
		if( (elPos.left + elDim.width) > (screenEdge.right) ){
			elDim = this.resetElement('left');
			elPos.left = trPos.left - elDim.width - this.options.buffer;
		}
		// switch to top if necessary 
		if( (elPos.left < screenEdge.left) || (elPos.top + elDim.height > screenEdge.bottom) ){
			elDim = this.resetElement('top');
			elPos.left = trPos.left - ((elDim.width / 2) - (trDim.width / 2));
			elPos.left = (elPos.left + elDim.width > screenEdge.right) ? (screenEdge.right - elDim.width) : elPos.left;
			elPos.left = (elPos.left < screenEdge.left) ? (screenEdge.left) : elPos.left;
			elPos.top = trPos.top - trDim.height - elDim.height - this.options.buffer; 
			HP.Tooltips.element.insert({bottom: HP.Tooltips.pointerEl});
		}
		// switch to bottom if necessary
		if(elPos.top < (screenEdge.top)){
			elDim = this.resetElement('bottom');
			// TODO what is this 40pc for?
			elPos.top = trPos.top + trDim.height + this.options.buffer + 40; 
		}
	
		HP.Tooltips.element.setStyle({top: elPos.top + 'px', left: elPos.left + 'px'});
	},
	resetElement: function(className){
		var cName = 'moduleTooltip' + className.capitalize() + " " + this.options.className;
		HP.Tooltips.element.className = cName;
		if(className == 'top'){
			HP.Tooltips.element.insert({bottom: HP.Tooltips.pointerEl});
		}else{
			HP.Tooltips.element.insert({top: HP.Tooltips.pointerEl});
		}
		return HP.Tooltips.element.getDimensions();
	},
	cancelEffect: function(){
		if(this.effect){
			this.effect.cancel();
			delete this.effect;
		}
	},
	moduleOffset: function(element) {
		var p = element.positionedOffset();
		if(Element.getStyle(element, 'position')  === 'static'){
			return p;
		}
		var valueT = p.top, valueL = p.left;
	    do {
			element = element.offsetParent;
			if (element) {
	        	if (element.hasClassName('module')) break;
	        	var p = Element.getStyle(element, 'position');
	        	if (p !== 'static'){ 
	        		var pos = element.positionedOffset();
					valueT += pos.top  || 0;
					valueL += pos.left || 0;
	        	}
			}
	    } while (element);
		return Element._returnOffset(valueL, valueT);
	}
});


/**
 * Base API for all things realted to modules (content, settings, popups, etc.)
 */
HP.ModuleBase = Class.create({
	initialize: function(moduleid){
		this.rootNode = $(HP.ID.MODULE + moduleid);
		this.moduleid = moduleid;
	},
	/**
	 * Retrieves the root node descendent element with the given id.
	 */
	getElementById: function(id){
		return Element.select(this.rootNode, '#' + id).first() ||
					Element.select(this.rootNode, '[id='+id+']').first() ||
					Element.select(this.rootNode, id).first();
	},
	$GEBI: function(id){
		return this.getElementById(id);
	},
	select: function(args){
		return this.rootNode.select(args);
	},
	doAJAX: function(action, pars, cb){
		pars['action'] = action;
		pars['moduleid'] = this.moduleid;
	    var request = 'service';
	    HP.doAjaxPost(request, pars, cb);
	},
	processForm: function(form){
		var form = (typeof form == String) ? this.$GEBI(form) : form;
		var pars = Form.serialize(form, true);
		pars['moduleid'] = this.moduleid;
		var module = HP.Main.Container.Grid.getModuleById(this.moduleid);
		HP.doAjaxGet("submitModuleForm", pars, module.updateContent.bind(module));
	},
	onenterHandler: function(ev){
		if(this.onenter){
			ev = ev || window.event;
			if((ev.keyCode || ev.which) == Event.KEY_RETURN){
				this.onenter.call(ev);
			}
		}
	},
	registerEvents: function(selector, handler){
		var elements = this.rootNode.select(selector);
		for(var i = 0; i < elements.length; i++ ){
			elements[i].observe('click',handler);
		}
	},
	getModuleId: function(){
		return this.moduleid;
	},
	getRootNode: function(){
		return this.rootNode;
	},
	refreshContent: function(){
		HP.Main.Container.Grid.getModuleById(this.moduleid).refreshContent();
	},
	moduleOffset: function(element) {
		var p = element.positionedOffset();
		if(Element.getStyle(element, 'position')  === 'static'){
			return p;
		}
		var valueT = p.top, valueL = p.left;
	    do {
			element = element.offsetParent;
			if (element) {
	        	if (element.hasClassName('module')) break;
	        	var p = Element.getStyle(element, 'position');
	        	if (p !== 'static'){ 
	        		var pos = element.positionedOffset();
					valueT += pos.top  || 0;
					valueL += pos.left || 0;
	        	}
			}
	    } while (element);
		return Element._returnOffset(valueL, valueT);
	},
	addModule: function(instanceName, constructorArgs){
		HP.doAjaxGet("addInternalModule",
			{instanceName:instanceName, constructorArgs:constructorArgs},
			HP.Main.Container.Grid.addModule.bind(HP.Container.Grid, true));
	}
});

HP.Modules = {};  // namespace for custom classes that extend HP.Module
/**
 * Module content panel API
 */
HP.Module = Class.create(HP.ModuleBase, {
	initialize: function($super, moduleid) {
		$super(moduleid);
		this.rootNode = this.rootNode.select('.moduleMainContent').first();
		new HP.Tooltips(HP.ID.MODULE + moduleid);
	},
	openSettings: function(){
		HP.Main.Container.Grid.getModuleById(this.moduleid).refreshSettings();
	},
	addModuleFromEvent: function(ev){
		var element = Event.element(ev);
		var args = element.readAttribute('constructorargs');
		if(args){
			args = args.evalJSON();
		}
		this.addModule(element.readAttribute('instanceName'),args);
	} 
});

HP.ModuleSettings = {}; // namespace for custom classes that extend HP.ModuleSetting
/**
 * Module settings panel API
 */
HP.ModuleSetting = Class.create(HP.ModuleBase, {
	initialize: function($super, moduleid){
		$super(moduleid);
		this.rootNode = this.rootNode.select('.moduleSettingsWrapper').first();
		this.form = this.rootNode.select('form').first();
		this.form.observe('submit',this.processAndCloseForm.bindAsEventListener(this));
		var saveBtn = $('buttonMainSave'+moduleid); 
		if(saveBtn){
			Event.observe(saveBtn,'click',this.processAndCloseForm.bindAsEventListener(this));
		}
		var cancelBtn = $('buttonMainCancel'+moduleid);
		if(cancelBtn){
			Event.observe(cancelBtn,'click',this.cancelAndCloseForm.bindAsEventListener(this));
		}
		this.onenter = this.processAndCloseForm.bindAsEventListener(this);
		this.form.observe('keypress',this.onenterHandler.bindAsEventListener(this));
	},
	processAndCloseForm: function(){
		this.processForm(this.form);
		HP.Main.Container.Grid.getModuleById(this.moduleid).hideSettings();
	},
	cancelAndCloseForm: function(){
		this.cancelForm();
		HP.Main.Container.Grid.getModuleById(this.moduleid).hideSettings();
	},
	cancelForm: function(){
		this.form.reset();
	}
});

HP.ModulePopups = {}; // namespace for custom classes that extend HP.ModulePopup
/**
 * Module popup panel API
 */
HP.ModulePopup = Class.create(HP.ModuleBase, {
	initialize: function($super, moduleid){
		$super(moduleid);
		this.popup = this.$GEBI('mpopup');
		this.title = this.popup.select('h3').first();
		this.form = this.popup.select('form').first();
		this.form.observe('keypress',this.onenterHandler.bindAsEventListener(this));
		this.popup.select('a[title=Save]').first().observe('click', this.submit.bind(this));
		this.popup.select('a[title=Cancel]').first().observe('click', this.close.bind(this));
		this.dim = this.popup.getDimensions();
		this.visible = false;
	},
	open: function(ev, pars){
		if(this.visible) return false;
		pars['moduleid'] = this.moduleid;
		HP.doAjaxGet("getModulePopup", pars, this.show.bind(this, ev.element()));
	},
	show: function(element, response){
		var element = $(element);
		var ePos = this.moduleOffset(element);
		var eDim = element.getDimensions();
		var t = ePos.top - (eDim.height / 2) - 15;
		var l = ePos.left - this.dim.width;
		var scr = this.getScroll(element);
		t -= scr.top;
		l -= scr.left;
		if(Prototype.Browser.IE){
			l -= 15;
		}
		this.popup.setStyle({position: 'absolute', top: t + 'px', left: l + 'px'});
		response = response.responseText.evalJSON();
		this.title.innerHTML = response.title;
		this.form.innerHTML = response.content;
		this.popup.show();
		var minSize = this.dim.height + t - 25; // minus height of mod header
		this.module = this.rootNode.select('.moduleMainContent').first();
		this.modSize = this.module.getDimensions();
		if(this.modSize.height < minSize){
			this.module.setStyle({height: minSize + 'px'});
		}
		this.onOpen.bind(this).call();
		if(this.form.elements[0]){
			this.form.elements[0].focus();
		}
		this.visible = true;
	},
	close: function(){
		this.popup.hide();
		this.form.reset();
		this.popup.select('span').each(function(err){err.hide()});
		this.module.setStyle({height: null});
		this.onClose.bind(this).call();
		this.visible = false;
	},
	submit: function(){
		var pars = Form.serialize(this.popup, true);
		pars = this.prepForm(pars);
		var valid = true;
		var errs = this.validate(pars);
		for(var err in errs){
			if(errs[err]){
				var e = this.popup.select('#error_' + err).first();
				if(e) e.show();
				valid = false;
			}
		}
		if(valid){
			this.module.setStyle({height: null});
			this.doAJAX(pars.popupid, pars, this.refreshContent.bind(this));
		}
	},
	onenterHandler: function(ev){
		ev = ev || window.event;
		var keyCode = ev.keyCode || ev.which;
		if(keyCode == Event.KEY_RETURN){
			this.submit();
		}else if (keyCode == Event.KEY_ESC){
			this.close();
		}
	},
	prepForm: function(pars){
		return pars;
	},
	validate: function(pars){
		var errs = {};
		for(var par in pars){
			errs[par] = false;
		}
		return errs;
	},
	getScroll: function(element) {
		var scr = {top:0, left:0};
		var scroller = this.rootNode.select('.scroller').first();
		if(scroller && element.descendantOf(scroller)){
	    	scr.top = scroller.scrollTop;
	    	scr.left = scroller.scrollLeft;
		}
		return scr;
	},
	onOpen: function(){
		// subclass should override
		// called right after the popup is opened
	},
	onClose: function(){
		// subclass should override
		// called right after the popup is closed
	}
});


/**
 * The 'Customize this page' panel
 */
HP.CustomizePanel = Class.create({
	initialize: function(){
		this.element = $('customizePage');
		HP.CustomizePanel.pageName = this.element.select('#pageName').first();

		HP.CustomizePanel.Themes = [];
		HP.CustomizePanel.Layouts = [];

		var themes = $('themes').firstDescendant().select('li');
		for(var i = 0; i < themes.length; i++){
			HP.CustomizePanel.Themes.push(new HP.Theme(themes[i]));
		}

		var layouts = $('columnsSelector').firstDescendant().select('li');
		for(var i = 0; i < layouts.length; i++){
			HP.CustomizePanel.Layouts.push(new HP.Layout(layouts[i]));
		}		

		this.applyAll = this.element.select('#applyAll').first().firstDescendant();
		this.element.select('#save').first().observe('click', this.saveChanges.bind(this));
	},
	saveChanges: function(){
		var pars = {tabid: HP.Main.Tabs.getActiveTab().id,
					tabName: $F(HP.CustomizePanel.pageName),
					layoutId: HP.CustomizePanel.layoutId,
					themeId: HP.CustomizePanel.themeId,
					applyAll: this.applyAll.checked};
		HP.doAjaxGet('updateTabInfo', pars, HP.reload);
	}
});

/**
 * A tab theme selector
 */
HP.Theme = Class.create({
	initialize: function(element){
		this.element = element;
		this.element.observe('click', this.select.bind(this));
		this.id = this.element.readAttribute('themeId');
		if(this.element.hasClassName(HP.CSS.SELECTED)){
			HP.CustomizePanel.themeId = this.id;
		}
	},
	select: function(){
		for(var i = 0; i < HP.CustomizePanel.Themes.length; i++){
			HP.CustomizePanel.Themes[i].element.removeClassName(HP.CSS.SELECTED);
		}
		this.element.addClassName(HP.CSS.SELECTED);
		HP.CustomizePanel.themeId = this.id;
	}
});

/**
 * A tab layout selector
 */
HP.Layout = Class.create({
	initialize: function(element){
		this.element = element;
		this.element.observe('click', this.select.bind(this));
		this.id = this.element.readAttribute('clId');
		if(this.element.hasClassName(HP.CSS.SELECTED)){
			HP.CustomizePanel.layoutId = this.id;
		}
	},
	select: function(){
		for(var i = 0; i < HP.CustomizePanel.Layouts.length; i++){
			HP.CustomizePanel.Layouts[i].element.removeClassName(HP.CSS.SELECTED);
		}
		this.element.addClassName(HP.CSS.SELECTED);
		HP.CustomizePanel.layoutId = this.id;
	}
});

/**
 * Announcement bar across the top of header
 */
HP.Announcement = Class.create({
	initialize: function(){
		if(this.trigger = $('createAccountButton')){
			// register createModifiedAccountDialog
			if(this.modifiedDialog = $('createModifiedAccountDialog')){
				this.modifiedDialog.select('.ok').first().firstDescendant().observe('click', this.saveAccount.bind(this));
				this.modifiedDialog.select('.cancel').first().firstDescendant().observe('click', this.doNotSaveAccount.bind(this));
				this.modifiedPopup = new HP.Dialog(this.modifiedDialog,
										{ closeTrigger:'closeCreateModifiedAccountDialog',
										masking: true,
										position: 'center'});
				var whyBtn = $$('[rel=openWhyDialog]').first();
				var pos = whyBtn.positionedOffset();
				pos.top += whyBtn.getHeight() + 5;
				new HP.Dialog($('whyCreateDialog'), {openTrigger:'openWhyDialog',
													closeTrigger:'closeWhyDialog',
													position: pos});
			}
			
			// register crateAccountDialog
			if(this.newDialog = $('createAccountDialog')){
				this.newPopup = new HP.Dialog(this.newDialog,
										{ closeTrigger:'closeCreateAccountDialog',
										masking: true,
										position: 'center'});
				this.newDialog.select('.ok').first().firstDescendant().observe('click', this.doNotSaveAccount.bind(this));
			}
			this.trigger.observe('click', this.doPopup.bind(this));	
		}
	},
	doPopup: function(){
		this.newPopup.show();
	},
	saveAccount: function(){
		HP.doAjaxGet('saveAccount',{persistAccount: true}, this.redirect.bind(this));
	},
	doNotSaveAccount: function(){
		HP.doAjaxGet('saveAccount',{persistAccount: false}, this.redirect.bind(this));
	},
	redirect: function(){
		window.location.href = HP.PanelPlusURL;
	}
});

/**
 * Corrects the 2 column fixed layout
 */
HP.LayoutCorrector = Class.create({
	initialize: function(){
		this.grid = $$('.' + HP.CSS.TWOCOLFIXED).first();
		if(!this.grid){
			return false;
		}
		this.rightWidth = 400;
		this.buffer = 20;
		this.columns = this.grid.childElements();
		this.columns[1].setStyle({width: this.rightWidth + 'px'});
		this.setWidth();
		Event.observe(window, 'resize', this.setWidth.bind(this));
	},
	setWidth: function(){
		var leftWidth = this.grid.getWidth() - this.rightWidth - this.buffer;
		this.columns[0].setStyle({width: leftWidth + 'px'});
	}
});

/**
 * CSS classnames
 */
HP.CSS = {
	MODULE:'draggableModule',
	MODULETITLE: 'moduleTitle',
	MODULEMAINCONTENT: 'moduleMainContent',
	MODULEHITAREA: 'dragHit',
	MODULECOLLAPSEBTN: 'collapseButton',
	MODULEEXPANDBTN: 'collapseButton',
	MODULEREFRESHBTN: 'refreshButton',
	MODULESETTINGSBTN: 'settingsButton',
	MODULECLOSEBTN: 'closeButton',
	MODULECOLLAPSED: 'collapsed',
	TABHOVER: 'dropIn',
	DRAGTAB: 'draggableTab',
	TABHITAREA: 'tabDragHit',
	DIALOGFRAME: 'dialogFrame',
	DIALOGMASK: 'dialogMask',
	TOGGLECOLLAPSED: 'collapsed',
	TOGGLEEXPANDED: 'expanded',
	SEARCHSELECT: 'engine',
	TWOCOLFIXED: 'grid2colFixed',
	ACTIVE: 'active',
	CHANGENAME: 'changeName',
	SELECTED: 'selected'
}

/**
 * DOM element IDs
 */
HP.ID = {
	SEARCHFORM: 'searchForm',
	SEARCHINPUT: 'search',
	SEARCHBUTTON: 'searchButton',
	SEARCHOPTIONS: 'searchOptions',
	TABS: 'tabs',
	MODULE: 'module_'
}