/*! Copyright (c) 2008 Brandon Aaron (http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Version: 1.0.3
 * Requires jQuery 1.1.3+
 * Docs: http://docs.jquery.com/Plugins/livequery
 */

(function($) {
	
$.extend($.fn, {
	livequery: function(type, fn, fn2) {
		var self = this, q;
		
		// Handle different call patterns
		if ($.isFunction(type))
			fn2 = fn, fn = type, type = undefined;
			
		// See if Live Query already exists
		$.each( $.livequery.queries, function(i, query) {
			if ( self.selector == query.selector && self.context == query.context &&
				type == query.type && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) )
					// Found the query, exit the each loop
					return (q = query) && false;
		});
		
		// Create new Live Query if it wasn't found
		q = q || new $.livequery(this.selector, this.context, type, fn, fn2);
		
		// Make sure it is running
		q.stopped = false;
		
		// Run it immediately for the first time
		q.run();
		
		// Contnue the chain
		return this;
	},
	
	expire: function(type, fn, fn2) {
		var self = this;
		
		// Handle different call patterns
		if ($.isFunction(type))
			fn2 = fn, fn = type, type = undefined;
			
		// Find the Live Query based on arguments and stop it
		$.each( $.livequery.queries, function(i, query) {
			if ( self.selector == query.selector && self.context == query.context && 
				(!type || type == query.type) && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) && !this.stopped )
					$.livequery.stop(query.id);
		});
		
		// Continue the chain
		return this;
	}
});

$.livequery = function(selector, context, type, fn, fn2) {
	this.selector = selector;
	this.context  = context || document;
	this.type     = type;
	this.fn       = fn;
	this.fn2      = fn2;
	this.elements = [];
	this.stopped  = false;
	
	// The id is the index of the Live Query in $.livequery.queries
	this.id = $.livequery.queries.push(this)-1;
	
	// Mark the functions for matching later on
	fn.$lqguid = fn.$lqguid || $.livequery.guid++;
	if (fn2) fn2.$lqguid = fn2.$lqguid || $.livequery.guid++;
	
	// Return the Live Query
	return this;
};

$.livequery.prototype = {
	stop: function() {
		var query = this;
		
		if ( this.type )
			// Unbind all bound events
			this.elements.unbind(this.type, this.fn);
		else if (this.fn2)
			// Call the second function for all matched elements
			this.elements.each(function(i, el) {
				query.fn2.apply(el);
			});
			
		// Clear out matched elements
		this.elements = [];
		
		// Stop the Live Query from running until restarted
		this.stopped = true;
	},
	
	run: function() {
		// Short-circuit if stopped
		if ( this.stopped ) return;
		var query = this;
		
		var oEls = this.elements,
			els  = $(this.selector, this.context),
			nEls = els.not(oEls);
		
		// Set elements to the latest set of matched elements
		this.elements = els;
		
		if (this.type) {
			// Bind events to newly matched elements
			nEls.bind(this.type, this.fn);
			
			// Unbind events to elements no longer matched
			if (oEls.length > 0)
				$.each(oEls, function(i, el) {
					if ( $.inArray(el, els) < 0 )
						$.event.remove(el, query.type, query.fn);
				});
		}
		else {
			// Call the first function for newly matched elements
			nEls.each(function() {
				query.fn.apply(this);
			});
			
			// Call the second function for elements no longer matched
			if ( this.fn2 && oEls.length > 0 )
				$.each(oEls, function(i, el) {
					if ( $.inArray(el, els) < 0 )
						query.fn2.apply(el);
				});
		}
	}
};

$.extend($.livequery, {
	guid: 0,
	queries: [],
	queue: [],
	running: false,
	timeout: null,
	
	checkQueue: function() {
		if ( $.livequery.running && $.livequery.queue.length ) {
			var length = $.livequery.queue.length;
			// Run each Live Query currently in the queue
			while ( length-- )
				$.livequery.queries[ $.livequery.queue.shift() ].run();
		}
	},
	
	pause: function() {
		// Don't run anymore Live Queries until restarted
		$.livequery.running = false;
	},
	
	play: function() {
		// Restart Live Queries
		$.livequery.running = true;
		// Request a run of the Live Queries
		$.livequery.run();
	},
	
	registerPlugin: function() {
		$.each( arguments, function(i,n) {
			// Short-circuit if the method doesn't exist
			if (!$.fn[n]) return;
			
			// Save a reference to the original method
			var old = $.fn[n];
			
			// Create a new method
			$.fn[n] = function() {
				// Call the original method
				var r = old.apply(this, arguments);
				
				// Request a run of the Live Queries
				$.livequery.run();
				
				// Return the original methods result
				return r;
			}
		});
	},
	
	run: function(id) {
		if (id != undefined) {
			// Put the particular Live Query in the queue if it doesn't already exist
			if ( $.inArray(id, $.livequery.queue) < 0 )
				$.livequery.queue.push( id );
		}
		else
			// Put each Live Query in the queue if it doesn't already exist
			$.each( $.livequery.queries, function(id) {
				if ( $.inArray(id, $.livequery.queue) < 0 )
					$.livequery.queue.push( id );
			});
		
		// Clear timeout if it already exists
		if ($.livequery.timeout) clearTimeout($.livequery.timeout);
		// Create a timeout to check the queue and actually run the Live Queries
		$.livequery.timeout = setTimeout($.livequery.checkQueue, 20);
	},
	
	stop: function(id) {
		if (id != undefined)
			// Stop are particular Live Query
			$.livequery.queries[ id ].stop();
		else
			// Stop all Live Queries
			$.each( $.livequery.queries, function(id) {
				$.livequery.queries[ id ].stop();
			});
	}
});

// Register core DOM manipulation methods
$.livequery.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove');

// Run Live Queries when the Document is ready
$(function() { $.livequery.play(); });


// Save a reference to the original init method
var init = $.prototype.init;

// Create a new init method that exposes two new properties: selector and context
$.prototype.init = function(a,c) {
	// Call the original init and save the result
	var r = init.apply(this, arguments);
	
	// Copy over properties if they exist already
	if (a && a.selector)
		r.context = a.context, r.selector = a.selector;
		
	// Set properties
	if ( typeof a == 'string' )
		r.context = c || document, r.selector = a;
	
	// Return the result
	return r;
};

// Give the init function the jQuery prototype for later instantiation (needed after Rev 4091)
$.prototype.init.prototype = $.prototype;
	
})(jQuery);/*!
 * jQuery xmlDOM Plugin v1.0
 * http://outwestmedia.com/jquery-plugins/xmldom/
 *
 * Released: 2009-04-06
 * Version: 1.0
 *
 * Copyright (c) 2009 Jonathan Sharp, Out West Media LLC.
 * Dual licensed under the MIT and GPL licenses.
 * http://docs.jquery.com/License
 */
(function($) {
	// IE DOMParser wrapper
	if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
		DOMParser = function() { };
		DOMParser.prototype.parseFromString = function( xmlString ) {
			var doc = new ActiveXObject('Microsoft.XMLDOM');
	        doc.async = 'false';
	        doc.loadXML( xmlString );
			return doc;
		};
	}
	
	$.xmlDOM = function(xml, onErrorFn) {
		try {
			var xmlDoc 	= ( new DOMParser() ).parseFromString( xml, 'text/xml' );
			if ( $.isXMLDoc( xmlDoc ) ) {
				var err = $('parsererror', xmlDoc);
				if ( err.length == 1 ) {
					throw('Error: ' + $(xmlDoc).text() );
				}
			} else {
				throw('Unable to parse XML');
			}
		} catch( e ) {
			var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
			if ( $.isFunction( onErrorFn ) ) {
				onErrorFn( msg );
			} else {
				$(document).trigger('xmlParseError', [ msg ]);
			}
			return $([]);
		}
		return $( xmlDoc );
	};
})(jQuery);// parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License

function parseUri (str) {
	var	o   = parseUri.options,
		m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
		uri = {},
		i   = 14;

	while (i--) uri[o.key[i]] = m[i] || "";

	uri[o.q.name] = {};
	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
		if ($1) uri[o.q.name][$1] = $2;
	});

	return uri;
};

parseUri.options = {
	strictMode: false,
	key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
	q:   {
		name:   "queryKey",
		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
	},
	parser: {
		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
		loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
	}
};
/**
 * Bambuser - Common JS
 * Author: Tom Sundström
 * Requires: jQuery
 */


(function($) {
	/**
	 * Add an overlay.
	 *
	 * Example:
	 * 	$("#target").b_overlay({'top': 10, 'left': 10}, '<h2>Headline</h2>', 'Misc <em>overlay</em> HTML including a <a href="http://example.com">link</a>');
	 */
	$.fn.b_overlay = function(contents, params) {
		closeOverlay(); // Make sure any open overlay is closed before opening a new one.
		if (params === undefined) {
			params = {};
		}
		setTimeout(function() {
			$('body').click(removeDialogEventHandler);
		}, 100);

		var $target = $(this);

		// Make an anchor for the dialog
		var html = '<div class="bambuser-dialog-anchor"></div>'
		$anchor = $(html);
		$anchor.css("width", "1px");
		$anchor.css("height", "1px");
		$anchor.css("position", "absolute");
		if ($target) {
			$anchor.css("left", $target.offset()['left'] + Math.round($target.width() / 2));
			if (params['position'] == "above") {
				$anchor.css("top", $target.offset()['top'] - 2 + "px");
				}
			else {
				$anchor.css("top", $target.offset()['top'] + $target.height() + 2 + "px");
			}
		}
		else {
			$anchor.css("margin-left", "50%");
			$anchor.css("margin-right", "50%");
			$anchor.css("top", "400px");
		}
		$('body').append($anchor);

		// Make the dialog
		html = '<div class="bambuser-dialog">';
		html += '</div>';
		var $dialog = $(html);
		var $contents_container = $('<div class="bambuser-dialog-contents"></div>');
		$contents_container.append(contents);
		$dialog.append($contents_container);
		var width, height;
		if (contents.width === undefined || contents.width() <= 0 || contents.height() <= 0) {
			width = 200;
			height = 30;
		}
		else {
			width = contents.width() + 20;
			height = contents.height() + 20;
		}
		if (typeof params['width'] == "number") {
			width = params['width'];
			$dialog.css("width", width + "px");
		}
		if (typeof params['height'] == "number") {
			height = params['height'];
			$contents_container.css("height", height + "px");
			$contents_container.addClass("locked-height");
		}

		// Position dialog
		if ($target) {
			var left = -Math.round(width / 2);
			if (params['position'] == "above") {
				$dialog.css("bottom", "5px");
			}
			else {
				$dialog.css("top", "5px");
			}
			if (typeof params['boundary-left'] == "number") {
				left = params['boundary-left'] - $anchor.offset()['left'];
			}
			if (typeof params['boundary-right'] == "number") {
				right = $anchor.offset()['left'] - params['boundary-right'];
				$dialog.css("right", right + "px");
			}
			else {
				$dialog.css("left", left + "px");
			}
		}
		else {
			$dialog.css("position", "absolute");
			$dialog.css("left", -Math.round(width / 2) + "px");
		}
		$anchor.append($dialog);

		// Make an arrow
		if ($target) {
			var $arrow = $('<div class="dialog-arrow"></div>');
			if (params['position'] == "above") {
				$arrow.addClass("arrow-down");
				$arrow.css("bottom",  "-1px");
			}
			else {
				$arrow.addClass("arrow-up");
				$arrow.css("top", "1px");
			}
			$anchor.append($arrow);
		}
	}

	$.fn.b_overlay_legacy = function(options, title, content) {
		var conf = $.extend({}, $.fn.b_overlay.defaults, options);
		b_log('add overlay:' + conf.top + ' ' + conf.left + ' ' + conf.width + ' ' + conf.height);
		$(".b-overlay-wrapper").remove(); // Remove any previously added overlays.
		var html = '<div class="b-overlay-wrapper">';
		html += '<div class="b-overlay">';
		html += '<div class="b-overlay-border"></div>';
		html += '<div class="b-overlay-content">';
		html += '<div class="b-overlay-close b-close"></div>';
		html += '<span class="b-overlay-title">' + title + '</span>';
		html += '<div class="b-overlay-content-inner">' + content + '</div>';
		html += '</div>';
		html += '</div>';
		html += '</div>';
		$(this).html($(this).html() + html);
		$(this).find(".b-overlay")
			.css("top", conf.top + "px")
			.css("left", conf.left + "px")
			.css("width", conf.width + "px")
			.css("height", conf.height + "px");
		$(this).find(".b-overlay-border")
			.css("width", conf.width + "px")
			.css("height", conf.height + "px");
		$(this).find(".b-overlay-content")
			.css("top", "10px")
			.css("left", "10px")
			.css("padding", "0px 0px 0px 15px")
			.css("width", (conf.width - 35) + "px")
			.css("height", (conf.height - 20) + "px");
		$(this).find(".b-overlay-content-inner")
			.css("top", "40px")
			.css("left", "0px")
			.css("padding", "0px 15px 15px 15px")
			.css("width", (conf.width - 50) + "px")
			.css("height", (conf.height - 75) + "px")
			.css("border", "1px solid red; ");
	}
	$.fn.b_overlay.defaults = {
		top: 40,
		left: 10,
		width: 400,
		height: 100
	}
})(jQuery);

