/* Apply widget board behaviour to the overall widget_board element.
 * Options:
 * clear: if true, removes all existing widget HTML
 * getWidgetSetUrl: A URL from which we can fetch an initial widget set definition
 */
 
 //used by a couple of the widgets to add horiz scrolling functionality
function setScroller(widgetDefinition, widget){
	var currentIndex = 1;
	var itemCount = $('ul.scroller_buttons li.number', widget).length;
	
	$('ul.scroller_buttons li.number a.index_1', widget).addClass('active');
	$('ul.scroller_buttons', widget).css({'width': (50 + (20*itemCount)) + 'px'});
	
	$('ul.scroller_buttons a', widget).click(function(event){
		var itemToShow = $(this).attr('href');
		var itemIndex = itemToShow.split('_item_')[1];
		
		if (itemIndex == 'previous') itemIndex = Math.max(1, currentIndex - 1);
		if (itemIndex == 'next') itemIndex = Math.min(itemCount, currentIndex + 1);
		
		$('ul.scrolling_list', widget).css({left: (-232 * (itemIndex - 1)) + 'px'});
		$('.page_number span', widget).text(itemIndex);
		$('ul.scroller_buttons li.number a', widget).removeClass('active');
		$('ul.scroller_buttons li.number a.index_' + itemIndex, widget).addClass('active');
		
		currentIndex = itemIndex;
		
		event.preventDefault();
	})
}
 
function WidgetBoard(obj, opts) {
	this.settings = $.extend({clear: false}, opts);
	this.obj = $(obj);
	this.widgetColumns = this.obj.find('.widget_column');
	
	this.widgetsByType = {};
	
	this.widgetAddedCallbacks = [];
	this.widgetUserAddedCallbacks = [];
	this.widgetRemovedCallbacks = [];
	this.widgetSetLoadedCallbacks = [];
	
	var widgetBoard = this;

	if (this.settings.clear || this.settings.getWidgetSetUrl) {
		this.clear();
	}
	if (this.settings.getWidgetSetUrl) {
		var urlWithTimestamp = this.settings.getWidgetSetUrl;
		if (urlWithTimestamp.indexOf('?') == -1) {
			urlWithTimestamp += '?ts=' + (new Date).getTime();
		} else {
			urlWithTimestamp += '&ts=' + (new Date).getTime();
		}
		$.getJSON(urlWithTimestamp, null, function(json) {
			widgetBoard.loadWidgetSet(json.widgetPreferences);
			/* run callbacks - pass widget set json */
			for (var i = 0; i < widgetBoard.widgetSetLoadedCallbacks.length; i++) {
				widgetBoard.widgetSetLoadedCallbacks[i](json);
			}
		});
	}
}
/* clear the board */
WidgetBoard.prototype.clear = function() {
	this.widgetsByType = {};
	this.widgetColumns.empty();
}

/* Add a callback function to be run whenever a widget is added to the board */
WidgetBoard.prototype.onAddWidget = function(callback) {
	this.widgetAddedCallbacks[this.widgetAddedCallbacks.length] = callback;
}
/* Add a callback function to be run whenever a widget is added to the board as an explicit user action */
WidgetBoard.prototype.onUserAddWidget = function(callback) {
	this.widgetUserAddedCallbacks[this.widgetUserAddedCallbacks.length] = callback;
}
/* Add a callback function to be run whenever a widget is removed from to the board */
WidgetBoard.prototype.onRemoveWidget = function(callback) {
	this.widgetRemovedCallbacks[this.widgetRemovedCallbacks.length] = callback;
}
WidgetBoard.prototype.onLoadWidgetSet = function(callback) {
	this.widgetSetLoadedCallbacks[this.widgetSetLoadedCallbacks.length] = callback;
}

/* Replace widget board content with widgets from a widget set definition -
 * an array of arrays (one per column) of widget definition objects */
