document.getElementsByClassName = function(cl) {
    var retnode = [];
    var myclass = new RegExp('\\b'+cl+'\\b');
    var elem = this.getElementsByTagName('*');
    for (var i = 0; i < elem.length; i++) {
    var classes = elem[i].className;
        if (myclass.test(classes)) retnode.push(elem[i]);
    }
    return retnode;
};

var oNeighborhoodMap = new function NeighborhoodMap() {
	
	//transforms from array to object, where original first array index is property names, remaining records are values
	function formatHeaderValueArrayToObject( aDoubleArray ) {
		
		var aObjects = [],
			aPropNames = aDoubleArray[0] || []; //electing not to alter original double array
		
		for ( var i=1; i<aDoubleArray.length; i++ )	{
			
			var oThisObject = {}
			
			for ( var j=0; j<aPropNames.length; j++ )
				oThisObject[ aPropNames[j] ] = aDoubleArray[i][j];
				
			aObjects.push( oThisObject );
		
		}
		
		return aObjects;
		
	}
	
	var oGMap,
		oGMgr,
		oMarkerGroups = new function MarkerGroupManager() {
			
			var o = {},
				nId = 0,
				aDebugRequiredProps = [ "type", "imgUrl", "name", "lat", "lng", "zoom" ];
				
			this.nGroupPropsIndex = 0;
			this.nPointsIndex = 4; //a place holder for which index is the point property array
			
			this.get = function getImpl( sId ) {
				return sId ? o[ sId ] : o;
			};
			
			this.add = function addImpl( aMarkersRaw ) {
				
				var aMarkers = formatHeaderValueArrayToObject( aMarkersRaw );
				
				for ( var i in aMarkers ) {
					
					var xType = aMarkers[i].type;
					
					if ( o[ xType ] )
						o[ xType ].addPoint( aMarkers[i] );
					
					else
						o[ xType ] = new MarkerGroup( aMarkers[i] );
					
				}
				
				oGMgr.refresh();
				
			};
			
			this.show = function showImpl() {
				
				for ( var i in o )
					o[i].show( true );
				
				oGMgr.refresh();
				
			};
			
			this.hide = function showImpl() {
				for ( var i in o )
					o[i].hide();
			};
			
		}(),
		oNeighborhoodMap = this,
		oGroupVisibilityControl;
	
	this.mAttachGroupControl;
	this.mHideViewGroupsList;
	
	//temp debug
	this.oMarkerGroups = oMarkerGroups;
	
	var getIcon = (function getIconInit() {
	
		var o = {};
		
		return function getIconImpl( sImgUrl ) {
		
			var oIcon = o[ sImgUrl ];
			
			if ( oIcon == null ) {
			
				oIcon = new GIcon();
				oIcon.image = sImgUrl;
				oIcon.iconSize = new GSize( 18, 18 );
				oIcon.iconAnchor = new GPoint( 9, 4 );
				
				o[ sImgUrl ] = oIcon;
					
			}
			
			return oIcon;
		
		};
	
	})();
	
	function getNodes( oNode, sTag ) {
		
		var a = [];
		
		//in this case we're looking for words not line breaks or tabs etc..
		if ( !sTag )
			a.push( oNode );
		
		else if ( oNode.tagName && oNode.tagName.toLowerCase() == sTag.toLowerCase() )
			a.push( oNode );
		
		if ( oNode.childNodes ) {
			
			var aChildNodes = oNode.childNodes;
			
			for ( var i=0; i<aChildNodes.length; i++ )
				a = a.concat( getNodes( aChildNodes[i], sTag ) );
				
		}
		
		return a;
		
	}
	
	function getTextNodes( oNode ) {
			
		var a = [];
		
		//in this case we're looking for words not line breaks or tabs etc..
		if ( oNode.nodeValue && /\w/.test( oNode.nodeValue ) )
			a.push( oNode );
		
		else if ( oNode.childNodes ) {
			
			var aChildNodes = oNode.childNodes;
			
			for ( var i=0; i<aChildNodes.length; i++ )
				a = a.concat( getTextNodes( aChildNodes[i] ) );
			
		}
		
		return a;
		
	}

	var oLegendDiv;
	
	var getTemplate = (function getTemplateInit() {
		
		var o = {};
		
		return function getTemplateImpl( sId ) {
			
			if ( !o[ sId ] )
				o[ sId ] = document.getElementById( sId );
				
			if ( o[ sId ] == null )
				throw new Error( "template element not found | id: " + sId );
			
			var oClone = o[ sId ].cloneNode( true );
			oClone.style.display = "";
			return oClone;
				
		};
		
	})();
	
	function getProcessedTemplate( sId, oProps ) {
		
		var oTemplate = getTemplate( sId ),
			aTextNodes = getTextNodes( oTemplate );
		
		//setup token reader
		var aTokens = [];
		
		for ( var i in oProps )
			aTokens.push( i );
		
		var rTokens = new RegExp( "\\{(?:" + aTokens.join( "|" ) + ")\\}", "g" );
		
		for ( var i=0; i<aTextNodes.length; i++ ) {
			
			var oNode = aTextNodes[i],
				sNodeValue = oNode.nodeValue,
				aMatch = sNodeValue.match( rTokens );
			
			if ( aMatch ) for ( var j=0; j<aMatch.length; j++ ) {
				var sMatch = aMatch[j];
				sNodeValue = sNodeValue.replace( sMatch, oProps[ sMatch.substring( 1, sMatch.length - 1 ) ] );
			}
			
			oNode.nodeValue = sNodeValue;
			
		}

		addIconAsBackground( oTemplate, oProps.imgUrl );
		
		return oTemplate;
		
	}

	function addIconAsBackground( oNodes, sImgUrl ) {
		
		var aNodes = getNodes( oNodes );
			
		for ( var i=0; i<aNodes.length; i++ ) {
			
			var oNode = aNodes[i];
			
			if ( oNode.id == "marker-icon" )
				oNode.style.backgroundImage = "url(" + sImgUrl + ")";
			
		}
			
	}

	function MarkerGroup( oMarker ) {
		
		var a = [], //markers
			z = [], //zoom levels
			bVisibilityDefault = true,
			bDefaultLegend = false,
			//first marker sets start visiblity for group
			bVisible = typeof oMarker.visible != "undefined" ? oMarker.visible : bVisibilityDefault,
			sType = oMarker.type,
			oLegendTitleNode,
			aLegendNodes = [],
			bToggleLegendVisibility = false,
			oPointTracker = {}; //indexes points by lat-lng to avoid duplication
		
		function show( bSuppressRefresh ) {
			
			for ( var i in a )
				oGMgr.addMarker( a[i], z[i] );
			
			if ( bToggleLegendVisibility ) {
			
				if ( oLegendTitleNode )
					oLegendTitleNode.style.display = "";
				
				for ( var i=0; i<aLegendNodes.length; i++ )
					aLegendNodes[i].style.display = "";
					
			}
			
			if ( !bSuppressRefresh )
				oGMgr.refresh();
				
			bVisible = true;
				
		}
		
		this.get = function getImpl() {
			return a;
		};
		
		this.getType = function getTypeImpl() {
			return sType;
		};
		
		this.addPoint = function addPointsImpl( oMarkerData ) {
			initPoint( oMarkerData );
		};
		
		this.isVisible = function isVisibleImpl() {
			return bVisible;
		};
		
		this.show = show;
		this.hide = hide;
	
		function hide() {
			
			for ( var i in a )
				oGMgr.removeMarker( a[i] );
			
			if ( bToggleLegendVisibility ) {

				if ( oLegendTitleNode )
					oLegendTitleNode.style.display = "none";
				
				for ( var i=0; i<aLegendNodes.length; i++ )
					aLegendNodes[i].style.display = "none";
					
			}
			
			bVisible = false;
			
		};
		
		function initPoint( oProps ) {
			
			//already on map?
			var sPropIndex = oProps.lat + "-" + oProps.lng;
			
			if ( oPointTracker[ sPropIndex ] )
				return;
			else
				oPointTracker[ sPropIndex ] = true;
			
			oProps.visible = typeof oProps.visible != "undefined" ? oProps.visible : bVisibilityDefault;
			oProps.legend = typeof oProps.legend != "undefined" ? oProps.legend : bDefaultLegend;
			
			var	oPoint = new GLatLng( oProps.lat, oProps.lng ),
				oGMarker = new GMarker( oPoint, { icon: getIcon( oProps.imgUrl ) } ),
				oInfoContents,
				mOpenInfoWindow = function infoWindowImpl() {
					oGMap.openInfoWindow( oPoint, oInfoContents );
					//this code always runs on click but its purpose is for 
					//a legend item click where the group is hidden - makes this marker reappear...
					oGMgr.addMarker( oGMarker, oProps.zoom || 0 );
				};
			
			oInfoContents = getProcessedTemplate( "info-window", oProps );
				
			GEvent.addListener( oGMarker, "click", mOpenInfoWindow );
			
			if ( oProps.legend ) try {
				
				if ( !oLegendTitleNode ) try {
					oLegendTitleNode = getProcessedTemplate( "legend-title", oProps );
					oLegendDiv.appendChild( oLegendTitleNode );
				} catch ( e ) {
					
				}
		
				var oLegendTemplate = getProcessedTemplate( "legend-marker", oProps );
				
				GEvent.addDomListener( oLegendTemplate, "click", mOpenInfoWindow );
				
				if ( aLegendNodes.length )
					oLegendDiv.insertBefore( oLegendTemplate, aLegendNodes[ aLegendNodes.length - 1 ].nextSibling );
				
				else
					oLegendDiv.appendChild( oLegendTemplate );
				
				aLegendNodes.push( oLegendTemplate );
				
			} catch ( e ) {
				oProps.legend = false;
			}
			
			a.push( oGMarker );
			z.push( oProps.zoom || 0 ); //0 = all zooms
			
			if ( oProps.visible && bVisible )
				oGMgr.addMarker( oGMarker, oProps.zoom || 0  );
			
		}
					
		//some init code
		oGroupVisibilityControl.addGroupControl( this );
		initPoint( oMarker );
		
	}
	
	//group visibility toggle... (extends GControl)
	//note - while this is a GControl for itself, I'm "inserting" it into 
	//the GHierarchicalMapTypeControl() for positioning
	//limitation - currently only supports 1 map on the page - for more needs the following:
		//timeout - calls a global function to attach control to map - needs to be managed if there's more than 1 map
		//attaches to parent document.getElementById( "hmtctl" ) - there will be 2 such elements if 2 maps...
	function GroupVisibilityControl() {
		
		function mMouseOver() {
			oMarkerGroupList.style.display = "";
			bMouseOut = false;
		}
		
		function mMouseOut() {
			bMouseOut = true;
			setTimeout( "oNeighborhoodMap.mHideViewGroupsList()", 2000 );
		}
		
		var oOuterButton = document.createElement( "div" ),
			oInnerButton = document.createElement( "div" ),
			oMarkerGroupList = document.createElement( "div" ),
			bMouseOut = true;
		
		this.init = function initImpl() {
			
			//copy the google styles
			oOuterButton.style.border = "1px solid black"; 
			oOuterButton.style.position = "absolute";
			oOuterButton.style.backgroundColor = "white"; 
			oOuterButton.style.textAlign = "center"; 
			oOuterButton.style.width = "5em"; 
			oOuterButton.style.cursor = "pointer"; 
			oOuterButton.style.right = "11em";
			
			this.setButtonStyle_( oInnerButton );
			
			oMarkerGroupList.style.border = "1px solid black"; 
			oMarkerGroupList.style.position = "absolute";
			oMarkerGroupList.style.backgroundColor = "white"; 
			oMarkerGroupList.style.textAlign = "left";
			oMarkerGroupList.style.cursor = "pointer"; 
			oMarkerGroupList.style.left = "-1px";
			oMarkerGroupList.style.padding = "1px 0px";
			
			oNeighborhoodMap.mHideViewGroupsList = function hideViewGroupsListImpl() {
				if ( bMouseOut )
					oMarkerGroupList.style.display = "none";
			};
			
			oNeighborhoodMap.mHideViewGroupsList();
			
			oNeighborhoodMap.mAttachGroupControl = function() {
				
				var oParent = document.getElementById( "hmtctl" );
				
				if ( oParent != null ) {
					
					oParent.appendChild( oOuterButton );
					oOuterButton.appendChild( oInnerButton );
					oInnerButton.appendChild( document.createTextNode( "More..." ) );
					oOuterButton.appendChild( oMarkerGroupList );
					
				}
				
				else
					setTimeout( "oNeighborhoodMap.mAttachGroupControl()", 100 );
				
			}
			
			oNeighborhoodMap.mAttachGroupControl();
			
			return oOuterButton;
			
		};
			
		//config'd to add mouseover event when first marker group added, not before...
		var mAddGroupControl = (function mAddGroupControlInit() {
			
			function addGroupControlFinal( oMarkerGroup ) {

				var oToggleOption = document.createElement( "div" ),
					oCheckBox = document.createElement( "input" );
				
				oToggleOption.style.whiteSpace = "nowrap";
				oToggleOption.style.fontSize = "11px";
				oToggleOption.style.padding = "0px 2px 0px 0px";

				GEvent.addDomListener( oToggleOption, "mouseover", mMouseOver );
				GEvent.addDomListener( oToggleOption, "mouseout", mMouseOut );
			
				oCheckBox.type = "checkbox";
				oCheckBox.style.verticalAlign = "middle";
				oToggleOption.appendChild( oCheckBox );
				oCheckBox.checked = oMarkerGroup.isVisible();
				
				oToggleOption.appendChild( document.createTextNode( oMarkerGroup.getType() ) );
				
				oMarkerGroupList.appendChild( oToggleOption );
				
				GEvent.addDomListener( oToggleOption, "click", function toggleImpl() {
					
					if ( oMarkerGroup.isVisible() ) {
						oCheckBox.checked = false
						oMarkerGroup.hide();
					}
					
					else {
						oCheckBox.checked = true;
						oMarkerGroup.show();
					}
					
				} );

			}
		
			return function mAddGroupControlFirstImpl( oMarkerGroup ) {
				GEvent.addDomListener( oInnerButton, "mouseover", mMouseOver );
				GEvent.addDomListener( oInnerButton, "mouseout", mMouseOut );
				addGroupControlFinal( oMarkerGroup );
				mAddGroupControl = addGroupControlFinal;
			};
		
		})();
		
		this.addGroupControl = function addGroupControlImpl( oMarkerGroup ) {
			mAddGroupControl( oMarkerGroup );
		};
		
	}
	
	GroupVisibilityControl.prototype = new GControl();
	
	GroupVisibilityControl.prototype.initialize = function() {
		return this.init();
	};
	
	// By default, the control will appear in the top left corner of the
	// map with 7 pixels of padding.
	GroupVisibilityControl.prototype.getDefaultPosition = function() {}
	
	// Sets the proper CSS for the given button element.
	GroupVisibilityControl.prototype.setButtonStyle_ = function( oButton ) {
		oButton.style.borderStyle = "solid"; 
		oButton.style.borderColor = "white rgb(176, 176, 176) rgb(176, 176, 176) white";
		oButton.style.borderWidth = "1px";
		oButton.style.fontSize = "12px";
		oButton.style.color = "black"
		oButton.style.cursor = "pointer";
		oButton.style.textAlign = "center";
		oButton.style.fontFamily = "Arial,sans-serif";
	}
	
	function fetchMarkers() {
		
		var oGLatLngBounds = oGMap.getBounds(),
			oNE = oGLatLngBounds.getNorthEast(),
			oSW = oGLatLngBounds.getSouthWest();
		
			/* Call the GetPointsOfInterestByBoundingBox web method.
			 * Pass MaxX, MaxY, MinX, MinY up to it.
			 */
			NeighborhoodPreviews.Services.PointOfInterestService.GetPointsOfInterestByBoundingBoxJson(
				oNE.lng(), oNE.lat(), oSW.lng(), oSW.lat(),
				function sucess( aMarkersRaw ) {
					oMarkerGroups.add( eval( "(" + aMarkersRaw + ")" ) );
				},
				function failed( aMarkersRaw ) {
					
				},
				null
			);
			
	}

	this.init = function initImpl() {
		
		if ( GBrowserIsCompatible() ) {
		
		    var zoom = 7;
		    
		    if (extent[2] != '' && extent[2] != null) {
		        zoom = parseInt(extent[2], null);
		    }
		    
		    zoom = 18;
		    
			//page init data for the map
			var oMapInitData = [
				
				//number: map center lat
				parseFloat(extent[0]),
				
				//number: map center long
				parseFloat(extent[1]),
				
				//number: map start zoom
				zoom
				
			];
			
			var nStartLat = oMapInitData[0],
				nStartLong = oMapInitData[1],
				nStartZoom = oMapInitData[2];
			
			var oMapDiv = document.getElementById( "map" );
			
			//init oLegendDiv for the legend methods
			oLegendDiv = document.getElementById( "legend" );
			
			//init map
			oGMap = new GMap2( oMapDiv );
			oGMap.addControl( new GScaleControl() );
			oGMap.addControl( new GSmallMapControl() );
			oGMap.addControl( new GHierarchicalMapTypeControl() );
			oGMap.setCenter( new GLatLng( nStartLat, nStartLong ), nStartZoom );
			oGMap.enableDoubleClickZoom();
			
			//init group visibility menu
			oGroupVisibilityControl = new GroupVisibilityControl();
			oGMap.addControl( oGroupVisibilityControl );
			
			// Have to hardcode the url here, as the file won't load correctly otherwise.
			// I think this has to do with IIS MIME types, but I'm not really sure.
			var geoXml = new GGeoXml('http://www.neighborhood-previews.com/assets/kml/' + document.getElementsByClassName('neighborhood-name')[0].innerHTML.replace(new RegExp(' ', 'g'), '') + '.kmz', function() {
			    if (geoXml.loadedCorrectly()) {
			        geoXml.gotoDefaultViewport(oGMap);
			    }
			    
			    //move
			    GEvent.addListener( oGMap, "moveend", fetchMarkers );
    			
			    //zoom
			    GEvent.addListener( oGMap, "zoomend", fetchMarkers );
			});
			oGMap.addOverlay(geoXml);
			
			oGMgr = new MarkerManager( oGMap );
			
			fetchMarkers();
		}
		
	}
	
}();