function removeDialogEventHandler(event) {
	var $target = $(event.target);
	var $parents = $target.parents();
	var i = 0;
	while ($parents[i++] !== undefined) {
		if ($($parents[i]).hasClass("bambuser-dialog-anchor")) {
			return;
		}
	}
	closeOverlay();
}

function closeOverlay() {
	$(".bambuser-dialog-anchor").remove();
	$('body').unbind('click', removeDialogEventHandler);
}

$(document).ready(function(){
	// Tab switching.
	$(".b-tabs > ul > li").livequery("click", function() {
		if (!$(this).hasClass("tab-selected")) {
			$(this).parent().find("li").removeClass("tab-selected");
			$(this).addClass("tab-selected");
		}
		return false;
	});
	$(".b-overlay-close").livequery("click", function() {
		b_log("close overlay");
		$(this).parent().parent().parent().remove();
	});
});


function queryVar(key) {
	hu = window.location.search.substring(1);
	gy = hu.split("&");
	for (i=0;i<gy.length;i++) {
		ft = gy[i].split("=");
		if (ft[0] == key) {
			return decodeQueryVar(ft[1]);
		}
	}
	return undefined;
}


function format_plural(value, singular, plural) {
	if (value == undefined) {
		return "";
	} else if (value == 1) {
		return value + " " + singular;
	}
	return value + " " + plural;
}


function js_set_message(message, type) {
	if (type == undefined) {
		type = 'status';
	}
	$('.messages').remove();
	$('body').prepend("<div class='messages " + type + "'>" + check_plain(message) + "</div>");
	// Scroll to top of page so user can see the message.
	$("html, body").animate({ scrollTop: 0 }, "fast");
}


function decodeQueryVar(str) {
	return decodeURIComponent((str + '').replace(/\+/g, ' '));
}


function check_plain(str) {
	return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\'/g,'&#39;').replace(/\"/g,'&quot;');
}


// Returns human-readable difference between a unix timestamp and current time.
// TODO: compare to server time when possible.
function timeSince(timestamp, format, segments) {
	var diff = Math.floor(new Date().getTime() / 1000) - timestamp;
	return formatSeconds(diff, format, segments);
}


function formatSeconds(seconds, format, segments) {
	if (segments === undefined) {
		segments = 2;
		if (format == 'short') {
			segments = 1;
		}
	}
	if (seconds < 0) {
		// Return empty string if timestamp is in the future.
		return '';
	}
	var timeUnits = Array(
		Array(60 * 60 * 24 * 365 , (format == 'short' ? 'y' : 'year'), (format == 'short' ? 'y' : 'years')),
		Array(60 * 60 * 24 * 30 , (format == 'short' ? 'mon' : 'month'), (format == 'short' ? 'mon' : 'months')),
		Array(60 * 60 * 24 * 7, (format == 'short' ? 'w' : 'week'), (format == 'short' ? 'w' : 'weeks')),
		Array(60 * 60 * 24 , (format == 'short' ? 'd' : 'day'), (format == 'short' ? 'd' : 'days')),
		Array(60 * 60 , (format == 'short' ? 'h' : 'hour'), (format == 'short' ? 'h' : 'hours')),
		Array(60 , (format == 'short' ? 'min' : 'minute'), (format == 'short' ? 'min' : 'minutes')),
		Array(1, (format == 'short' ? 's' : 'second'), (format == 'short' ? 's' : 'seconds'))
	);
	var result = "";
	var timeSegments = [];
	var previousMatchingChunk = 100;
	for (var chunk = 0; chunk < timeUnits.length; chunk++) {
		if (segments <= 0) {
			break;
		}
		var cnt = 0;
		if (seconds >= timeUnits[chunk][0]) {
			cnt = (segments > 1 ? Math.floor(seconds / timeUnits[chunk][0]) : Math.round(seconds / timeUnits[chunk][0]));
		}
		if (previousMatchingChunk < chunk - 1) {
			// Break to avoid silly times. For example: "2 months, 3 seconds" when segments is set to 2
			break;
		}
		if (cnt > 0) {
			timeSegments.push([cnt, chunk]);
			previousMatchingChunk = chunk;
			segments--;
			seconds -= cnt * timeUnits[chunk][0];
		}
	}
	for (var i=0; i<timeSegments.length; i++) {
		var cnt = timeSegments[i][0];
		var chunk = timeSegments[i][1];
		var unit = timeUnits[chunk];
		result += cnt + " " + (cnt > 1 ? unit[2] : unit[1]) + (i != timeSegments.length - 1 ? ", " : "");
	}
	return result;
}


function formatTimestamp(seconds, format) {
	if (format == undefined) {
		format = "short";
	}
	if (seconds < 0) {
		// Return empty string if timestamp is in the future.
		return '';
	}
	var timeUnits = Array(
		Array(60 * 60 * 24 , (format == 'digital' ? 'd ' : 'd')),
		Array(60 * 60 , (format == 'digital' ? ':' : 'h')),
		Array(60 , (format == 'digital' ? ':' : 'min')),
		Array(1, (format == 'digital' ? '' : 's'))
	);
	var result = "";
	var segments = timeUnits.length;
	var timeSegments = [];
	var previousMatchingChunk = 100;
	for (var chunk = 0; chunk < timeUnits.length; chunk++) {
		if (segments <= 0) {
			break;
		}
		var cnt = 0;
		if (seconds >= timeUnits[chunk][0]) {
			cnt = (segments > 1 ? Math.floor(seconds / timeUnits[chunk][0]) : Math.round(seconds / timeUnits[chunk][0]));
		}
		if (cnt > 0 || format == "digital") {
			if (cnt > 0 || previousMatchingChunk != 100) {
				timeSegments.push([cnt, chunk]);
			}
			if (cnt > 0 || (format == "digital" && segments - chunk <= 3)) {
				previousMatchingChunk = chunk;
			}
			segments--;
			seconds -= cnt * timeUnits[chunk][0];
		}
	}
	for (var i=0; i<timeSegments.length; i++) {
		var cnt = styledCnt = timeSegments[i][0];
		var chunk = timeSegments[i][1];
		var unit = timeUnits[chunk];
		if (format == 'digital') {
			// Set 0 before 1-9 > 01-09 if a bigger time unit is present.
			if (cnt < 10 && i - 1 >= 0) {
				styledCnt = '0' + cnt;
			}
		}
		result += styledCnt + "" + unit[1];
	}
	return result;
}

function secondsToTime(seconds) {
	var sec = seconds % 60;
	seconds -= sec;
	if (sec < 10) {
		sec = '0' + sec;
	}
	var min = (seconds / 60) % 60;
	seconds = seconds - (min * 60);
	if (seconds <= 0) {
		return min + ':' + sec;
	}
	if (min < 10) {
		min = '0' + min;
	}
	return (seconds / 3600) + ':' + min + ':' + sec;
}


/**
 * Spinner handling function.
 * 
 * @param string size
 * 	'small': 16x16px (default)
 * 	'large': 32x32px
 *
 * @param int delay
 * 	Timeout in milliseconds after which the spinner is inserted.
 * 	Default: 1000ms.
 *
 * @param string color
 * 	'dark': Used for dark backgrounds
 * 	''(default): white.
 *
 * @returns string
 * 	Returns HTML to caller, including a placeholder.
 * 	After the timeout, the spinner image will be injected
 * 	in the placeholder, which caller is supposed to have injected
 * 	into DOM by then.
 */
function renderSpinner(size, delay, color) {
	if (typeof delay == 'undefined') {
		delay = 1000;
	}
	var date = new Date();
	id = 'spin_' + Math.floor(date.getTime()  / 100000);
	if (size == undefined) {
		size = "small";
	}
	var model = size;
	if (color != undefined && color != "") {
		model += "-" + color;
	}
	setTimeout(function () {
		src = getVar('static-url', 'http://static.bambuser.com/') + 'r/img/spinner-' + model + ".gif";
		$("#" + id).html('<img src="' + src + '" alt="Loading" title="Loading"/>');
	}, delay);
	return '<span id="' + id + '"></span>';
}


/**
 * Make a direct call to Bambuser API.
 */
function b_api(method, params, successCallback, errorCallback) {
	callAPI(method, params, function(data) {
		successCallback({'data': data})
	}, function(data) {
		errorCallback({'data': data})
	});
}


/**
 * DEPRECATED: use b_api() instead.
 * Make a direct call to Bambuser API.
 */
function callAPI(method, params, successCallback, errorCallback) {
	if (params == "" || params == undefined) {
		params = {}
	}
	params['r'] = Math.random();
	var url;
	if (method.substr(0, 7) == 'http://' || method.substr(0, 8) == 'https://') {
		url = method;
	} else {
		url = getVar("api-url") + method + ".json";
		if (("https:" == document.location.protocol)) {
			url = url.replace("http://", "https://")
		}
	}
	dataType = "jsonp";
	var url_parts = parseUri(url);
	var current_url_parts = parseUri(document.URL);
	if (url_parts.host && url_parts.host == current_url_parts.host && url_parts.protocol == current_url_parts.protocol) {
		dataType = "json";
	}
	$.ajax({
		type: "GET",
		url: url,
		dataType: dataType,
		data: params,
		cache: false,
		timeout: 10000,
		error: function(data) {
			b_log("API error");
			b_log(data);
			if (errorCallback != undefined) {
				errorCallback(data);
			}
		},
		success: function(data) {
			if (typeof(data.errorCode) !== 'undefined' && data.errorCode > 0) {
				b_log("API error " + data.errorCode + ": " + data.error);
				if (typeof(errorCallback) !== 'undefined') {
					errorCallback(data);
				}
			} else if (typeof(data.error) !== 'undefined' && typeof(data.error.type) !== 'undefined') {
				b_log("API error " + data.error.type + (typeof(data.error.message) !== 'undefined') ? ": " + data.error.message : "");
				if (typeof(errorCallback) !== 'undefined') {
					errorCallback(data);
				}
			} else if (typeof(data.errors) !== 'undefined') {
				b_log("API error");
				b_log(data);
				if (typeof(errorCallback) !== 'undefined') {
					errorCallback(data);
				}
			} else if (typeof(data.error) !== 'undefined') {
				// Catch legacy error response without error code
				b_log("API error");
				b_log(data);
				if (typeof(errorCallback) !== 'undefined') {
					errorCallback(data);
				}
			} else if (typeof(successCallback) !== 'undefined') {
				successCallback(data);
			}
		}
	});
}


/**
 * Return server-side config.
 *
 * Optionally provide a default value to be used
 * if no server-side value is found.
 */