WidgetBoard.prototype.loadWidgetSet = function(widgetSet) {
	var widgetBoard = this;
	this.clear();
	this.widgetColumns.each(function(i) {
		var widgets = widgetSet[i];
		if (widgets) {
			for (var j = 0; j < widgets.length; j++) {
				var widgetPlaceholder = $('<div></div>');
				$(this).append(widgetPlaceholder);
				widgetBoard.loadWidget(widgets[j], widgetPlaceholder);
			}
		}
	})
}
/* Load and initialize a widget.
 * widgetDefinition: a hash containing a {widget: 'nameOfWidget'} field plus other fields for widget-specific config
 * placeholder: jQuery element to replace with the widget HTML
 * opts: hash of options. Supported:
 *   save - if true, saves the widget set once the widget is added
 *   animate - if true, shows the widget via an animation
 */
WidgetBoard.prototype.loadWidget = function(widgetDefinition, placeholder, opts) {
	var widgetBoard = this;
	opts = opts || {};
	
	$.get('/applications/widgets/' + widgetDefinition.widget + '.rm', null, function(html) {
		widget = $(html);
		if (opts.animate) widget.hide();
		placeholder.replaceWith(widget);
		widgetBoard.applyWidgetBehaviour(widget);
		if (opts.animate) widget.slideDown(); /* TODO: animate by opening gap + fading in instead */
		/* store widget definition with the HTML element */
		$.data(widget.get(0), 'widgetDefinition', widgetDefinition);
		if (opts.save) widgetBoard.saveState();
		widgetBoard.widgetsByType[widgetDefinition.widget] = true;
		if (WidgetInitializers[widgetDefinition.widget]) {
			WidgetInitializers[widgetDefinition.widget](widgetDefinition, widget);
		}
		/* run callbacks - pass widget definition and jquery object */
		for (var i = 0; i < widgetBoard.widgetAddedCallbacks.length; i++) {
			widgetBoard.widgetAddedCallbacks[i](widgetDefinition, widget);
		}
	})
}

/* Add a widget to the board with the specified definition, as a user-initiated action; adds it to the top of the
 * shortest column, and saves the widget board state */
WidgetBoard.prototype.addWidget = function(widgetDefinition) {
	var columnToUse = null;
	var bestColumnLength = null;
	for (var i = 0; i < this.widgetColumns.length; i++) {
		var column = this.widgetColumns.eq(i);
		var columnLength = column.find('.widget_panel').length;
		if (columnToUse == null || columnLength < bestColumnLength) {
			columnToUse = column;
			bestColumnLength = columnLength;
		}
	}
	var widgetPlaceholder = $('<div></div>');
	columnToUse.prepend(widgetPlaceholder);
	this.loadWidget(widgetDefinition, widgetPlaceholder, {animate: true, save: true});
	
	/* run callbacks - pass widget definition */
	for (var i = 0; i < this.widgetUserAddedCallbacks.length; i++) {
		this.widgetUserAddedCallbacks[i](widgetDefinition);
	}
}

/* Remove widgets with the specified widget type from the widget board, as a user-initiated action; saves board state afterwards */
WidgetBoard.prototype.removeWidget = function(typeName) {
	var widgetBoard = this;
	this.obj.find('.widget_panel_'+typeName).slideUp('normal', function() { /* TODO: animate by fading out and closing gap */
		$(this).remove();
		widgetBoard.widgetsByType[typeName] = false;
		widgetBoard.saveState();

		/* run callbacks - pass widget type name */
		for (var i = 0; i < widgetBoard.widgetRemovedCallbacks.length; i++) {
			widgetBoard.widgetRemovedCallbacks[i](typeName);
		}
	})
}

/* Determine whether this widget board has a widget of the specified type. NB This only works after a clear() or loadWidgetSet() */
WidgetBoard.prototype.hasWidgetOfType = function(typeName) {
	return this.widgetsByType[typeName];
}

/* Recompile the widget set definition from the individual widgets */
WidgetBoard.prototype.widgetSet = function() {
	var result = [];
	this.widgetColumns.each(function(i) {
		result[i] = [];
		$(this).find('.widget_panel').each(function(j) {
			result[i][j] = $.data(this, 'widgetDefinition');
		})
	});
	return result;
}

