/* The global map variable */
var map;

/* a hash of icons, specified by name in the <metadata> of each file */
var icons = new Array();

/* a hash of datasets, by key, assumed to be a unique DB Key, to prevent double loading of datasets */
var DATASETS = new Array();

/* variable tracking ALL markers that have been loaded */
var gMarkers = new Array();
//var bounds;

var STARTING_ZOOM = 4;
var STARTING_CENTER = new GLatLng( 37, -120 );
var gLastZoom = STARTING_ZOOM;

var REGION_SEL; // = document.getElementById("marketarea");
var CITY_SEL; // = document.getElementById("cityid");
var STATE_SEL; // = document.getElementById("statecode");
var DRILL_DOWN_URL = "/altos/app?service=googleAltosMap&ma=";

/* onLoad creates the icons, loads the market areas, add mapp controls, and sets up the basic listeners. */
function onLoad()
{
    if( GBrowserIsCompatible() )
	{
        initIcons();
		map = new GMap2( document.getElementById( "map" ) );
		map.addControl( new GLargeMapControl() );
		map.addControl( new GMapTypeControl() );
		map.addControl( new GScaleControl() );
        map.addControl(new GOverviewMapControl());

        // Add a cluster for the map
        //var clusterer = new Clusterer(map);
        //clusterer.SetMaxVisibleMarkers(250);
        //clusterer.SetMinMarkersPerCluster(10);

        // Load the market area
        loadMarketAreas();

        GEvent.addListener( map, "moveend", function()
		{
			var zoom = map.getZoom();
			if (gLastZoom == zoom) {
				return;
			}
			gLastZoom = zoom;
			
			// Check to see that we are displaying the right markers...
			
			try {
			// useful for debugging/knowing where you are...
			//GLog.write("zoom = " + zoom + ", focus = " + map.getCenter() + ", sw=" + map.getBounds().getSouthWest() + ", ne=" + map.getBounds().getNorthEast());
			
			// loop over each marker that we have loaded, and check to see if it is visuble in the current zoom 
			// this might be more efficient if gMarkers was a hash keyed off a "visibility object." or something like that
			for (i in gMarkers) {
				var m = gMarkers[i];
				if (zoom > m.visible.max || zoom < m.visible.min) {
					//warn("Checking marker");
                    //clusterer.RemoveMarker(m.marker);
					map.removeOverlay(m.marker);
					m.visible.flag = false;
				}
				if ((zoom >= m.visible.min && zoom <= m.visible.max) 
					 && m.visible.flag == false) {
                    //clusterer.AddMarker(m.marker);
					map.addOverlay(m.marker);
                    m.visible.flag = true;
				}
				
				// this loads data on demand - if you have entered a marker's bounds at a certain zoom level...
				// this pushes the responsibility of figuring out what data should be loaded dynamically to the client.
				// The alternative is to just send the current bounds to a server process and let it figure out what to send down.
                // TODO:
                if (m.canDrilldown == true
					&& zoom >= m.drilldownZoomTrigger
					&& (m.bounds.containsBounds(map.getBounds())
						|| m.bounds.intersects(map.getBounds()))) {
					loadData(m.name, m.requestURL);
				}
                //warn(" marker -> " + m + " zoom --> " + zoom + " map.getBounds --> " + map.getBounds());
            }
			
			// the catch will handle a variety of runtime errors
			} catch (e) {
				warn(e);
			}
		} );

        // Get the parameter values from the hidden fields
        REGION_SEL = document.getElementById("marketarea");
        CITY_SEL = document.getElementById("cityid");
        STATE_SEL = document.getElementById("statecode");
        CITY_LAT = document.getElementById("citylat");
        CITY_LNG = document.getElementById("citylng");
        if (REGION_SEL.value != null && REGION_SEL.value != "" &&
            CITY_SEL.value != null && STATE_SEL.value != null  &&
            CITY_LAT.value != null && CITY_LAT.value != "" &&
            CITY_LNG.value != null && CITY_LNG.value != "") {

            var citylat = parseFloat( CITY_LAT.value );
            var citylng = parseFloat( CITY_LNG.value );
            STARTING_ZOOM = 9;
            STARTING_CENTER = new GLatLng( citylat, citylng )
            var is_mozilla = winNavUsrAgn.indexOf("gecko") != -1;
            if (!is_mozilla) {
                setTimeout("loadDatasetAndOpenWindow()", 1000);
            } else {
                loadDatasetAndOpenWindow();
            }
            // Open the marker and then center it
            // for the region option
            GEvent.addListener(DATASETS, "dataloadend", function(datasetName, data) {
                debug("Finding Marker for Custom Location... " + datasetName);
                if (datasetName != "region") {
                    try {
                        var selectedMarker = null;
                        for (i in data) {
                            var tempmarker = data[i];
                            debug("marker.cid = " + tempmarker.cid);
                            if (tempmarker.cid != null && tempmarker.cid == CITY_SEL.value ) {
                                selectedMarker = tempmarker;
                                break;
                            }
                        }
                        if (selectedMarker != null ) {
                            debug("template = " + selectedMarker.template);
                            // Open the info window
                            selectedMarker.marker.openInfoWindowHtml(selectedMarker.template);
                            debug("Opening the Info WindowHtml for " + selectedMarker.name);
                        }
                    } catch (e) {
                        warn("Loading Custom Location dataloadend: " + e);
                    }
                    // Clearing the listener
                    GEvent.clearListeners(DATASETS, "dataloadend");
                }
            });

        }

		map.setCenter( STARTING_CENTER, STARTING_ZOOM);
	}
}

