/* 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
 */
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.widgetRemovedCallbacks = [];
	
	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);
		});
	}
}
/* 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 removed from to the board */
WidgetBoard.prototype.onRemoveWidget = function(callback) {
	this.widgetRemovedCallbacks[this.widgetRemovedCallbacks.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});
}

/* 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>');
		
		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');
				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 = {};
	this.obj = $('<ul class="widget_palette"></ul>');
	$(selector).append(this.obj);
	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;
	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');
	},
	'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');
	},
	'events': function(widgetDefinition, widget) {
		var currentEventIndex = 1;
		$('ul.scroller_buttons li.number a.index_1', widget).addClass('active');
		var eventCount = $('.events ul.scroller_buttons li.number').length;
		$('ul.scroller_buttons', widget).css({'width': (50 + 20*eventCount) + 'px'});
		$('ul.scroller_buttons a', widget).click(function(event){
			var eventToShow = $(this).attr('href');
			var eventIndex = eventToShow.split('event_item_')[1];
			if (eventIndex == 'previous') eventIndex = Math.max(1, currentEventIndex - 1);
			if (eventIndex == 'next') eventIndex = Math.min(eventCount, currentEventIndex + 1);
			$('ul.scrolling_list', widget).css({left: (-232 * (eventIndex - 1)) + 'px'});
			$('span.event_item', widget).text(eventIndex);
			$('ul.scroller_buttons li.number a', widget).removeClass('active');
			$('ul.scroller_buttons li.number a.index_' + eventIndex, widget).addClass('active');
			currentEventIndex = eventIndex;
			event.preventDefault();
		})
	},
	'publication': function(widgetDefinition, widget) {
	  initStandardTeasersRollover($('.widget_content .hover-wrapper', widget), 'hover-wrapper-hovered');
		var currentPublicationIndex = 1;
		$('ul.scroller_buttons li.number a.index_1', widget).addClass('active');
		var publicationCount = $('.publication ul.scroller_buttons li.number').length;
		$('ul.scroller_buttons', widget).css({'width': (50 + 20*publicationCount) + 'px'});
		$('ul.scroller_buttons a', widget).click(function(publication){
			var publicationToShow = $(this).attr('href');
			var publicationIndex = publicationToShow.split('publication_item_')[1];
			if (publicationIndex == 'previous') publicationIndex = Math.max(1, currentPublicationIndex - 1);
			if (publicationIndex == 'next') publicationIndex = Math.min(publicationCount, currentPublicationIndex + 1);
			$('ul.scrolling_list', widget).css({left: (-232 * (publicationIndex - 1)) + 'px'});
			$('span.publication_item', widget).text(publicationIndex);
			$('ul.scroller_buttons li.number a', widget).removeClass('active');
			$('ul.scroller_buttons li.number a.index_' + publicationIndex, widget).addClass('active');
			currentPublicationIndex = publicationIndex;
			publication.preventDefault();
		})
	},	
	'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');
	},
	'developing_leaders': 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');
	}
}