/* Save widget set state to the server (if setWidgetSetUrl was supplied when initializing the widget board) */
WidgetBoard.prototype.saveState = function() {
	if (this.settings.setWidgetSetUrl) {
		$.post(this.settings.setWidgetSetUrl, {widgets: $.toJSON(this.widgetSet())});
	}
}

/* Return a hash of properties to indicate what will happen if a panel is dropped at a specified pixel position.
 * x,y: pixel coordinates relative to top-left corner of widget_board
 * Returns:
 * targetColumn: jQuery object of the column element, or null if the point is not over a column
 * targetColumnIndex: Index number of the column element (i.e. 0 for the first column of the widget_board, 1 for the second...)
 *   or null if not over a column
 * and, if the above are not null:
 * successorPanel: jQuery object of the widget panel immediately below the drop position,
 *   or null if the target is the bottom of the column
 * targetIndex: Index number that panel would be dropped into (i.e. 0 for the top of the column, 1 for second in the column...)
 * atPanelOrigin: true if panel would be dropped into its existing position
 */
WidgetBoard.prototype.findWidgetTargetPosition = function(x, y) {
	var targetPosition = {};

	for (var i = 0; i < this.widgetColumns.length; i++) {
		var column = this.widgetColumns.eq(i);
		var columnLeft = column.position().left;
		if (columnLeft <= x && x <= columnLeft + column.outerWidth()) {
			targetPosition.targetColumn = column;
			targetPosition.targetColumnIndex = i;
			break;
		}
	}
	if (targetPosition.targetColumn) {
		var targetColumnPanels = targetPosition.targetColumn.children('.widget_panel');
		targetPosition.targetIndex = targetColumnPanels.length;
		targetPosition.successorPanel = null;

		/* walk down the column until we find a panel whose top is below the top of the dragged panel */
		for (var i = 0; i < targetColumnPanels.length; i++) {
			var candidatePanel = targetColumnPanels.eq(i);
			var candidatePanelTop = candidatePanel.get(0).offsetTop;
			/* we have to blindly hope that offsetTop gives us an offset relative to the top of widget_board
			(which is what the passed x/y coords are supposed to be relative to).
			If we calculate it properly, IE6 adds a bogus 284px offset between widget_column and widget_board. */
			
			if (candidatePanelTop > y) {
				targetPosition.successorPanel = candidatePanel;
				targetPosition.targetIndex = i;
				break;
			}
		}
		targetPosition.atPanelOrigin = (targetPosition.targetIndex != 0 &&
			targetColumnPanels.eq(targetPosition.targetIndex - 1).hasClass('widget_panel_mid_drag'));
	}
	return targetPosition;
}

