
/// Easy access to most commonly used functions.


/// e for element.
var e = function(id) { return document.getElementById(id); }

/// h for hide
var h = function(id) { addStyle(e(id), "disHide"); }

/// s for show
var s = function(id) { removeStyle(e(id), "disHide"); }

////////////////////////////////////////////////////////////////////////////////
/// finds the max height of the elements then sets all the elements to that
/// height. Returns the height.
function conformElementHeights(elements)
{
	var h = 0;
	
	/// Find max height.
	for(i = 0; i < elements.length; i++) h = Math.max(elements[i].offsetHeight, h);

	/// Apply max height.
	for(i = 0; i < elements.length; i++) elements[i].style.height = h + "px";
	
	return h;
}

////////////////////////////////////////////////////////////////////////////////
/// Removes a specified style from an elements space delimited list of class
/// names.
function removeStyle(element, style)
{
	var className = element.className
	if(className.indexOf(style) != -1)
	{
		var classes = className.split(" ");
		var newClassName = "";
		for(i in classes)
		{
			if(classes[i] != style) newClassName += classes[i] + " ";
		}
		element.className = newClassName.substring(0, newClassName.length - 1);
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Adds a style to an elements class list. Real simple.
function addStyle(element, style)
{
	if(element.className.indexOf(style) == -1)
	{
		if(element.className.length) element.className += " " + style;
		else element.className = style;
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Cross browser mouse location from event. Sends x and y to a callback.
function mouseXYFromEvent(e, callback)
{
	if(document.all) ///< IE
	{
		x = event.clientX + document.body.scrollLeft;
		y = event.clientY + document.body.scrollTop;
	}
	else ///< not IE
	{
		x = e.pageX;
		y = e.pageY;
	}
	callback(x, y);
}


////////////////////////////////////////////////////////////////////////////////
/// Once called, the current mouse position will be sent to callback() whenever
/// the mouse moves. 
function startMouseTracking(callback)
{
	if(!document.all) document.captureEvents(Event.MOUSEMOVE);
	
	document.onmousemove = function(e) { mouseXYFromEvent(e, callback); }
}

////////////////////////////////////////////////////////////////////////////////
/// Don't send mouse position to the callback anymore.
function endMouseTracking()
{
	if(!document.all) document.releaseEvents(Event.MOUSEMOVE);
	document.onmousemove = null;
}

////////////////////////////////////////////////////////////////////////////////
/// Get an elements absolute position on the page. X and y are returned via a 
/// callback.
function getAbsolutePosition(element, callback)
{
	x = element.offsetLeft;
	y = element.offsetTop;
	while(element.offsetParent)
	{
		element = element.offsetParent;
		x += element.offsetLeft;
		y += element.offsetTop;
	}
	callback(x, y);
}

////////////////////////////////////////////////////////////////////////////////
/// Position an element in absolute coordinates on a page.
function positionAbsolute(element, x, y)
{
	relx = element.offsetLeft;
	rely = element.offsetTop;
	
	callback = function(absx, absy)
	{
		element.style.left = (x - absx + relx) + "px";
		element.style.top = (y - absy + rely) + "px";
	}
	
	getAbsolutePosition(element, callback);
}

////////////////////////////////////////////////////////////////////////////////
/// Removes a node and puts it right back where it was. Tells Safari that the
/// DOM cache is invalid and makes it refresh.
function invalidateNodeHack(element)
{
	if(navigator.userAgent.indexOf("Safari") != -1)
	{
		var p = element.parentNode;
		var n = element.nextSibling;
		p.removeChild(element);
		p.insertBefore(element, n);
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Find elements with the same id besides a numeric prefix. First is most
/// likely 0 or 1 (optional, defaults to 0.
function getElementsByIdEnum(id, first)
{
	i = first ? parseInt(first) : 0;
	elements = Array();
	while(e = document.getElementById(id + i)) elements[i++] = e;
	return elements;
}

////////////////////////////////////////////////////////////////////////////////
/// Sets up callbacks on an input field so that helper text disappears when it 
/// is clicked and reappears when focus is lost and the input is empty.
function setInputHelperText(element, text)
{
	element.onfocus = function() { if(element.value == text) element.value = ""; }
	element.onblur = function() { if(element.value == "") element.value = text; }
}

////////////////////////////////////////////////////////////////////////////////
/// browser-specific XMLHttpRequest initiation.
function newXMLHttpRequest()
{
	/// everything but ie
	if(window.XMLHttpRequest) return new XMLHttpRequest();

	/// ie
	else if(window.ActiveXObject) return new ActiveXObject("Microsoft.XMLHTTP");
	
	/// incompatible.
	else return false;
}

////////////////////////////////////////////////////////////////////////////////
/// Facilitates simpler syntax for getting the selected value from a select
/// field.
function getSelectedValue(element)
{
	return element.options[element.selectedIndex].value;
}

////////////////////////////////////////////////////////////////////////////////
/// Even simpler syntax. usage: onSelectValue("select-id", function(value){ ... });
function onSelectValue(id, callback)
{
	e(id).onchange = function() { callback( getSelectedValue( e(id) ) ); }
}




















////////////////////////////////////////////////////////////////////////////////
/// Constructs an object that holds a panels header and content div.
function accordianPanel(g, h, c, i)
{
	this.group = g;
	this.header = h;
	this.content = c;
	this.index = i;
	
	this.locked = false;
	
	var instance = this; 
	h.onclick = function() { instance.toggle(); }
}

////////////////////////////////////////////////////////////////////////////////
/// Callback for header clicks
accordianPanel.prototype.toggle = function()
{
	if(!this.locked)
	{
		/// close the previous panel
		if(this.group.open != -1) { this.group.open.close(); }
		/// open the new panel unless it was just the open one
		if(this.group.open != this) this.open();
		else this.group.open = -1; ///< no panels open
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Restyles the panel so it is closed
accordianPanel.prototype.close = function()
{
	addStyle(this.content, "posHide") ///< Hides the div.
	
	this.header.className = "panelHeader";

	/// For notifications of when panel is closed.
	this.group.panelCloseCallback(this.index);
}

////////////////////////////////////////////////////////////////////////////////
/// Restyles the panel so it is open
accordianPanel.prototype.open = function()
{	
	/// Remove the hiding class
	removeStyle(this.content, "posHide");
	
	/// This is a terrible hack to get Safari to invalidate it's DOM cache.
	/// Without it Safari wont show the newly opened panel.
	invalidateNodeHack(this.content);
	
	this.header.className = "panelHeaderOpen";
	this.group.open = this;
	
	/// For notifications of when panel is opened.
	this.group.panelOpenCallback(this.index);
}

////////////////////////////////////////////////////////////////////////////////
///
accordianPanel.prototype.makeOpenIfNot = function()
{
	if(this.group.open != this) this.toggle();
}

////////////////////////////////////////////////////////////////////////////////
///
accordianPanel.prototype.lock = function()
{
	this.locked = true;
	this.header.className = "panelHeaderLocked";
}

////////////////////////////////////////////////////////////////////////////////
///
accordianPanel.prototype.unlock = function()
{
	this.locked = false;
	this.header.className = "panelHeader";
}



////////////////////////////////////////////////////////////////////////////////
/// Accordian constructor. Finds "header" and "content" divs by the name
/// attributes of divs then assigns onHeaderClick to all the headers. Open
/// should be the index (zero based) of the initially open panel or -1.
function accordian(groupID, open)
{

	var headers = Array();
	var content = Array();
	var i = 0;
	while(h = document.getElementById(groupID + "_header_" + i))
	{
		content[i] = document.getElementById(groupID + "_content_" + i);
		headers[i++] = h;
	}

	conformElementHeights(content);
	this.open = -1;
	
	this.panels = Array();
	
	/// Create panel objects for all of the panels
	for(i = 0; i < headers.length; i++)
	{
		panel = new accordianPanel(this, headers[i], content[i], i);
		if(i == open) this.open = panel;
		this.panels[i] = panel;
	}
}

////////////////////////////////////////////////////////////////////////////////
///
accordian.prototype.collapse = function()
{
	for(i in this.panels) this.panels[i].close();
	this.open = -1;
}

////////////////////////////////////////////////////////////////////////////////
///
accordian.prototype.lock = function()
{
	if(this.open != -1) this.open.close();
	for(i in this.panels) this.panels[i].lock();
}

////////////////////////////////////////////////////////////////////////////////
///
accordian.prototype.unlock = function()
{
	for(i in this.panels) this.panels[i].unlock();
}

////////////////////////////////////////////////////////////////////////////////
///
accordian.prototype.toggle = function(i)
{
	this.panels[i].toggle();
}

////////////////////////////////////////////////////////////////////////////////
/// Redefine.
accordian.prototype.panelOpenCallback = function(i) {}
accordian.prototype.panelCloseCallback = function(i) {}




























////////////////////////////////////////////////////////////////////////////////
///
function simpleajax(id)
{
	this.id = id;
	/// everything but ie
	if(window.XMLHttpRequest) this.ajax = new XMLHttpRequest();
	/// ie
	else if(window.ActiveXObject) this.ajax = new ActiveXObject("Microsoft.XMLHTTP");
}

////////////////////////////////////////////////////////////////////////////////
///
simpleajax.prototype.request = function(url)
{
	/// Abort the current request if there is one.
	
	if(this.ajax.readyState) this.ajax.abort();
	
	/// Make a new request.
	this.ajax.open("get", url + "&ajaxid=" + this.id);
	var instance = this;
	this.ajax.onreadystatechange = function() { instance.onReadyStateChange(); }
	this.ajax.send('');
}

////////////////////////////////////////////////////////////////////////////////
///
simpleajax.prototype.onReadyStateChange = function()
{
	/// readyState == 4 indicates request is complete
	if(this.ajax.readyState == 4)
	{
		/// status == 200 indicates OK
		if(this.ajax.status == 200)
		{
			this.parseXML();
		}
		else this.onError("No Response");
	}
}

////////////////////////////////////////////////////////////////////////////////
///
simpleajax.prototype.parseXML = function()
{
	var dom = this.ajax.responseXML;
	if(dom)
	{	
		if(navigator.userAgent.indexOf("MSIE") != -1)
		{
			var tags = dom.childNodes[1].childNodes;	
		}
		else
		{
			var tags = dom.childNodes[0].childNodes;
		}
		//if(!tags.length) ;
		
		for(i = 0; i < tags.length; i++)
		{
			
			/// Does it have an id?
			if(id = tags[i].getAttribute("id"))
			{
				
				/// Is there an element in the document with the same id?
				if(swapout = document.getElementById(id))
				{
					
					/// Recreate the node in the document
					swapin = document.createElement(tags[i].nodeName);
					attrs = tags[i].attributes;
					if(!attrs.length) alert("no attributes");
					for(j = 0; j < attrs.length; j++)
					{
						;
						swapin.setAttribute(attrs[j].nodeName, attrs[j].nodeValue);
					}
					if(tags[i].firstChild) swapin.innerHTML = tags[i].firstChild.nodeValue;
					/// Replace the old node with the new node.
					swapout.parentNode.replaceChild(swapin, swapout);
				}
				
			}
			
		}
		this.onSuccess();
	}
	else this.onError(this.ajax.responseText);
}

////////////////////////////////////////////////////////////////////////////////
///
simpleajax.prototype.getElement = function(id)
{
	return document.getElementById(this.id + "_" + id);
}



simpleajax.prototype.onError = function(e){}
simpleajax.prototype.onSuccess = function(){}

















































/*





////////////////////////////////////////////////////////////////////////////////
/// calendar widget constructor.
function calendar()
{
	var instance = this;
	
	/// Get all the days (IDs start at 1).
	this.days = getElementsByIdEnum("calendar-day-" , 1);
	
	/// Set the category selector callback.
	this.categorySelect = document.getElementById("calendar-category-select");
	this.categorySelect.onchange = function() { instance.onCategorySelect(); };
}

////////////////////////////////////////////////////////////////////////////////
/// Category select onchange callback.
calendar.prototype.onCategorySelect = function()
{
	/// Get the selected value.
	var category = getSelectedValue(this.categorySelect);
	
	for(i in this.days)
	{
		/// only select / deselect days that have events.
		if(this.days[i].className.indexOf("has-event") != -1)
		{
			if(category == -1) ///< 'All Categories' has been selected.
			{
				/// Add the "selected" style to all days with events.
				appendClass(this.days[i], "selected");
			}
			else if(this.days[i].className.indexOf("category-" + category) != -1)
			{
				/// Day is in category. Append the "selected" style to it.
				addStyle(this.days[i], "selected");
			}
			else
			{
				/// Day is not in category. Remove the "selected" style from it.
				removeStyle(this.days[i], "selected");
			}
		}
	}
}









*/
















////////////////////////////////////////////////////////////////////////////////
/// rssBrowser constructor. Needs the total categories, and the total categories
/// initially open passed to it.
function rssBrowser(catCount, openCatCount)
{	
	/// List of rssBrowserCategory objects.
	this.categories = Array();
	
	var i;
	
	/// Loop through all the categories and initialize...
	for(i = 0; i < catCount; i++)
	{
		this.categories[i] = new rssBrowserCategory(i, i < openCatCount);
	}
	/*
	
	commented out - rss item description rollovers.
	
	
	/// Get item links and their floating descriptions.
	this.items = getElementsByIdEnum("rss-browser-item-");
	this.itemDescriptions = getElementsByIdEnum("rss-browser-description-");
	
	/// Wire up the item headlines mouse over / out callbacks.
	/// This convoluted callback syntax is to preserve the value (instead of
	/// the instance) of loop variable 'i'.
	var instance = this;
	makeFuncOver = function(preserve) { return function() { instance.onItemMouseOver(preserve); } }
	makeFuncOut =  function(preserve) { return function() { instance.onItemMouseOut(preserve); } }
	
	for(i in this.items)
	{
		this.items[i].onmouseover = makeFuncOver(i);
		this.items[i].onmouseout = makeFuncOut(i);
	}*/
}

////////////////////////////////////////////////////////////////////////////////
/// rss item headline mouse out callback.
rssBrowser.prototype.onItemMouseOut = function(i)
{
	/// The currently floating element.
	var desc = this.itemDescriptions[i];

	/// Stop repositioning and mouse tracking.
	endMouseTracking();

	/// Send element into oblivion.
	desc.style.left = "-5000px";
	desc.style.top  = "-5000px";
	addStyle(desc, "disHide");
}

////////////////////////////////////////////////////////////////////////////////
/// rss item headline mouse over callback.
rssBrowser.prototype.onItemMouseOver = function(i)
{
	/// Element to make float.
	var desc = this.itemDescriptions[i];
	
	/// This makes it visible, but still off the screen.
	removeStyle(desc, "disHide");
	
	/// The callback makes it visible on screen.
	var instance = this;
	callback = function(absx, absy) { positionAbsolute(instance.itemDescriptions[i], absx + 20, absy + 20); }
	
	/// Reposition whenever the mouse moves...
	startMouseTracking(callback);
}



////////////////////////////////////////////////////////////////////////////////
/// rssBrowserCategory constructor. Pass it a unique index coorisponding to
/// element id enumeration.
function rssBrowserCategory(i, visible)
{
	var instance = this;
	
	/// Save 'i' as index.
	this.index = i;
	
	/// Is this category initially open?
	this.visible = visible;
	
	/// First one is always open.
	this.openSource = 0;
	
	/// Get the open / close button for the entire category.
	this.openCloseImg = document.getElementById("rss-browser-open-close-" + i);
	
	/// Get all the source links in this category.
	this.sourceLinks = getElementsByIdEnum("rss-browser-feed-link-" + i + "-");

	/// Get all the source item lists in this category.
	this.itemLists = getElementsByIdEnum("rss-browser-items-" + i + "-");
	
	
	/// Link the image to a callback.
	this.openCloseImg.onclick = function()
	{
		instance.onOpenCloseClick();
	}
	
	/// Link the source links to their item lists.
	makeFunc = function(preserve) { return function() { instance.onSourceSelect(preserve); return false; } }
	var j;
	for(j in this.sourceLinks)
	{
		this.sourceLinks[j].onclick = makeFunc(j);
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Callback for the open / close button.
rssBrowserCategory.prototype.onOpenCloseClick = function()
{
	if(this.visible) ///< Hide it!
	{
		this.closeAll();
	}
	else ///< Show it!
	{
		this.openAll();	
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Callback for a source selection.
rssBrowserCategory.prototype.onSourceSelect = function(i)
{
	if(!this.visible)
	{
		this.openSource = i;
		this.openAll();
	}
	else if(this.openSource == i)
	{
		this.closeAll();
	}
	else
	{
		this.closeList(this.openSource);
		this.openList(i);
		this.openSource = i;
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Makes the list at index i render open.
rssBrowserCategory.prototype.openList = function(i)
{
	addStyle(this.sourceLinks[i], "open");
	removeStyle(this.itemLists[i], "disHide");
}

////////////////////////////////////////////////////////////////////////////////
/// Makes the list at index i render closed.
rssBrowserCategory.prototype.closeList = function(i)
{
	removeStyle(this.sourceLinks[i], "open");
	addStyle(this.itemLists[i], "disHide");
}

////////////////////////////////////////////////////////////////////////////////
/// Makes the entire list render closed
rssBrowserCategory.prototype.openAll = function()
{
	this.openList(this.openSource);
	this.openCloseImg.src = "/i-p/blue-minus.gif";
	this.visible = true;
}

////////////////////////////////////////////////////////////////////////////////
/// Makes the entire list render closed
rssBrowserCategory.prototype.closeAll = function()
{
	this.closeList(this.openSource);
	this.openCloseImg.src = "/i-p/blue-plus.gif";
	this.visible = false;
}





























/// NOTE: Code for thumbnails has been commented out.

/// How much time to idle before switching to the next story when "playing."
var storyBrowserIdleInterval = 8000;

/// Play and pause image locations.
var storyBrowserPauseSrc = "/i-p/pause-story.gif";
var storyBrowserPlaySrc = "/i-p/play-story.gif";

/// Should the storys be "playing" initially?
var storyBrowserAutoPlay = true;

/// Class swaping definitions.
var storyBrowserUpThumbClass = "up";
var storyBrowserHideStoryClass = "disHide";

////////////////////////////////////////////////////////////////////////////////
/// storyBrowser constructor
function storyBrowser(pendingImageURLs)
{
	this.pendingImages = Array();
	for(i in pendingImageURLs)
	{
		this.pendingImages[i] = new Image();
		this.pendingImages[i].src = pendingImageURLs[i];
	}
	

	/// all story divs have an id of the form "story-browser-story-n" and all 
	/// thumbs have an id of the form "story-browser-thumb-n" where "n" is the
	/// name of the story.
	this.stories = getElementsByIdEnum("story-browser-story-");
	///this.thumbs = getElementsByIdEnum("story-browser-thumb-");
	
	/// Which story is currently visible.
	this.storyIndex = 0;
	
	/// Preload the "play" and "pause" button image states.
	this.playImg = new Image();
	this.playImg.src = storyBrowserPlaySrc;
	this.pauseImg = new Image();
	this.pauseImg.src = storyBrowserPauseSrc;
	
	/// Keeps track of the interval where stories are swapped.
	this.playInterval = false;
	
	/// Set the callback for the play/pause button.
	var instance = this;
	this.playPauseImg = document.getElementById("story-browser-play-pause");
	this.playPauseImg.onclick = function() { instance.onPlayPauseClick(); };
	
	/// Initially start playing?
	if(storyBrowserAutoPlay) this.play();
	
	/// Wire up the previous/ next story buttons.
	document.getElementById("story-browser-previous").onclick = function() { instance.onPrevNextClick(-1); };
	document.getElementById("story-browser-next").onclick = function() { instance.onPrevNextClick(1); };
	
	/// Dynamically set the callbacks for thumbnail clicks.
	///makef = function(j) { return function() { instance.onThumbClickAtIndex(j); } }
	///for(i = 0; i < this.thumbs.length; i++) instance.thumbs[i].onclick = makef(i);
}

////////////////////////////////////////////////////////////////////////////////
storyBrowser.prototype.onPlayPauseClick = function()
{
	if(this.playInterval) this.stop();
	else 
	{
		this.play();
	
		/// Immediately go to the next story when play is clicked.
		this.offsetStory(1);
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Callback for the "previous-story", "next-story" buttons.
storyBrowser.prototype.onPrevNextClick = function(direction)
{
	this.stop();
	this.offsetStory(direction);
}

////////////////////////////////////////////////////////////////////////////////
/// Callback for thumbnail clicks.
///storyBrowser.prototype.onThumbClickAtIndex = function(i)
///{
///	this.stop();
///	this.displayStoryAtIndex(i);
///}

////////////////////////////////////////////////////////////////////////////////
/// Stop the story looping if it is playing.
storyBrowser.prototype.stop = function()
{
	if(this.playInterval)
	{
		/// Stories are "playing." Stop them.
		clearInterval(this.playInterval);
		this.playInterval = false;
		
		/// Change the button to "play."
		// this.playPauseImg.src = this.playImg.src;
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Start the story looping if it is paused.
storyBrowser.prototype.play = function()
{
	if(!this.playInterval)
	{
		/// Stories are "paused." Play them.
		var instance = this;
		var callback = function() { instance.offsetStory(1); }
		this.playInterval = setInterval(callback, storyBrowserIdleInterval);
		
		/// Change the button to "pause."
		this.playPauseImg.src = this.pauseImg.src;
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Go to the next story. setInterval callback.
storyBrowser.prototype.offsetStory = function(offset)
{
	var i = this.storyIndex + offset;
	
	/// Increment index and make sure it's valid.
	if(i >= this.stories.length) i = 0;
	else if(i < 0) i = this.stories.length -1;
	
	/// Display it.
	this.displayStoryAtIndex(i);
}

////////////////////////////////////////////////////////////////////////////////
/// Makes a story visible and hides the current one. Doesn't check if index is
/// out of range.
storyBrowser.prototype.displayStoryAtIndex = function(i)
{
	/// It would be silly to hide and then show the story if index is the same. 
	if(i != this.storyIndex)
	{
	
		if(i != 0)
		{
			document.getElementById("story-browser-photo-" + i).src = this.pendingImages[i - 1].src;
		}
	
		/// Hide old.
		///removeStyle(this.thumbs[this.storyIndex], storyBrowserUpThumbClass);
		addStyle(this.stories[this.storyIndex], storyBrowserHideStoryClass);
		
		/// Show new.
		///addStyle(this.thumbs[i], storyBrowserUpThumbClass);
		removeStyle(this.stories[i], storyBrowserHideStoryClass);
		
		/// Remember state.
		this.storyIndex = i;
	}
}














































/// Configurables

// icon definitions

var YMAP_ICON_911 = 			{src: "/i-p/mapicons/911-incident-icon.gif", 		w:15, h:15 };
var YMAP_ICON_INCIDENT_1 =		{src: "/i-p/mapicons/incident-yellow.gif", 			w:15, h:15 };
var YMAP_ICON_INCIDENT_2 =		{src: "/i-p/mapicons/incident-orange.gif", 			w:15, h:15 };
var YMAP_ICON_INCIDENT_3 =		{src: "/i-p/mapicons/incident-red.gif", 			w:15, h:15 };
var YMAP_ICON_CONSTRUCTION =	{src: "/i-p/mapicons/roadwork.gif", 				w:15, h:15 };
var YMAP_ICON_SEX_OFFENDER =	{src: "/i-p/mapicons/sex-offender-icon.gif", 		w:15, h:15 };
var YMAP_ICON_SEX_OFFENDERS = 	{src: "/i-p/mapicons/sex-offender-multiples.gif", 	w:20, h:20 };

/// urls

var ymapFetchOffendersURL = 	"/widgets2/ymap.php?task=outputXML";
var ymapLocationSearchURL = 	"/widgets2/ymap.php?task=locationSearch";
var ymapYahooJavascriptURL = 	"/widgets2/ymap.php?task=yahoojsc";

/// initial map positions

var ymapInitialLatitude = 		43.155853;
var ymapInitialLongitude = 		-77.608546;
var ymapInitialScale = 			8;
var ymapTinyInitialScale = 		8;

/// template bindings

var ymapMapDivID =				"ymap";
var ymapTinyMapID = 			"ymap-tiny";
var ymapItemDivIDPrefix = 		"ymap-item-";	
var ymapHighlightClass = 		"ymap-highlight";

////////////////////////////////////////////////////////////////////////////////
/// yahoomap constructor. 
function ymap() {}

////////////////////////////////////////////////////////////////////////////////
/// Defers map drawing until agreement is pressed.
ymap.prototype.waitForAgreement = function()
{
	var instance = this;
	document.getElementById("ymap-confirm-button").onclick = function()
	{
		/// Strip ymap of it's class.
		document.getElementById("ymap").className = "";
	
		/// Hide agreement, draw map and load the offenders via ajax.
		document.getElementById("ymap-disclaimer").className = "disHide";
		instance.drawMap();
		instance.loadOffenders();
		
		/// Hide agreement button, show search form
		addStyle(document.getElementById("ymap-confirm"), "disHide");
		removeStyle(document.getElementById("ymap-search"), "disHide");
		
		/// Labels for "street", "city" and "zip" are displayed right in the
		/// input field.
		instance.inputStreet = document.getElementById("ymap-search-street");
		instance.inputCity = document.getElementById("ymap-search-city");
		instance.inputZip = document.getElementById("ymap-search-zip")
		setInputHelperText(instance.inputStreet, "street");
		setInputHelperText(instance.inputCity, "city");
		setInputHelperText(instance.inputZip, "zip");
		
		/// Wire the "go" button to the location search.
		var this_onLocationSearch = function() { instance.onLocationSearch() };
		document.getElementById("ymap-search-submit").onclick = this_onLocationSearch;
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Defers map drawing until the panel has been opened. Panel is the accordion
/// object containing the map, lists is the accordion object containing the
/// incident lists. Struct should be an array of arrays with values for: lat,
/// long, image (one of the objects defined above), div (id of info div), panel#
/// (panel the info div belongs to). For info divs with no location, just the
/// panel is specified (instead of the array).
ymap.prototype.waitForOpenBlockPanel = function(panel, lists, struct)
{
	var instance = this;
	this.listPanels = lists;
	this.mapPanel = panel;
	this.markerData = struct;
	this.mapLoaded = false;
	this.highlightedInfo = false;
	
	/// When map panel is opened obviously the map needs to be loaded.
	this.mapPanel.panelOpenCallback = function() { instance.requireLoadedOpenMap(); }
	
	/// Wire up the descriptions so they open the map panel.
	var makef = function(j) { return function()
	{
		if(instance.slaveMap)
		{
			instance.slaveMap.goToMarker(j);
		}
		else
		{
			this.pendingMarker = j;
			instance.requireLoadedOpenMap();
		}
	}}
	var markerDiv;
	for(i in this.markerData)
	{
		if(this.markerData[i].lat && this.markerData[i].lon)
		{
			markerDiv = document.getElementById(ymapItemDivIDPrefix + this.markerData[i].div);
			markerDiv.onclick = makef(i);
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
///
ymap.prototype.initializeIframeMap = function()
{
	this.controllerMap = window.parent.ymapInstance;
	
	this.scale = ymapTinyInitialScale;
	
	/// Create a lat/lon object
	var monroe = new YGeoPoint(ymapInitialLatitude, ymapInitialLongitude);
	
	this.map = new YMap(document.getElementById('ymap'));
	
	this.map.drawZoomAndCenter(monroe, this.scale);
	
	this.markerData = this.controllerMap.markerData;
	
	/// Put all the stored markers in the map.
	var i;
	var mark;
	var data; 
	/// makes callback functions
	var instance = this;
	var makef = function(j) { return function()
	{
		instance.goToMarker(j);
	}}
	for(i in this.markerData)
	{
		data = this.markerData[i];
		
		/// Add marker to map, only if it has location information.
		if(data.lon && data.lat) 
		{
			mark = this.addMarker(data.lat, data.lon, data.img);
		
			/// Wire up click callback.
			YEvent.Capture(mark, EventsList.MouseClick, makef(i));
		}
	}
	
	this.controllerMap.slaveMap = this;
	
	if(this.controllerMap.pendingMarker)
	{
		this.goToMarker(this.controllerMap.pendingMarker);
	}
	
	addStyle(document.getElementById("ymap-scale-" + this.scale), "highlight");
	makef = function(j) { return function() { instance.setScale(j); }}
	for(i = 1; i <= 16; i++)
	{
		document.getElementById("ymap-scale-" + i).onclick = makef(i);
	}
	document.getElementById("ymap-zoom-in").onclick = function() { instance.incScale(); }
	document.getElementById("ymap-zoom-out").onclick = function() { instance.decScale(); }
}

////////////////////////////////////////////////////////////////////////////////
/// Loads the yahoo javascript and creates the map.
ymap.prototype.requireLoadedOpenMap = function()
{
	if(!this.mapLoaded)
	{
		var instance = this;
		
		/// Make the browser load the Yahoo ajax api.
		///var this_iframeCallback = function(map){ instance.iframeCallback(map); }
		///includejs(ymapYahooJavascriptURL, this_iframeCallback);
		
		var container = document.getElementById("ymap-tiny");
		
		container.innerHTML = '<iframe id="ymap-iframe" src="/widgets2/ymap.php?task=iframe"></iframe>';
		
		/// don't do this function ever again.
		this.mapPanel.panelOpenCallback = function(){};
	
		this.mapLoaded = true;
	}
	if(this.mapPanel.open == -1) this.mapPanel.panels[0].open();
}

////////////////////////////////////////////////////////////////////////////////
/// Centers the marker indexed by index in markerData, and highlights the info
/// div
ymap.prototype.goToMarker = function(index)
{
	var point = new YGeoPoint(this.markerData[index].lat, this.markerData[index].lon);
	this.map.panToLatLon(point);
	this.highlightInfoAtIndex(index);
}

////////////////////////////////////////////////////////////////////////////////
/// unhighlight an info div by removing the highlight class
ymap.prototype.unhighlightInfo = function()
{
	if(this.highlightedInfo)
	{
		removeStyle(this.highlightedInfo, ymapHighlightClass);
		this.highlightedInfo = false;
	}
}

////////////////////////////////////////////////////////////////////////////////
/// highlight an info div by adding the highlight class
ymap.prototype.highlightInfoAtIndex = function(i)
{
	this.unhighlightInfo();
	
	var div = window.parent.document.getElementById(ymapItemDivIDPrefix + this.markerData[i].div);
	addStyle(div, ymapHighlightClass);
	this.highlightedInfo = div;
	
	/// Also gotta make sure the right panel is open.
	this.controllerMap.listPanels.panels[this.markerData[i].pan].makeOpenIfNot();
	
	/// Also gotta scroll to it.
	var scrollTop = 0;
	while(div = div.previousSibling)
	{
		if(div.offsetHeight) scrollTop += parseInt(div.offsetHeight);
	}
	this.controllerMap.listPanels.panels[this.markerData[i].pan].content.scrollTop = scrollTop;
}

////////////////////////////////////////////////////////////////////////////////
/// Load offenders, ajax style.
ymap.prototype.loadOffenders = function()
{
	/// a for Ajax.
	var a = newXMLHttpRequest();
	a.open("get", ymapFetchOffendersURL);
	
	var instance = this;
	a.onreadystatechange = function()
	{
		if(a.readyState == 4) ///< Request complete.
		{
			if(a.status == 200) ///< Request successfull.
			{
				instance.parseOffenderDOM(a.responseXML);
			}
			else alert("error retrieving offender xml.");
		}
	}
	a.send('');
}

////////////////////////////////////////////////////////////////////////////////
/// parse the DOM and turn it into offender markers.
ymap.prototype.parseOffenderDOM = function(dom)
{
	/// n for nodes.
	var n = dom.firstChild.childNodes;
	
	for(i = 0; i < n.length; i++)
	{
		this.addMarkerWithText(	parseFloat(n[i].getAttribute("latitude")), 
								parseFloat(n[i].getAttribute("longitude")),
								eval( n[i].getAttribute("image") ),
								n[i].firstChild.nodeValue);
	}
}

////////////////////////////////////////////////////////////////////////////////
/// Query Yahoo for the latitude and longitude of an address via ajax.
ymap.prototype.onLocationSearch = function()
{
	if(!this.ajax) this.ajax = newXMLHttpRequest();
	if(this.ajax.readyState) this.ajax.abort();
	
	this.ajax.open("get", ymapLocationSearchURL + "&street=" + this.inputStreet.value + "&zip=" + this.inputZip.value + "&city=" + this.inputCity.value + "&state=NY");
	
	var instance = this;
	this.ajax.onreadystatechange = function()
	{
		if(instance.ajax.readyState == 4) ///< Request complete.
		{
			if(instance.ajax.status == 200) ///< Request successfull.
			{
				instance.parseLocationDOM(instance.ajax.responseXML);
			}
			else alert("error retrieving search xml.");
		}
	}
	this.ajax.send('');
}

////////////////////////////////////////////////////////////////////////////////
/// parse the yahoo location DOM and center the map on the result.
ymap.prototype.parseLocationDOM = function(dom)
{	
	var latNode = dom.getElementsByTagName("Latitude");
	var lonNode = dom.getElementsByTagName("Longitude");
	
	/// If both nodes are present then it's a valid response.
	if(latNode.length & lonNode.length)
	{
		var address = dom.getElementsByTagName("Address")[0].firstChild.nodeValue;
		var city = dom.getElementsByTagName("City")[0].firstChild.nodeValue;
		var state = dom.getElementsByTagName("State")[0].firstChild.nodeValue;
		var zip = dom.getElementsByTagName("Zip")[0].firstChild.nodeValue;
		
		/// Make the location object.
		var loc = new YGeoPoint(parseFloat(latNode[0].firstChild.nodeValue), parseFloat(lonNode[0].firstChild.nodeValue));
		
		/// Create the new marker.
		var mark = new YMarker(loc);
		mark.addLabel("*");
		
		/// Setup the onclick callback that opens the "smart window"
		var openSmartWindow = function() { mark.openSmartWindow("<p>" + address + "</p><p>" + city + ", " + state + " " + zip + "<p>"); }
		YEvent.Capture(mark, EventsList.MouseClick, openSmartWindow);
		
		/// Add marker to map.
		this.map.addOverlay(mark);
		this.map.drawZoomAndCenter(loc, 2);
	}
	else alert("location not found");
}

////////////////////////////////////////////////////////////////////////////////
/// Initialize and display the Yahoo map.
ymap.prototype.drawMap = function()
{
	this.scale = ymapInitialScale;
	
	/// Create a lat/lon object
	var monroe = new YGeoPoint(ymapInitialLatitude, ymapInitialLongitude);
 	
 	/// Create a map object
 	this.map = new  YMap(document.getElementById(ymapMapDivID));
 	
 	/// Add controls.
  	this.map.addZoomShort();
 	this.map.addPanControl();
 		
 	/// TO DO: implement a safari fix so it doesn't crash.
 	if(navigator.userAgent.indexOf("Safari") == -1)	
 	{
 	}
 	
 	/// Display the map centered on a latitude and longitude
  	this.map.drawZoomAndCenter(monroe, this.scale);
}

////////////////////////////////////////////////////////////////////////////////
/// Pulls text out of a container div then forwards all the parameters to
/// addMarker.
ymap.prototype.addMarkerByDivId = function(latitude, longitude, image, textid, open)
{
	this.addMarkerWithText(latitude, longitude, image, document.getElementById(textid).innerHTML, open);
}

////////////////////////////////////////////////////////////////////////////////
/// Add a marker with a label and text to the Yahoo map. image should be one
/// of the preset variables above
ymap.prototype.addMarkerWithText = function(latitude, longitude, image, text, open)
{
	var mark = this.addMarker(latitude, longitude, image);
	
	/// Simple callback function to open a "smart window."
	var instance = this;
	openMarker = function() { mark.openSmartWindow(text); }
	YEvent.Capture(mark, EventsList.MouseClick, openMarker);
	
	if(open) openMarker();
}

////////////////////////////////////////////////////////////////////////////////
/// Low level (no text) marker adding. Image should be one of the image objects
/// configured above. Returns the marker.
ymap.prototype.addMarker = function(latitude, longitude, image)
{
	/// Set up the image.
	var icon = new YImage();
	icon.size = new YSize(image.w, image.h);
	icon.src = image.src;
	icon.offsetSmartWindow = new YCoordPoint(7, 7);
	
	// Set up the location.
	var loc = new YGeoPoint(latitude, longitude);
	
	/// Mark it.
	var mark = new YMarker(loc, icon);
	this.map.addOverlay(mark);
	
	return mark;
}

ymap.prototype.setScale = function(scale)
{
	/// unhighlight old scale
	removeStyle(document.getElementById("ymap-scale-" + this.scale), "highlight")
	
	/// highlight new scale
	addStyle(document.getElementById("ymap-scale-" + scale), "highlight")
		
	this.scale = scale;
	this.map.setZoomLevel(scale);
}

ymap.prototype.incScale = function(){if(this.scale > 1) this.setScale(this.scale - 1);}
ymap.prototype.decScale = function() {if(this.scale > 1) this.setScale(this.scale - 1);}














































////////////////////////////////////////////////////////////////////////////////
/// yweather constructor.
function yweather(id, accordian, ajax, radarURLs)
{
	
	/// External unique identifier
	this.id = id;
	
	/// Set the form callbacks
	var zipinput = document.getElementById(this.id + "_search");
	var zipgo = document.getElementById(this.id + "_go");
	
	var instance = this;
	
	zipinput.onkeypress = function(e) { return instance.inputReturnCheck(e); }
	zipgo.onclick = function(e) { return instance.onSearch(); }
	
	this.accordian = accordian;
	
	this_onAjaxError = function(e) { instance.onAjaxError(e); }
	this_onAjaxSuccess = function() { instance.onAjaxSuccess(); }
	
	this.ajax = ajax;
	this.ajax.onError = this_onAjaxError;
	this.ajax.onSuccess = this_onAjaxSuccess;
	
	this.radarURLs = radarURLs;
	this.initRadarAnimation();
	
	
	
	/// Simple callbacks to handle clearing and displaying the "zip code search" string.
	zipinput.onfocus = function(e) { if(zipinput.value = "zip code search") zipinput.value = ""; }
	zipinput.onblur = function(e) { if(!zipinput.value) zipinput.value = "zip code search"; }
	
	
}

////////////////////////////////////////////////////////////////////////////////
/// 
yweather.prototype.onAjaxError = function(e)
{
	this.ajax.getElement("location").innerHTML = e;
}

////////////////////////////////////////////////////////////////////////////////
/// 
yweather.prototype.onAjaxSuccess = function()
{ 
	this.accordian.unlock();
	this.accordian.toggle(0);
	this.initRadarAnimation();
}

////////////////////////////////////////////////////////////////////////////////
/// 
yweather.prototype.initRadarAnimation = function()
{
	this.radarDivs = getElementsByIdEnum("yweather_radar_");
	
	/*
	for(i in this.radarURLs)
	{
		this.radarImages[i] = new Image();
		this.radarImages[i].src = this.radarURLs[i];
	}*/
	
	this.radarVisible = 0;
	var instance = this;
	this_animateRadar = function() { instance.animateRadar(); }
	this.radarInterval = setInterval(this_animateRadar, 100);
}

////////////////////////////////////////////////////////////////////////////////
/// 
yweather.prototype.animateRadar = function()
{
	if(this.accordian.open.index == 1) ///< Only animate if the panel is open
	{
		var nextRadar = this.radarVisible + 1;
		if(nextRadar >= this.radarDivs.length) nextRadar = 0;
		
		addStyle(this.radarDivs[this.radarVisible], "posHide");
		removeStyle(this.radarDivs[nextRadar], "posHide");
		
		this.radarVisible = nextRadar;
		/// Make sure image within div is loaded or don't animate.
		/// Some browsers don't support img.complete (safari), they will just
		/// show it.	
		/*
		var img = this.radarImages[nextRadar];
		if(img.complete != false || typeof img.complete == "undefined")
		{
			visibleImg = document.getElementById("yweather_radar");
			visibleImg.src = img.src;
			this.radarVisible = nextRadar;
		}*/
	}
}

////////////////////////////////////////////////////////////////////////////////
/// onkeypress callback for textbox. Treats press like a submit if they hit
/// return.
yweather.prototype.inputReturnCheck = function(e)
{
	if(!e) e = window.event;
	var k = e.kcode ? e.kcode : (e.which ? e.which : e.keyCode );
	if(k == 13) this.onSearch();
}

////////////////////////////////////////////////////////////////////////////////
/// onclick callback for the zip search button. Gets the value from the search
/// box and sends it to zipSearch.
yweather.prototype.onSearch = function()
{
	this.zipSearch(document.getElementById(this.id + "_search").value);
}

////////////////////////////////////////////////////////////////////////////////
/// Sends the zip code to the php widget object and waits for a response of
/// Yahoo Weather RSS.
yweather.prototype.zipSearch = function(zip)
{
	clearInterval(this.radarInterval);
	
	var loc = this.ajax.getElement("location");
	loc.innerHTML = "Loading...";
	
	this.accordian.collapse();
	this.accordian.lock();
	
	this.ajax.request("/widgets2/yweather.php?search=" + zip);
}




