//unresolved bug; sometimes jumps around in Opera when hovering markers after scrolling
//unresolved bug; cannot hover or click markers in Opera after zooming in too far - known Opera bug with no workaround

var hasSetUpMarkIndex = false, searchfield;
function setUpMarkIndex(ev) {
	function searchonload() {
		//set zoom buttons correctly
		//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);
		}
	}
	function doImg(link) {
		//Opera is the only browser smart enough to load images only when they are displayed
		//other browsers load them anyway - hundreds of images for no reason - so this script adds the images only when needed
		//add images to the spans
		if( link.doneImg ) { return; }
		link.doneImg = true;
		if( link.firstChild && link.firstChild.title ) {
			var newImg = document.createElement('img');
			newImg.alt = '';
			newImg.src = link.firstChild.title;
			link.firstChild.appendChild(newImg);
			link.firstChild.title = '';
			link.hasImg = true;
		}
	}
	function zoomBy(zoomin,wasmouse,x,y) {
		//zoom and centre
		if( !mapimg.complete ) { return; } //can't zoom until the image has loaded
		if( !origsize ) {
			origsize = [mapimg.width,mapimg.height];
			curzoom = 1;
			maxzoom = 32768 / Math.max(mapimg.width,mapimg.height); //Opera has a limit of image size to 15 bits
			maxzoom = Math.pow( 2, Math.floor( Math.log(maxzoom) / Math.log(2) ) ); //floor to nearest power-of-two
		}
		zoomout.className = ( !zoomin && curzoom < 4 ) ? 'limit' : '';
		zoominb.className = ( zoomin && curzoom > maxzoom / 4 ) ? 'limit' : '';
		if( !zoomin && curzoom == 1 ) { return; } //too small
		if( zoomin && curzoom == maxzoom ) { return; } //too big
		if( zoomin ) {
			curzoom *= 2;
		} else {
			curzoom /= 2;
		}
		//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;
		//get image position
		for( var imgleft = 0, imgtop = 0, oElement = mapimg; oElement; oElement = oElement.offsetParent ) {
			imgleft += oElement.offsetLeft;
			imgtop += oElement.offsetTop;
		}
		var panelwidth = ( document.documentElement.className == 'withscriptedlist' ) ? indexhead.offsetWidth : 0;
		var viewportX, viewportY, vpimgX, vpimgY;
		if( wasmouse ) {
			//click point sits at this point in the image
			vpimgX = x + scrollx - imgleft;
			vpimgY = y + scrolly - imgtop;
		} else {
			//viewport centre
			viewportX = scrollx + panelwidth + ( ( winwidth - panelwidth ) / 2 );
			viewportY = scrolly + ( winheight / 2 );
			//which sits at this point in the image
			vpimgX = viewportX - imgleft;
			vpimgY = viewportY - imgtop;
		}
		//and at the new zoom, that will be at this point in the image
		vpimgX *= zoomin ? 2 : 0.5;
		vpimgY *= zoomin ? 2 : 0.5;
		//so the total offset is
		vpimgX += imgleft;
		vpimgY += imgtop;
		mapimg.width = origsize[0] * curzoom;
		mapimg.height = origsize[1] * curzoom;
		if( mapimg.width != origsize[0] * curzoom ) {
			//Safari bug - it thinks "none" means "naturalWidth", but it correctly handles "inherit", even though it is inheriting "none"
			mapimg.style.maxWidth = 'inherit !important';
			mapimg.width = origsize[0] * curzoom;
		}
		//set container dimensions to make IE 7 behave, and make page stretch to fit width
		document.body.style.width = mapimg.width + 'px';
		wrapDiv.style.height = mapimg.height + 'px';
		if( window.opera ) { fixListHeight(); }
		//so the new top corner of the window is at
		window.scrollTo( Math.round( vpimgX - ( ( winwidth - panelwidth ) / 2 ) - panelwidth ), Math.round( vpimgY - ( winheight / 2 ) ) );
	}
	function fixListHeight() {
		//Opera bug fails to update the list height when the viewport changes height (first load of map image, show/hide the list, window resizing)
		//causes list to be too short, or overlay search box and fall behind scrollbar - need to force Opera to recalculate the bottom position
		//using padding:x% does the trick for viewport height changes, but not scrollbar-appearing cases
		ctnul.style.height = ctnul.offsetHeight + 'px';
		setInterval(function () {
			ctnul.style.height = '';
		},10);
	}
	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 wrapDiv = document.getElementsByTagName('div')[0];
	wrapDiv.onmouseover = wrapDiv.onfocusin = function (e) {
		//fires whenever a link gets mouseover, which bubbles to the div (allows the images to load on demand, even though the tooltip is shown using :hover)
		e = e || window.event;
		if( !e ) { return true; }
		e = e.target || e.srcElement;
		if( !e ) { return true; }
		if( e.tagName && e.tagName.toLowerCase() == 'a' ) {
			doImg(e);
		}
		return true;
	};
	if( wrapDiv.addEventListener ) {
		//event does not bubble, use capturing
		wrapDiv.addEventListener('focus',wrapDiv.onmouseover,true);
	}
	var lastHit = null, newli, newlink, scrollinterval, grabinterval = null, step = 0,
	    allmarks = wrapDiv.getElementsByTagName('a'),
	    ctnul = document.createElement('ul'),
	    mapimg = document.getElementsByTagName('img')[0], origsize, curzoom, maxzoom, zoomout, zoominb;
	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';
		document.documentElement.className = document.documentElement.className ? '' : 'withscriptedlist';
		if( window.opera ) { fixListHeight(); }
		return false;
	};
	if( candozoom ) {
		zoomout = indexhead.appendChild(document.createElement('a'));
		zoomout.appendChild(document.createTextNode('-'));
		zoomout.title = 'Zoom out (or Shift+double-click the map)';
		zoomout.href = '#';
		zoomout.className = 'limit';
		zoomout.onclick = function () { zoomBy(false); return false; };
		zoominb = indexhead.appendChild(document.createElement('a'));
		zoominb.appendChild(document.createTextNode('+'));
		zoominb.title = 'Zoom in (or double-click the map)';
		zoominb.href = '#';
		zoominb.onclick = function () { zoomBy(true); return false; };
		zoomout.onmousedown = zoominb.onmousedown = function () { return false; };
	}
	indexhead.className = 'indexhead';
	if( indexhead.lastChild.offsetTop ) {
		//IE 7 needs the non floating text to be last, or the floats drop to the end of the line
		//illogical ordering so will not be used by default - detect the bug instead
		indexhead.appendChild(indexhead.firstChild);
	}
	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';
			doImg(this.relmark);
			//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];
			var extraheight = this.relmark.hasImg ? 100 : 0; //assume all images are 100 pixels high (portrait) even though most are 67 or 75
			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 + extraheight > 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 {
					//need to add in extraheight here to prevent images being cropped by the viewport
					needstomove[1] = Math.min( posy + 150 + extraheight - winheight, Math.round( posy + extraheight - ( 2 * winheight / 3 ) ) ); //about 1/3 of the way from the bottom of the viewport, up to 150px+extraheight
				}
				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);
			}
			//possible for Opera's bug to show up (when the marker is close to the viewport's right edge), but reflows may cause infinite mouseover loops
			//do not apply workaround
		};
		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';
	mapimg.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);
		if( document.activeElement && document.activeElement.tagName && document.activeElement.tagName.toLowerCase() == 'a' ) {
			//stop links from remaining focused (return false prevents this from natively happening in some browsers)
			document.activeElement.blur();
		}
		//prevent text/image selection
		return false;
	};
	if(candozoom ) {
		mapimg.ondblclick = function (e) {
			//zoom to point
			e = e || window.event;
			if( !e ) { return true; }
			zoomBy(!e.shiftKey,true,e.clientX || 0,e.clientY || 0);
		};
	}
	ev = ev || window.event || {};
	document.body.appendChild(ctnul);
	if( ev.type == 'load' ) { searchonload(); } //running onload instead of DOMContentLoaded
	if( window.opera ) {
		window.addEventListener('load',function () {
			//fixed position elements misplaced when reloading
			var tmp = [window.pageXOffset,window.pageYOffset];
			if( !tmp[0] && tmp[1] ) {
				window.scrollBy(1,1);
				window.scrollBy(-2,-2);
				window.scrollTo(tmp[0],tmp[1]);
			}
			fixListHeight();
		},false);
		mapimg.addEventListener('load',fixListHeight,false);
		window.onresize = fixListHeight;
		fixListHeight();
	}
}
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;
}