/* Apply generic widget panel behaviour (i.e. draggability) to elements matching the given jQuery selector */
WidgetBoard.prototype.applyWidgetBehaviour = function(selector) {
	var widgetBoard = this;
	$(selector, this.obj).each(function() {
		var panelHtml = this;
		var panel = $(this);
		var handle = $('<div class="widget_handle"></div>');
		panel.prepend(handle);
		
		var hole = $('<div class="widget_hole"></div>');
		var initialPosition;
		var width, height;
		var lastTargetColumnIndex, lastTargetIndex;
		
		var target = $('<div class="widget_target"></div>');
		
		panel.hover(function() {
			panel.addClass('widget_panel_hover');
		}, function() {
			panel.removeClass('widget_panel_hover');
		})
		
		handle.drag(

			function() { /* mousedown event */
				/* fix explicit dimensions while it's being dragged */
				/* TODO: subtract padding, which is included in inner* but not CSS width */
				width = panel.innerWidth();
				height = panel.innerHeight();
				
				/* tortuous route to get left offset of panel relative to widgetBoard.obj */
				var relativeLeft = 0;
				var offsetObject = panel.get(0);
				var targetObject = widgetBoard.obj.get(0);
				while (offsetObject != targetObject) {
					relativeLeft += offsetObject.offsetLeft;
					offsetObject = offsetObject.offsetParent;
				}

				initialPosition = {top: panel.get(0).offsetTop, left: relativeLeft};
				panel.css({
					width: width + 'px',
					height: height + 'px',
					left: initialPosition.left + 'px',
					top: initialPosition.top + 'px'
				});
				hole.stop(false, true).css({
					width: width + 'px',
					height: height + 'px'
				}).show();
				
				panel.addClass('widget_panel_mid_drag');
				panel.before(hole);
				lastTargetColumnIndex = null;
				lastTargetIndex = null;
			},

			function(event) { /* drag event */
				panel.css({
					left: (initialPosition.left + event.offsetX) + 'px',
					top: (initialPosition.top + event.offsetY) + 'px'
				});

				var midpointX = initialPosition.left + event.offsetX + (width / 2);
				var panelTop = initialPosition.top + event.offsetY;
				var targetPosition = widgetBoard.findWidgetTargetPosition(midpointX, panelTop);
				
				if (targetPosition.targetColumn == null) {
					/* not within a column */
					target.remove();
					/* TODO: indicate that dropping here is invalid */
				} else {
					if (
						targetPosition.targetColumnIndex == lastTargetColumnIndex &&
						targetPosition.targetIndex == lastTargetIndex) {
						/* leave target unchaged */
					} else {
						target.remove();
						if (targetPosition.atPanelOrigin) {
							/* target is over the original panel - don't show target */
						} else if (targetPosition.successorPanel) {
							targetPosition.successorPanel.before(target);
						} else {
							targetPosition.targetColumn.append(target);
						}
						lastTargetColumnIndex = targetPosition.targetColumnIndex;
						lastTargetIndex = targetPosition.targetIndex;
					}
				}
			},

			function(event) { /* mouseup event */
				var midpointX = initialPosition.left + event.offsetX + (width / 2);
				var panelTop = initialPosition.top + event.offsetY;
				var targetPosition = widgetBoard.findWidgetTargetPosition(midpointX, panelTop);
				
				if (targetPosition.targetColumn == null) {
					/* drop position invalid; do not move */
					hole.remove();
				} else if (targetPosition.atPanelOrigin) {
					/* panel remains in same place */
					hole.remove();
				} else if (targetPosition.successorPanel) {
					targetPosition.successorPanel.before(panel);
					hole.addClass('widget_hole_closing').slideUp('normal', function() {
						$(this).removeClass('widget_hole_closing').remove()
					});
					widgetBoard.saveState();
				} else {
					targetPosition.targetColumn.append(panel);
					hole.addClass('widget_hole_closing').slideUp('normal', function() {
						$(this).removeClass('widget_hole_closing').remove()
					});
					widgetBoard.saveState();
				}
				
				/* TODO: animate into place (open gap and move panel) */
				/* reinstate default CSS dimensions */
				panel.css({
					width: 'auto',
					height: 'auto'
				});
				panel.removeClass('widget_panel_mid_drag').removeClass('widget_panel_hover');
				target.remove();
			}

		);
		
		var closeButton = $('<div class="widget_close">close</div>');
		closeButton.click(function() {
			widgetBoard.removeWidget($.data(panelHtml, 'widgetDefinition').widget);
		}).mousedown(function(event) {
			event.stopPropagation(); /* stop mousedown from propagating to the drag mechanism */
		});
		handle.append(closeButton);

	});
	
}


