var hasSetUpMarkIndex = false, searchfield;
function setUpMarkIndex(ev) {
	function searchonload() {
		//onload, if there is a location hash, put it in the search field, and search for it
		var searchstr, blinkinterval, numblinks = 0, notifypanel;
		if( location.hash ) {
			searchstr = location.hash.replace( /^#/, '' );
			if( document.getElementById(searchstr) || document.anchors[searchstr] || ( document.querySelector && searchstr.match(/^[a-z][a-z0-9-_]+$/i) && document.querySelector('a[name="'+searchstr+'"]') ) ) { return; }
			if( searchstr ) {
				try {
					searchstr = decodeURIComponent(searchstr);
				} catch(e) {
					searchstr = unescape(searchstr); //also copes with %FB vs %C3%BB
				}
				searchfield.value = searchstr;
				searchfield.onchange();
				notifypanel = document.body.appendChild(document.createElement('div'));
				notifypanel.className = 'overnotice';
				notifypanel.id = 'overnotice';
				notifypanel.appendChild(document.createElement('a')).href = '#';
				notifypanel.lastChild.appendChild(document.createTextNode('X'));
				notifypanel.lastChild.title = 'Close notification';
				notifypanel.lastChild.onclick = function () {
					this.parentNode.parentNode.removeChild(this.parentNode);
					clearInterval(blinkinterval);
					return false;
				};
				notifypanel.appendChild(document.createTextNode('Automatic search mode. Clear the search text below to resume normal viewing.'));
				blinkinterval = setInterval(function () {
					numblinks++;
					notifypanel.className = 'overnotice' + ( ( numblinks % 2 ) ? ' flashnotice' : '' );
					if( numblinks == 10 ) {
						clearInterval(blinkinterval);
					}
				},500);
			}
		}
	}
	function stopanimation() {
		if( scrollinterval ) {
			clearInterval(scrollinterval);
		}
	}
	if( hasSetUpMarkIndex ) {
		//already ran on DOMContentLoaded, don't recreate everything
		if( searchfield ) { searchonload(); }
		return;
	}
	hasSetUpMarkIndex = true;
	if( !document.body || !document.getElementsByTagName || !document.createElement || !document.childNodes || screen.width < 1000 ) {
		return;
	}
	var lastHit = null, newli, newlink, scrollinterval, grabinterval = null, step = 0,
	    allmarks = document.getElementsByTagName('div')[0].getElementsByTagName('a'),
	    ctnul = document.createElement('ul');
	var indexhead = document.body.appendChild(document.createElement('h2'));
	indexhead.appendChild(document.createTextNode('Index'));
	indexhead.appendChild(document.createElement('a'));
	indexhead.lastChild.appendChild(document.createTextNode('<'));
	indexhead.lastChild.title = 'Hide the index of waterfalls';
	indexhead.lastChild.href = '#';
	indexhead.lastChild.onclick = function () {
		this.firstChild.nodeValue = document.documentElement.className ? '>' : '<';
		this.title = ( document.documentElement.className ? 'Show' : 'Hide' ) + ' the index of waterfalls';
		if( window.opera && !document.documentElement.className ) {
			ctnul.style.display = 'none';
			setTimeout(function () {
				//Opera bug puts fixed elements behind the scrollbar when it appears - reflow in timeout normally forces scrollbar to appear first so the bug does not show up
				ctnul.style.display = '';
			},10);
		}
		document.documentElement.className = document.documentElement.className ? '' : 'withscriptedlist';
		return false;
	};
	indexhead.className = 'indexhead';
	for( var i = 0, onelink, numlinks = allmarks.length; i < numlinks; i++ ) {
		onelink = allmarks[i];
		newli = document.createElement('li');
		newlink = document.createElement('a');
		newlink.relmark = onelink;
		newlink.href = onelink.href;
		newlink.onfocus = function () { this.onmouseover(); };
		newlink.onblur = function () { this.onmouseout(); }; //Opera 10 fires this too early - immediately after onfocus - no workaround
		newlink.onmouseover = function () {
			stopanimation();
			this.relmark.className = 'simfocus';
			//get viewport scrolling
			var scrollx = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
			var scrolly = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
			//get viewport size
			var winwidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0;
			var winheight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0;
			var paneloffset = ctnul.offsetWidth;
			//get position of the mark
			var oElement = this.relmark;
			var markwidth = 50 + oElement.firstChild.offsetWidth; //50 allows for tooltip offset, and the viewport scrollbar width
			for( var posx = 0, posy = 0; oElement; oElement = oElement.offsetParent ) {
				posx += oElement.offsetLeft;
				posy += oElement.offsetTop;
			}
			//work out if mark is visible
			//if mark is not visible, work out what position to scroll to in order to get it into view - preferably near the middleish of the viewport
			var needstomove = [0,0];
			if( posx < scrollx + paneloffset + 20 ) { //scrolled off the left of the viewport
				needstomove[0] = posx - ( paneloffset + Math.round( ( winwidth - paneloffset ) / 3 ) ); //about 1/3 of the way from the left side of the viewport
				if( posx + markwidth > needstomove[0] + winwidth ) {
					//scrolling to that position will cut off the text, so just scroll to the left edge instead of 1/3 of the way over (it might still cut off for long text, but it's better than nothing)
					needstomove[0] = posx - ( paneloffset + 20 );
				}
				needstomove[0] = Math.max( needstomove[0], 0 );
			} else if( posx + markwidth > scrollx + winwidth ) { //scrolled off the right of the viewport
				needstomove[0] = Math.round( ( posx + markwidth ) - winwidth ); //just enough space for the tooltip (browser will prevent scrolling too far)
				needstomove[0] = Math.min( needstomove[0], posx - ( paneloffset + 20 ) ); //don't scroll so far for a long tooltip that it causes the mark to disappear
				needstomove[0] = Math.max( needstomove[0], 0 );
			} else {
				needstomove[0] = scrollx; //no scrolling needed in this direction
			}
			if( posy < scrolly + 20 ) { //scrolled off the top of the viewport
				if( winheight < 100 ) {
					needstomove[1] = posy - 10; //really short window, keep it near the top
				} else {
					needstomove[1] = Math.round( posy - ( winheight / 3 ) ); //about 1/3 of the way from the top of the viewport
				}
				needstomove[1] = Math.max( needstomove[1], 0 );
			} else if( posy + 55 > scrolly + winheight ) { //scrolled off the bottom of the viewport
				if( winheight < 160 ) {
					needstomove[1] = posy - 20; //really short window, keep it near the top
				} else {
					needstomove[1] = Math.min( posy + 150 - winheight, Math.round( posy - ( 2 * winheight / 3 ) ) ); //about 1/3 of the way from the bottom of the viewport, up to 150px
				}
				needstomove[1] = Math.max( needstomove[1], 0 );
			} else {
				needstomove[1] = scrolly; //no scrolling needed in this direction
			}
			//animate scrolling to that point
			if( needstomove[0] != scrollx || needstomove[1] != scrolly ) {
				if( grabinterval ) {
						clearInterval(grabinterval); //stop grab & scroll or it will go mad
						document.onmouseup = document.onmousemove = null;
						grabinterval = null;
				}
				step = 0;
				scrollinterval = setInterval(function () {
					var numsteps = 20;
					step++;
					if( step == numsteps ) {
						stopanimation();
					}
					//use S curve to make it easy to see, and prevent sudden motion flickers when rapidly moving over multiple items in the list
					var factor = ( 1 - Math.cos( ( step / numsteps ) * Math.PI ) ) / 2;
					window.scrollTo(scrollx+Math.round(factor*(needstomove[0]-scrollx)),scrolly+Math.round(factor*(needstomove[1]-scrolly)));
				},30);
			}
		};
		newlink.onmouseout = function () {
			stopanimation();
			if( this.relmark.className ) {
				//test is almost always true, but can be false if you hover items in the list while searching
				this.relmark.className = '';
			}
		};
		newlink.appendChild(document.createTextNode(onelink.firstChild.firstChild.nodeValue.replace(/ \(\d+,\d+\)/,'')));
		newli.appendChild(newlink);
		ctnul.appendChild(newli);
	}
	newli = document.createElement('div');
	newli.appendChild(document.createElement('a')).href = '#';
	newli.lastChild.onclick = function () {
		searchfield.value = '';
		searchfield.onchange();
		searchfield.onblur();
		return false;
	};
	newli.lastChild.appendChild(document.createTextNode('X'));
	newli.lastChild.title = 'Cancel search';
	searchfield = document.createElement('input');
	searchfield.defaultValue = searchfield.value = 'Quick find - search waterfalls';
	searchfield.onfocus = function () { if( this.value == this.defaultValue ) { this.value = ''; } };
	searchfield.onblur = function () {
		if( this.value == this.defaultValue ) {
			//eep, they put the defaultValue back in there - onfocus, the box will then be empty, but without this next part,
			//it will still show an empty list until they start typing after focusing it
			this.value = '';
			this.onkeyup();
		}
		if( !this.value ) { this.value = this.defaultValue; }
	};
	searchfield.onchange = searchfield.onkeyup = function (e,doclass) {
		var overnotice, searchstr = this.value.toLowerCase(), isSearch = !!this.value; //save repeated casts
		if( overnotice = document.getElementById('overnotice') ) { overnotice.firstChild.onclick(); }
		var listofLIs = ctnul.getElementsByTagName('li');
		for( var i = 0, onetest, numhits = 0, curhit; i < numlinks; i++ ) {
			//go through all links in the index, searching for the text
			onetest = listofLIs[i];
			if( isSearch ) {
				if( ( doclass === true ? ( onetest.firstChild.relmark.parentNode.className || '' ) : onetest.firstChild.firstChild.nodeValue ).toLowerCase().indexOf(searchstr) + 1 ) {
					numhits++;
					curhit = onetest;
					if( onetest.className ) {
						//just returned into view (they pressed backspace/delete/etc.)
						onetest.className = '';
						onetest.firstChild.relmark.className = '';
						//it's possible this could now become the only match, but that's fine since it will be given its simfocus class in the mouseover handler
					}
				} else if( !onetest.className ) {
					//first time this failed to match, kill it
					onetest.className = 'failedsearch';
					onetest.firstChild.relmark.className = 'failedsearch';
				}
			} else if( onetest.className ) {
				//stopped searching, clean up
				onetest.className = '';
				onetest.firstChild.relmark.className = '';
			}
		}
		//there are two ways that there can be a lastHit; if more items are being displayed, or if this item no longer matches
		if( isSearch && numhits == 1 ) {
			//they entered some search text, and it found one hit
			if( lastHit != curhit ) {
				//first time this item got matched
				stopanimation();
				lastHit = curhit;
				curhit.firstChild.onmouseover();
			}
		} else if( lastHit ) {
			//clean up
			stopanimation();
			if( !lastHit.className ) {
				//it was simfocused but is no longer the only match - return it to regular visible state
				lastHit.firstChild.relmark.className = '';
			}
			lastHit = null;
		}
		if( !numhits && !doclass && arguments.callee && arguments.callee.call ) {
			//no hits, so search on class instead of text
			arguments.callee.call(this,e,true);
		}
	};
	newli.className = 'quickfind';
	newli.appendChild(searchfield);
	document.body.appendChild(newli);
	ctnul.className = 'scriptedlist';
	document.documentElement.className = 'withscriptedlist';
	document.getElementsByTagName('img')[0].onmousedown = function (e) {
		//grab & scroll
		e = e || window.event;
		if( !e ) { return true; }
		if( e.which > 1 || e.button > 1 ) { return true; }
		if( grabinterval ) {
				clearInterval(grabinterval); //just in case they upset it by mousing down twice with middle+left button in a DOM .button browser
				grabinterval = null;
		}
		var origScrollX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
		var origScrollY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
		var origMouseX = e.clientX || 0;
		var origMouseY = e.clientY || 0;
		var curMouseX = origMouseX, curMouseY = origMouseY;
		document.onmouseup = document.onmousemove = function (e) {
			e = e || window.event;
			if( !e ) { return true; }
			if( e.type == 'mouseup' ) {
				if( e.which > 1 || e.button > 1 ) { return true; }
				document.onmouseup = document.onmousemove = null;
				clearInterval(grabinterval);
				grabinterval = null;
			}
			curMouseX = e.clientX || 0;
			curMouseY = e.clientY || 0;
			return false;
		};
		grabinterval = setInterval(function () {
			//can't scroll in the mouse event handler, or it goes crazy in Opera - the scroll action fires the event with massive clientX/Y
			window.scrollTo( origScrollX + origMouseX - curMouseX, origScrollY + origMouseY - curMouseY );
		},20);
		return false;
	};
	ev = ev || window.event || {};
	if( window.opera ) {
		setTimeout(function () {
			//Opera bug puts fixed elements behind the scrollbar when it appears - timeout normally forces scrollbar to appear first so the bug does not show up
			document.body.appendChild(ctnul);
			if( ev.type == 'load' ) { searchonload(); } //running onload instead of DOMContentLoaded
		},20);
	} else {
		document.body.appendChild(ctnul);
		if( ev.type == 'load' ) { searchonload(); } //running onload instead of DOMContentLoaded
	}
};
if( window.addEventListener ) {
	window.addEventListener('DOMContentLoaded',setUpMarkIndex,false);
	window.addEventListener('load',setUpMarkIndex,false);
} else if( document.addEventListener ) {
	document.addEventListener('DOMContentLoaded',setUpMarkIndex,false);
	document.addEventListener('load',setUpMarkIndex,false);
} else if( window.attachEvent ) {
	window.attachEvent('onload',setUpMarkIndex);
} else {
	window.onload = setUpMarkIndex;
}