function loadDatasetAndOpenWindow() {
    var validURL = DRILL_DOWN_URL + REGION_SEL.value + "&st=" + STATE_SEL.value;
    map.closeInfoWindow();
    loadData('MA-' + REGION_SEL.value, validURL);
}

function showProgressBar() {
    debug('Loading the progress bar...');
    //document.getElementById("loadingBar").style.visibility = "visible";
    //document.getElementById("loadingBar").style.display = "";
    //alert("document" + document.getElementById("loadingBar").innerHTML);
}

function hideProgressBar() {
    debug('Hiding the progress bar...');
    //document.getElementById("loadingBar").style.visibility = "hidden";
    //document.getElementById("loadingBar").style.display = "none";
}

/*
	Loads marker data. Triggers a "dataloading" event and a "dataloaded" event for the UI to figure out as it needs.
*/
function loadData(name, url) {
    // Enable Progress bar
    showProgressBar();

    GEvent.trigger(DATASETS, "loadstart", name);
	if (DATASETS[name] != null) {
		debug(name + " found cached");
		GEvent.trigger(DATASETS, "dataloadend", name, DATASETS[name]);
        // Disable progress bar
        setTimeout("hideProgressBar();", 4000);
		return;
	}
	//debug("Fetching " + url);
	DATASETS[name] = ""; // just to prevent double load
	GDownloadUrl(url, function(data, responseCode) {
		try {
			debug("Loading " + url);
			var xmlDoc = GXml.parse(data);
			DATASETS[name] = createMarkers(xmlDoc);
			displayMarkers(DATASETS[name]);
            GEvent.trigger(DATASETS, "dataloadend", name, DATASETS[name]);
            // Disable progress bar
            setTimeout("hideProgressBar();", 4000);
        } catch (e) {
			warn(e);
            // Disable progress bar
            setTimeout("hideProgressBar();", 4000);
		}
	});
}

/*
	Preloads the market areas
*/
function loadMarketAreas() {
	loadData('region', "/altos/scripts/map/tdata/region.xml");
}

/*
	Adds all the markers; checking for redundancies in location and object.
*/
function displayMarkers(ms) {
    if (ms != null && ms.length != null ) {
        warn("ms.length:" + ms.length);
        for (var i = 0; i < ms.length; i++) {
            debug("ms[i] :-> " + ms[i]);
            if (gMarkers[ms[i].key] != null) {
                if (gMarkers[ms[i].key].equals(ms[i])) {
                    warn("Adding marker twice.");
                    continue;
                }
                warn("Marker found with duplicate location for:" + ms[i].name);
            }
            gMarkers[ms[i].key] = ms[i];
            if (isNumber(ms[i].lat) && isNumber(ms[i].lng)) {
                //warn("adding marker " + gMarkers[ms[i].key]);
                //clusterer.AddMarker(m.marker);
                map.addOverlay(ms[i].marker);
                ms[i].visible.flag = true;
            }
        }
    }
}
/*
	Loops over a set of markers to hide them.
*/
function hideMarkers(ms) {
	for (var i = 0; i < ms.length; i++) {
		gMarkers[ms[i].key] = null;
	}
}