/* Widget palette: provides a list of widgets that can be added / removed from the board */
function WidgetPalette(widgetBoard, selector, items) {
	this.widgetBoard = widgetBoard;
	this.listItemsByType = {};
	
	var panel = $('<div class="widget_palette_panel"></div>');
	var closeLink = $('<a class="widget_palette_close" href="javascript:void(0)">close</a>');
	closeLink.click(function() {
		try{_gaq.push(['_trackEvent', 'Widget palette', 'close', document.location.href + ' - ' + $('title').html().replace(/\'/g,"")]);}catch(e){}
		$('.widget_palette_panel').slideUp('slow');
	})
	this.obj = $('<ul class="widget_palette"></ul>');
	
	panel.append(closeLink, this.obj);
	$(selector).append(panel);
	for (var i = 0; i < items.length; i++) {
		this.addItem(items[i]);
	}
	
	var widgetPalette = this;
	/* when widget is added, highlight the corresponding list item */
	widgetBoard.onAddWidget(function(widgetDefinition) {
		if (widgetPalette.listItemsByType[widgetDefinition.widget]) {
			widgetPalette.listItemsByType[widgetDefinition.widget].addClass('widget_palette_item_active');
		}
	})
	/* when widget is removed, unhighlight the corresponding list item */
	widgetBoard.onRemoveWidget(function(widgetType) {
		if (widgetPalette.listItemsByType[widgetType]) {
			widgetPalette.listItemsByType[widgetType].removeClass('widget_palette_item_active');
		}
	})

}

/* add an item to the widget palette */
WidgetPalette.prototype.addItem = function(item) {
	var widgetBoard = this.widgetBoard;
	if(item){
		var widgetType = item.definition.widget;
		var li = $('<li class="widget_palette_item"></li>');
		this.listItemsByType[widgetType] = li;
		if (this.widgetBoard.hasWidgetOfType(widgetType)) {
			li.addClass('widget_palette_item_active');
		}
		li.text(item.label);
		li.click(function() {
			if (widgetBoard.hasWidgetOfType(widgetType)) {
				widgetBoard.removeWidget(widgetType);
			} else {
				widgetBoard.addWidget(item.definition);
			}
		});
		this.obj.append(li);
	}
}

WidgetInitializers = {
	'venue_hire': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	/*
	'annual_conference': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	*/
	'waiting_times': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'topics_a_to_z': function(widgetDefinition, widget) {
		widget.find('ul').jScrollPane({showArrows:true, scrollbarWidth: 15, arrowSize: 15});
	},
	'projects_a_to_z': function(widgetDefinition, widget) {
		widget.find('ul').jScrollPane({showArrows:true, scrollbarWidth: 15, arrowSize: 15});
	},
	'press_release': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'general_election': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'events': function(widgetDefinition, widget) {
		setScroller(widgetDefinition, widget);
	},
	'publication': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
		setScroller(widgetDefinition, widget);
	},	
	'priorities': function(widgetDefinition, widget) {
		setScroller(widgetDefinition, widget);
	},	
	'blog': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'audio': function(widgetDefinition, widget) {
		//page is complaining that there is no swfobject though it is included..
		//loadSWFMovInTab('#widget_featured_audio_flash'); //defined in homepage.js
	},	
	'latest_views_and_analysis': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'leadership': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'making_connections': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'health_news_on_the_web': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	},
	'most_popular': function(widgetDefinition, widget) {
		var currentList = 'most_read';
		$('ol:not(.' + currentList + ')', widget).hide();
		$('h3.' + currentList + '_title', widget).addClass('active');
		
		function applyMostPopularOnclick(className) {
			$('.' + className + '_title', widget).click(function() {
				if (currentList == className) {
					currentList = null;
					$('ol', widget).slideUp();
					$('h3', widget).removeClass('active');
				} else {
					currentList = className;
					$('ol.' + className, widget).slideDown();
					$('ol:not(.' + className + ')', widget).slideUp();
					$('h3.' + className + '_title', widget).addClass('active');
					$('h3:not(.' + className + '_title)', widget).removeClass('active');
				}
			})
		}
		applyMostPopularOnclick('most_read');
		applyMostPopularOnclick('most_watched_listened');
		applyMostPopularOnclick('most_shared');
	},
	'twitter': function(widgetDefinition, widget){
		initTwitter($('.widget_content', widget));
	},
	'gsk': function(widgetDefinition, widget) {
		initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
	}
}
