﻿/*extern $, Element, Lang, XMLDocument, XPathResult, document, window, google,
         Dom, Event, YAHOO, MarkerManager
*/

/*members "0", Connect, Event, LatLng, Locations, Map, Map2, 
    MapTypeControl, Marker, Message, ORDERED_NODE_SNAPSHOT_TYPE, 
    ScaleControl, SmallZoomControl, Types, Unload, addControl, addListener, 
    addMarkers, address, appendChild, asyncRequest, base, batch, className, 
    clearMarkers, closeInfoWindow, createElement, createNSResolver, 
    createTextNode, currTypeID, data, doClickType, doMarkerClick, 
    doOtherLoc, doclickLocation, documentElement, els, enableContinuousZoom, 
    evaluate, failure, firstChild, getAttribute, getElementsByTagName, 
    getLatLng, getLocationTypeID, getTarget, hasChildNodes, hasFeature, 
    hasOwnProperty, href, id, implementation, init, init2, init3, innerHTML, 
    lat, length, lng, load, locality, locations, makeInfoWindowForChoices, 
    makeInfoWindowForLocation, manager, map, maps, marginTop, markers, name, 
    nodeName, openInfoWindow, opera, org, otherLoc, ownerDocument, 
    parentNode, placeMarkers, postalCode, prototype, push, refresh, region, 
    removeChild, responseXML, scope, selectNodes, selectSingleNode, 
    setCenter, setOnLoadCallback, setTimeout, snapshotItem, snapshotLength, 
    sort, split, stopEvent, streetAddress, style, success, target, timeout, 
    title, toString, util
*/

"use strict";


/*! mozXPath [http://km0ti0n.blunted.co.uk/mozxpath/] km0ti0n@gmail.com
  * Code licensed under Creative Commons Attribution-ShareAlike License
  * (slightly modified to avoid errors in IE)
  * http://creativecommons.org/licenses/by-sa/2.5/
  */