function getVar(key, def) {
	var val = $("#" + key).attr("title");
	if (typeof val === "undefined") {
		return def;
	}
	return val;
}


function b_log(msg, type) {
	if (typeof console !== "undefined") {
		if (type == "" || type == undefined) {
			type = "notice";
		}
		if(typeof(msg) == 'object') {
			console.log(msg)
		} else {
			console.log(type + ': ' + msg);
		}
	}
}
function bambuser_map(params) {
	this.params = params;
	this.map = null;
	this.map_id = null;
	this.width = 750;
	this.height = 500;
	this.debug = false;
	this.broadcasts = {};
	this.b_indexes = [];
	this.active_infowindow = null;
	this.selected_vid = null;
	this.precision_circle = null;
	this.trail = null;
	this.focus = null;
	this.initial_zoom_radius = null;
	this.map_zoomed = false;
	this.buzy_zooming = false;
	this.loading = 0;
	this.loading_indicator;
	this.latest = null;
	this.flash_player = null;
	this.positioning_overlay = null;
	this.container = null;
	this.clickable_markers = true;
	this.draggable_markers = false;
	this.radius = 0;
	this.map_initialized = false;
	this.view_grace = 50;
	this.broadcast_filters = {};
	this.live_updates = true;
	this.geocoder = new google.maps.Geocoder();
	var marker_base_url = (getVar('static-url') ? getVar('static-url') : 'http://static.bambuser.com/') + "modules/b/pages/bambuser_broadcasts/maps_markers/";
	this.marker_icons = {
		'selected': marker_base_url + "maps_marker_selected.png",
		'live': marker_base_url + "maps_marker_live.png",
		'default': marker_base_url + "maps_marker_6.png",
		'age_frames': [
			{'label': '24hours', 'icon': marker_base_url + "maps_marker_0.png", 'age': (60*60*24)},
			{'label': '2days', 'icon': marker_base_url + "maps_marker_1.png", 'age': (60*60*24*2)},
			{'label': '7days', 'icon': marker_base_url + "maps_marker_2.png", 'age': (60*60*24*7)},
			{'label': '30days', 'icon': marker_base_url + "maps_marker_3.png", 'age': (60*60*24*31)},
			{'label': '6months', 'icon': marker_base_url + "maps_marker_4.png", 'age': (60*60*24*180)},
			{'label': 'year', 'icon': marker_base_url + "maps_marker_5.png", 'age': (60*60*24*365)}
		]
	}

	// Setup grid
	this.zoom_grid = [];
	this.visited_grid_points = {};
	var resolution = 180;
	this.zoom_grid.push(resolution*2);
	for (var i=1;i<25;i++) {
		resolution /= 2;
		this.zoom_grid.push(resolution*2);
	}
	this.broadcast_search_params = {
		'allow_bc_listing': 1,
		'access_mode': 0, // Only display public broadcasts
		'limit': 50,
		'sort': 'live'
	};

	// Set default map settings
	this.settings = {
		mapTypeId: google.maps.MapTypeId.ROADMAP,
		center: new google.maps.LatLng(0, 0),
		zoom: 2,
		draggable: true,
		zoomControl: true,
		scrollwheel: true,
		scaleControl: false,
		mapTypeControl: true,
		overviewMapControl: false,
		panControl: true,
		rotateControl: false,
		streetViewControl: false
	};

	this.init = function() {
		// Check required parameters
		if (this.params == null) {
			b_log("parameters array is not defined!");
			return false;
		}
		var required_params = ['map_id'];
		for (rqp in required_params) {
			if (this.params[required_params[rqp]] === undefined) {
				b_log("missing parameter: " + required_params[rqp]);
				return false;
			}
		}

		this.map_id = this.params['map_id'];

		// Make sure width and height are integers
		if (parseInt(this.params['map_width'])) {
			this.width = parseInt(this.params['map_width']);
		}
		else {
			this.width = $("#" + this.map_id).width();
			if (this.width == 0) {
				this.width = $(window).width();
			}
			else {
				params['map_width'] = this.width;
			}
		}
		if (parseInt(this.params['map_height'])) {
			this.height = parseInt(this.params['map_height']);
		}
		else {
			this.height = $("#" + this.map_id).height();
			if (this.height == 0) {
				this.height = $(window).height();
			}
			else {
				params['map_height'] = this.height;
			}
		}

		// Parse valid parameters
		for (key in this.params) {
			var value = this.params[key];
			switch (key) {
				// Search settings
				case 'debug':
					this.debug = true;
					break;
				case 'username':
					this.broadcast_search_params['username'] = value;
					this.add_broadcast_filter('username', 'equals', value);
					break;
				case 'vid':
					value = parseInt(value);
					this.params['vid'] = value;
					this.broadcast_search_params['vid'] = value;
					this.add_broadcast_filter('vid', 'equals', value);
					if (this.params['focus'] === undefined) {
						this.focus = value;
					}
					break;
				case 'live_updates':
					value = parseBoolean(value);
					this.live_updates = value;
					break;
				// Custom icons
				case 'icon_default':
					this.marker_icons['default'] = value;
					this.marker_icons['age_frames'] = null; // disable age markers
					break;
				case 'icon_selected':
					this.marker_icons['selected'] = value;
					break;
				case 'icon_live':
					this.marker_icons['live'] = value;
					break;
				// Set map settings
				case 'center':
					if (value['lat'] !== undefined && value['lon'] !== undefined) {
						this.settings.center = new google.maps.LatLng(parseFloat(value['lat']), parseFloat(value['lon']));
					}
					break;
				case 'static':
					value = parseBoolean(value);
					this.settings.draggable = !value;
					this.settings.scrollwheel = !value;
					break;
				case 'zoom':
					this.settings.zoom = parseInt(value);
					break;
				case 'initial_zoom_radius':
					if (parseFloat(value)) {
						this.initial_zoom_radius = parseFloat(value);
					}
					if (this.initial_zoom_radius < 1000) {
						this.initial_zoom_radius = 1000;
					}
				case 'show_zoom_control':
					value = parseBoolean(value);
					this.settings.zoomControl = value;
					break;
				case 'show_pan_control':
					value = parseBoolean(value);
					this.settings.panControl = value;
					break;
				case 'show_maptype':
					value = parseBoolean(value);
					this.settings.mapTypeControl = value;
					break;
				case 'clickable':
					value = parseBoolean(value);
					this.clickable_markers = value;
					break;
				case 'draggable':
					value = parseBoolean(value);
					this.draggable_markers = value;
					break;
				case 'focus':
					if (parseInt(value)) {
						this.focus = parseInt(value);
					}
					else {
						this.focus = value;
					}
					break;
			}
		}

		// Parse filters
		if (typeof(this.params['filters']) == "object") {
			for (i in this.params['filters']) {
				var filter = this.params['filters'][i];
				if (filter['type'] && filter['comparator'] && filter['value']) {
					this.add_broadcast_filter(filter['type'], filter['comparator'], filter['value']);
				}
			}
		}

		// Resize map container div
		this.container = $("#" + this.map_id);
		this.container.css("width", this.width + "px");
		this.container.css("height", this.height + "px");

		// Set map position if specified in params
		if (this.settings.lat && this.settings.lon) {
			this.settings['center'] = new google.maps.LatLng(this.settings.lat, this.settings.lon);
		}

		// Initialize map and flash player
		this.map = new google.maps.Map(document.getElementById(this.map_id), this.settings);
		this.map.b_map = this;
		this.flash_player = new flash_player();
		this.flash_player.init(this.map.getDiv());

		// Overlay for positioning
		this.positioning_overlay = new google.maps.OverlayView();
		this.positioning_overlay.draw = function() {};
		this.positioning_overlay.setMap(this.map);

		// Map events
		google.maps.event.addListener(this.map, "tilesloaded", function(event) {
			var b_map = this.b_map;
			b_map.update_map_radius();
			b_map.get_broadcasts();
			b_map.update_marker_visibility();
			if (!b_map.map_initialized) {
				b_map.map_initialized = true;
				b_map.loading_indicator = new loading_indicator(b_map);
				b_map.loading_indicator.start();
			}
		});
		google.maps.event.addListener(this.map, "center_changed", function(event) {
			var b_map = this.b_map;
			if (b_map.active_infowindow) {
				b_map.update_flashplayer();
			}
			b_map.get_broadcasts();
		});
		google.maps.event.addListener(this.map, "zoom_changed", function(event) {
			var b_map = this.b_map;
			if (b_map.active_infowindow) {
				b_map.update_flashplayer();
			}
			b_map.update_map_radius();
			b_map.get_broadcasts();
		});
		google.maps.event.addListener(this.map, "drag", function(event) {
			var b_map = this.b_map;
			if (b_map.active_infowindow) {
				b_map.update_flashplayer();
			}
		});
		google.maps.event.addListener(this.map, "idle", function(event) {
			var b_map = this.b_map;
			if (b_map.active_infowindow) {
				b_map.update_flashplayer();
			}
			b_map.update_map_radius();
			b_map.update_marker_visibility();
		});

		// Refresh map size when window size changes unless map size was set in params
		var b_map = this;
		if (!this.params['map_width']) {
			$(window).resize(function() {
				b_map.width = $(window).width();
				b_map.height = $(window).height();
				b_map.container.css("width", b_map.width + "px");
				b_map.container.css("height", b_map.height + "px");
				google.maps.event.trigger(b_map.map, 'resize');
				b_map.loading_indicator.reposition();
			});
		}

		// Start comet server listener
		if (this.live_updates) {
			this.listen();
		}

		// Zoom to initial zoom radius
		if (this.initial_zoom_radius != null) {
			this.zoom_radius(this.initial_zoom_radius);
			this.map_zoomed = true;
		}

		this.get_broadcasts();

		return this.map;
	}

	this.parse_metadata = function(metadata, update) {
		if (metadata.data) {
			metadata = metadata.data;
		}
		if (metadata.result) {
			metadata = metadata.result;
		}

		// Treat metadata as array of broadcasts
		if (metadata.vid === undefined && parseInt(metadata.length) > 0) {
			for (i in metadata) {
				this.parse_broadcast_metadata(metadata[i], update);
			}
			return true;
		}

		// Treat metadata as single broadcast
		else {
			this.parse_broadcast_metadata(metadata, update);
			return true;
		}
		return false;
	}

	this.parse_broadcast_metadata = function(metadata, update) {
		if (metadata.vid === undefined) {
			return false;
		}
		metadata.vid = parseInt(metadata.vid);

		// Update existing broadcast
		if (this.broadcasts[metadata.vid] != null) {
			if (update) {
				var broadcast = this.broadcasts[metadata.vid];
				var updated = broadcast.update_broadcast_metadata(metadata);
				b_log(updated);
				if (updated.removed) {
					if (broadcast.vid == this.selected_vid) {
						this.selected_vid = null;
						if (this.active_infowindow != null) {
							this.flash_player.clear();
							$("#infowindow-" + this.map_id).html("This broadcast has been removed.");
						}
					}
					this.remove_broadcast(metadata);
					return true;
				}

				if (this.active_infowindow != null && this.selected_vid == broadcast.vid) {
					if (updated.type || updated.title || updated.age) {
						$("#infowindow-" + this.map_id).html(create_broadcast_info(broadcast));
					}
				}

				if (updated.position) {
					if (this.selected_vid == broadcast.vid || this.params['vid'] == broadcast.vid) {
						this.update_precisioncircle(broadcast);
						this.update_trail(broadcast);
						if (this.focus == 'selected' && updated.position_diff) {
							var next_center = new google.maps.LatLng(this.map.getCenter().lat() + updated.position_diff.lat, this.map.getCenter().lng() + updated.position_diff.lng);
							this.map.panTo(next_center);
						}
					}
					if (this.focus == broadcast.vid) {
						this.focus_map();
					}
				}
				return true;
			}
			return false;
		}

		// Insert new broadcast
		else {
			if (update && metadata.type != 'live') {
				// Don't add new broadcast unless it's currently live
				return false;
			}
			var broadcast = new bambuser_broadcast(metadata);
			if (broadcast.init()) {
				if (this.broadcast_search_params['username'] != undefined && broadcast.username != this.broadcast_search_params['username']) {
					// Skip if map is limited to a specific username
					return false;
				}
				if (this.broadcast_search_params['vid'] != undefined && broadcast.vid != this.broadcast_search_params['vid']) {
					// Skip if map is limited to a specific vid
					return false;
				}
				broadcast.found_at_zoomlvl = this.map.getZoom();
				this.broadcasts[broadcast.vid] = broadcast;
				this.b_indexes.push(broadcast.vid);
				broadcast.b_map = this;
				// Apply broadcast filters
				if (!this.filter_broadcast(broadcast)) {
					broadcast.hidden = true;
				}
				broadcast.create_marker();
				this.update_latest(broadcast);
				if (this.focus == broadcast.vid) {
					this.focus_map();
					this.update_trail(broadcast);
					this.update_precisioncircle(broadcast);
				}
				if (this.focus == "all") {
					this.focus_map();
				}
				if (this.params['vid'] == broadcast.vid) {
					broadcast.update_external();
				}
				return true;
			}
			return false;
		}
	}

	this.update_latest = function(broadcast) {
		if (!this.latest) {
			this.latest = broadcast.vid;
			return true;
		}
		if (broadcast.created === undefined) {
			return false;
		}
		if (broadcast.created > this.broadcasts[this.latest].created) {
			this.latest = broadcast.vid;
			return true;
		}
		return false;
	}

	this.remove_broadcast = function(broadcast) {
		if (this.broadcasts[broadcast.vid]) {
			/*
			if (this.broadcasts[broadcast.vid].marker) {
				this.broadcasts[broadcast.vid].marker.setMap(null);
			}
			*/
			this.broadcasts[broadcast.vid] = null;
			return true;
		}
		return false;
	}

	this.focus_map = function() {
		b_log("focusing on : " + this.focus);
		this.buzy_zooming = true;
		if (this.focus == 'all') {
			this.focus_all();
		}
		if (this.focus == 'selected') {
			// This is handled when the selected marker is updated
			// Do nothing here
		}
		if (this.focus == 'latest') {
			if (this.latest != null && this.broadcasts[this.latest] != undefined) {
				this.focus_broadcast(this.broadcasts[this.latest]);
			}
		}
		if (typeof(this.focus) == "number") {
			if (this.broadcasts[this.focus] != undefined) {
				this.focus_broadcast(this.broadcasts[this.focus]);
			}
		}
		this.buzy_zooming = false;
		return true;
	}

	this.focus_all = function() {
		if (this.b_indexes.length <= 0) {
			return false;
		}
		var bounds = new google.maps.LatLngBounds();
		for (vid in this.broadcasts) {
			var broadcast = this.broadcasts[vid];
			if (broadcast == null || broadcast.hidden) {
				continue;
			}
			bounds.extend(broadcast.marker.getPosition());
		}
		this.map.fitBounds(bounds);
		return true;
	}

	this.focus_broadcast = function(broadcast) {
		if (broadcast.position == null || broadcast.marker == null) {
			return false;
		}
		this.map.panTo(broadcast.position);
		if (broadcast.position_accuracy > 0) {
			this.zoom_radius(broadcast.position_accuracy);
		}
		return true;
	}

	this.update_map_radius = function() {
		var corner = this.map.getBounds().getNorthEast();
		this.radius = google.maps.geometry.spherical.computeDistanceBetween(this.map.getCenter(), corner);
	}

	this.get_visible_grid_points = function() {
		var grid_points = new Array();
		if (this.map.getBounds() == undefined) {
			return false;
		}
		var ne = this.map.getBounds().getNorthEast();
		var sw = this.map.getBounds().getSouthWest();
		var view_width = ne.lng() - sw.lng();
		var view_height = ne.lat() - sw.lat();
		var resolution = this.zoom_grid[this.map.getZoom()];
		var y = ne.lat() - (ne.lat() % resolution);
		while (y >= sw.lat()) {
			var x = ne.lng() - (ne.lng() % resolution);
			while (x >= sw.lng()) {
				grid_points.push(new google.maps.LatLng(y, (x == 0 ? 1 : x)));
				x -= resolution;
			}
			y -= resolution / 2;
		}
		grid_points.push(this.map.getCenter());
		//draw_gridpoints(this, grid_points); // This can be used for debugging search areas
		return grid_points;
	}

	this.get_broadcasts = function() {
		if (this.buzy_zooming || this.loading > 0) {
			return;
		}
		var grid_points = this.get_visible_grid_points();
		if (grid_points) {
			if (typeof(this.params['vid']) != "number") {
				for (i in grid_points) {
					if (this.visited_grid_points[grid_points[i]]) {
						continue;
					}
					var params = {
						'lat': grid_points[i].lat(),
						'lon': grid_points[i].lng()
					};
					this.visited_grid_points[grid_points[i]] = true;
					this.call_api(params);
				}
			}
			else {
				// Skip if vid is set and the broadcast is loaded.
				if (typeof this.params['vid'] == "number" && this.broadcasts[this.params['vid']] !== undefined) {
					return;
				}
				this.call_api(null);
			}
		}
	}

	this.call_api = function(extra_params, update) {
		if (update === undefined) {
			update = false;
		}
		// Make request parameters unless map is limited to one broadcast
		var params = {};
		if (typeof(this.params['vid']) != "number") {
			for (key in this.broadcast_search_params) {
				params[key] = this.broadcast_search_params[key];
			}
			var resolution = this.zoom_grid[this.map.getZoom()];
			params['geo_distance'] = resolution * 50000;
			for (key in extra_params) {
				params[key] = extra_params[key];
			}
		}

		// Request broadcasts
		this.loading++;
		var b_map = this;
		var url = 'broadcast' + (typeof(this.broadcast_search_params['vid']) == "number" ? "/" + this.broadcast_search_params['vid'] : '');
		b_api(url, params,
			function(data) {
				b_map.parse_metadata(data, update);
				b_map.loading--;
			}, function() {
				b_map.loading--;
			}
		);
	}

	this.zoom_radius = function(radius) {
		this.buzy_zooming = true;
		var c = new google.maps.Circle();
		c.setCenter(this.map.getCenter());
		c.setRadius(radius / 2);
		this.map.fitBounds(c.getBounds());
		this.buzy_zooming = false;
	}

	this.update_marker_visibility = function() {
		for (vid in this.broadcasts) {
			if (vid == this.selected_vid) {
				continue;
			};
			if (this.broadcasts[vid] == null || this.broadcasts[vid] === undefined) {
				return;
			}
			var broadcast = this.broadcasts[vid];
			var hide = false;

			// Check if inside view
			if (broadcast.marker) {
				var position = broadcast.marker.get_div_position();
				if (position['x'] < -this.view_grace || position['y'] < -this.view_grace) {
					hide = true;
				}
				if (position['x'] > this.width + this.view_grace || position['y'] > this.height + this.view_grace) {
					hide = true;
				}
			}

			// Apply broadcast filters if not already hidden
			if (!hide && !this.filter_broadcast(broadcast)) {
				hide = true;
			}

			if (hide) {
				broadcast.hide();
			}
			else {
				broadcast.show();
			}
		}

		return;
	}

	this.update_trail = function(broadcast) {
		b_log("updating trail");
		if (broadcast == null || broadcast.trail == null) {
			if (this.trail != null) {
				this.trail.setMap(null);
				return;
			}
		}
		if (this.trail != null) {
			this.trail.setMap(null);
		}
		var time = new Date();
		var rand = "r=" + time.getTime();
		this.trail = new google.maps.KmlLayer(broadcast.trail + "?" + rand, {
			preserveViewport: true,
			map: this.map
			});
		return;
	}

	this.update_precisioncircle = function(broadcast) {
		b_log("updating precisioncircle");
		if (broadcast == null) {
			if (this.precision_circle != null) {
				this.precision_circle.setMap(null);
			}
			return;
		}
		var settings = {
			strokeColor: "#222",
			strokeOpacity: 1,
			strokeWeight: 2,
			strokeWidth: 1,
			fillColor: "#999",
			fillOpacity: 0.25,
			radius: broadcast.position_accuracy,
			center: broadcast.position,
			map: this.map
		};
		if (this.precision_circle == null) {
			this.precision_circle = new google.maps.Circle(settings);
		}
		else {
			this.precision_circle.setOptions(settings);
		}
	}

	this.update_flashplayer = function() {
		if (this.active_infowindow == null || this.selected_vid == null || this.flash_player == null) {
			return false;
		}

		// Reposition player
		var active_vid = this.active_infowindow.broadcast.vid;
		var $anchor = $("#player-anchor-" + active_vid);
		if ($anchor.length == 0) {
			return false;
		}
		var map_div_position = $("#" + this.map_id).offset();
		var player_position = this.flash_player.container().offset();
		var anchor_position = $anchor.offset();
		player_position['top'] -= map_div_position['top'];
		player_position['left'] -= map_div_position['left'];
		anchor_position['top'] -= map_div_position['top'];
		anchor_position['left'] -= map_div_position['left'];
		if (player_position['left'] != anchor_position['left'] || player_position['top'] != anchor_position['top']) {
			this.flash_player.reposition(anchor_position['left'], anchor_position['top']);
		}

		// Start player
		if (this.selected_vid != this.flash_player.playing_vid) {
			this.flash_player.play_broadcast(this.active_infowindow.broadcast);
			this.flash_player.playing_vid = this.selected_vid; // Force vid in flash player
		}
		return true;
	}

	this.add_broadcast_filter = function(type, comparator, value) {
		var filter = new broadcast_filter(type, comparator, value);
		if (this.broadcast_filters[filter.filter_id] != null && this.broadcast_filters[filter.filter_id] !== undefined) {
			return;
		}
		this.broadcast_filters[filter.filter_id] = filter;
	}

	this.remove_broadcast_filter = function(type, comparator, value) {
		var filter = new broadcast_filter(type, comparator, value);
		if (this.broadcast_filters[filter.filter_id] !== undefined) {
			this.broadcast_filters[filter.filter_id] = null;
			return true;
		}
		if (type != null && comparator == null && value == null) {
			for (key in this.broadcast_filters) {
				if (key.indexOf(type) == 0) {
					this.broadcast_filters[key] = null;
				}
			}
			return true;
		}
		return false;
	}

	this.filter_broadcast = function(broadcast) {
		for (filter_id in this.broadcast_filters) {
			var filter = this.broadcast_filters[filter_id];
			if (filter != null && !filter.eval(broadcast)) {
				return false;
			}
		}
		return true;
	}

	this.sort_markers = function() {
		var b_map = this;
		this.b_indexes.sort(function(vid1, vid2) {
			if (b_map.broadcasts[vid1].type == 'live') {
				return 1;
			}
			return b_map.broadcasts[vid2].age - b_map.broadcasts[vid1].age;
		});
		var z_index = 1000;
		for (i in this.b_indexes) {
			var vid = this.b_indexes[i];
			if (this.broadcasts[vid]) {
				this.broadcasts[vid].marker.setZIndex(z_index++);
			}
		}
	}

	this.listen = function() {
		if (bc_listener == null) {
			b_log("initializing broadcast listener");
			bc_listener = new broadcast_listener();
		}
		bc_listener.register(this);
		b_log("starting broadcast listener");
		bc_listener.start();
	}

	this.stop = function() {
		if (bc_listener == null) {
			return;
		}
		b_log("stopping broadcast listener");
		bc_listener.abort();
	}

	this.get_address = function(broadcast, $target) {
		this.geocoder.geocode({'location': broadcast.position}, function(results, status) {
			if (status == google.maps.GeocoderStatus.OK) {
				var country = false;
				for (c in results[0]['address_components']) {
					var component = results[0]['address_components'][c];
					if ($.inArray("country", component['types']) != -1) {
						country = component.long_name;
					}
				}
				if (country) {
					$target.html(country);
				}
			}
		});
	}

	this.force_load = function() {
		this.call_api(null, true);
	}

	return this;
}

