var gmap = {};

gmap.init = function(domId, callback, mapConfigCallback){
    console.log("Starting gmap.init");
    gmap.startTime = new Date();
    google.load("maps", 2);
    console.log("loading maps");
    google.setOnLoadCallback(function(){
	if(!GBrowserIsCompatible()) return false;
	console.log("maps loaded, initializing");
	dojo.addOnUnload(GUnload);
	var map = new GMap2(dojo.byId(domId));
	gmap.map = map;
	if(mapConfigCallback)
	    mapConfigCallback(map);
	else{
	    map.setMapType(G_HYBRID_MAP);
	    map.addControl(new GSmallMapControl());
	}
	var l = GEvent.addListener(map, "click", gmap.onClick);
	dojo.addOnUnload(function(){GEvent.removeListener(l);});

	if(callback)
	    callback(map);
    });
};

gmap.onClick = function(overlay, point){
    if(overlay && overlay.originalMark){
	//guard against multiple events
	if(overlay.isLoading) return;
	overlay.isLoading = true;
	var mark = overlay.originalMark;
	if(mark.url){
	    var url = mark.url;
	    //if this is a redirct, set window.location
	    if(mark.redirect)
		window.location = url;
	    else{
		GDownloadUrl(url, function(content){
		    overlay.openInfoWindowHtml(content);
		    overlay.isLoading = false;
		});
	    }
	}
    }
}

//ensure this namespace exists so the MarkerManager code won't freak.
if(typeof(GBounds) == "undefined") {
    GBounds = {};
    GBounds.prototype = {};
}

gmap.createPoint = function(thing){
    //google uses "lng" for it's prefix, and we use "lon" usually, so check both, just in case.
    return new GLatLng(thing.lat, thing.lon || thing.lng);
};

gmap.loadJsonMarkers = function(url, setCenter){
    gmap.addLayer(url, {autoFocus:setCenter}).show();
};

gmap.loadJsonMarkersInManager = function(manager, url, setCenter, opts){
    opts = opts || {};
    var fn = function(doc){
	console.log("processing result of", url);
	var markers = dojo.fromJson(doc);
	GEvent.trigger(gmap, 'markers-loaded', markers);
	console.log("parsed into", markers.length, "markers");
	if(!opts.bounds)
	    opts.bounds = new GLatLngBounds();

	opts.gmarkers = opts.gmarkers || {};
	var shouldFinalize = true;

	var iter = function(i){
	    if(i >= markers.length) {
		if(shouldFinalize) finalize(opts.gmarkers, opts.bounds);
		return;
	    };
	    var mark = markers[i];
	    if(mark.moreResultsUrl){
		console.log("Fetching next batch of ", mark.totalResultCount);
		gmap.loadJsonMarkersInManager(manager, mark.moreResultsUrl, setCenter, opts);
		shouldFinalize = false;
	    }
	    var gmark = gmap.createMarker(mark, opts);
	    GEvent.trigger(opts, 'marker-created', gmark, mark);
	    var minZoom = mark.minZoom || 0;
	    var maxZoom = mark.maxZoom || 17;
	    if(!opts.gmarkers[minZoom]) opts.gmarkers[minZoom] = {};	    
	    if(!opts.gmarkers[minZoom][maxZoom]) opts.gmarkers[minZoom][maxZoom] = [];
	    
	    opts.gmarkers[minZoom][maxZoom].push(gmark);

	    opts.bounds.extend(gmark.getLatLng());

	    if(i % 8 == 0)
		window.setTimeout(dojo.hitch(null, iter, i+1), 0);
	    else
		iter(i+1);


	};
	iter(0);
    };
    
    var finalize = function(gmarkers, bounds){
	console.log('finalizing a layer', manager, bounds);

	if(setCenter){
	    console.log("Setting center, bounds:",bounds);
	    var l = GEvent.addListener(gmap.map, 'tilesloaded', function(){
		console.log('refresing the manager', mgr);
                mgr.refresh();
		GEvent.removeListener(l);
	    });
	    gmap.map.setCenter(bounds.getCenter(), gmap.map.getBoundsZoomLevel(bounds) - 1);
	    
	}
	//http://gmaps-utility-library-dev.googlecode.com/svn/tags/markermanager/1.1/docs/examples.html
	//indicate we should be setting the center before
	//instantiating the manager, and this fixed bug
	//local-getaways:#799.
	var mgr = gmap.ensureMarkerManager(manager);

	console.log('adding markers: ', gmarkers);
	for(minZoom in gmarkers){
	    for(maxZoom in gmarkers[minZoom]){
		console.log("adding", gmarkers[minZoom][maxZoom].length, "markers for zoom range", 
			    minZoom, maxZoom);
		mgr.addMarkers(gmarkers[minZoom][maxZoom], minZoom, maxZoom);
	    }
	}

	if(!setCenter) 	mgr.refresh();
	console.log("time since init:", new Date() - gmap.startTime);

	if(opts.finalizeCallback)
	    opts.finalizeCallback({manager:mgr});

	window.setTimeout(function(){ console.log('final refresh'); mgr.refresh();}, 250);
	GEvent.trigger(opts, 'markers-finalized', bounds);
    };
    console.log("starting to load markers from", url);
    GDownloadUrl(url, fn);
};

