/**
* Copyright (c) 2006 Mashup Technologies LLC. All Rights Reserved.
* P.O. Box 397, Bellwood, PA 16617 
* http://www.mapbuilder.biz
*
* This code (including but not limited to the actual source code, documentation) 
* is not freeware and is intended for the use under special agreement between parties 
* according to the license agreement which is available at 
* http://mapbuilder.net/Integrator/License/
* 
* THIS SOFTWARE PRODUCT IS PROVIDED "AS IS" AND LICENSOR MAKE NO WARRANTY AS TO ITS USE, PERFORMANCE, 
* OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

var App = {
	Version: '2.01',
	// Set map title and description dynamically
	setMapTitle: false,
	setMapDescription: false,
	// Display checkbox on a sidebar
	checkBox: false,
	 
	debug: false,
	LogReader: null, 
	LogWriter: null,

	DataFeed: null,
	DetailFeed: null,
	// Use for server side (AJAX) filtering
	SearchOptions: {},
	// Client side filtering
	FilterOptions: {},

	gMap: null, 
	gMapAPIVersion: 1,
	geocoder: 'Google',
	gGeocoder: null, 
	gIcons: [], 
	gmarkers: [],
	activeMarker: null,
	activeMarkerID: null, 
	
	//Tooltip functionality
	flipMarkers: true,
	tooltip: null,
	tooltipTable: null,


	// Sidebar
	sideBarHtml: "",
	// Background color of an active row
	sideBarRowActive: "#F0DFC5",
	sideBarRowOver: "#E4E2D4",
	// Tooltip engines: v1, v2 
	// v2 uses enhanced positioning
	TooltipEngine: 'v1',
	//Possible values: Overlay, MarkerManager
	MarkersEngine: 'Overlay',
	//navigate to location
	navigateToLocation: false,
	//Show logo and information
	showAbout: true,

	Table: null,
	Rows: new Array(),
	
	//data object
	jsonData: null,
	imgURL: "",

	/* Accuracy Levels
	* 0 	Unknown location. (Since 2.59)
	* 1 	Country level accuracy. (Since 2.59)
	* 2 	Region (state, province, prefecture, etc.) level accuracy. (Since 2.59)
	* 3 	Sub-region (county, municipality, etc.) level accuracy. (Since 2.59)
	* 4 	Town (city, village) level accuracy. (Since 2.59)
	* 5 	Post code (zip code) level accuracy. (Since 2.59)
	* 6 	Street level accuracy. (Since 2.59)
	* 7 	Intersection level accuracy. (Since 2.59)
	* 8 	Address level accuracy. (Since 2.59)
	*/

	accuracy: [
		{
			version: '2.67',
			levels: [
				1, //0
				4, //1
				7, //2
				9, //3
				10, //4
				11, //5
				13, //6
				14, //7
				16 //8
			]
		}
	],


	load: function() {
		if((typeof Prototype=='undefined') || 
		parseFloat(Prototype.Version.split(".")[0] + "." +
					Prototype.Version.split(".")[1]) < 1.4)
		throw("MapObject requires the Prototype JavaScript framework >= 1.4.0");

		// Get API version
		this.gMapAPIVersion = this.GetGoogleMapsAPIVersion();

		// Instantiate a logreader and a logwriter, also init map if possible
		//ch Event.observe(window, 'load', this.init.bind(this)); 
	},

	init: function() {
		if (this.debug)
		{
			this.LogReader= new YAHOO.widget.LogReader(null, {right:"10px"});
			this.LogWriter = new YAHOO.widget.LogWriter("App Log Writer");
			this.LogWriter.log("Init  Started", "info");
		}

		//Hide map elements
		this.setMapElements(false);
			
		if (GBrowserIsCompatible()) {

			//First of all, add event to free resources on window unload
			Event.observe(window, 'unload', GUnload); 
			
			//Create SideBar table
			this.Table = document.createElement("table");
			this.Table.className="General";
			this.Table.cellPadding="0";
			this.Table.cellSpacing="0";
			this.Table.width="100%";
			this.TBody = document.createElement('tbody');
			this.Table.appendChild(this.TBody);

			//Load data
			//moved outside init methid this.loadData();

			// Register sidebar events
			//this.sideBarHtml += '<a href="javascript:myclick(' + id + ')" onmouseover="mymouseover('+id+')" onmouseout="mymouseout('+id+')">' + name + '</a><br>';
			var sideBar = document.getElementById("sideBar");
			Event.observe(sideBar, 'click', this.sideBarOnClick.bind(this)); 
			Event.observe(sideBar, 'mouseover', this.sideBarOnMouseOver.bind(this)); 
			Event.observe(sideBar, 'mouseout', this.sideBarOnMouseOut.bind(this)); 
		}
		else {
			alert("Sorry, the Google Maps API is not compatible with this browser");
		}
	},

	loadData: function()
	{
	    // We are using JSON instead of XML
		// Read the data from example.xml
		/* AJAX XML CAll
		/*
		var request = GXmlHttp.create();
		request.open("GET", "../data/example_xml.php", true);

		request.onreadystatechange = function() {

			
			if (request.readyState == 4) {
				var oTimer = new Util.Timer();
				var xmlDoc = request.responseXML;
				// obtain the array of markers and loop through it
				var markers = xmlDoc.documentElement.getElementsByTagName("marker");
          
				for (var i = 0; i < markers.length; i++) {
					// obtain the attribues of each marker
					var lat = parseFloat(markers[i].getAttribute("lat"));
					var lng = parseFloat(markers[i].getAttribute("lng"));
					var point = new GLatLng(lat,lng);
					var html = markers[i].getAttribute("html");
					var label = markers[i].getAttribute("label");
					// create the marker
					var marker = createMarker(point,label,html);

				}
				// put the assembled sideBarHtml contents into the sideBar div
				document.getElementById("sideBar").innerHTML = this.sideBarHtml;

			    if (this.debug) this.LogWriter.log(oTimer.Tick(1, "Loding finished: "), "time");
			}


		}
		request.send(null);
		*/

		//Hide tooltips - this fixes an issue when tooltip is visible and we reload data using keyboard navigation
		if (this.tooltip != null)
			this.tooltip.style.visibility="hidden";

		// Clear some properties
		this.activeMarkerID = null;

		// ****************************************************************
		// Fetch the JSON data file 
		var oDate=new Date();
		var url = this.DataFeed + "?Options=" + encodeURIComponent(Serialize({SearchOptions: this.SearchOptions})) + "&NC=" + oDate.getTime(); 

        if (this.debug) this.LogWriter.log("Going to download data from "+ url + "  ...");
		//GDownloadUrl("../data/example2.json", processData);

		//GDownloadUrl("service/FeedJSON_gz.php", this.processData.bind(this));
		//Util.getRadioValue(document.getElementById('F').GroupSize);

		//alert (Serialize({SearchOptions: this.SearchOptions}));
		GDownloadUrl(url, this.processData.bind(this));

		// ****************************************************************
	},

        // ****************************************************************
        // Reset map: set original center and zoom 
        resetMap: function(doc) {
                // Set map center and zoom
                this.gMap.setCenter(new GLatLng(parseFloat(this.jsonData.map.lat), parseFloat(this.jsonData.map.lng)), parseFloat(this.jsonData.map.zoom));
	}, 

	// ****************************************************************
	// Define the function thats going to process the JSON file
	processData: function(doc) {
        if (this.debug) this.LogWriter.log("Data received...");

		var oTimer = new Util.Timer();
		
		// UI Processing
	  	document.getElementById('LoadingImage').style.display = "none";
	  	document.getElementById('map').style.display = "block";
		// EOF UI Processing

		// load map 
		this.loadMap();

		// Parse the JSON document
		this.jsonData = eval('(' + doc + ')');
        
		//alert(jsonData.map.markers.length);
		var lat = null;
		var lng = null;
		var point = null;

		// Set page header
		if (this.setMapTitle)
			document.getElementById("MapTitle").innerHTML = this.jsonData.map.title;
		if (this.setMapDescription)
			document.getElementById("Map").innerHTML = this.jsonData.map.info.note;

		// Set map center and zoom
		this.gMap.setCenter(new GLatLng(parseFloat(this.jsonData.map.lat), parseFloat(this.jsonData.map.lng)), parseFloat(this.jsonData.map.zoom));

		// set up marker mouseover tooltip div
		this.tooltip = document.createElement("div");
		/* element.setAttribute("class", "somename") works in Firefox and Safari, but not IE. 
		* The gotca is that IE requires element.setAttribute("className", "somename").
		* element.className = "somename"; which works in all browsers.
		* 
		* this.tooltip.setAttribute('className', 'tooltip');
		* this.tooltip.setAttribute('class', 'tooltip');
		*/
		this.tooltip.className =  "tooltip";

		this.gMap.getPane(G_MAP_FLOAT_PANE).appendChild(this.tooltip);
		this.tooltip.style.visibility="hidden";

		// *************** Build icons ***************
		this.buildIcons();

		// *************** Build map legend ***************
		this.buildMapLegend();
		
		// *************** Search & Build markers ***************
		this.doSearch();

		if (this.debug)
		{
			this.LogWriter.log("No of Markers downloaded: " + this.jsonData.map.markers.length, "info");
            this.LogWriter.log(oTimer.Tick(1, "Data processing finished in "), "time");
		}
	},

	renderMarkers: function() {
		var oTimer = new Util.Timer();

		//Hide tooltips - this fixes an issue when tooltip is visible and we reload data using keyboard navigation
		if (this.tooltip != null)
			this.tooltip.style.visibility="hidden";

	    // Clear overlay
	    this.gMap.clearOverlays();

		// Hide loading image
		document.getElementById('sidebarLoading').style.display = "none";

		// Remove old sidebar
		try
		{
			document.getElementById("sideBar").removeChild(this.Table);
		}
		catch (e) {}

	    // Empty table
		this.Table = document.createElement("table");
		this.Table.className="SideBarTable";
		this.Table.cellPadding="0";
		this.Table.cellSpacing="0";
		this.TBody = document.createElement('tbody');
		this.TBody.className = "SideBarTableBody";
		this.Table.appendChild(this.TBody);
		var tr	= document.createElement('tr');
		var td = document.createElement('th');
		//[17:10] alexsian: sidebar: city state zip, carrier, network, download/upload
		td.appendChild(document.createTextNode(" "));
		tr.appendChild(td);
		td = document.createElement('th');
		td.appendChild(document.createTextNode("City"));
		tr.appendChild(td);
		td = document.createElement('th');
		td.appendChild(document.createTextNode("State"));
		tr.appendChild(td);
		td = document.createElement('th');
		td.appendChild(document.createTextNode("Zip"));
		tr.appendChild(td);
		td = document.createElement('th');
		td.appendChild(document.createTextNode("Carrier"));
		tr.appendChild(td);
		td = document.createElement('th');
		td.appendChild(document.createTextNode("Download/ Upload"));
		tr.appendChild(td);
		td = document.createElement('th');
		td.appendChild(document.createTextNode("Create Date"));
		tr.appendChild(td);

		this.TBody.appendChild(tr);

		var marker = null;
		this.activeMarkerID = null;
		var batch = [];
		for (var i=0; i<this.jsonData.map.markers.length; i++) {
			// Is marker visible?
			if (!this.jsonData.map.markers[i].visible) continue;

			// obtain the attribues of each marker
			lat = parseFloat(this.jsonData.map.markers[i].lat);
			lng = parseFloat(this.jsonData.map.markers[i].lng);
			point = new GLatLng(lat,lng);

			marker = this.createMarker(i, point);
            batch.push(marker);

    		//Show information for the 1st marker - trigger onclick event. We need to have an ID
			if (this.activeMarkerID == null)
			{
				this.activeMarkerID = marker.id;	
			}

		}

		// Do we have any active location?
		if (this.activeMarkerID == null)
		{	
			// Hide property information box 
			document.getElementById('dFeatures').style.display = 'none';
		}

		// put the assembled sideBarHtml contents into the sideBar div
		document.getElementById("sideBar").appendChild(this.Table);

		//Marker Manager
		if (this.MarkersEngine == 'MarkerManager')
		{
			var minZoom = 7;
			// Do we have min zoom in configuration?
			if (this.jsonData.map.MarkerManager.minZoom) minZoom = this.jsonData.map.MarkerManager.minZoom;

			mgr = new GMarkerManager(this.gMap);
			mgr.addMarkers(batch, minZoom);
			mgr.refresh();
		}

		//Show information for the 1st marker - trigger onclick event
		//GEvent.trigger(marker2, 'click', this);
		if (this.activeMarkerID != null)
		{
			this.onMarkerClick(this.activeMarkerID);
		}

		if (this.debug) this.LogWriter.log(oTimer.Tick(1, "renderMarkers("+this.MarkersEngine+") finished in "), "time");

	},

	// A function to create the marker and set up the event window
	createMarker: function(id, point) {
		
		// Active record
		var actRecord = this.jsonData.map.markers[id];

		// Standard icon
		var markerOptions = {}; 
		// Set icon
		try 
		{
			markerOptions.icon = this.gIcons[actRecord.iconname]; 
		}
		catch (e)
		{
			markerOptions.icon = new GIcon(G_DEFAULT_ICON); 
		    /*
			markerOptions.icon.shadow = "http://www.google.com/mapfiles/shadow50.png";
			markerOptions.icon.iconSize = new GSize(15, 22);
			markerOptions.icon.shadowSize = new GSize(10, 15);
			*/
		}

		// Instatiate new Gmarker
		var marker = new GMarker(point, markerOptions);

		// Set marker id
		marker.id =  id;
		
		// build tootip
		// Optimization: lets byuild tooltip only when we need it
		//opt var tootip = actRecord.address;

		// *************** store the name so that the tooltip function can use it ************
		//opt marker.tooltip = '<div class="tooltip">'+tootip+'</div>';
		
		GEvent.addListener(marker, "click", function() {
			this.onMarkerClick(id);
		}.bind(this));

		this.gmarkers[id] = marker;

		
		var a = null;
		// ***************** Create Sidebar Rows ************************
		var tr	= document.createElement('tr');
		tr.id = "row_" + id;

		//Cell 1 - (checkbox? ) and image 
		var td = document.createElement('td');
		if (this.checkBox)
		{
			var include	= document.createElement('input');
			include.setAttribute("rowid", id);
			include.type = "checkbox";
			include.value = "1";

			td.appendChild(include);
			tr.appendChild(td);
		}

		//[17:10] alexsian: sidebar: city state zip, carrier, network, download/upload
		
		//Cell - icon 
		td = document.createElement('td');
		var img = document.createElement('img');
		img.src = markerOptions.icon.image
		img.style.marginRight = "5px";
		td.appendChild(img);
		tr.appendChild(td);
		
		//Cell - city 
		td = document.createElement('td');
		a = document.createElement('a');
		// Set custom attribute(unique id) to identify an element
		a.id = "city_" + id;
		a.setAttribute("rowid", id);
		a.href = "javascript: void(null)";
		a.appendChild(document.createTextNode((actRecord.city == '') ? 'N/A': actRecord.city));
		td.appendChild(a);
		tr.appendChild(td);

		//Cell - state
		td = document.createElement('td');
		a = document.createElement('a');
		// Set custom attribute(unique id) to identify an element
		a.id = "state_" + id;
		a.setAttribute("rowid", id);
		a.href = "javascript: void(null)";
		a.appendChild(document.createTextNode(actRecord.state));
		td.appendChild(a);
		tr.appendChild(td);


		//Cell - zip 
		td = document.createElement('td');
		a = document.createElement('a');
		// Set custom attribute(unique id) to identify an element
		a.id = "zip_" + id;
		a.setAttribute("rowid", id);
		a.href = "javascript: void(null)";
		a.appendChild(document.createTextNode(actRecord.zip));
		td.appendChild(a);
		tr.appendChild(td);

		//Cell - carrier
		td = document.createElement('td');
		a = document.createElement('a');
		// Set custom attribute(unique id) to identify an element
		a.id = "carrier_" + id;
		a.setAttribute("rowid", id);
		a.href = "javascript: void(null)";
		a.appendChild(document.createTextNode(actRecord.carrier));
		td.appendChild(a);
		tr.appendChild(td);

		//Cell - download/upload
		td = document.createElement('td');
		a = document.createElement('a');
		// Set custom attribute(unique id) to identify an element
		a.id = "downup_" + id;
		a.setAttribute("rowid", id);
		a.href = "javascript: void(null)";
		a.appendChild(document.createTextNode(actRecord.download + "/" + actRecord.upload));
		td.appendChild(a);
		tr.appendChild(td);

		//Cell - Create Date
		td = document.createElement('td');
		a = document.createElement('a');
		// Set custom attribute(unique id) to identify an element
		a.id = "createdate_" + id;
		a.setAttribute("rowid", id);
		a.href = "javascript: void(null)";
		a.appendChild(document.createTextNode(actRecord.createdate));
		td.appendChild(a);
		tr.appendChild(td);

		//Append this TR to the TBody
		this.TBody.appendChild(tr);

		// Add overley to a marker
		//Do we use Marker Manager 
		if (this.MarkersEngine != 'MarkerManager')
		{
			this.gMap.addOverlay(marker);
		}

		// *********** The new marker "mouseover" and "mouseout" listeners ***********
		GEvent.addListener(marker,"mouseover", function() {
			this.showTooltip(marker);
			// Change active sidebar row background color
		}.bind(this));
		   
		GEvent.addListener(marker,"mouseout", function() {
			this.tooltip.style.visibility="hidden";
			// Change active sidebar row background color back to white for non active marker
			if (this.activeMarkerID != id)
			{
				document.getElementById('row_'+ id).style.backgroundColor = "#FFF";
			}

		}.bind(this));

     	// Finally return marker
     	return marker; 
	},


	// A function to create the marker and set up the event window
	buildIcons: function() {
		var oTimer = new Util.Timer();
		var icons = this.jsonData.map.icons;

		var appIcon = null;
		var name = null;
		var x = null;
		var y = null;
		for (var i = 0; i < icons.length ; i++)
		{

			// Required: icon name
			name = icons[i].name;		

			// Test Variable
			x = "" ;				
			// Test Variable
			y = "" ;				

			// Check for an icon copy
			x = icons[i].copy;

			// Make sure icon to copy has been built
			if (x && gicons[x])			
			{
				appIcon = new GIcon(gicons[x]);
			} 
			else
			{
				appIcon = new GIcon();
			}

			// Required: Icon Image
			appIcon.image = icons[i].image.src;	

			x = "" ;
			y = "" ;
			// Check for an icon anchor
			x = icons[i].xanchor;
			y = icons[i].yanchor;

			if (x != null && y!= null && x && y)
			{
				appIcon.iconAnchor = new GPoint(parseInt(x),parseInt(y));
			}

			x = "" ;
			y = "" ;

			// Check for an icon infowindow anchor
			x = icons[i].xiwanchor;
			y = icons[i].yiwanchor;

			if (x != null && y!= null && x && y)
			{
				appIcon.infoWindowAnchor = new GPoint(parseInt(x),parseInt(y));
			}

			x = "" ;
			y = "" ;
			// Check for icon image height and width
			x = icons[i].image.width;
			y = icons[i].image.height;

			if (x != null && y!= null && x && y)
			{
				appIcon.iconSize = new GSize(parseInt(x),parseInt(y));
			}

			x = "" ;
			// Check for an shadow image
			x = icons[i].shadow.src;

			if (x != null && x)
			{
				appIcon.shadow = x ;
			}

			x = "" ;
			y = "" ;
			// Check for an icon shadow dimension
			x = icons[i].shadow.width;
			y = icons[i].shadow.height;

			if (x != null && y!= null && x && y)
			{
				appIcon.shadowSize = new GSize(parseInt(x),parseInt(y));
			}
      
			this.gIcons[name] = appIcon;

		}

		if (this.debug) this.LogWriter.log(oTimer.Tick(1, "Icons building finished in "), "time");
	},
	// A function to create the marker and set up the event window
	buildMapLegend: function() {
		// Do not build legent twice
		if (document.getElementById('mapLegendTable') != null) return; 

		var oTimer = new Util.Timer();
		var icons = this.jsonData.map.icons;

  		var el = document.getElementById('mapLegend');

	    // Build table
		var Table = document.createElement("table");
		Table.className="mapLegendTable";
		Table.id="mapLegendTable";
		var TBody = document.createElement('tbody');
		Table.appendChild(TBody);
  		
  		var td = null;
  		var img = null;
  		var tr = null;
  		if (icons.length > 1)
  		{
			tr	= document.createElement('tr');
			td = document.createElement('th');
			td.colSpan = "4";
			td.appendChild(document.createTextNode("Map Legend"));
			tr.appendChild(td);
			//Append this TR to the TBody
			TBody.appendChild(tr);

			// Build legend
			var i = 0;
			var mid = Math.ceil(icons.length/2);
			do
			{

				tr	= document.createElement('tr');
				// Columns 1-2
				td = document.createElement('td');
				img = document.createElement('img');
				img.src = icons[i].image.src;
				img.style.marginRight = "5px";
				td.appendChild(img);
				tr.appendChild(td);

				td = document.createElement('td');
				td.appendChild(document.createTextNode(icons[i].caption));
				tr.appendChild(td);

				// Columns 3-4
				if (icons[i + mid] != null)
				{
					td = document.createElement('td');
					img = document.createElement('img');
					img.src = icons[i + mid].image.src;
					img.style.marginRight = "5px";
					td.appendChild(img);
					tr.appendChild(td);

					td = document.createElement('td');
					td.appendChild(document.createTextNode(icons[i + mid].caption));
					tr.appendChild(td);
				}
				i++;

				//Append this TR to the TBody
				TBody.appendChild(tr);
			}
			while (i < mid)

			el.appendChild(Table);
			el.style.display = "block";
		}

		if (this.debug) this.LogWriter.log(oTimer.Tick(1, "Map legend building finished in "), "time");
	},

	loadMap: function()
	{

		if (this.gMap == null)
		{
			this.gMap = new GMap2(document.getElementById("map")); 

			// Add controls
			this.gMap.addControl(new GLargeMapControl());
			this.gMap.addControl(new GMapTypeControl());

			/* Insert MapBuilder Logo */
			if (this.showAbout)
			{
				var info=document.createElement('div');
				info.id='MapBuilderInfo';
				info.style.position='absolute';
				info.style.right='1px';
				info.style.bottom='25px';
				info.style.backgroundColor='transparent';
				info.style.zIndex=25500;
				info.innerHTML='<a id="CreatedByMapBuilder" href="http://www.mapbuilder.net" title="Powered by MapBuilder Integrator&copy v'+this.Version+', GMap v'+this.gMapAPIVersion+'"><img src="http://mapbuilder.net/img/mo24.gif" alt="Powered By MapBuilder Integrator&copy v'+this.Version+', GMap v'+this.gMapAPIVersion+'" style="border:0; margin: 2px;"/></a>';
				this.gMap.getContainer().appendChild(info);
			}

			this.setMapElements(true);
		}
	},

	GetGoogleMapsAPIVersion: function() 
	{
		var v = 0;
		var scripts = document.getElementsByTagName("SCRIPT")
		for (var i=0; i<scripts.length; i++) {
			var pattern = /\/maps([0-9])?(\.?[0-9]+)(\.api)?\.js/;
			var m = pattern.exec(scripts[i].src);
			if (m != null) {
				if (m[1] == null) { v = parseFloat('1'+m[2]); }
				else { v = parseFloat(m[1]+m[2]); }
				break;
			}
		}
		return v;
	},

	// Event handler
	sideBarOnClick: function(e)
	{
		//if (this.debug) this.LogWriter.log('sideBarOnClick', 'information');
		var el = Event.element(e);
		// Run event for a checkbox
		if (el.nodeName == "INPUT") 
		{
			ProcessProperty(el.getAttribute("rowid"), el);
		}

		if (el.nodeName != "A")  return;
		this.onMarkerClick(el.getAttribute("rowid"));
	},

	// Event handler
	sideBarOnMouseOver: function(e)
	{
		//if (this.debug) this.LogWriter.log('sideBarOnMouseOver', 'information');
		var el = Event.element(e);
		if (el.nodeName != "A")  return;
		
		id = el.getAttribute("rowid")

		// Change active sidebar row background color
		if (this.activeMarkerID != id)
		{
			document.getElementById('row_'+ id).style.backgroundColor = this.sideBarRowOver;
		}

		// Show tootip
		this.showTooltip(this.gmarkers[id])

		if (this.flipMarkers)
		{
			// Create new marker with ACTIVE icon.
			// We'll use the same icon with bigger size
			var markerOptions2 = {}; 
			markerOptions2.icon = new GIcon(this.gmarkers[id].getIcon()); 
			markerOptions2.icon.image = markerOptions2.icon.image.replace(/_20_/, '_34_');
			markerOptions2.icon.shadow = "http://www.google.com/mapfiles/shadow50.png";
			markerOptions2.icon.iconSize = new GSize(20, 34);
			markerOptions2.icon.shadowSize = new GSize(37, 34);
			markerOptions2.icon.iconAnchor = new GPoint(10,34);
			this.activeMarker = new GMarker(this.gmarkers[id].getPoint(), markerOptions2);

			this.gMap.removeOverlay(this.gmarkers[id]);
			this.gMap.addOverlay(this.activeMarker);
		}
	},

	// Event handler
	sideBarOnMouseOut: function(e)
	{
		//if (this.debug) this.LogWriter.log('sideBarOnMouseOut', 'information');
		var el = Event.element(e);
		if (el.nodeName != "A")  return;

		id = el.getAttribute("rowid")

		// Change active sidebar row background color back to white for non active marker
		if (this.activeMarkerID != id)
		{
			document.getElementById('row_'+ id).style.backgroundColor = "#FFF";
		}

		// Remove tootip
		this.tooltip.style.visibility="hidden"

		// Do we change icon
		if (this.flipMarkers)
		{

			if (this.activeMarker != null)
				this.gMap.removeOverlay(this.activeMarker);
			this.gMap.addOverlay(this.gmarkers[id]);

			// The new marker "mouseover" and "mouseout" listeners
			GEvent.addListener(this.gmarkers[id],"mouseover", function() {
				this.showTooltip(this.gmarkers[id]);
			}.bind(this));
			       
			GEvent.addListener(this.gmarkers[id],"mouseout", function() {
				this.tooltip.style.visibility="hidden"
			}.bind(this));        
		}
	},

	// ***************** This function displays the tooltip *****************
	// it can be called from an icon mousover or a sideBar mouseover
	showTooltip: function(marker) {
		//if (this.debug) this.LogWriter.log("showTooltip start", "info");

		// Active record
		var actRecord = this.jsonData.map.markers[marker.id];

		//Create Images
		/*
		var imgTag = '';
		for (var i = 0; i < actRecord.Property.Images.length; i++)
		{
			if (i==0)
			{ 
				imgTag = '<img width="150" src="' + this.imgURL + actRecord.Property.Images[i].ImageUrl+'" alt="Image">';
				break;
			}
		}
		*/

		//Remove previous tooltip
		try
		{
			this.tooltip.removeChild(this.tooltipTable); 
		}
		catch(e) {}

		/* DHTML Table tooltip doesn't work in IE properly
		//Build tooltip table
		this.tooltipTable = document.createElement("table");
		this.tooltipTable.setAttribute('class', 'tooltipTable');
		this.tooltipTable.cellPadding="0";
		this.tooltipTable.cellSpacing="0";

		// Row 1
		var tr	= document.createElement('tr');
		//Cell 1
		var td = document.createElement('td');
		td.innerHTML = imgTag;
		tr.appendChild(td);

		//Cell 1
		var td = document.createElement('td');
		td.appendChild(document.createTextNode(actRecord.address));
		tr.appendChild(td);
		
		//
		this.tooltipTable.appendChild(tr);
		
		this.tooltip.appendChild(this.tooltipTable); 
		*/
		this.tooltip.innerHTML = '<h1><img src="' + this.gIcons[actRecord.iconname].image  + '" alt="Icon" />  ' + actRecord.address + " " + actRecord.city + ", " +  actRecord.state + " " + actRecord.zip + '</h1>' +
			'<p>Download: ' + actRecord.download + '&nbsp;Upload: ' +	actRecord.upload + ((actRecord.latency == "") ? '' : '&nbsp;Latency:' + actRecord.latency) + '</p>';
			//((actRecord.dslreportsurl != '') ? '<img src="' + actRecord.dslreportsurl + '" alt="dslreport" /><br />' : '') + 
			//((actRecord.speedtesturl != '') ? '<img src="' + actRecord.speedtesturl + '" alt="speedtest" /><br />' : '');

		if (this.TooltipEngine == "v2")
		{
			var point = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.fromDivPixelToLatLng(new GPoint(0,0),true),this.gMap.getZoom());
			var offset = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(),this.gMap.getZoom());
			var anchor = marker.getIcon().iconAnchor;
			var iconW = marker.getIcon().iconSize.width;
			var iconH = marker.getIcon().iconSize.height;

			// Get tooltip width using PrototypeLib
			var oTTDim = Element.getDimensions(this.tooltip);
			// -- or --
			// use simple DOM methods
			/*
			var oTTDim = {}
			oTTDim.width = this.tooltip.clientWidth; 
			oTTDim.height = this.tooltip.clientHeight; 
			*/

			// Map size in pixels
			// Useless var oMapSize = this.gMap.getSize();
			var maxOffset_NE = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.getBounds().getNorthEast(), this.gMap.getZoom());
			var maxOffset_SW = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.getBounds().getSouthWest(), this.gMap.getZoom());
			var ttOffset = this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.fromDivPixelToLatLng(new GPoint(oTTDim.width, oTTDim.height),true),this.gMap.getZoom());

			if (this.debug) 
			{
				var logMsg = "oTTDim.height: " + oTTDim.height;
				logMsg +=  " maxOffset_NE.x:  " +  maxOffset_NE.x + " maxOffset_NE.y: " + maxOffset_NE.y;
				logMsg +=  " maxOffset_SW.x:  " +  maxOffset_SW.x + " maxOffset_SW.y: " + maxOffset_SW.y;
				logMsg +=  " offset.y: " + offset.y + " point.y: " + point.y + " anchor.y: " + anchor.y;

				logMsg +=  " COND X: " + (maxOffset_NE.x - offset.x ) + " < " + oTTDim.width;
			}

			if (maxOffset_NE.x - offset.x  <  oTTDim.width)
			{
				// Display left side tooltip
				posX = offset.x - point.x - anchor.x - oTTDim.width;
			}
			else
			{
				// Display right side tooltip
				posX = offset.x - point.x - anchor.x + iconW;
			}

			// Check top position
			if (this.debug) logMsg +=  " COND Y: " + (offset.y - maxOffset_NE.y - oTTDim.height) + "< 0";

			// offset.y - maxOffset_NE.y === actual Y marker's coordinate relative to the NORTH map border 
			if (offset.y - maxOffset_NE.y - oTTDim.height < 0)
			{
				posY = offset.y - point.y - anchor.y /* + iconH */;
			}
			else
			{
				posY = offset.y - point.y - anchor.y /* - iconH */ - oTTDim.height;
			}

			var pos  =  new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(posX, posY)); 
			if (this.debug) this.LogWriter.log(logMsg, "info");

			pos.apply(this.tooltip);
		}		
		else 
		// Use default engine
		{
			var point=this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(this.gMap.fromDivPixelToLatLng(new GPoint(0,0),true),this.gMap.getZoom());
			var offset=this.gMap.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(),this.gMap.getZoom());
			var anchor=marker.getIcon().iconAnchor;
			var width=marker.getIcon().iconSize.width;
			var height=this.tooltip.clientHeight;
			var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(offset.x - point.x - anchor.x + width, offset.y - point.y -anchor.y -height)); 
			pos.apply(this.tooltip);
		}

		this.tooltip.style.visibility = "visible";

		//if (this.debug) this.LogWriter.log("showTooltip end", "info");
	},

	// Search
	doSearch: function()
	{
		var oTimer = new Util.Timer();
		
		//Hide tooltips - this fixes an issue when tooltip is visible and we reload data using keyboard navigation
		if (this.tooltip != null)
			this.tooltip.style.visibility="hidden";

		//Remove image and images on Image panel
		document.getElementById('propertyInfo').style.display = 'none';
		// Hide property information box 
		document.getElementById('dFeatures').style.display = 'none';

		// Update status bar
		document.getElementById('statusBar').innerHTML = 'Searching...';
    
    
        var nFound = 0;
        var marker = null;
		// Loop through markers
		for (var i = 0; i < this.jsonData.map.markers.length ; i++)
		{
			marker = this.jsonData.map.markers[i];
			// Set location visibility
			this.jsonData.map.markers[i].visible = true;

			/* Filter Structure
			App.FilterOptions.Carrier = []
			App.FilterOptions.Network = []
			App.FilterOptions.Download = $('Download').value;
			App.FilterOptions.Upload = $('Upload').value;
			*/

			// Loac vars
			var max = 0;
			var min = 0;
			var found = false;

			///////////////////////////////////////////////////////////////////////
            // Filter download. 0 means any value
            //alert(App.FilterOptions.Upload);
            if (App.FilterOptions.Download > 0)
            {
            	var incr = 200;
            	//Is it < X condition
            	if (App.FilterOptions.Download == 11)
            	{
            		if (marker.download <= (incr*10) )
            		{
	            		this.jsonData.map.markers[i].visible = false;
						continue;
            		}
            	}
            	else
            	{
					max = App.FilterOptions.Download * incr;
					min = max - incr;
					if ( !(marker.download >= min && marker.download <= max))
					{ 
						this.jsonData.map.markers[i].visible = false;
						continue;
					}
				}
			}

			///////////////////////////////////////////////////////////////////////
            // Filter upload. 0 means any value
            //alert(App.FilterOptions.Upload);
            if (App.FilterOptions.Upload > 0)
            {
            	var incr = 100;
            	//Is it < X condition
            	if (App.FilterOptions.Upload == 11)
            	{
            		if (marker.upload <= (incr*10) )
            		{
	            		this.jsonData.map.markers[i].visible = false;
						continue;
            		}
            	}
            	else
            	{
					max = App.FilterOptions.Upload * incr;
					min = max - incr;
					if ( !(marker.upload >= min && marker.upload <= max))
					{ 
						this.jsonData.map.markers[i].visible = false;
						continue;
					}
				}
			}

			///////////////////////////////////////////////////////////////////////
			// Filter Carrier
			found = false;
			for(var n = 0; n < App.FilterOptions.Carrier.length; n++)
			{
				//alert(marker.carrier +"="+ App.FilterOptions.Carrier[n])		;
				if (marker.carrier == App.FilterOptions.Carrier[n])
				{
					//alert("xxx");
					found = true;
					break;
				}
			}
			if (!found)
			{
				this.jsonData.map.markers[i].visible = false;
				continue;
			}

			///////////////////////////////////////////////////////////////////////
			// Filter Network
			/*
			found = false;			
			for(var n = 0; n < App.FilterOptions.Network.length; n++)
			{
				if (marker.network == App.FilterOptions.Network[n])
				{
					found = true;
					break;
				}
			}
			if (!found)
			{
				this.jsonData.map.markers[i].visible = false;
				continue;
			}
			*/

			// Set active marker
			// Show first available location anyway 
			// if (this.activeMarkerID == null || this.activeMarkerID == 0)
			if (nFound == 0)
				this.activeMarkerID = i;

			// Done with filtering
			nFound ++;
		} // for

		// Do we have list of states available?
		var label = null;
		if (Element.getStyle($('QuickLinksList'), 'display') == 'block')
		{
			var el = document.getElementById('QuickLinksList');
			var val =  el.value;
			label = el.options[el.selectedIndex].text;
		}

		// Do not show All States
		if (label == '---- States Filtering ----')
		{
			label = null;
		}

		// Add status bar information
		document.getElementById('statusBar').innerHTML = ((label!=null) ? (label + ': ') : '') + nFound +  ((nFound == 0 || nFound == 1) ? (' location displayed') : ' locations displayed') + '.';

		if (this.debug) this.LogWriter.log(oTimer.Tick(1, "Search finished in "), "time");

		// And finally re-render markers
		this.renderMarkers();

	},

	// This functions starts when user click on a marker
	onMarkerClick: function(id)
	{
		//Remove old active marker if available
		try 
		{
			document.getElementById('row_'+ this.activeMarkerID).style.backgroundColor = "#FFF";
		}
		catch (e) {} 

		// Change active sidebar row background color
		try 
		{
			document.getElementById('row_'+ id).style.backgroundColor = this.sideBarRowActive;
		}
		catch (e) {} 

		this.activeMarkerID = id;

      
		// And Finally make panel visible and hide NoInfo panel
		document.getElementById('propertyInfoNo').style.display = 'none';
		document.getElementById('propertyInfo').style.display = 'block';
      
		// Hide information box
		document.getElementById('dFeatures').style.display = 'none';
        // Show downloading message 
		document.getElementById('propertyInfoLoading').style.display = 'block';

		//re-center map
		if(this.navigateToLocation)
		{
			try
			{
				var locationToCheck = new GLatLng(parseFloat(this.jsonData.map.markers[id].lat), parseFloat(this.jsonData.map.markers[id].lng));
				// Do not recenter if marker is visible
				if (!this.gMap.getBounds().contains(locationToCheck))
				{
					// maybe use panTo ?
					this.gMap.setCenter(locationToCheck);
				}
			}
			catch(e){}
		}

        // And now, download all information for the selected marker using JSON call
        if (this.debug) this.LogWriter.log("Going to download PROPERTY data ...");

		var oDate=new Date();
		// Use real record id which we received from a server
		GDownloadUrl(this.DetailFeed + "?ID="+encodeURIComponent(this.jsonData.map.markers[id].id) + "&NC=" + oDate.getTime(), this.showLocationInfo.bind(this));
	},

	//Show proprty information 
	showLocationInfo: function(doc) 
	{
        if (this.debug) this.LogWriter.log("PROPERTY data downloaded");

		// Parse the JSON document
		var jsonLocationData = eval('(' + doc + ')');
		

		// ADD BR to an address: Property Detail title: center text and put a line break between property street address and city/state/zip
		//$sAddress = $aLocation['Address'] . "<br/> " . $aLocation['City'] . ", " . $aLocation['State'] . " " . $aLocation['Zip'];

		if (jsonLocationData.desc != null)
		{
			setText("dAddress", jsonLocationData.address + " "  + jsonLocationData.city + ", " + jsonLocationData.state + " " + jsonLocationData.zip +"<br />");
			setText("dRemarks", jsonLocationData.desc);
			// Show information
			document.getElementById('dFeatures').style.display = 'block';
		}

        // Hide downloading message 
		document.getElementById('propertyInfoLoading').style.display = 'none';
	},


	/**
	* Geocode given address and re-center map. Geocode using GClientGeocoder or Yahoo service.
	* @param	{String}	location	Location address
	*/
	addrSearch: function(address) 
	{
		//Hide tooltips - this fixes an issue when tooltip is visible and we reload data using keyboard navigation
		if (this.tooltip != null)
			this.tooltip.style.visibility="hidden";

		if (this.geocoder = 'Google')
		{
			// Is GClientGeocoder defined
			if (this.gGeocoder == null)
				this.gGeocoder = new GClientGeocoder();
			
			if (this.gGeocoder) 
			{
				/* use more advanced method with results analyses
				this.gGeocoder.getLatLng(
					address,
					function(point) 
					{
						if (!point) 
						{
							alert(address + " not found");
						} else 
						{
							this.gMap.setCenter(point, 14);
						}
					}.bind(this));
				*/

				// Use accuracy for setting proper zoom level
				this.gGeocoder.getLocations(
					address,
					function(response) 
					{
						if (!response || response.Status.code != 200) {

							alert("\"" + address + "\" not found");
						} else {
							place = response.Placemark[0];
							accuracy = place.AddressDetails.Accuracy;
							point = new GLatLng(place.Point.coordinates[1],
												place.Point.coordinates[0]);
							if (this.debug) this.LogWriter.log('"' + address + '" geocoded to accuracy ' + accuracy + ", which is converted to " + this.accuracy[0].levels[accuracy] + ' zoom level', "Info");
							this.gMap.setCenter(point, this.accuracy[0].levels[accuracy]);
						}
					}.bind(this));
			}
		}
		else
		// Use Yahoo service
		{
			// Build the Yahoo! web services call
			request = 'YOUR YOURL ?appid=mapobject.net&location=' + address  + '&output=json&callback=App.addrSearchShow';
			// Create a new script object
			var oLoader = new Util.JSLoader(request);
			// Build the script tag
			oLoader.buildScriptTag();
			// Execute (add) the script tag
			oLoader.addScriptTag();
		}
	},

	/**
	* This is address search/geocoding callback function.
	* This is the callback function that is specified in the request url and gets executed after the data is returned 
	* by Yahoo! Geocoding API http://developer.yahoo.com/maps/rest/V1/geocode.html.
	* @param	{JavaScript}	jData Geocoding results
	*/
	addrSearchShow: function(jData) 
	{
		// Parse the JSON document
		if (jData == null)
		{
			alert("There was a problem parsing search results.");
			return;
		}

		//get the values out of the object
		var lat = jData.ResultSet.Result[0].Latitude;
		var lon = jData.ResultSet.Result[0].Longitude;
		this.gMap.setCenter(new GLatLng(lat, lon), 15);
	},
	/////////////////////////////////////////////////////////////////////////
	// EOF Geocode given address re-center map
	/////////////////////////////////////////////////////////////////////////


	// Show/hide map elements
	setMapElements: function(vis) 
	{
		if (vis)
		{
			// Show status bar
			document.getElementById('statusBar').style.display = 'block';
			document.getElementById('mapBottomBar').style.display = 'block';
		}
		else
		{
			// hode status bar
			document.getElementById('statusBar').style.display = 'none';
			document.getElementById('mapBottomBar').style.display = 'none';

		}
	}
}
App.load();