var gridpoints = [];
function draw_gridpoints(b_map, new_gridpoints) {
	b_log("drawing grid points");
	for (i in gridpoints) {
		gridpoints[i].circle.setMap(null);
		gridpoints[i].marker.setMap(null);
	}
	var radius = b_map.zoom_grid[b_map.map.getZoom()] * 50000;
	b_log(radius);
	gridpoints = [];
	for (i in new_gridpoints) {
		b_log("adding grid point");
		var gp = new_gridpoints[i];
		var circle = new google.maps.Circle({
			map: b_map.map,
			center: gp,
			radius: radius
		});
		var marker = new google.maps.Marker({
			map: b_map.map,
			position: gp
		});
		gridpoints.push({'circle': circle, 'marker': marker});
	}
}

function flash_player() {
	var time = new Date();
	this.player_id = "flash_player_" + time.getTime();
	this.playing_vid = null;
	this.width = 250;
	this.height = 200;
	this.url_params = {
		'context': 'b_simple'
	}

	this.init = function(target) {
		var playerContainer = document.createElement("div");
		playerContainer.id = this.player_id;
		playerContainer.style.position = "absolute";
		playerContainer.style.zIndex = "5";
		target.appendChild(playerContainer);
	}

	this.set_size = function(width, height) {
		this.width = width;
		this.height = height;
	}

	this.reposition = function(x, y) {
		var $container = this.container();
		$container.css("left", x + "px");
		$container.css("top", y + "px");
		return true;
	};

	this.clear = function() {
		this.playing_vid = null;
		var $container = this.container();
		if ($container.length > 0) {
			$container.html("");
		}
		return true;
	};

	this.play_broadcast = function(broadcast) {
		this.clear();
		var $container = this.container();
		$container.css("background", "black");
		if ($container.length == 0) {
			b_log("could not find player container");
			return false;
		}
		var url = getVar('api-url') + "embed/broadcast/" + broadcast.vid + ".html?"
		for (key in this.url_params) {
			url += key + "=" + this.url_params[key] + "&";
		}
		$container.html('<iframe title="Bambuser video player" style="border: none;" class="bambuser-player" type="text/html" width="' + this.width + '" height="' + this.height + '" src=' + url + '></iframe>');
		this.playing_vid = broadcast.vid;
		return true;
	}

	this.container = function() {
		var $container = $("#" + this.player_id);
		if ($container.length != 0) {
			return $container;
		}
		return null;
	}

	return this;
}

