// userbox support stuff
// see editorial/includes/blog_boxes.php for the other side of all this

// Many thanks to http://www.quirksmode.org/ for info about MSIE event handling & cross-platform tips.

// This should be a const, but MSIE pukes if you try to use that keyword.
var REFRESH_PERIOD=20000;  // period (milliseconds) to rescan the userbox list and check for updates

var MAX_USERBOXES;        // gets defined by StartUserboxes()
var MAX_USERBOX_ITEMS;    // gets defined by StartUserboxes()
var READONLY=false;       // also gets set by StartUserboxes()

var menu_displayed=false;  // Is the popup menu currently being displayed?

var fetch_queue=[];   // list of userboxes waiting to be refreshed
var servicing_queue=false; // is the queue processor currently running?
var currently_servicing;
var glob_prefs; // working around a scoping problem, I guess

// 8/29/7 work around a ff weirdness
function NextElement(element) {
	while (element=element.nextSibling) {
		if (element.nodeType==1)
			return element;
	}
	return null;
}

// 7/3/7: I'm using a queue to handle the ajax-fetching for the boxes now, mainly to somewhat throttle
// the number of outstanding ajax requests that are happening, and also (more importantly, actually) to
// prevent redundant re-requests.  Before I added this, it was possible to build up quite a backlog of
// pending requests, just by quickly adding/removing a few boxes, due to the resizing.
function AddToFetchQueue(boxid) {
	// Check to see if this object is already in the queue
	//alert('queuing '+boxid);
	i=0;
	while (i<fetch_queue.length) {
		if (fetch_queue[i]==boxid) {
			// This box is already scheduled to be refreshed.  No need to do it twice.
			//alert(box.id+' already scheduled');

			return;
		} else
			i++;
	}
	
	// It's not in the queue yet, so..
	fetch_queue.push(boxid); // ..add it
	
	if (!servicing_queue) {
		// If the servicer is not yet running, then start it
		servicing_queue=true;
		setTimeout(FetchQueueServicer,1000); // 10/1/9 let other stuff (e.g. toc_reiterate) happen first
	}
}

function FetchQueueServicer() {
	if (!servicing_queue) {
		servicing_queue=true;
		//alert('Bug?  I thought I was not servicing, but apparently I really was.');
	}

	//alert(fetch_queue.length+' items in queue');
	if (fetch_queue.length>0) {
		currently_servicing=fetch_queue.shift(); // take an entry from the front
		//alert('currently servicing: '+currently_servicing);
		box=document.getElementById(currently_servicing);
		if (box) { // in case the item destructed while in queue
		
			//alert('blogbox_type:'+currently_servicing.substring(3,4)+', blogbox_info:'+ currently_servicing.substring(4));
		
			//alert('requesting '+currently_servicing.id);
	
			// make the ajax call to update the box
			// sajax way:
			//x_userbox_contents(box.id.substring(3,4),box.id.substring(4),Number(box.getAttribute('x-num-items')),FetchCallback);
			
			// prototype way:
			new Ajax.Request('/',
				{
					method: 'get',
					parameters: new Hash( { ajax_function: 'userbox_contents',
					                         blogbox_type: currently_servicing.substring(3,4),
					                         blogbox_info: currently_servicing.substring(4),
					                            num_items: box.getAttribute('x-num-items')
					                    } ),
					onSuccess: function(transport) { // Hm.. I've heard MSIE has memory leak bugs with anon functions... slightly worried about this.
						box=document.getElementById(currently_servicing);
						if (box) { // check for existence, in case it destructed while we were waiting
							// We have fetched the contents for this userbox
							//alert('request for '+currently_servicing.id+' returned');
		
							was_minimized=(box.getAttribute('x-minimized')=='y');
							
							// 4/8/8
							changed=0;
							if (box.innerHTML != transport.responseText) {
								box.innerHTML=transport.responseText;
								changed=1;
								// If it had been minimized, then re-hide the items
								if (was_minimized)
									MinimizeBox(box);
								// close/minimize wigets
								AddWidgetsToBox(box);
							}
							// so.. this looks bad. It was just an idea.
							//if (changed)
							//	new Effect.Highlight(box,{duration:2.0});

							
							currently_servicing=null;
						} else {
							// it's ok if this happens; the user is probably closing a lot of boxes really quickly
							//alert('box '+currently_servicing+' apparently died while awaiting reply from server');
						}
						
						// resume the servicer
						setTimeout(FetchQueueServicer,1);
					}
				}
			);
			//alert('I believe I sent the request for '+currently_servicing);
		} else {
			// it's ok if this happens; the user is probably closing a lot of boxes really quickly
			//alert('box '+currently_servicing+' apparently died while in queue');

			// resume the servicer
			setTimeout(FetchQueueServicer,1);
		}
	} else {
		//alert('fetch queue empty, stopping.');
		servicing_queue=false;
	}
}