/*
	Creates a set of markers from an XML document.
*/
function createMarkers(xmlDoc) {
	var metadata = xmlDoc.getElementsByTagName("metadata")[0];
	var icon = icons[metadata.getAttribute("icon")];
	var events = xmlDoc.getElementsByTagName("event");
	var markersXML = xmlDoc.getElementsByTagName( "marker");
	var newMarkers = new Array( markersXML.length );

	for( var i = 0; i < markersXML.length; i++ ) {
		debug("processing marker: " + markersXML[i].getAttribute("name"));
		newMarkers[i] = new MarkerObject(markersXML[i], events, metadata, icon);
	}
    return newMarkers;
}

/*
	Creates a marker from an XML Node and other nodes from the XML doc.  See #createMarkers(xmlDoc)
*/
function MarkerObject(xmlm, xmlevents, xmlmeta, icon) {
	this.name = xmlm.getAttribute("name");
	this.lat = parseFloat( xmlm.getAttribute( "lat" ) );
	this.lng = parseFloat( xmlm.getAttribute( "lng" ) );
    this.cid = xmlm.getAttribute("cid");
    this.visible = new Object();
	var vrange = xmlmeta.getAttribute("visibleRange").split(",");
	this.visible.min = vrange[0];
	this.visible.max = vrange[1];
	this.key = this.lat + "," + this.lng;
	this.zoom = getAttribute("zoom", xmlm, xmlmeta);
	this.canDrilldown = (xmlmeta.getAttribute("canDrilldown").toLowerCase() == "true");
    if (this.canDrilldown) {
		this.drillDownZoomTrigger = xmlmeta.getAttribute("drilldownZoomTrigger");
		// TODO: this could be funny
		this.requestURL = xmlmeta.getAttribute("drilldownURL") + xmlm.getAttribute("name");
        var boundattrib = xmlm.getAttribute("bounds");
        if (boundattrib != null) {
            var points = coordinatesToGLatLngArray(boundattrib);
		    this.bounds = new GLatLngBounds(points[0],points[1]);
        } else {
            this.bounds = null;
        }
    }
    debug('marker details lat: ' + this.lat + ' lng: ' + this.lng + ' cid : ' + this.cid + ' key: ' + this.key);
    //this.marker = new GMarker( new GLatLng( this.lat, this.lng ), icon);
    this.marker = new GMarker( new GLatLng( this.lat, this.lng ), {title:this.name, icon:icon});

    for (var k = 0; k < xmlevents.length; k++) {
	    addEvent(this, this.marker, xmlm, xmlevents[k],xmlmeta);
    }
	return this;
}

MarkerObject.prototype.equals = function (b) {
    debug("ms.equals " + this.lat + ", " + b.lat + ", " + this.lng + ", " + b.lng);
	return this.lat == b.lat && this.lng == b.lng && this.name == b.name;
};