function bambuser_broadcast(metadata) {
	this.metadata = metadata;
	this.vid = null;
	this.b_map = null;
	this.marker = null;
	this.created = 0;
	this.title = null;
	this.username = null;
	this.age = 0;
	this.type = null;
	this.position = null;
	this.position_accuracy = null;
	this.position_type = null;
	this.country = "";
	this.lat = null;
	this.lon = null;
	this.hidden = false;
	this.removed = false;
	this.trail = null;

	this.init = function() {
		// Check required parameters
		if (this.metadata === undefined) {
			return false;
		}
		var required_params = ['vid', 'username', 'lat', 'lon', 'created'];
		for (rqp in required_params) {
			if (this.metadata[required_params[rqp]] === undefined) {
				return false;
			}
		}

		// Set metadata values
		for (key in metadata) {
			var value = metadata[key];
			this[key] = value;
		}

		// Parse numbers
		this.position_accuracy = metadata.position_accuracy ? parseFloat(this.position_accuracy) : -1;
		if (metadata.position_type && metadata.position_type == "country") {
			this.position_accuracy /= 2;
		}
		this.created = parseInt(this.created);

		this.position = new google.maps.LatLng(this.lat, this.lon);
		this.update_age();
		return true;
	}

	this.create_marker = function() {
		if (this.marker != null) {
			this.marker.setMap(null);
		}
		if (this.removed) {
			b_log("broadcast removed, will not draw marker");
			return false;
		}
		if (this.b_map && this.position) {
			this.marker = create_marker(this);
			if (this.marker) {
				return true;
			}
		}
		return false;
	}

	this.update_age = function() {
		var now = new Date();
		now = now.getTime() / 1000;
		this.age = now - this.created;
	}

	this.hide = function() {
		if (this.marker != null) {
			this.marker.setVisible(false);
		}
		this.hidden = true;
	}

	this.show = function() {
		if (this.marker !== undefined && this.hidden) {
			this.marker.setVisible(true);
		}
		this.hidden = false;
	}

	this.update_broadcast_metadata = function(metadata) {
		var updated_params = {};

		if (metadata === undefined) {
			return false;
		}
		if (metadata.removed || metadata.private) {
			this.removed = true;
			updated_params['removed'] = true;
			return updated_params;
		}
		if (metadata.type && this.type != metadata.type) {
			this.type = metadata.type;
			if (this.marker != null) {
				this.marker.setIcon(create_marker_icon(this));
			}
			updated_params['type'] = this.type;
		}
		if (metadata.title && this.title != metadata.title) {
			this.title = metadata.title;
			updated_params['title'] = this.title;
		}
		if (metadata.position_accuracy && this.position_accuracy != metadata.position_accuracy) {
			this.position_accuracy = metadata.position_accuracy;
			if (metadata.position_type && metadata.position_type == "country") {
				this.position_accuracy /= 2;
			}
			updated_params['position_accuracy'] = parseFloat(this.position_accuracy);
		}
		if (metadata.trail && this.trail != metadata.trail) {
			this.trail = metadata.trail;
			updated_params['trail'] = this.trail;
		}
		if (metadata.position_type && this.position_type != metadata.position_type) {
			this.position_type = metadata.position_type;
			updated_params['position_type'] = this.position_type;
		}

		// Calculate change in broadcast position in order to know how much the map needs to be panned
		if ((metadata.lat || metadata.lon) && (this.lat != metadata.lat || this.lon != metadata.lon)) {
			updated_params['position_diff'] = {'lat': Math.abs(this.lat - metadata.lat), 'lng': Math.abs(this.lon - metadata.lon) };
			if (metadata.lat < this.lat) {
				updated_params['position_diff'].lat = -updated_params['position_diff'].lat;
			}
			if (metadata.lon < this.lon) {
				updated_params['position_diff'].lng = -updated_params['position_diff'].lng;
			}

			this.lat = metadata.lat;
			this.lon = metadata.lon;
			this.position = new google.maps.LatLng(parseFloat(this.lat), parseFloat(this.lon));
			updated_params['lat'] = this.lat;
			updated_params['lon'] = this.lon;
			updated_params['position'] = this.position;

			if (this.marker) {
				this.marker.setPosition(this.position);
			}

			this.update_external();
		}

		this.update_age();
		updated_params['age'] = this.age;
		return updated_params;
	}

	this.validate_metadata = function(metadata) {
		return true;
	}

	this.update_external = function() {
		// Update position DOM
		$lat_container = $("#broadcast-" + this.vid + "-lat");
		$lon_container = $("#broadcast-" + this.vid + "-lon");
		$position_container = $("#broadcast-" + this.vid + "-position");
		if ($position_container.length > 0) {
			if (this.position_type == "country") {
				$position_container.hide();
			}
			else {
				$position_container.show();
			}
		}
		if ($lat_container.length > 0) {
			$lat_container.html(this.lat);
		}
		if ($lon_container.length > 0) {
			$lon_container.html(this.lon);
		}

		// Update country DOM
		$country_container = $("#broadcast-" + this.vid + "-country");
		if ($country_container.length > 0 && this.position_type != "country") {
			this.b_map.get_address(this, $country_container);
		}

		// Update position type
		if (this.metadata.lat != this.lat || this.metadata.lon != this.lon) {
			$position_type_container = $("#broadcast-" + this.vid + "-position-type");
			if ($position_type_container.length > 0) {
				if (this.position_type == "manual") {
					$position_type_container.html('Manually geotagged');
				}
				if (this.position_type == "GPS") {
					$position_type_container.html('Geotagged using GPS');
				}
				if (this.position_type == "network") {
					$position_type_container.html('Geotagged using network');
				}
			}
		}
	}

	return this;
}

function create_marker(broadcast) {
	if (broadcast.b_map === undefined) {
		return false;
	}
	var b_map = broadcast.b_map;

	if (broadcast.marker) {
		broadcast.marker.setMap(null);
	}

	var marker = new google.maps.Marker({
		position: broadcast.position,
		icon: create_marker_icon(broadcast),
		clickable: b_map.clickable_markers,
		draggable: b_map.draggable_markers,
		zIndex: (broadcast.type == 'live' ? 1000000 : Math.round((broadcast.created - 1000000000) / 1000))
	});
	marker.vid = broadcast.vid;

	// Set click event listener
	if (b_map.clickable_markers) {
		google.maps.event.addListener(marker, 'click', function () {
			// Update icons
			b_map.selected_vid = broadcast.vid;
			if (b_map.active_infowindow != null) {
				var previous = b_map.active_infowindow.broadcast;
				if (previous.removed) {
					previous.create_marker();
				}
				else {
					previous.marker.setIcon(create_marker_icon(previous));
				}
				if (previous.marker.default_zindex) {
					previous.marker.setZIndex(previous.marker.default_zindex);
				}
			}
			this.setIcon(create_marker_icon(broadcast));
			this.default_zindex = this.getZIndex();
			this.setZIndex(1000001);

			// Kill existing player
			if (b_map.flash_player != null) {
				b_map.flash_player.clear();
			}

			// Close active infowindow
			var infowindow = create_infowindow(broadcast);
			if (b_map.active_infowindow && b_map.active_infowindow != infowindow) {
				b_map.active_infowindow.close();
			}

			// Open infowindow
			infowindow.open(b_map.map, this);

			b_map.active_infowindow = infowindow;
			b_map.update_precisioncircle(broadcast);
			b_map.update_trail(broadcast);
		});
	}
	// Set drag event listener
	if (b_map.draggable_markers) {
		google.maps.event.addListener(marker, 'dragend', function(event) {
			var response = confirm('Store new position for this broadcast?');
			var old_pos = broadcast.position;
			if(response){
				var vid = marker.vid;
				var corner = marker.getMap().getBounds().getNorthEast();
				var map_radius = google.maps.geometry.spherical.computeDistanceBetween(marker.getMap().getCenter(), corner);
				map_radius = map_radius / 2;
				var decimals = Math.round((b_map.map.getZoom() + 1) / 2);
				var rounded_lat = event.latLng.lat().toFixed(decimals);
				var rounded_lon = event.latLng.lng().toFixed(decimals);
				var params = {
					'lat': rounded_lat,
					'lon': rounded_lon,
					'positionPrecision': map_radius,
					'positionRemoved': 0,
					'positionProvider': 'manual'
				};
				b_api("broadcast/" + vid + "/update", params,
					function() {
						broadcast.position = marker.getPosition();
						broadcast.lat = rounded_lat;
						broadcast.lon = rounded_lon;
						broadcast.position_accuracy = map_radius;
						broadcast.position_type = "manual";
						broadcast.found_at_zoomlvl = marker.getMap().getZoom();
						b_map.update_precisioncircle(broadcast);
						b_map.focus_map();
						broadcast.update_external();
					},
					function() {
						broadcast.position = old_pos;
						broadcast.marker.setPosition(old_pos);
					}
				);
			}
			else {
				broadcast.position = old_pos;
				broadcast.marker.setPosition(old_pos);
			}
		});
	}

	marker.get_div_position = function() {
		var position = b_map.positioning_overlay.getProjection().fromLatLngToContainerPixel(marker.getPosition());
		return position;
	}

	if (broadcast.type == 'live' && broadcast.marker == null) {
		marker.setAnimation(google.maps.Animation.DROP);
	}
	if (!broadcast.hidden) {
		marker.setMap(b_map.map);
	}

	return marker;
}