function is_safari()
{
	// I feel filthy for doing this... but .. damn reflow bug.  No way to detect it except browser sniffing.
	return navigator.userAgent.indexOf('Safari') != -1;
}

function MakeDraggable() {
	//alert('telling scriptaculous to rescan userboxes (and make draggable)');
	// make the userboxes draggable, thanks to scriptaculous library
	// 8/31/7 made draggable by header only
	// Sortable.create('userboxes',{ghosting:false,tag:'li',constraint:'vertical',onUpdate:SaveUserBoxPrefs,handle:'userbox_head'});
	// 9/21/7 undid that
	Sortable.create('userboxes',{ghosting:false,tag:'li',constraint:'vertical',onUpdate:SaveUserBoxPrefs});
}

function AddClickEvent(element,handler) {
	// w3c version:
	//element.addEventListener('click',handler,false);
	
	// netscape3 version, limited capabilities (but good enough) and seems to work with more browsers
	element.onclick=handler;
	if (element.captureEvents)
		element.captureEvents(Event.CLICK);
}

function RemoveClickEvent(element,handler) {
	// w3c version:
	//element.removeEventListener('click',handler,false);
	
	// netscape3 version:
	element.onclick=null;
}

function CountUserboxes()
{
	// how many userboxes are currently being displayed?

	count=0;
	boxes=document.getElementById('userboxes');
	if (boxes) {
		box=boxes.firstChild;
		while (box) {
			//alert('DEBUG: (counting boxes).. '+count+': '+box);
			count++;
			box=box.nextSibling;
		}
	}
	return count;
}

function EnableAddButton()
{
	addbutton=document.getElementById('userbox_addbutton');
	if (addbutton) {
		addbutton.style.visibility='visible';
		addbutton.style.display='block';
	
		// make the button do something
		AddClickEvent(addbutton,ClickedAddButton);
	}
}
function DisableAddButton()
{
	addbutton=document.getElementById('userbox_addbutton');
	if (addbutton) {
		addbutton.style.visibility='hidden';
		addbutton.style.display='none';
	
		// make the button do something
		RemoveClickEvent(addbutton,ClickedAddButton);
	}
}

function StartUserboxes(maxboxes,maxitems,readonly)
{
	MAX_USERBOXES=maxboxes;
	MAX_USERBOX_ITEMS=maxitems;
	READONLY=readonly;

	if (!READONLY) {
		// Javascript is enabled (otherwise this wouldn't be happening!), so show the add button..
		
		if (CountUserboxes() < MAX_USERBOXES)
			EnableAddButton();
		//else
		//	alert('DEBUG: not showing add button because you already have enough boxes');
	
		// add close, minimize widgets
		boxes=document.getElementById('userboxes');
		if (boxes) {
			box=boxes.firstChild;
			while (box) {
				if (box.className == 'userbox') {
					AddWidgetsToBox(box);
				}
				box=box.nextSibling;
			}
		}
		
		MakeDraggable();
	}
	setTimeout(UserboxRefresher,REFRESH_PERIOD);
	
	// 6/24/9 delayed loading of video boxes
	if ($('ub_3recentvideos'))
		AddToFetchQueue('ub_3recentvideos');
	if ($('ub_3uservideos'))
		AddToFetchQueue('ub_3uservideos');

}