/* *********************************************************************************
* Helper functions.
* *********************************************************************************/

function buildAddrStr (a, c, z, s, co)
{
	var addr = (a == '') ? '' : (a + '<br />');
	addr = (addr == '') ? ((c == '') ? '' : c) : (addr + ((c == '') ? '' : (c + ', ')));
	addr = (addr == '') ? ((s == '') ? '' : s) : (addr + ((s == '') ? '' : (s + ' ')));
	addr = (addr == '') ? ((z == '') ? '' : z) : (addr + ((z == '') ? '' : (z)));
	return addr;
}


// Set text value
function setText(id, value)
{
	document.getElementById(id).innerHTML = value;
}
//set boolean value converted to Y/N
function setBool(id, value)
{
	document.getElementById(id).innerHTML = (value=='1') ? 'Y' : 'N';
}


//Serialization
function _Serialize(o,name, arrayname)
{
	var s = '';
	switch(typeof(o))
	{
		case 'string':
		case 'number':
			if(arrayname)
			{
				s += encodeURIComponent(arrayname + '[' + name + "]") + "=" + encodeURIComponent(o) + "&";
			}
			else
			{
				s += encodeURIComponent(name) + "=" + encodeURIComponent(o) + "&";
			}
			break;
		case 'boolean':
			if(arrayname)
			{
				s += encodeURIComponent(arrayname + '[' + name + "]") + "=" + (o ? 1 : 0) + "&";
			}
			else
			{
				s += encodeURIComponent(name) + "=" + (o ? 1 : 0) + "&";
			}
			break;
		case 'array':
			for(i = 0; i < o.length; i++)
			{
				s += _Serialize(o[i], i, arrayname ? arrayname + '[' + name + ']' : name);
			}
			break;
		case 'object':
			for(i in o)
			{
				s += _Serialize(o[i], i, arrayname ? arrayname + '[' + name + ']' : name);
			}
			break;
		case 'function':
			//Do nothing
			break;
		default:
			throw ("Attemt to serialize an unknown type of " + typeof(o) + ", name='" + name + "', value='" + o + "'");
	}
	return s;
}

//Call this with an array or object (can be nested, probabally DOES NOT HANDLE CYCLIC REFERENCES)
function Serialize(o)
{
	var s = _Serialize(o, null, null);
	return s.substr(0, s.length - 1);
}	