/**
 * ensureMarkerManager makes sure a MarkerManager is present.
 * @param {Object} obj the thing should be an initialized manager
 * @return {MarkerManager} the manager that was added.
 */
gmap.ensureMarkerManager = function(manager){
    if(!manager || !manager.initalized){
	console.log("Initializing new manager, gmap.map:",gmap.map);
	manager = new MarkerManager(gmap.map, {borderPadding:50, trackMarkers:true});
	manager.initalized = true;
	GEvent.trigger(gmap, 'managerCreated', manager);
    }
    return manager;
};

gmap.createMarker = function(mark, opts){
    var opts = opts || {};
    var markopts = {};
    var iconUrl = mark.icon || opts.icon;
    if (iconUrl){
	//keep an icon cache for GIcon objects, which are pretty common
	gmap.iconCache = gmap.iconCache || {};
	if(!gmap.iconCache[iconUrl]){
	    var icon = new GIcon(G_DEFAULT_ICON, iconUrl);
	    icon.shadow = "";
	    gmap.iconCache[iconUrl] = icon;
	}
	markopts.icon = gmap.iconCache[iconUrl];
    }
    
    if (mark.title)
	markopts.title = mark.title;

    markopts.draggable = opts.draggable;

    //if we have no URL, leave this as unclickable, which consumes less resources
    markopts.clickable = mark.url || mark.clickable;


    //google uses "lng" for it's prefix, and we use "lon" usually, so check both, just in case.
    var gm = new GMarker(gmap.createPoint(mark), markopts);
    gm.originalMark = mark;

    if(opts.markerCreateCallback)
	opts.markerCreateCallback(gm, mark);

    return gm;
};

/*
 * Creates a new Layer that will show/hide a collection of markers
 *
 * @constructor
 * @param {String} url The url to fetch markers from, as JSON data.
 * @param {Object} opts A container for optional arguments:
 *   {Boolean} visible Whether or not this layer is visible by default.
 */
gmap.Layer = function(url, opts){
    var me = this;
    me.opts = opts || {};
    me.url = url;
    me.manager = me.opts.manager;
    me.visible = me.opts.visible;
    me.autoFocus = me.opts.autoFocus == undefined ? true : me.opts.autoFocus;
};

gmap.Layer.prototype.show = function(){    
    var me = this;
    if(me.visible) return;
    if(!me.manager)
	var l = GEvent.addListener(gmap, 'managerCreated', function(mgr){
	    me.manager = mgr;
	    GEvent.removeListener(l);
	});
    var l2 = GEvent.addListener(me.opts, 'markers-finalized', function(bounds){
	me.bounds = bounds;
	GEvent.removeListener(l2);
    });

    gmap.loadJsonMarkersInManager(me.manager, me.url, me.autoFocus, me.opts);
    me.visible = true;
};

gmap.Layer.prototype.hide = function(){    
    var me = this;
    if(!me.visible) return;
    console.log("hiding layer");
    me.manager.clearMarkers();
    me.manager.refresh();
    me.visible = false;
};

gmap.Layer.prototype.toggle = function(){    
    var me = this;
    if(me.visible) me.hide();
    else me.show();
};

gmap.addLayer = function(url, opts){
    var layer = new gmap.Layer(url, opts);
    if(!gmap.layers) gmap.layers = [];
    gmap.layers.push(layer);
    return layer;
}