function AddWidgetsToBox(box)
{
	boxid=box.id.substring(3);
	header=box.firstChild;
	if (header) { // 10/9/7
		if (header.className == 'userbox_head') {
			// look for where the widgets go
			widgets=header.firstChild;
			while (widgets) {
				if (widgets.className == 'ubh_widgets') {
					// found where they belong in this userbox
					if (!is_safari()) {
						minimize_widget=document.createElement('img');
						minimize_widget.setAttribute('src','/editorial/images/userbox_minimize.gif'); // 8/2/7 gif for msie6
						AddClickEvent(minimize_widget,MinimizeButton);
						widgets.appendChild(minimize_widget);
					}
					close_widget=document.createElement('img');
					close_widget.setAttribute('src','/editorial/images/userbox_close.gif'); // 8/2/7 gif for msie6
					AddClickEvent(close_widget,UserBoxClose);
		
					widgets.appendChild(close_widget);
		
					break;
				}
				widgets=widgets.nextSibling;
			}
		}
	}
}

function HideMenu() {
	// MSIE chokes if you try to have a variable called "menu."  Good grief.
	menux=document.getElementById('userbox_menu');
	if (menux) {
		menux.style.visibility='hidden';
		menux.style.left='-42px'; // 10/4/7
		//menux.style.zIndex=-1; // 10/2/7 didn't help
		menu_displayed=false;
	
		if (body=document.getElementsByTagName('body')[0])
			RemoveClickEvent(body,HideMenu);
	}
	return true;
}

function ShowMenu() {
	// MSIE chokes if you try to have a variable called "menu."  Good grief.
	menux=document.getElementById('userbox_menu');

	if (menux) {
		menux.style.left='-202px'; // 10/4/7
		menux.style.visibility='visible';
		//menux.style.zIndex=2; // 10/2/7 didn't help
		menu_displayed=true;
	}
	
	// Add the handler that removes the menu, but not right away.  For some reason, if I add it
	// right _now_, it responds to the CURRENT(!) event, even though it wasn't a handler when
	// the event happened.
	setTimeout(AddClickAnywhere,0);
}

function AddClickAnywhere() {
	body=document.getElementsByTagName('body')[0];
	AddClickEvent(body,HideMenu);
}

function ClickedAddButton()
{
	if (!menu_displayed)
		ShowMenu();
	return true;
}

function UserboxRefresher()
{
	// This looks at all the userboxes, and decides which ones need to be refreshed
	// boxes to be refreshed are now added to a queue, instead of being refreshed _here_
	
	boxes=document.getElementById('userboxes');
	if (boxes) {
		//boxes=boxes.childNodes;
		box=boxes.firstChild;
		while (box) {
			refresh=box.getAttribute('x-refresh-period');  // the period
			if (refresh) {
				countdown=box.getAttribute('x-next-refresh');  // time remaining in this cycle
				if (countdown == null)
					countdown=refresh;
				countdown = Number(countdown) - REFRESH_PERIOD;
				if (countdown<=0) {
					AddToFetchQueue(box.id);
					// 2/24/9 if we added it to queue, then stop even considering further
					// refreshes of this box.
					box.setAttribute('x-next-refresh',null);
					// 8/4/9 undid this
					//box.setAttribute('x-refresh-period',null);
				} else {
					box.setAttribute('x-next-refresh',countdown);
				}
			}
			box=box.nextSibling;
		}
	}
	setTimeout(UserboxRefresher,REFRESH_PERIOD);
}


function SaveUserBoxPrefs()
{
	if (!READONLY) {
		// Update the server with box preferences.
	
		// build the list (with implicit order) of the boxes that are displayed
		boxes = document.getElementById('userboxes');
		
		//if (!boxes)
		//	alert('SaveUserBoxPrefs:did not find userboxes');
			
		box=boxes.firstChild;
		glob_prefs='';
		while(box) {
			glob_prefs = glob_prefs + box.id + ':' + (box.getAttribute('x-minimized')=='y'?'1':'0')+ ',';
			box=box.nextSibling;
		}
		
		// make the ajax call to save it. See Save_Userbox_Prefs() in blog_boxes.php
		if (glob_prefs.length==0) {
			//alert('SaveUserBoxPrefs: empty prefs');
			glob_prefs='none';
		}
		//alert('sending these prefs:'+glob_prefs);
		new Ajax.Request('/',
			{
				method: 'get',
				parameters: new Hash( { ajax_function: 'save_userbox_prefs', prefs: glob_prefs } )
				// no callback needed
			}
		);
	
	}
}