if (document.implementation &&
	document.implementation.hasFeature &&
	document.implementation.hasFeature("XPath", "3.0") &&
	(!window.opera) &&
	XMLDocument &&
	!XMLDocument.selectSingleNode) {

	XMLDocument.prototype.selectNodes = function (cXPathString, xNode) {
		if (!xNode) {
			xNode = this;
		}
		var oNSResolver = this.createNSResolver(this.documentElement);
		var aItems = this.evaluate(cXPathString, xNode, oNSResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		var aResult = [ ];
		for (var i = 0; i < aItems.snapshotLength; i++) {
			aResult[i] =  aItems.snapshotItem(i);
		}
		return aResult;
	};

	XMLDocument.prototype.selectSingleNode = function (cXPathString, xNode) {
		if (!xNode) {
			xNode = this;
		}
		var xItems = this.selectNodes(cXPathString, xNode);
		if (xItems.length > 0) {
			return xItems[0];
		} else {
			return null;
		}
	};

	Element.prototype.selectNodes = function (cXPathString) {
		if (this.ownerDocument.selectNodes) {
			return this.ownerDocument.selectNodes(cXPathString, this);
		} else {
			throw "For XML Elements Only";
		}
	};

	Element.prototype.selectSingleNode = function (cXPathString) {
		if (this.ownerDocument.selectSingleNode) {
			return this.ownerDocument.selectSingleNode(cXPathString, this);
		} else {
			throw "For XML Elements Only";
		}
	};
}


/** @namespace */
var LOCMAP = {


	/** Google map */
	map: null,

	/** collection of markers, by typeID */
	markers: { },

	/** Google Marker Manager */
	manager: null,

	/** MLHLocations.xml
	 * @type {xml} */
	data: null,
	
	/** Current Location Type ID selected **/
	currTypeID: "0",

	/** References to elements used */
	els: {
		Message     : 'Messages',
		Types       : 'Types',
		Locations   : 'Locations',
		Map         : 'GoogleMap'
	},

	/** Return the currently selected Type ID */
	getLocationTypeID: function () {
		return this.currTypeID;
	},


	/** Begin initialization of the location map.
	 *  This is an unfortunately lengthy process, and requires
	 *  a considerable amount of AJAX and XML processing and such.
	 *  I have done what I can to reduce processing time, but in order
	 *  to avoid the "Script is taking too long!" errors, I am inserting
	 *  breaks and continuations in the initialization process.  There are
	 *  a couple of places where window.setTimeout is used with a very low
	 *  timer--this is to give the browser a chance to breathe.
	 *  To make scope simpler, initialization continues in a series of sequential
	 *  functions: init, init2, init3, etc.
	 *  This first one, init, is called automatically upon page load. */
	init: function () {
		var x;
		var self = this;

		// Resolve elements
		for (x in this.els) {
			if (Lang.hasOwnProperty(this.els, x)) {
				this.els[x] = $(this.els[x]);
			}
		}

		// Selection should always be "All" upon first load
		$('Type-0').className = 'selected';

		// Initialize the map
		google.load("maps", "2");
		google.setOnLoadCallback(function () {
			self.map = new google.maps.Map2(self.els.Map);
			self.map.setCenter(new google.maps.LatLng(39.986, -75.4133), 10);

			self.map.enableContinuousZoom();
			self.map.addControl(new google.maps.SmallZoomControl());
			self.map.addControl(new google.maps.MapTypeControl());
			self.map.addControl(new google.maps.ScaleControl());
			google.maps.Event.addListener(self.map, 'infowindowclose', function () {
				Dom.batch(self.els.Locations.getElementsByTagName('li'), function (o) {
					o.className = '';
				});
			});

			Event.addListener(window, 'unload', google.maps.Unload);

			if (self.data === null) {
				self.els.Message.innerHTML = 'Loading Locations...';
			} else if (self.data !== false) {
				self.els.Message.innerHTML = 'Creating Markers...';
				window.setTimeout(function () {
					self.init2();
				}, 1);
			}

		});

		// We need data.
		// Sending off AJAX upon first page load is bad form.
		// But doing otherwise would be an incredible complication.
		// Opting for simplicity.
		// Initialization is continued within the "Success" case.
		YAHOO.util.Connect.asyncRequest('GET', '/xml/Generated/MLHLocations.xml', {
			scope  : this,
			timeout: 10000,
			failure: function () {
				this.els.Message.innerHTML = 'ERROR: Could not load location information.';
				this.data = false;
			},
			success: function (o) {
				this.data = o.responseXML;

				if (this.map === null) {
					this.els.Message.innerHTML = 'Loading Map...';
				} else {
					this.els.Message.innerHTML = 'Creating Markers...';
					window.setTimeout(function () {
						self.init2();
					}, 1);
				}

			}
		}, null);
	},

	init2: function () {
		var self = this;

		function doMarkerClick() {
			self.doMarkerClick(this);
		}

		// Get a list of all TypeIDs and make arrays for them.
		var el = this.els.Types;
		var id;
		var li, lis = el.getElementsByTagName('li');
		var x = lis.length;
		while (x--) {
			li = lis[x];
			id = li.id.split('-')[1];
			this.markers[id] = [ ];
		}

		// Create a marker for each address.
		// Put it into the correct Type array.
		var addys = this.data.selectNodes("/MLHLocations/Address");
		var addy, point, lat, lng, marker, LocNodes, LocArray, title, y;
		x = addys.length;
		while (x--) {
			addy   = addys[x];
			lat    = parseFloat(addy.getAttribute("Latitude"));
			lng    = parseFloat(addy.getAttribute("Longitude"));

			// Get an array of locations for this marker.
			LocNodes = addy.selectNodes("Locations");
			LocArray = [ ];
			y = LocNodes.length;
			while (y--) {
				LocArray.push(LocNodes[y].getAttribute("id"));
			}

			// Find the title for this marker
			title = LocNodes[0].getAttribute("Name");

			// Create the marker
			point  = new google.maps.LatLng(lat, lng);
			marker = new google.maps.Marker(point, { title: title });
			marker.locations = LocArray;
			google.maps.Event.addListener(marker, 'click', doMarkerClick);

			// Add this marker to the Location Type arrays
			this.markers["0"].push(marker);
			y = LocNodes.length;
			while (y--) {
				this.markers[LocNodes[y].getAttribute("typeID")].push(marker);
			}

		}

		this.els.Message.innerHTML = 'Initializing...';
		window.setTimeout(function () {
			self.init3();
		}, 10);
	},

	init3: function () {

		// Clicking on a location name should spawn an info window.
		Event.addListener(this.els.Locations, 'click', this.doclickLocation, this, true);

		this.els.Message.innerHTML = 'Placing Markers...';
		var self = this;
		window.setTimeout(function () {
			self.placeMarkers();
		}, 10);

	},

	placeMarkers: function () {

		var typeID  = this.getLocationTypeID();

		if (this.manager === null) {
			this.manager = new MarkerManager(this.map);
		} else {
			this.manager.clearMarkers();
		}

		this.manager.addMarkers(this.markers[typeID], 3);
		this.manager.refresh();
		this.els.Message.innerHTML = '';
	},



	makeInfoWindowForChoices: function (locations, lat, lng) {
		var location;
		var list = [];
		var base = document.createElement('div');
		var h2   = document.createElement('h2');
		var ul   = document.createElement('ul');
		var li, a;

		var x = locations.length;
		while (x--) {
			location = this.data.selectSingleNode("/MLHLocations/Locations[@id = " + locations[x] + "]");
			list.push({
				id   : location.getAttribute("id"),
				name : location.getAttribute("Name")
			});
		}
		list = list.sort(function (a, b) {
			return (a.name < b.name) ? 1 : -1;
		});

		h2.appendChild(document.createTextNode('There are multiple location types at this location:'));
		x = list.length;
		while (x--) {
			li = document.createElement('li');
			li.style.marginTop = '0em';
			a  = document.createElement('a');
			a.appendChild(document.createTextNode(list[x].name));
			a.href = "javascript: LOCMAP.doOtherLoc('"  + list[x].id + "', '" + lat  + "', '" +  lng + "');";
			li.appendChild(a);
			ul.appendChild(li);
		}
		base.appendChild(h2);
		base.appendChild(ul);
		return base;
	},



	/** Returns a DOM representation of a location for the given address.
	 *  @param  {MLHLocations/Locations}         location
	 *  @return {DOM Element} */
	makeInfoWindowForLocation: function (location) {
		var typeID = this.getLocationTypeID();
		var locationID = location.getAttribute("id");
		var lat    = location.getAttribute("Latitude");
		var lng    = location.getAttribute("Longitude");
		var landingPage = location.getAttribute("LandingPage");
		var landingTarg = location.getAttribute("LandingTarget");
		var thumbnail   = location.getAttribute("thumbnail");
		var googAddr    = location.getAttribute('GoogleAddress');
		var lat         = location.getAttribute('Latitude');
		var lng         = location.getAttribute('Longitude');
		var others;
		var x, a, img, directions;

		// Find values for this location at this location.
		var val = {
			fn:               location.getAttribute("Name"),
			orgName:          location.getAttribute('orgName'),
			orgUnit:          location.getAttribute('orgUnit'),
			streetAddress:    location.getAttribute("streetAddress"),
			extendedAddress:  location.getAttribute('extendedAddress'),
			locality:         location.getAttribute("City"),
			region:           location.getAttribute("State"),
			postalCode:       location.getAttribute("Zip")
		};

		// Find other location type at this location.
		others = (typeID === "0") ?
		this.data.selectNodes(
			"/MLHLocations/Locations[" +
			"@Latitude = '" + lat + "' and @Longitude = '" + lng + "' and " +
			"not(@id = " + locationID + ")]") :
		this.data.selectNodes(
			"/MLHLocations/Practice[" +
			"typeID = '" + typeID + "' and " +
			"@Latitude = '" + lat + "' and @Longitude = '" + lng + "' and " +
			"not(@id = " + locationID + ")]");

		// Create DOM elements used to build the final product.
		var els = {
			base:             document.createElement('div'),
			fn:               document.createElement('h2'),
			org:              document.createElement('div'),
			orgName:          document.createElement('div'),
			orgUnit:          document.createElement('div'),
			adr:              document.createElement('div'),
			streetAddress:    document.createElement('div'),
			extendedAddress:  document.createElement('div'),
			locality:         document.createElement('span'),
			region:           document.createElement('span'),
			postalCode:       document.createElement('span'),
			otherLoc:         null
		};

		// Resolve all values to text nodes.
		for (x in val) {
			if (Lang.hasOwnProperty(val, x)) {
				if (val[x] === null) {
					val[x] = '';
				}
				val[x] = document.createTextNode(val[x]);
			}
		}

		els.base.className = 'vcard';

		// Location name
		if (val.fn.length) {
			if (landingPage && landingPage.length) {
				a = document.createElement('a');
				a.className = 'fn';
				a.href = landingPage;
				a.appendChild(val.fn);
				if (landingTarg && landingTarg.length) {
					a.target = landingTarg;
				}
				els.fn.appendChild(a);
			} else {
				els.fn.appendChild(val.fn);
			}
			els.base.appendChild(els.fn);
		}

		// Thumbnail
		if (thumbnail && thumbnail.length) {
			img = new Image(150, 88);
			img.className = 'photo';
			img.src = thumbnail;
			img.align = 'right';
			els.base.appendChild(img);
		}

		// orgName
		if (val.orgName.length) {
			els.orgName.appendChild(val.orgName);
			els.orgName.className = 'organization-name';
			els.org.appendChild(els.orgName);
		}
		if (val.orgUnit.length) {
			els.orgUnit.appendChild(val.orgUnit);
			els.orgUnit.className = 'organization-unit';
			els.org.appendChild(els.orgUnit);
		}
		if (val.orgName.length || val.orgUnit.length) {
			els.org.className = 'org';
			els.base.appendChild(els.org);
		}

		// Street address
		if (val.streetAddress.length) {
			els.streetAddress.appendChild(val.streetAddress);
			els.streetAddress.className = 'street-address';
			els.adr.appendChild(els.streetAddress);
		}

		// Extended address
		if (val.extendedAddress.length) {
			els.extendedAddress.appendChild(val.extendedAddress);
			els.extendedAddress.className = 'extended-address';
			els.adr.appendChild(els.extendedAddress);
		}

		// Locality (city)
		if (val.locality.length) {
			els.locality.appendChild(val.locality);
			els.locality.className = 'locality';
			els.adr.appendChild(els.locality);
			if (val.region.length) {
				els.adr.appendChild(document.createTextNode(', '));
			}
		}

		// Region (state)
		if (val.region.length) {
			els.region.appendChild(val.region);
			els.region.className = 'region';
			els.adr.appendChild(els.region);
			if (val.postalCode.length) {
				els.adr.appendChild(document.createTextNode('  '));
			}
		}

		// Postal code (zip)
		if (val.postalCode.length) {
			els.postalCode.appendChild(val.postalCode);
			els.postalCode.className = 'postal-code';
			els.adr.appendChild(els.postalCode);
		}

		// Address (Street, Extended, Locality, Region, Postal Code)
		if (els.adr.hasChildNodes()) {
			els.adr.className = 'adr';
			if (thumbnail) {
		//		els.adr.style.width = '320px';
				els.adr.style.whiteSpace = 'pre';
				els.adr.style.cssFloat = 'left';
			}
			els.base.appendChild(els.adr);
		}

		// Directions
		if (googAddr && googAddr.length) {
			directions = googAddr;
			if (lat && lng && lat.length && lng.length) {
				directions += ' @' + lat + ',' + lng;
			}
			directions = encodeURIComponent(directions);

			a = document.createElement('a');
			a.href = "http://maps.google.com/maps?f=d&q=" + directions;
			a.appendChild(document.createTextNode('Get Directions'));
			a.target = '_blank';
			if (els.adr.hasChildNodes()) {
				els.adr.appendChild(document.createElement('br'));
				els.adr.appendChild(a);
			} else {
				els.base.appendChild(a);
			}
		}

		return els.base;
	},



	doOtherLoc: function (locationID, lat, lng) {
		var location = this.data.selectSingleNode("/MLHLocations/Locations[@id = " + locationID + "]");
		//var addy     = location.selectSingleNode("Address[@Latitude = '" + lat + "' and @Longitude = '" + lng + "']");
		var latlng   = new google.maps.LatLng(parseFloat(lat), parseFloat(lng));
		var info     = this.makeInfoWindowForLocation(location);
		this.map.openInfoWindow(latlng, info);
		$('Location-' + locationID).className = 'selected';
	},


	doMarkerClick: function (marker) {
		var typeID    = this.getLocationTypeID();
		var locations = [];
		var latlng    = marker.getLatLng();
		var lat       = latlng.lat().toString();
		var lng       = latlng.lng().toString();
		var info;

		var nodes;
		var location, x;

		// Need a list of practices at this location for this selected specialty.
		if (typeID === '0') {
			locations = marker.locations;
		} else {
			nodes = this.data.selectNodes(
				"/MLHLocations/Locations[@Latitude = '" + lat + "' and @Longitude = '" + lng + "']");
			x = nodes.length;
			while (x--) {
				locations.push(nodes[x].getAttribute("id"));
			}
		}

		location = this.data.selectSingleNode("/MLHLocations/Locations[@id = " + locations[0] + "]");
		info     = this.makeInfoWindowForLocation(location);
		this.map.openInfoWindow(latlng, info);

	},

	
	doClickType: function (typeID) {
		var x;
		var ul     = this.els.Locations;
		
		/* Select the Location Type we were just on */
		$('Type-' + this.currTypeID).className = ''; 
		
		this.currTypeID = typeID;   /* set global variable currTypeID to currently selected Location Type ID */
		
		/* Select the new Location Type we just clicked */
		$('Type-' + this.currTypeID).className = 'selected'; 

		var aTypes = (typeID === "0") ?
		this.data.selectNodes("/MLHLocations/Locations") :
		this.data.selectNodes("/MLHLocations/Locations[@typeID = " + typeID + "]");

		function fnMakeNode(location) {
			var lat  = location.getAttribute("Latitude");
			var lng  = location.getAttribute("Longitude");
			var name = location.getAttribute("Name");
			var li   = document.createElement('li');
			var a    = document.createElement('a');

			li.id  = "Location-" + location.getAttribute("id");
			a.appendChild(document.createTextNode(name));

			a.href  =
			"http://maps.google.com/maps?f=q&q=" +
			encodeURIComponent(lat) + '+' +
			encodeURIComponent(lng) + '+' +
			encodeURIComponent("(" + name + ")") + "&z=15";

			a.target = "googlemap";

			li.appendChild(a);
			return li;
		}

		while (ul.firstChild) {
			ul.removeChild(ul.firstChild);
		}
		for (x = 0; x < aTypes.length; x++) {
			if (x > 0 && aTypes[x].getAttribute("Name") === aTypes[x - 1].getAttribute("Name")) {
			} else {
				ul.appendChild(fnMakeNode(aTypes[x]));
			}
		}

		// If an info windows is open, we should close it.
		this.map.closeInfoWindow();

		this.placeMarkers();

	},


	doclickLocation: function (e) {
		var oTarget = Event.getTarget(e);
		var li      = oTarget;
		var locID;
		var location, lat, lng, latlng;
		var info;
		Event.stopEvent(e);

		// Resolve click to LI node
		while (li.nodeName !== 'LI' && li.nodeName !== 'UL' && li.parentNode) {
			li = li.parentNode;
		}

		if (li.nodeName === 'UL') {
			return;
		}

		// Get location ID
		locID = li.id.split('-')[1];

		// Make an info window for this location.
		location = this.data.selectSingleNode("/MLHLocations/Locations[@id = " + locID + "]");
		info     = this.makeInfoWindowForLocation(location);

		// Determine location for the info window.
		lat      = parseFloat(location.getAttribute("Latitude"));
		lng      = parseFloat(location.getAttribute("Longitude"));
		latlng   = new google.maps.LatLng(lat, lng);

		// Although markers can open info windows by themselves, this
		// will break if the marker is hidden by the MarkerManager.
		// as a result, we will open the info window on the marker's
		// location, which will scroll the map to the window, which will
		// cause the marker manager to make the marker visible. TADA!
		this.map.openInfoWindow(latlng, info);

		// Highlight the selected location.
		li.className = "selected";

	}


};


LOCMAP.init();