function create_marker_icon(broadcast) {
	if (broadcast.b_map === undefined) {
		return false;
	}
	var b_map = broadcast.b_map;

	var icon_image = b_map.marker_icons['default'];

	if (b_map.selected_vid == broadcast.vid) {
		icon_image = b_map.marker_icons['selected'];
	}
	else {
		if (broadcast.type == "live") {
			icon_image = b_map.marker_icons['live'];
		}
		else if (typeof b_map.marker_icons.age_frames === "object" && b_map.marker_icons.age_frames.length > 0) {
			frame_index = 0;
			while (frame_index < b_map.marker_icons.age_frames.length) {
				var frame = b_map.marker_icons.age_frames[frame_index];
				if (broadcast.age < frame['age']) {
					break;
				}
				frame_index++;
			}
			if (frame_index < b_map.marker_icons.age_frames.length) {
				icon_image = b_map.marker_icons.age_frames[frame_index]['icon'];
			} else {
				// Use default icon for really old broadcasts.
			}
		}
	}

	var markerIcon = new google.maps.MarkerImage(
		icon_image,
		new google.maps.Size(23, 24),
		new google.maps.Point(0, 0),
		new google.maps.Point(10, 23)
	);
	return markerIcon;
}

function create_infowindow(broadcast) {
	if (broadcast.b_map === undefined) {
		return false;
	}
	var b_map = broadcast.b_map;
	var player_width = 240;
	var player_height = 0;
	if (b_map.flash_player != null) {
		player_width = b_map.flash_player.width;
		player_height = b_map.flash_player.height;
	}
	var info_height = 55;
	if (!broadcast.title) {
		info_height = 33;
	}
	if (b_map.debug) {
		info_height = 200;
	}

	var html = '<div style="width:' + player_width + 'px; height: ' + (player_height + info_height) + 'px; overflow: hidden;">';
	html += '<div id="infowindow-' + b_map.map_id + '" style="font-size: 10px; height: ' + info_height + 'px; overflow: hidden;">';
	html += create_broadcast_info(broadcast);
	html += '</div>';
	html += '<div id="player-anchor-' + broadcast.vid + '" style="width: ' + player_width + 'px; height: ' + player_height + 'px; background: whitesmoke;"></div>';
	html += '</div>';

	var infowindow = new google.maps.InfoWindow({
		content: html
	});

	infowindow.broadcast = broadcast;

	google.maps.event.addListener(infowindow, 'closeclick', function () {
		b_map.update_precisioncircle(null);
		b_map.update_trail(null);
		if (b_map.flash_player != null) {
			b_map.flash_player.clear();
		}
		b_map.active_infowindow = null;
		b_map.selected_vid = null;
		this.broadcast.create_marker();
	});
	google.maps.event.addListener(infowindow, 'domready', function() {
		b_map.update_flashplayer();
	});
	return infowindow;
}

function create_broadcast_info(broadcast) {
	if (broadcast.b_map.debug) {
		html = '<div class="infowindow-broadcast-info">';
		var params = ['vid', 'type', 'title', 'username', 'created', 'age', 'lat', 'lon', 'position_accuracy', 'found_at_zoomlvl', 'trail'];
		for (key in params) {
			html += params[key] + ": " + broadcast[params[key]] + '<br />';
		}
		html += '</div>';
		return html;
	}
	var broadcast_url = getVar("site-url") + '/v/' + broadcast.vid;
	var channel_url = getVar("site-url") + '/channel/' + escape(broadcast.username);
	var username = broadcast.username;
	try {
		username = decodeURIComponent(escape(broadcast.username));
	} catch (e) { }
	var title = broadcast.title;
	try {
		title = title ? decodeURIComponent(escape(broadcast.title)) : "";
	} catch (e) { }
	
	var alt_title = "";
	if (title.length >= 20) {
		alt_title = title;
		title = title.substring(0, 20) + "...";
	}

	html = '<div class="infowindow-broadcast-info">';
	if (broadcast.title) {
		html += '<div class="broadcast-info-title" title="' + alt_title + '"><a href="' + broadcast_url + '">' + title + '</a></div>';
	}
	html += '<div class"maps-infowindow-textbox">';
	html += '<div class="broadcast-info-user">';
	if (!broadcast.title) {
		html += '<a href="' + broadcast_url + '">broadcast</a> by ';
	}
	else {
		html += "by ";
	}
	html += '<a href="' + channel_url + '">' + username + '</a>';
	html += '</div>';
	html += '<div class="broadcast-info-time">' + (broadcast.type == "live" ? "Live now!" : timeSince(broadcast.created) + " ago") + '</div>';
	html += '</div>';
	html += '</div>';
	return html;
}

function broadcast_filter(type, comparator, value) {
	this.type = type;
	this.comparator = comparator;
	this.value = value;
	this.filter_id = type + "_" + comparator + "_" + value;

	this.eval = function(broadcast) {
		switch (this.type) {
			case 'vid':
				return this.eval_nr(broadcast.vid);
			case 'age':
				return this.eval_nr(broadcast.age);
			case 'username':
				return this.eval_str(broadcast.username);
			case 'created':
				return this.eval_nr(broadcast.created);
			case 'lon':
				return this.eval_nr(broadcast.lon);
			case 'lat':
				return this.eval_nr(broadcast.lat);
			case 'title':
				return this.eval_str(broadcast.title);
			case 'position_accuracy':
				return this.eval_nr(broadcast.position_accuracy);
			case 'type':
				return this.eval_str(broadcast.type);
			case 'zoom_diff':
				var diff = Math.abs(broadcast.b_map.map.getZoom() - broadcast.found_at_zoomlvl);
				return this.eval_nr(diff);
			case 'zoom_diff_out':
				var diff = broadcast.found_at_zoomlvl - broadcast.b_map.map.getZoom();
				if (diff <= 0) {
					return true;
				}
				return this.eval_nr(diff);
			case 'zoom_diff_in':
				var diff = broadcast.b_map.map.getZoom() - broadcast.found_at_zoomlvl;
				if (diff <= 0) {
					return true;
				}
				return this.eval_nr(diff);
		}
		return true;
	}


	this.eval_nr = function(val) {
		val = parseFloat(val);
		if (typeof(val) != "number") {
			return false;
		}
		switch (this.comparator) {
			case 'gt':
				if (val >= this.value) {
					return true;
				}
				break;
			case 'lt':
				if (val <= this.value) {
					return true;
				}
				break;
			case 'equals':
				if (val == this.value) {
					return true;
				}
				break;
		}
		return false;
	}

	this.eval_str = function(str) {
		if (typeof(str) != "string") {
			return false;
		}
		switch (this.comparator) {
			case 'equals':
				if (str === this.value) {
					return true;
				}
				break;
			case 'not_equals':
				if (str !== this.value) {
					return true;
				}
				break;
			case 'contains':
				if (str.indexOf(this.value) != -1) {
					return true;
				}
				break;
		}
		return false;
	}

	return this;
}

function loading_indicator(b_map) {
	if (b_map == null) {
		return null;
	}
	this.b_map = b_map;
	this.show_loading = true;
	this.style = {
		'z-index': 5000,
		'position': 'absolute',
		'width': 200,
		'top': 5,
		'left': (Math.round(this.b_map.width / 2)) - 100,
		'text-align': 'center'
	};

	this.start = function() {
		b_log("starting loading indicator");
		if (this.show_loading && this.b_map != null) {
			var $map_container = $("#" + this.b_map.map_id);
			if ($map_container.length == 0) {
				return;
			}
			if ($map_container.find(".loading-indicator").length <= 0) {
				var loading_label = $('<div class="loading-indicator"><span class="loading-indicator-text">Loading...</span></div>');
				loading_label.css(this.style);
				$map_container.append(loading_label);
			}
			setInterval(function() {
				var loading_label = $map_container.find(".loading-indicator");
				if (b_map.loading > 0) {
					loading_label.fadeIn('fast');
				}
				else {
					loading_label.fadeOut('slow');
				}
			}, 1000);
		}
	}

	this.reposition = function() {
		var $map_container = $("#" + this.b_map.map_id);
		var $loading_label = $map_container.find(".loading-indicator");
		$loading_label.css("left", (Math.round(this.b_map.width / 2)) - 100 + "px");
	}

	return this;
}

function broadcast_listener() {
	this.comet_url = getVar('comet-url') ? getVar('comet-url') : "http://misc00.bambuser.com:3333/";
	this.receive_updates = false;
	this.error_wait = 1000;
	this.b_maps = null;

	this.register = function(b_map) {
		if (this.b_maps == null) {
			this.b_maps = {};
		}
		this.b_maps[b_map.params['map_id']] = b_map;
	}

	this.start = function() {
		if (this.b_maps == null) {
			b_log("no target map specified");
			return;
		}
		this.receive_updates = true;
		this.poll();
	}

	this.abort = function() {
		if (this.receive_updates) {
			this.receive_updates = false;
		}
	}

	this.poll = function() {
		var params = {};
		if (this.comet_url == '' || this.comet_url == undefined) {
			this.abort();
			return;
		}
		$.ajax({
			url: this.comet_url + "broadcasts",
			type: "GET",
			data: params,
			cache: false,
			dataType: "jsonp",
			success: bc_listener.poll_success,
			error: bc_listener.poll_error
		});
	}

	this.poll_success = function(response) {
		var broadcasts = [];
		if (response.updates) {
			for (var i=0; i<response.updates.length; i++) {
				broadcasts.push(response.updates[i].broadcast);
			}
			for (map_id in bc_listener.b_maps) {
				bc_listener.b_maps[map_id].parse_metadata(broadcasts, true);
			}
		}
		if (bc_listener.receive_updates) {
			bc_listener.error_wait = 3000;
			bc_listener.poll();
		}
	}

	this.poll_error = function(response) {
		this.error_wait *= 2;
		if (bc_listener.receive_updates) {
			setTimeout(bc_listener.poll(), this.error_wait);
		}
	}

	return this;
}
var bc_listener = new broadcast_listener();

function parseBoolean(value) {
	if (typeof value == "string") {
		if (value.toLowerCase() == "false" || value == "0") {
			value = false;
			return value;
		}
		if (value.toLowerCase() == "true" || value == "1") {
			value = true;
			return value;
		}
	}
	return value;
}/**
 * Facebook Javascript API handler
 * http://developers.facebook.com/docs/reference/javascript/
 *
 * Assumption: Facebook app id is provided via DOM.
 * Example: <span id="fb_app_id" title="APPID"></span>
 *
 * Usage:
 *	b_fb.init(function() {
 *		// Custom code to fire once initialization of FB object is done.
 *		// Example:
 *		FB.getLoginStatus(function(response) {
 *			if (response.authResponse) {
 *				alert('User has an active session with user id' + response.authResponse.userID);
 *			} else {
 *				alert('User does not have a session.');
 *			}
 *		}
 *	}
 *
 * Author: Tom Sundström
 * Requires: jQuery
 */

var b_fb = {
	isLoaded: false,
	isLoading: false,
	loadCallbacks: [],

	init: function(callback) {
		if (b_fb.isLoaded) { // Already initialized.
			callback(); // Execute callback right away.
			return;
		}
		b_fb.loadCallbacks.push(callback); // Postpone callback execution until initialized.
		if (b_fb.isLoading) {
			return; // Don't initialize twice if already in progress.
		}
		b_fb.isLoading = true;
		b_log("Loading Facebook JavaScript SDK");

		// Standard way of asynchronously loading Facebook JavaScript SDK:
		window.fbAsyncInit = function() {
			var site_url = parseUri(getVar('site-url', 'http://bambuser.com/'));
			var channel_url = document.location.protocol + '//' + site_url.host;
			if (site_url.port) channel_url += ':' + site_url.port;
			channel_url += '/fb/channel';
			b_log("Facebook channel url is: " + channel_url);
			b_log("channel url: " + channel_url);
			FB.init({
				// Look up app id from DOM.
				'appId': getVar('fb_app_id'),

				// Check login status.
				'status': true,

				 // Enable cookies to allow the server to access the session.
				'cookie': true,

				// Don't traverse DOM and look for FBML automatically.
				// Parsing can be triggered as needed with FB.XFBML.parse().
				// http://developers.facebook.com/docs/reference/javascript/FB.XFBML.parse
				'xfbml': false,

				'channelUrl': channel_url,

				// Enable OAuth 2.0
				'oauth': true
			});
			b_fb.isLoaded = true;
			b_fb.isLoading = false;
			b_log("Facebook JavaScript SDK loaded");

			for (i in b_fb.loadCallbacks) {
				// Execute postponed callbacks.
				b_fb.loadCallbacks[i]();
			}
		};
		(function() {
			$("body").append('<div id="fb-root"></div>');
			var e = document.createElement('script');
			e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js';
			e.async = true;
			document.getElementById('fb-root').appendChild(e);
		}());
	}
}