function UserBoxClose(e) {
	// This is called when they click on the box-closing widget (X)
	
	// for w3c browsers, e is the event
	if (!e) {
		// special case for MSIE
		e = window.event;
	}
	if (e.target) {
		/// w3c browsers
		targ = e.target;
	} else if (e.srcElement) {
		// special case for MSIE
		targ = e.srcElement;
	}

	box=targ.parentNode.parentNode.parentNode;
	id=box.id.substring(3);  // to find the menu item, below..

	// remove the box
	list=document.getElementById('userboxes');
	list.removeChild(box);

	// enable that box on the menu, so they can re-add it
	menuitem=document.getElementById('bbmenuitem_'+id);
	if (menuitem) {
		//menuitem.setAttribute('style','display:block');
		menuitem.style.display='block';
	
		// Only save prefs if you have a menu.  If you don't have menu, it's because you weren't logged in anyway.
		// save prefs so that editorial/includes/blog_boxes.php doesn't display it next time
		SaveUserBoxPrefs();
	}

	MakeDraggable(); // Scriptaculous needs to rescan userboxes

	RecomputeSizes(); // the remaining boxes probably need to expand
	
	// They were at max, but now they are not
	if (CountUserboxes() == MAX_USERBOXES-1)
		EnableAddButton();
}


function MinimizeButton(e) {
	// called by clicking on the .. uh .. minimize(?) widget
	// hide/display the list part of the box, and flip the minimized attribute

	// for w3c browsers, e is the event
	if (!e) {
		// special case for MSIE
		e = window.event;
	}
	if (e.target) {
		/// w3c browsers
		targ = e.target;
	} else if (e.srcElement) {
		// special case for MSIE
		targ = e.srcElement;
	}
	
	box=targ.parentNode.parentNode.parentNode;
	if (box.getAttribute('x-minimized')=='y')
		RestoreBox(box);
	else
		MinimizeBox(box);
	
	SaveUserBoxPrefs();
}

function MinimizeBox(box)
{
	id=box.id.substring(3);
	items_ul=document.getElementById('ubitems_'+id);
	
	items_ul.style.display='none';
	items_ul.style.visibility='hidden';
	box.setAttribute('x-minimized','y');
}
function RestoreBox(box)
{
	id=box.id.substring(3);
	items_ul=document.getElementById('ubitems_'+id);
	
	items_ul.style.display='block';
	items_ul.style.visibility='visible';
	box.setAttribute('x-minimized','n');
}