/*
	Separates the event code from the marker logic.
*/
function addEvent(localMarker, marker, xmlm, xmle, xmlmeta)
{
    debug("adding Event..." );
    var drilldowntokens = xmle.getAttribute("drilldowntokens");
    if (drilldowntokens != null) {
        debug("drilldowntokens " + drilldowntokens);
        while (drilldowntokens.indexOf('{') != -1) {
            drilldowntokens = replaceKeysWithAttributes(drilldowntokens,[xmlm,xmlmeta]);
        }
        // Split the strings in the drilldowntokens
        var tokens = drilldowntokens.split(",");
        var dbkey = tokens[0].replace(/'/g, "");
        var url = tokens[1].replace(/'/g, "");
        var lat = parseFloat( tokens[2] );
        var lng = parseFloat( tokens[3] );
        var lod = parseInt( tokens[4] );
        debug("tokens:-> " + dbkey + ", " + url + ", " + lat + ", " + lng + ", " + lod );
        GEvent.addListener( marker, xmle.getAttribute("name"), function() {
            drilldown(dbkey, url, lat, lng, lod);
        });
    }

    if (xmle.getAttribute("showInfo").toLowerCase() == "true") {
		var template = xmle.getElementsByTagName("infoWindow")[0].firstChild.nodeValue;
		while (template.indexOf('{') != -1) {
			template = replaceKeysWithAttributes(template,[xmlm,xmlmeta]);
		}
        localMarker.template = template;
        GEvent.addListener( marker, xmle.getAttribute("name"), function() {
			marker.openInfoWindowHtml(template);
		});
	}

	if (xmle.getAttribute("showBoundary").toLowerCase() == "true") {
		try {
			// expects a text list lat,lng,lat,lng,lat,lng, ...
		  	var points = coordinatesToGLatLngArray(xmlm.firstChild.nodeValue,true);
			var polyline = new GPolyline(points,"#ff0000", 3, .5);
			GEvent.addListener(marker, xmle.getAttribute("name"), function()
			{
				map.addOverlay(polyline);
			});
			GEvent.addListener(marker,xmle.getAttribute("endevent"), function() {
				map.removeOverlay(polyline);						
			});

		} catch(e) {
			warn("ShowBoundary:-> Some error occured during program processing:" + e) ;
		}   
	}										 
}


/**
	Icons code.
*/

function initIcons() {
	var baseIcon = createBaseIcon();
	createIcon( "region", baseIcon);
	createIcon( "city", baseIcon);
	createIcon( "zip" , baseIcon);
}

function createBaseIcon() {
	var baseIcon = new GIcon();
	baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
	baseIcon.iconSize = new GSize(20, 34);
	baseIcon.shadowSize = new GSize(37, 34);
	baseIcon.iconAnchor = new GPoint(9, 34);
	baseIcon.infoWindowAnchor = new GPoint(9, 2);
	baseIcon.infoShadowAnchor = new GPoint(18, 25);
	return baseIcon;
}

function createIcon( type, baseIcon)
{
	var icon = new GIcon(baseIcon);
	var letter = type.toUpperCase().charAt(0);
	icon.image = "http://www.google.com/mapfiles/marker" + letter + ".png";
	icons[type] = icon;
}

// Return true if value is a number
function isNumber(value) {
	if (value=="") return false;
	var d = parseFloat(value);
	if (!isNaN(d)) return true; else return false;
}

/**
	Navigation and presentation.
*/

function drilldown(dbkey, url, lat, lng, lod) {
	// could trigger a fetch here...
	map.closeInfoWindow();
    debug('dbkey ' + dbkey);
    if (url != null) {
		loadData(dbkey, url);
	}
    debug("Setting the Map to center: " + lat + ", " + lng + ", " + lod);
    map.setCenter( new GLatLng( lat, lng ), lod);
}

/**
	Utilities
*/
function replaceKeysWithAttributes(s, elements) {
        for (;;) {
            var i = s.lastIndexOf('{');
            if (i < 0) {
                break;
            }
            var j = s.indexOf('}', i);
            if (i + 1 >= j) {
                break;
            }
            var k = s.substring(i + 1, j);
			var v = null;
			for (l in elements) {
				v = elements[l].getAttribute(k);
				if (v != null) {
					break;
				}
			}
			if (v == null) {
				warn("no matching value found for " + k);
			}
            s = s.substring(0, i) + v + s.substring(j + 1);
        }
        return s;
}	

function getAttribute(a, n, m) {
	if (n.getAttribute(a) != null) {
		return n.getAttribute(a);
	}
	return m.getAttribute(a);
}

function coordinatesToGLatLngArray(str,reversed) {
	var coords = str.split(",");
	var points = [];
    if (coords != null && coords.length != null) {
        for (var i = 0; i < coords.length; i=i+2) {
            if (reversed != null) {
                points.push( new GLatLng(parseFloat(coords[i+1]),parseFloat(coords[i])));
            } else {
                points.push( new GLatLng(parseFloat(coords[i]),parseFloat(coords[i+1])));
            }
        }
    }
    return points;
}

function warn(str) {
	//GLog.write("WARNING: " + str);
}

function debug(str) {
	//GLog.write("DEBUG: " + str);
}
