function fbhoroscopes_init(api_key, friendbox_selector, interps, friends_list) {
	var elem_height    = 52;
	var per_page       = 5;
	var page_height    = elem_height * per_page;
	jQuery.each(interps, function(i,v){
		v.interp60 = ellipsis(v.interp, 60);
		v.interp260 = ellipsis(v.interp, 260);
	});

	// we're doing a lot here so we pre-cache anything we can
	var elems = {
		heading:          $('.heading',                 friendbox_selector),
		subheading:       $('.sub-heading',             friendbox_selector),
		disclose:         $('.heading .disclose',       friendbox_selector),
		hider:            $(".hider",                   friendbox_selector),
		buttons:          $('.buttons',                 friendbox_selector),
		friend_container: $('.friend-container',        friendbox_selector),
		fbicon:           $('.fb-icon',                 friendbox_selector),
		friend_list:      $(".friend-list",             friendbox_selector),
		main_viewport:    $(".friend-viewport",         friendbox_selector),
		cur_viewport:     $(".friend-viewport-current", friendbox_selector),
		search_results:   $(".buttons .search-results", friendbox_selector),
		search_box:       $(".buttons .searchbox",      friendbox_selector),
		button_up:        $(".buttons a.scroll-up",     friendbox_selector),
		button_down:      $(".buttons a.scroll-down",   friendbox_selector)
	};
	var fb_uid;

	FB.init(api_key, '/facebook/xd_receiver.htm');
	FB.ensureInit(function () {
		if (friends_list) { // friends list was passed in
			fbhoroscope_stats('logged_in');
			populate_friends(friends_list);
		} else {
			FB.Connect.get_status().waitUntilReady(function(status) {
				switch(status) {
					case FB.ConnectState.connected:
                        fb_uid = FB.Connect.get_loggedInUser();
						fbhoroscope_stats('logged_in');
						display_logged_in();
						break;
					default:
                        fb_uid = null;
						fbhoroscope_stats('logged_out');
						display_logged_out();
				}
			});
		}
	});

	function attempt_login() {
		FB.Connect.requireSession(function(){
            FB.Connect.showPermissionDialog( "friends_birthday", permsCallback);
		}, function(){
		}, true);
	}
    
    function permsCallback(perms){
        display_logged_in();
        fbhoroscope_stats('logged_in');
    }

	function display_logged_out() {
		elems.subheading.empty().html("<a></a><span>to see your friends' horoscopes</span>").show();
		$('a', elems.subheading).text('Facebook Connect').addClass('login-link').attr('href','#').click( function(e){ attempt_login(); e.preventDefault(); });
		elems.buttons.hide();
		elems.friend_container.hide();
		elems.fbicon.removeClass('fb-icon-in').addClass('fb-icon-out');
		$('#facebook-user-picture').empty().remove();
		$("#dhod_welcome_message").removeClass('has-fb-picture');
	}

	function month_offset(month, offset) {
		// add 11 (to make january=0), subtract offset, mod by 12 to get a number between 0-11, add 1 to normalize that back to jan=1
		return ((month + 11 - offset) % 12) + 1;
	}


	function find_sign(bday) {
		if (!bday) {
			return {number:0, name:'General'};
		}

		
		if (/^\d\d\/\d\d$/.test(bday)) {
			bday += "/2000";
		}

		var sign_names = ['General', "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"];
		// this is the day of each month that the sign changes over, jan 20, feb 19, etc
		var thresholds = [ 0, 20, 19, 21, 20, 21, 21, 23, 23, 23, 23, 22, 22 ];

		var d = new Date( Date.parse(bday) );
		if (! d) {
			return {number:0, name:'General'};
		}
		var month = d.getMonth()+1;
		var day   = d.getDate();

		var when = thresholds[month];
		var sign_int = (day < when) ? month_offset(month, 3) : month_offset(month, 2);

		return {number:sign_int, name:sign_names[sign_int]};
	}

	function ellipsis(txt, len) {
		if (txt.length < len) {
			return txt;
		}
		return txt.substring(0, txt.indexOf(' ', len)) + "...";
	}

	function share_with_friend(friend, sign, interp) {
		var the_url = 'http://www.tarot.com/go2/site/fb-hs-'+sign.name.toLowerCase();
		if (sign.name.toLowerCase() == 'general') {
			the_url += '-horo';
		}
		var todays_date = new Date();
		var months = ['Jan','Feb','Mar','Apr','May','June','July','Aug','Sep','Oct','Nov','Dec'];
		var days   = ['Sun','Mon','Tues','Wed','Thu','Fri','Sat'];
		var date_string = days[ todays_date.getDay() ] + ", " +
			months[ todays_date.getMonth() ] + " " +
			todays_date.getDate() + ", " +
			todays_date.getFullYear();
		var attachment = {
			title: 'Your ' + sign.name + ' horoscope for today from Tarot.com',
			media: [{
				type: 'image',
				src:  'http://gfx.tarot.com/images/astrology/sun-signs/90x90/' + sign.number + '.gif',
				href: the_url
			}],
			description: interp,
			properties: {Sign: sign.name },
			name: sign.name+ ' Horoscope: ' + date_string,
			href: the_url,
			caption: 'www.tarot.com'
		};
		var action_links = [
			{
				text: 'View your horoscope',
				href: the_url
			}
		];
		FB.Connect.streamPublish(attachment.title, attachment, action_links, friend.uid, '', function(){
			// this is a no-op for now
		});

	}

	function mk_friend(friend) {
		var friend_entry = $("<div></div>").addClass('friend');
		var name_short = friend.first_name + ' ' + friend.last_name.substr(0,1) + '.';
		var sign = find_sign(friend.birthday_date);
		var x_pic = '<div class="no-profile-img"></div>';
		if (friend.pic_square) {
			x_pic = '<img src="'+ friend.pic_square +'"/>';
		}
		var x_name  = "<span class='name'>"+ name_short +"</span>";
		var x_fname = "<span class='full-name' style='display:none;'>"+ friend.first_name +" "+ friend.last_name +"</span>";
		var x_sign  = "<span class='sign'>"+ sign.name +"</span>";
		var x_icon  = "<img src='http://gfx.tarot.com/images/astrology/sun-signs/50x50/"+ sign.number +".gif'/>";
		var x_short = "<div class='short'>"+ interps[sign.number].interp60 +"</div>";
		var x_full  = "<div class='full'>"+ interps[sign.number].interp +"</div>";
		var x_share = "<a href='#' class='share-link'>Post to "+ friend.first_name +"'s wall</a>";
		var x_shct  = "<div class='share-container'><div class='share'><div class='share-icon'></div>"+ x_share +"</div></div>";

		friend_entry.html(
			"<div class='pic'>"+ x_pic +"</div>" +
			"<div class='info'>"+ x_name + x_fname + x_sign +"</div>" +
			"<div class='icon'>"+ x_icon +"</div>" +
			"<div class='horo'>"+ x_short + x_full + x_shct +"</div>" +
			'<div class="more"><a href="#" class="go-more" title="Show full horoscope">More</a></div>'+
			'<div style="clear:both;"></div>'
		);
		friend_entry.hover(function(){ $(this).addClass('friend-hover'); }, function(){	$(this).removeClass('friend-hover'); });
		elems.friend_list.append(friend_entry);

		friend_entry.find('a.go-more').click(function(e){
			var more = $(this);
			var f = more.parents('.friend'); // we can't use friend_entry here because post-cloning friend_entry refers to the wrong thing!
			if (f.hasClass('expanded')) {
				more.text('More').attr('title','Show full horoscope');
				$('.horo .short', f).animate({height:'show',opacity:'show'}, 250);
				$('.horo .full', f).animate({height:'hide',opacity:'hide'}, 250);
				if ($.browser.msie && $.browser.version <= 6) { // fixes a rendering bug in ie6
					$('.friend', elems.cur_viewport).css('display','none').css('display', 'block');
				}
				f.removeClass('expanded');
			} else {
				unexpand(function(){
					more.text('Less').attr('title','Hide full horoscope');
					$('.horo .short', f).animate({height:'hide',opacity:'hide'}, 250);
					$('.horo .full', f).animate({height:'show',opacity:'show'}, 250);
					if ($.browser.msie && $.browser.version <= 6) {
						$('.friend', elems.cur_viewport).css('display','none').css('display', 'block');
					}
					f.addClass('expanded');
				}, false);
			}
			//setTimeout(function(){ alert(elems.friend_container.height()) }, 500);
			e.preventDefault();
		});
		friend_entry.find('a.share-link').click(function(e){
			share_with_friend(friend, sign, interps[sign.number].interp260 );
			e.preventDefault();
		});
	}

	function unexpand(cb, wait_for_anim) {
		var expanded = $('.friend.expanded', elems.cur_viewport);
		if (expanded.length == 0) {
			cb();
		} else {
			$('.friend.expanded a.go-more',    elems.cur_viewport).text('More').attr('title','Show full horoscope');
			$('.friend.expanded .horo .short', elems.cur_viewport).animate({height:'show',opacity:'show'}, 250);
			if (wait_for_anim) {
				$('.friend.expanded .horo .full',  elems.cur_viewport).animate({height:'hide',opacity:'hide'}, 250, cb);
			} else {
				$('.friend.expanded .horo .full',  elems.cur_viewport).animate({height:'hide',opacity:'hide'}, 250);
			}
			expanded.removeClass('expanded');
			if (!wait_for_anim) { cb(); }
		}
	}

	function display_logged_in() {
		FB_RequireFeatures(["Api", "Connect", "Base", "Common"], function(){
                        var fb_user_pic = fb_uid ? 'http://graph.facebook.com/'+fb_uid+'/picture' : 'http://gfx.tarot.com/images/affiliates/facebook/anonymous-user-50x50.gif';
                        elems.subheading.html('<i>Loading your friends...</i>').show();
			if ($('#facebook-user-picture').length == 0) {
                $("<div>")
                .attr('id', 'facebook-user-picture')
                .append(
                    $("<img>").attr({'src': fb_user_pic,width:50,height:50})
                )
                .append(
                    $("<img>").attr({'src': 'http://gfx.tarot.com/images/astrology/daily/fb-pic-ico-14x14.png',width:14,height:14}).attr({'id': 'fb-img-icon'})
                )
                .append(
                    $("<a></a>").attr('href','#').text('Disconnect').click(function(e){
                        e.preventDefault();
                        e.stopPropagation();
                        FB.Connect.logout(function(){
                            display_logged_out();
                            window.location.reload(true);
                        });
                    })
                )
                .prependTo('#dhod_welcome_message');
                $("#dhod_welcome_message").addClass('has-fb-picture');
			} else {
                $('#facebook-user-picture img').attr('src', 'http://graph.facebook.com/'+fb_uid+'/picture');
                $("#dhod_welcome_message").addClass('has-fb-picture');
			}
			FB.Facebook.apiClient.friends_get(null, function(friends_ids) {
				if (!friends_ids) {
					display_logged_out();
					return;
				}
				// this hack is required for Safari and IE6 since facebook programmers can't write a non-broken json parser
				FB.JSON.parse=function(text,reviver){
					var j;
					function walk(a,c) {
						var b,d,e=a[c];
						if(e && typeof e ==='object') {
							for(b in e) {
								if(Object.hasOwnProperty.call(e,b)) {
									d=walk(e,b);
									if(d!==undefined){
										e[b]=d;
									} else {
										delete e[b];
									}
								}
							}
						}
						return reviver.call(a,c,e);
					}
					var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
					cx.lastIndex=0;
					if(cx.test(text)){
						text=text.replace(cx,function(a){ return '\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4); });
					}

					var replacedText = text.replace(/\\\'/g, '\'');
					replacedText = replacedText.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,'');
					if(/^[\],:{}\s]*$/.test(replacedText))
					{
						j=eval('('+text+')');
						return typeof reviver==='function'?walk({'':j},''):j;
					}
					throw new SyntaxError('JSON.parse');
				};

				FB.Facebook.apiClient.users_getInfo(friends_ids, ['last_name', 'first_name', 'birthday_date', 'pic_square'], function(friends) {
					if (!friends) {
						display_logged_out();
						return;
					}
					populate_friends(friends);
				});
			});
		});
	}

	function populate_friends(friends) {
		friends = friends.sort(function(a,b){
			var _a = (a.first_name + " " + a.last_name).toLowerCase();
			var _b = (b.first_name + " " + b.last_name).toLowerCase();
			if (_a>_b) return 1;
			if (_a<_b) return -1;
			return 0;
		});
		jQuery.each(friends,function(i,friend) {
			mk_friend(friend);
		});
		elems.subheading.hide();
		elems.buttons.show();
		elems.friend_container.show();
		elems.fbicon.removeClass('.fb-icon-out').addClass('.fb-icon-in');
		elems.main_viewport.css({height:page_height,overflow:'hidden'});
		elems.friend_list.css({top:'0px'});
		fix_visibility();
		generate_curpage();
	}

	function generate_curpage() {
		elems.cur_viewport.html(''); // clear curpage div
		var top_offset = -parseInt( elems.friend_list.css('top') );
		var first_idx = Math.floor( top_offset / elem_height ) - 1;
		$('.friend:not(.no-show):gt('+first_idx+'):lt('+per_page+')', elems.friend_list).each(function(i,v){
			$(this).clone(true).appendTo( elems.cur_viewport );
		});
		elems.main_viewport.css({display:'none'});
		elems.cur_viewport.css({display:'block'});
	}
	function hide_curpage() {
		elems.main_viewport.css({display:'block'});
		elems.cur_viewport.css({display:'none'});
	}

	function fix_visibility(){
		$(".friend.no-show", elems.friend_list).css({display:'none'});
		$(".friend:not(.no-show)", elems.friend_list).css({display:'block'});
		fix_buttons();
	}

	function fix_buttons(){
		var pos = -parseInt( elems.friend_list.css('top') );
		if (pos <= 0) {
			elems.button_up.addClass('scroll-up-disabled');
		} else {
			elems.button_up.removeClass('scroll-up-disabled');
		}
		var total_height = $('.friend:not(.no-show)', elems.friend_list).length * elem_height;
		var max_pos = total_height - page_height;
		if (pos >= max_pos)	{
			elems.button_down.addClass('scroll-down-disabled');
		} else {
			elems.button_down.removeClass('scroll-down-disabled');
		}
	}

	function do_search(filter_string) {
		hide_curpage();
		elems.friend_list.css({top:'0px'});
		elems.button_up.addClass('scroll-up-disabled');
		if (filter_string == "") {
			elems.search_results.text('');
			$(".friend", elems.friend_list).removeClass('no-show');
			fix_visibility();
			generate_curpage();
			return;
		}
		$(".friend", elems.friend_list).each(function(){
			if ( $('.info .full-name', this).html().toLowerCase().indexOf(filter_string.toLowerCase()) > -1) {
				$(this).removeClass('no-show');
			} else {
				$(this).addClass('no-show');
			}
		});
		var showing_friends = $(".friend:not(.no-show)", elems.friend_list).length;
		if (showing_friends == 0) {
			elems.search_results.text('No friends match "'+filter_string+'"');
		} else if (showing_friends == 1) {
			elems.search_results.text('1 friend matches "'+filter_string+'"');
		} else {
			elems.search_results.text(showing_friends+' friends match "'+filter_string+'"');
		}

		fix_visibility();
		generate_curpage();
	}

	var animating = false;
	elems.button_down.click(function(e){
		unexpand(function(){
			if (animating) {
				return;
			}
			var total_height = $('.friend:not(.no-show)', elems.friend_list).length * elem_height;

			if (total_height < page_height) {
				return;
			}
			var cur_pos = -parseInt( elems.friend_list.css('top') );
			var new_pos = cur_pos + page_height;
			var max_pos = total_height - page_height;
			if (new_pos > max_pos) {
				new_pos = max_pos;
			}

			if (new_pos == cur_pos) {
				return;
			}

			elems.button_up.removeClass('scroll-up-disabled');
			animating = true;
			hide_curpage();
			elems.friend_list.animate({top:-new_pos+"px"}, {
				duration: 250,
				complete: function(){
					generate_curpage();
					animating = false;
					fix_buttons();
				}
			});
		}, true);
		e.preventDefault();
	});
	elems.button_up.click(function(e){
		unexpand(function(){
			if (animating) {
				return;
			}
			if ($('.friend:not(.no-show)', elems.friend_list).length*elem_height < page_height ) {
				return;
			}
			var cur_pos = -parseInt( elems.friend_list.css('top') );
			var new_pos = cur_pos - page_height;
			if (new_pos < 0) {
				new_pos = 0;
			}
			if (new_pos == cur_pos) {
				return;
			}

			elems.button_down.removeClass('scroll-down-disabled');
			animating = true;
			hide_curpage();
			elems.friend_list.animate({top:-new_pos+"px"}, {
				duration: 250,
				complete: function(){
					generate_curpage();
					animating = false;
					fix_buttons();
				}
			});
		}, true);
		e.preventDefault();
	});

	elems.heading.click(function(){
		if (elems.disclose.hasClass('disclose-hidden')) {
			elems.hider.animate({height:'show',opacity:'show'}, 250);
			elems.disclose.removeClass('disclose-hidden');
		} else {
			elems.hider.animate({height:'hide',opacity:'hide'}, 250);
			elems.disclose.addClass('disclose-hidden');
		}
	});

	var search_timer;
	var last_search; // optimization: cache the last search, don't re-search if its the same

	var search_placeholder_text = 'Search Friends...';

	if (Modernizr.inputtypes.search) {
		elems.search_box.bind('search', function(){
			var filter_string = $(this).val();
			if (filter_string == last_search) {
				return;
			}
			last_search = filter_string;
			elems.search_results.text('Searching...');
			do_search(filter_string);
		});
	} else {
		elems.search_box.keyup(function(e){
			var filter_string = $(this).val();
			if (filter_string == last_search || filter_string == search_placeholder_text) {
				return;
			}
			last_search = filter_string;
			elems.search_results.text('Searching...');
			
			if (elems.search_clear) {
				if (filter_string == "") {
					elems.search_clear.hide();	
				} else {
					elems.search_clear.show();	
				}
			}
			if ($.browser.msie && $.browser.version < 7.0) {
				//ie6 does this part way slowly so we add in a 100ms delay after typing to start the search
				if (search_timer) {
					window.clearTimeout(search_timer);
				}
				search_timer = window.setTimeout(function(){
					do_search(filter_string);
				}, 100);
			}
			else {
				do_search(filter_string);
			}
		});

	}

	if (Modernizr.input.placeholder) {
		elems.search_box.attr('placeholder', search_placeholder_text);
	} else {
		elems.buttons.append("<a class='search-clear' href='#'></a>");
		elems.search_clear = $('a.search-clear', elems.buttons).hide();
		elems.search_clear.click(function(e){
			elems.search_clear.hide();
			elems.search_box.val('');
			elems.search_box.blur();
			do_search('');
			e.preventDefault();
		});
		elems.search_box.focus(function(){
			if (elems.search_box.val() == search_placeholder_text) {
				elems.search_box.val('');
				elems.search_box.removeClass('search-box-placeholder');
			}
		});
		elems.search_box.blur(function(){
			if (elems.search_box.val() == '') {
				elems.search_box.val(search_placeholder_text);
				elems.search_box.addClass('search-box-placeholder');
			}
		});
		elems.search_box.blur();
	}
}

function fbhoroscopes_ie6login(friendbox_selector) {
	var elems = {
		heading:          $('.heading',                 friendbox_selector),
		disclose:         $('.heading .disclose',       friendbox_selector),
		hider:            $(".hider",                   friendbox_selector),
		subheading:       $('.sub-heading',             friendbox_selector),
		friend_container: $('.friend-container',        friendbox_selector)
	};
	elems.subheading.show();
	elems.friend_container.hide();

	elems.heading.click(function(){
		if (elems.disclose.hasClass('disclose-hidden')) {
			elems.hider.animate({height:'show',opacity:'show'}, 250);
			elems.disclose.removeClass('disclose-hidden');
		} else {
			elems.hider.animate({height:'hide',opacity:'hide'}, 250);
			elems.disclose.addClass('disclose-hidden');
		}
	});
	fbhoroscope_stats('logged_out');

}

function fbhoroscope_stats(mode) {
	jQuery.ajax({
		type: 'POST',
		url: '/facebook/stats.php',
		data: {mode:mode},
		dataType: 'json'
	});
}