function UserBoxAdd(type,info) {
	// add a userbox, called by making a menu selection

	// take it off the menu
	document.getElementById('bbmenuitem_'+type+info).style.display='none';

	// create it
	newitem=document.createElement('li');
	newitem.id='ub_'+type+info;
	newitem.className='userbox';
	
	newitem.innerHTML='loading...';
	newitem.setAttribute('x-num-items',0);
	
	// put the new box into <ul id='userboxes'>
	list=document.getElementById('userboxes');
	items=list.childNodes;
	if (items.length < 1) {
		// no items to insert before
		list.appendChild(newitem);
	} else {
		// insert at beginning of list
		list.insertBefore(newitem,items[0]);
	}

	// They have reached max
	if (CountUserboxes() == MAX_USERBOXES)
		DisableAddButton();

	MakeDraggable(); // Scriptaculous needs to rescan userboxes
	
	// Save preferences so that editorial/includes/blog_boxes.php will display it every time	
	SaveUserBoxPrefs();

	// fetch the shrinkable/nonshrinkable status
	new Ajax.Request('/',
		{
			method: 'get',
			parameters: new Hash( { ajax_function: 'userbox_noshrink',
									blogbox_type: type,
									blogbox_info: info
								   } ),
			onSuccess: function(transport) {
				newitem.setAttribute('x-noshrink',transport.responseText);
				
				if (transport.responseText=='y') {
					// if it's not resizable, then fetch the size
					
					new Ajax.Request('/',
						{
							method: 'get',
							parameters: new Hash( { ajax_function: 'userbox_size',
													blogbox_type: type,
													blogbox_info: info
												   } ),
							onSuccess: function(transport) {
								newitem.setAttribute('x-num-items',Number(transport.responseText));
								
								// fetch the contents
								AddToFetchQueue(newitem.id);
								RecomputeSizes(); // the remaining boxes will need to shrink
							}
						}
					);
				} else {
					// fetch the contents
					AddToFetchQueue(newitem.id);
					RecomputeSizes(); // the remaining boxes will need to shrink
				}
			}
		}
	);

	// fetch the refresh-period
	new Ajax.Request('/',
		{
			method: 'get',
			parameters: new Hash( { ajax_function: 'userbox_refresh_period',
									blogbox_type: type,
									blogbox_info: info
								   } ),
			onSuccess: function(transport) {
				newitem.setAttribute('x-refresh-period',Number(transport.responseText));
			}
		}
	);
}

function ResizeBox(box,targetitems)
{
	// If we're reducing the number of items, then do a quick chop-off, so they don't have to wait for the refresh.

	id=box.id.substring(3);
	items_found=0;
	ul_items=document.getElementById('ubitems_'+id);
	if (ul_items) {
		// can't have a variable called "item" in msie
		xitem=ul_items.firstChild;
		
		while (xitem) {
			nextitem=xitem.nextSibling; // get it now, before you delete it
			
			if (items_found < targetitems)
				items_found++;
			else {
				// excess -- chop it off
				ul_items.removeChild(xitem);
			}
			xitem=nextitem;
		}
	}
	
	AddToFetchQueue(box.id); // in cases where we shrunk (rather than expanded) the box, this should actually only be required for the threaded displays
}

function RecomputeSizes()
{
	//alert('recomputesizes');
	// Go through the userbox list, recomputing what the x-num-items attributes should be.
	// For any where it happens to be wrong (and usually, that'll be all or none), refresh them
	
	items_to_go=MAX_USERBOX_ITEMS;
	
	boxes=document.getElementById('userboxes');
	if (boxes) {
	
		resizable_boxes=0;
		box=boxes.firstChild;
		debugtotal=0;
		while (box) {
			debugtotal++;
			if (box.nodeType==1) { // firefox sometimes has non-element "#text" thingies
				if (box.getAttribute('x-noshrink')=='y') {
					// deduct this box's size from totals
					items_to_go -= Number(box.getAttribute('x-num-items'));
				} else {
					resizable_boxes++;
				}
			}
			box=box.nextSibling;
		}

		boxes=document.getElementById('userboxes'); // shouldn't have to do this
		if (boxes) {
			box=boxes.firstChild;
			while (box) {
				if (box.nodeType==1) {
					// This code look familiar?  It's pretty much a copy of the sizing code in blog_boxes.php
					//alert('about to look at noshrink attr');
					noshrink=box.getAttribute('x-noshrink');
					//alert('ok');
					if (noshrink=='n') {
						items_to_show=Math.floor(Number(items_to_go/resizable_boxes));
						items_to_go -= items_to_show;
						resizable_boxes--;
						
						if (items_to_show != Number(box.getAttribute('x-num-items'))) {
							//alert(box.id+' must resize');
							box.setAttribute('x-num-items',items_to_show);
							ResizeBox(box,items_to_show);
							
							// 8/29/7: at this point, we should just be able to just loop to the next element,
							// but firefox is getting confused.  So.. my work-around is that if we end up
							// resizing anything, then ABORT the loop and recall.  Hopefully Firefox's
							// confusion about the DOM will have recovered by then.  (and indeed, this appears to work.)
							
							setTimeout(RecomputeSizes,1)
							return;
						}
					}
				}
				box=box.nextSibling;
			}
		}
	}
}