/**
 * Misc OAuth related functionality
 *
 * Author: Tom Sundström
 * Requires: jQuery
 */

var oauth = {
	window: null,
	timer: null,
	updateCallback: null,
	checkWindow: function() {
		if (!oauth.window || oauth.window.closed) {
			window.clearInterval(oauth.timer);
			if (typeof oauth.updateCallback !== 'null') {
				oauth.updateCallback();
			}
		}
	},
	reloadDashboard: function() {
		window.location.href = getVar('site-url') + 'dashboard?sidebar_tab=share&r=' + Math.random() + "#settings-share";
	},
	reload: function() {
		window.location.reload(true);
	},
	connect: function(service_id, callback) {
		return oauth.openWindow(service_id, 'connect', callback);
	},
	disconnect: function(service_id, callback) {
		return oauth.openWindow(service_id, 'disconnect', callback);
	},
	openWindow: function(service_id, action, callback) {
		if (typeof callback !== 'undefined') {
			oauth.updateCallback = callback
		}
		oauth.window = window.open(getVar('site-url') + 'oauth/' + service_id + '/'  + action, 'BambuserOAuth', 'menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes,width=800,height=400');
		if (!oauth.window) {
			// Fall back to regular link.
			return true;
		}
		window.clearInterval(oauth.timer);
		oauth.timer = window.setInterval(oauth.checkWindow, 300);
		return false;
	}
}
/**
 * JavaScript Share menu
 * Depends on $.overlay() in bambuser.js
 * Styling provided via share.css
 *
 * Author: Tom Sundström
 * Requires: jQuery
 */

var share_config_window = null;
var share_config_window_timer = null;
var share_recent_settings = null;

$(document).ready(function(){
	$(".share-target-link").livequery('click', function() {
		toggleTarget($(this).parent().parent().parent(), true);
	});
	/*
	$(".share-target-links .share-target-link span").livequery('click', function() {
		toggleTarget($(this).parent().parent().parent().parent(), true);
	});
	*/
	$(".share-target-checkbox").livequery('change', function() {
		toggleTarget($(this).parent(), false);
	});
	$(".share-target-toggler").livequery('click', function() {
		$(this).parent().append('<div class="spinner">' + renderSpinner() + '</div>');
		if ($(this).attr("checked") == undefined) {
			$(this).attr("checked", true);
		}
		else {
			$(this).removeAttr("checked");
		}
		toggleTarget($(this).parent().parent().parent(), false);
	});

	$(".share-share-button").livequery('click', function() {
		if ($(this).parent().parent().parent().find(".share-target-messageAllowed").attr("title") != "true") {
			$(this).parent().append('<div class="spinner">' + renderSpinner() + '</div>');
		}
	});

	$(".share-send-button").livequery('click', function() {
		$(this).parent().parent().append('<div class="spinner">' + renderSpinner() + '</div>');
	});

	$("#include-player-position").livequery('click', function() {
		updateBroadcastLinkUrl();
	});
});

function toggleTarget($target, toggleCheckbox) {
	var type = $target.find(".share-target-type").attr('title');
	if (type == 'check') {
		$checkbox = $target.find(".share-target-checkbox");
		$toggler = $target.find(".share-target-toggler");
		$checkbox.attr('disabled', true);
		if (toggleCheckbox) {
			if ($checkbox.attr('checked')) {
				b_log("uncheck");
				$checkbox.removeAttr("checked");
			} else {
				b_log("check");
				$checkbox.attr("checked", "checked");
			}
			$checkbox.attr('disabled', true);
		}
	}
	var vid = $target.find(".share-target-vid").attr('title');
	var id = $target.find(".share-target-id").attr('title');
	var messageAllowed = ($target.find(".share-target-messageAllowed").attr('title') === 'true' ? true : false);
	$target.find('.share-target-body').fadeTo(500, 0.5, function() {
		$msg_dialog = $target.find('.share-target-msg');
		if (messageAllowed && !$msg_dialog.hasClass("dialog-visible")) {
			// Show message dialog.
			$msg_dialog = $target.find('.share-target-msg');
			$msg_dialog.show();
			$msg_dialog.addClass("dialog-visible");
			return;
		} else {
			var params = {'id': id}
			if (messageAllowed) {
				params['msg'] = $msg_dialog.find('input').val();
				$msg_dialog.removeClass("dialog-visible");
				$msg_dialog.hide();
			}
			if (type == 'check') {
				params['checked'] = ($checkbox.attr('checked') ? 'true' : 'false');
				if ($toggler.length > 0) {
					params['checked'] = ($toggler.attr('checked') ? 'true' : 'false');
				}
			}
			b_api('sendto/broadcast/' + vid + '/update/' + type, params, function(response) {
				// Sucessfully sent broacdast update.
				$share_link = $target.find(".share-target-links > .share-target-link");
				if ($share_link.length == 0) {
					$share_link = $target.find(".share-target-link");
				}
				$toggler = $target.find(".share-target-toggler");
				$share_link.removeClass('share-target-link');
				$share_link.removeClass('b-link');
				b_log('type=' + type + ', checked=' + params['checked']);
				if (!response.data.result) {
					$share_link.html('<span class="error">Sharing failed</span>');
				} else if (type == 'check' && params['checked'] == 'false') {
					$share_link.html('unshared');
					$toggler.removeClass("share-target-toggler-shared");
				} else {
					$share_link.html('shared!');
					$toggler.addClass("share-target-toggler-shared");
				}
				if (type == 'check') {
					setTimeout(function() {
						b_log("clean up");
						b_log($share_link);
						$share_link.fadeTo(1000, 0.0, function() {
							$share_link.html(params['checked'] == 'false' ? 'share' : 'unshare');
							$share_link.addClass('share-target-link');
							$share_link.addClass('b-link');
							$checkbox.removeAttr('disabled');
							$share_link.fadeTo(2000, 1.0, function() {});
						});
					}, 1000);
				}
				else if (response.data.result) {
					$share_link.addClass("share-target-link-shared");
				}
				else {
					$share_link.removeClass("share-share-button");
					if ($share_link.hasClass("mobile-dashboard")) {
						setTimeout(function() {
							$share_link.fadeTo(1000, 0.0, function() {
								$share_link.addClass("share-share-button share-target-link");
								$share_link.html("");
								$share_link.fadeTo(2000, 1.0, function() {});
							});
						}, 1500);
					}
				}
				$target.find('.share-target-body').fadeTo(500, 1.0, function() {});
				if ($target.find(".spinner").length > 0) {
					$target.find(".spinner").remove();
				}
			}, function(response) {
				$share_link = $target.find(".share-target-links > .share-target-link");
				if ($share_link.length == 0) {
					$share_link = $target.find(".share-target-link");
				}
				$share_link.removeClass('share-target-link');
				$share_link.removeClass('b-link');
				$share_link.addClass('error');
				$share_link.html('Sharing failed');
				try {
					$checkbox.removeAttr('disabled');
				} catch(e) {}
				$target.find('.share-target-body').fadeTo(500, 1.0, function() {});
				if ($target.find(".spinner").length > 0) {
					$target.find(".spinner").remove();
				}
			});
		}
	});
}


function findIconInElements($elements, size, format) {
	var ret = false;
	if ($elements.length > 0) {
		for (var i=0; i<$elements.length; i++) {
			$node = $elements[i];
			if ($node.nodeName != "icon") {
				continue;
			}
			var attributes = {};
			for (var a=0; a<$node.attributes.length; a++) {
				attributes[$node.attributes[a].nodeName] = $node.attributes[a].nodeValue;
			}
			if (attributes['format'] == format && attributes['size'] == size) {
				ret = attributes['url'];
			}
		}
	}
	return ret;
}


function generateShareTarget($share_data, vid) {
	html = "";
	var disabled = ($share_data.attr('disabled') === 'true' ? true : false);
	var configure_url = $share_data.attr('configureUrl');
	var description = $share_data.attr('description');
	html += '<li class="share-target share-target-' + (disabled ? 'disabled' : 'enabled') + '" id="share-target-' + vid + '-' + $share_data.attr('id') + '">';
	html += '<span class="share-target-vid" title="' + vid + '"></span>';
	html += '<span class="share-target-id" title="' + $share_data.attr('id') + '"></span>';
	html += '<span class="share-target-type" title="' + $share_data.attr('type') + '"></span>';
	html += '<span class="share-target-messageAllowed" title="' + $share_data.attr('messageAllowed') + '"></span>';

	html += '<div class="share-target-body">';
	html += '<div class="share-label" title="' + description + '">';
	var iconUrl = findIconInElements($share_data[0].childNodes, "16x16", "png");
	if (iconUrl && iconBaseUrl) {
		html += '<img class="share-icon" src="' + iconBaseUrl + iconUrl + '">';
	}
	html += '<span class="share-label-text">' + check_plain($share_data.attr('label')) + '</span>';
	html += '</div>';

	html += '<div class="share-buttons">';
	if (configure_url) {
		var configure_button_onclick = "window.open('" + configure_url + "','Bambuser | Configure " + $share_data.attr('label') + "','height=500,width=350,toolbar=no,directories=no,status=no,menubar=no,scrollbars=auto,resizable=yes'); return false;";
		html += '<a class="share-configure-button" href="' + configure_url + '" onClick="' + configure_button_onclick + '"></a>';
	}
	else if (disabled && $share_data.attr('description') != undefined) {
		html += '<span class="no-configure">Configure on your <a href="' + getVar('site_url', 'http://bambuser.com/') + 'dashboard">dashboard</a></span>';
	}

	if (!disabled) {
		if ($share_data.attr('type') != 'check') {
			html += '<div class="mobile-dashboard share-share-button share-target-link"></div>';
		}
		else {
			html += '<div class="share-target-toggler ' + ($share_data.attr('checked') == 'true' ? 'share-target-toggler-shared' : '') + '" ' + ($share_data.attr('checked') == 'true' ? 'checked="true"' : '') + '></div>';
		}
	}

	html += '</div>';
	html += '<hr class="divider" />';
	html += '</div>';
	html += '<div class="share-target-msg" style="display: none;">';
	html += '<div class="dummy"><div class="share-target-link share-send-button b-link"></div></div>';
	html += '<div class="desc">Enter a message:</div>';
	html += '<input maxlength="100" size="20" type="text" value="' + ($share_data.attr('defaultMessage') == undefined ? '' : check_plain($share_data.attr('defaultMessage'))) + '" />';
	html += '</div>';
	

	html += '</li>';
	return html;
}


function generateShareMenu($element, vid, level) {
	response = '<ul>';
	response += '<li class="share-group">';

	$element.children().each(function() {
		if ($(this).is("group")) {
			if ($(this).attr('label') == "Categories" || $(this).attr('label') == "Tagging") {
				return;
			}
			response += '<div class="group closed">';
			response += '<input type="hidden" class="group-level" value="' + level + '" />';
			response += '<div class="group-header group-toggle-visible">';
			response += '<div class="group-label">';
			var iconUrl = findIconInElements($(this)[0].childNodes, "16x16", "png");
			if (iconUrl && iconBaseUrl) {
				response += '<img class="group-icon" src="' + iconBaseUrl + iconUrl + '" alt="' + check_plain($(this).attr('label')) + '" title="' + check_plain($(this).attr('label')) + '" />';
			}
			response += '<span class="group-label-text ' + (iconUrl ? 'has-icon' : '') + '">' + check_plain($(this).attr("label")) + '</span>';
			response += '</div>';

			response += '<div class="group-toggler">';
			response += '</div>';

			response += '</div>';

			if ($(this).attr("description") != undefined) {
				response += '<div class="group-description">';
				if ($(this).attr("configureUrl") != undefined) {
					var configure_button_onclick = "window.open('" + $(this).attr('configureUrl') + "','Bambuser | Configure " + $(this).attr('label') + "','height=500,width=350,toolbar=no,directories=no,status=no,menubar=no,scrollbars=auto,resizable=yes'); return false;";
					response += '<a class="group-configure-button" href="' + $(this).attr('configureUrl') + '" onClick="' + configure_button_onclick + '"></a>';
				}
				response += '<span class="group-description-text">' + $(this).attr("description") + '</span>';
				response += '</div>';
			}

			response += '<div class="group-contents group-level-' + level + ' ' + ( $(this).attr("description") == undefined ? "no-description" : "" ) + '">';
			response += generateShareMenu($(this), vid, level+1);
			response += '</div>';
			response += '</div>';
		}
		if ($(this).is("target")) {
			var target_html = generateShareTarget($(this), vid);
			if (target_html != undefined) {
				response += target_html;
			}
		}
	});

	response += '</li>';
	response += '</ul>';
	return response;
}

function renderPostServicesContents(services) {
	var html = "<div>";
	html += "<p>Post to:</p>";
	for (s in services) {
		if (html.length == 0) {
		}
		var service = services[s];
		html += '<div class="post-service" title="Post broadcast to ' + check_plain(service['name']) + '">';
		if (typeof service.html !== 'undefined') {
			html += service.html;
		}
		if (typeof service.post_url !== 'undefined' && typeof service.icon !== 'undefined') {
			html += '<a target="_blank" href="' + service['post_url'] + '">';
			html += '<img src="' + service['icon'] + '" />';
			html += '</a>';
		}
		html += '</div>';
	}
	html += "</div>";
	return html;
}

function autoRefreshBroadcastLinkUrl() {
	setTimeout(function() {
		if (updateBroadcastLinkUrl()) {
			autoRefreshBroadcastLinkUrl();
		}
	}, 1000);
}

function updateBroadcastLinkUrl() {
	var $player_position = $("#player-position");

	if ($player_position.length == 0) {
		return false;
	}

	// Update player position value
	var player_position = $player_position.val();
	if (typeof getPlayerPosition == 'function') {
		player_position = getPlayerPosition();
	}

	// Update url
	var $checkbox = $("#player-position-checkbox");
	var $broadcast_base_url = $("#broadcast-base-url");
	if ($checkbox.length == 0 || $broadcast_base_url.length == 0) {
		return false;
	}
	var checked = $checkbox.is(':checked');
	if (checked && player_position) {
		$("#broadcast-link-url").val($broadcast_base_url.val() + "#t=" + player_position + "s");
	}
	else {
		$("#broadcast-link-url").val($broadcast_base_url.val());
	}

	return true;
}

function openShareDialog($target, vid, player_position) {
	b_api('broadcast/' + vid, {}, function(response) {
		if (response.data.result === undefined) {
			return;
		}
		var broadcast = response.data.result;
		var html = '<div class="share-menu-container">';
		html += '<div class="share-targets-container"></div>';
		html += '<div class="post-services-container"></div>';
		html += '<div class="link-container">';
		var use_timetag_by_default = false;
		if (broadcast['access_mode'] == 0) {
			html += '<p>Share broadcast URL</p>';
			var broadcast_base_url = getVar('site_url', 'http://bambuser.com/') + 'v/' + vid;
			var broadcast_url = broadcast_base_url;
			if (use_timetag_by_default && player_position) {
				broadcast_url += '#t=' + player_position + 's';
			}
			html += '<input type="text" id="broadcast-link-url" value="' + broadcast_url + '" />';
			if (player_position) {
				html += '<label style="display: block; cursor: pointer;" for="player-position-checkbox" id="include-player-position">';
				html += '<input type="hidden" id="broadcast-base-url" value="' + broadcast_base_url + '" />';
				html += '<input type="hidden" id="player-position" value="' + player_position + '" />';
				html += '<input type="checkbox" id="player-position-checkbox"' + (use_timetag_by_default ? ' checked="checked"' : '') + ' />';
				html += '<span>Link to current time in player</span>';
				html += '</label>';
				autoRefreshBroadcastLinkUrl(); // Update player position continuously
			}
		}
		if (broadcast['access_mode'] == 0 || broadcast['access_mode'] == 1) {
			b_api('broadcast/' +  vid + '/embedcode', {}, function(response) {
				if (response.data.result) {
					$("#broadcast-embed-code").val(response.data.result);
				}
			});
			html += '<p>Embed this broadcast</p>';
			html += '<input type="text" id="broadcast-embed-code" value="" />';
		}
		html += '</div>';
		if (broadcast['access_mode'] == 2) {
			html += '<div class="disclaimer">';
			html += '<p>This broadcast is set to private - sharing is only available to friends on Facebook</p>'
			html += '</div>';
		}
		html += '</div>';
		$container = $(html);
	
		// Load share targets
		b_api('sendto/broadcast/' + vid + '/info', {}, function(response) {
				var $info = $.xmlDOM(response.data.result).find("broadcast");
				try {
					iconBaseUrl = $info.find("targets").attr("iconBaseUrl");
				} catch (e) {}
				var vid = $info.attr('vid');
				var share_menu_contents = generateShareMenu($info.find("targets"), vid, 0);
				if ($(share_menu_contents).find(".group").length > 0) {
					$(".share-targets-container").append(share_menu_contents);
				}
			}, function(response) {
			}
		);
	
		// Load post services
		b_api('sendto/broadcast/' + vid + '/post_services', {}, function(response) {
				var services = response.data.result;
				// Do a quick count of the services
				var service_count = 0;
				for (s in services) {
					service_count++;
				}
				// Render the services
				if (service_count > 0 && $(".post-services-container").length > 0) {
					var contents = renderPostServicesContents(services);
					$(".post-services-container").html(contents);
				};
			}, function(response) {
				// Ignore errors
			}
		);
	
		// Align with right edge of player
		var boundary_right = $("#embedded-player").offset()['left'] + $("#embedded-player").width();
		var params = {
			'boundary-right': boundary_right,
			'width': 300
		};
		$target.b_overlay($container, params);
	});
}


// Legacy
function openShareConfigWindow(url) {
		share_config_window = window.open(url, 'BambuserShareConf', 'menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes,width=800,height=400');
		if (!share_config_window) {
			// Fall back to regular link.
			return true;
		}
		window.clearInterval(share_config_window_timer);
		share_config_window_timer = window.setInterval(checkShareConfigWindow, 300);
		return false;
}

function checkShareConfigWindow() {
	if (!share_config_window || share_config_window.closed) {
		window.clearInterval(share_config_window_timer);
		// Refresh share dialog.
		addShareDialog(share_recent_settings['vid'], share_recent_settings['target'], share_recent_settings['params']);
	}
}

function addShareDialog(vid, $target, params) {
	share_recent_settings = {'vid': vid, 'target': $target, 'params': params}
	b_api('sendto/broadcast/' + vid + '/info', {}, function(response) {
			var title = '<h3 class="b-share-headline">Share this broadcast to...</h3>';
			$info = $.xmlDOM(response.data.result).find("broadcast");
			vid = $info.attr('vid');
			var content = '<div class="b-share-menu">' + renderShareMenu($info.find("targets"), vid) + '</div>';
			if (typeof params === 'undefined') {
				params = {'top': -180, 'left': 160, 'width': 320, 'height': 300}
			}
			$target.b_overlay_legacy(params, title, content);
		}, function(response) {
			alert("Failed to load share menu. Please try again later.");
		}
	);
}

function renderShareMenu($element, vid) {
	response = '<ul>';
	$element.children().each(function() {
		if ($(this).is("target")) {
			b_log('target ' + $(this).attr('label') + ': ' + $(this).attr('disabled'));
			var disabled = ($(this).attr('disabled') === 'true' ? true : false);
			var links = Array();
			response += '<li class="share-target share-target-' + (disabled ? 'disabled' : 'enabled') + '" id="share-target-' + vid + '-' + $(this).attr('id') + '">';
			response += '<span class="share-target-vid" title="' + vid + '"></span>';
			response += '<span class="share-target-id" title="' + $(this).attr('id') + '"></span>';
			response += '<span class="share-target-type" title="' + $(this).attr('type') + '"></span>';
			response += '<span class="share-target-messageAllowed" title="' + $(this).attr('messageAllowed') + '"></span>';
			if ($(this).attr('type') == 'check') {
				response += '<input class="share-target-checkbox" type="checkbox"' + ($(this).attr('checked') == 'true' ? ' checked="checked"' : '') + ' /> ';
			}
			response += '<div class="share-target-body">';
			if ($(this).attr('icon') != undefined) {
				var label = '<img src="' + $(this).attr('icon') + '" alt="' + check_plain($(this).attr('label')) + '" title="' + check_plain($(this).attr('label')) + '" />';
			} else {
				var label = '<span class="no-icon">' + check_plain($(this).attr('label')) + '</span>';
			}
			if (!disabled) {
				label = '<span class="b-link"><span class="share-target-link">' + label + '</span></span>';
				var action_lbl = 'share';
				if ($(this).attr('type') == 'check' && $(this).attr('checked') == 'true') {
					action_lbl = 'unshare';
				}
				links[links.length] = '<span class="share-target-link b-link">' + action_lbl + '</span>';
			}
			if ($(this).attr('configureUrl') != undefined) {
				links[links.length] = '<a onClick="return openShareConfigWindow(\'' + $(this).attr('configureUrl') + '\');" target="_blank" href="' + $(this).attr('configureUrl') + '">configure</a>';
				if (disabled) {
					label = '<a onClick="return openShareConfigWindow(\'' + $(this).attr('configureUrl') + '\');" target="_blank" href="' + $(this).attr('configureUrl') + '">' + label + '</a>';
				}
			}
			if (links.length > 0) {
				response += '<span class="share-target-links">';
				for (l in links) {
					if (l > 0) {
						response += ' | ';
					}
					response += links[l];
				}
				response += '</span>';
			}
			response += label;
			if ($(this).attr('description') != undefined) {
				response += '<div class="desc">' + $(this).attr('description') + '</div>';
			}
			response += '</div>';

			response += '<div class="share-target-msg" style="display: none; ">';
			response += '<div class="b-btn"><div class="share-target-link b-btn-inner b-link">send</div></div>';
			response += '<div class="desc">Enter a message:</div>';
			response += '<input maxlength="100" size="20" type="text" value="' + ($(this).attr('defaultMessage') == undefined ? '' : check_plain($(this).attr('defaultMessage'))) + '" />';
			response += '</div>';

			response += '</li>';
		} else if ($(this).is("group")) {
			b_log('group ' + $(this).attr('label') + ': ' + $(this).attr('disabled'));
			var disabled = ($(this).attr('disabled') === 'true' ? true : false);
			response += '<li class="share-group share-group-' + (disabled ? 'disabled' : 'enabled') + '">';
			response += '<h5 class="share-group-title">' + check_plain($(this).attr('label')) + '</h5>';
			if ($(this).attr('description') != undefined) {
				response += '<div class="desc">' + $(this).attr('description') + '</div>';
			}
			if (!disabled) {
				response += renderShareMenu($(this), vid);
			}
			response += '</li>';
		}
	});
	response += '</ul>';
	return response;
}

