/**
 * Locations map javascript.
 *
 * Handles writing the markers on the map appropriately.
 *
 * This requires the use of Google Maps API > 3.4
 *
 * <code>
 * $(function(){
 *		var script = document.createElement("script");
 *		script.type = "text/javascript";
 *		script.src = "http://maps.googleapis.com/maps/api/js?v=3.5&amp;sensor=false&amp;callback=initialize";
 *		document.body.appendChild(script);
 * 	});
 * 	</code>
 *
 * 	@author Charlie Powell <cpowell@blindacre.com>
 * 	@since 2011.11.29
 */

function locationsmap(){
	//// private functions and variables. \\\\

		// throwerror is used internally to notify the user/developer of errors.
	var throwerror,
		// Map object, raw DOM object.
		dommap = null,
		// Map object, google.maps.Map
		map,
		// Default map options
		options = {},
		// Has this object been initialized yet?
		initialized = false,
		// The geocoder object, useful for looking up address.
		geocoder,
		// The queue of commands to do as soon as the system is initialized.
		// This is required because sometimes the map has options set, ie: setCenter, before Google is ready.
		queue = [],
		// Pointer.  Useful for events that do not have "this" the same as this right now.
		mapobject = this,
		// Function used to ensure that certain commands are executed after and ONLY after google has initialized.
		execAfterInit,
		// Array of markers on the map.
		// Each "marker" is an object containing that marker's metadata and the acutal google objects.
		markers = [],
		// Internal function used to get the marker setup on the map.
		setupmarker,
		// Remember which window is open, (if any).  Used to close the previous window when clicking on a new node.
		openwindow = null;

	//// public methods \\\\

	/**
	 * Initialize this javascript.
	 */
	this.init = function(){
		var i;

		if(initialized) return true;
		if(dommap == null){
			throwerror('Map is not bound to any element!');
			return false;
		}
		if(typeof google == 'undefined'){
			throwerror('Please include Google API first!');
			return false;
		}
		if(google.maps.version < 3.4){
			throwerror('Please include Google Maps >= 3.4!');
			return false;
		}
		/*if(!GBrowserIsCompatible()){
			throwerror('Your browser does not support the necessary Javascript.');
			return false;
		}*/

		options = $.extend(
			{},
			{
				zoom: 6,
				mapTypeId: google.maps.MapTypeId.ROADMAP
			},
			options
		);

		map = new google.maps.Map(dommap, options);
		geocoder = new google.maps.Geocoder();

		initialized = true;

		// I can now run any queued up commands, if there are any.
		for(i=0; i<queue.length; i++){

			if(queue[i].args.length > 0){
				queue[i].fn.apply(this, queue[i].args);
			}
			else{
				queue[i].fn();
			}
		}

		return true;
	};

	/**
	 * Bind this map javascript onto a given HTML DOM node.
	 *
	 * el can be either a jquery string, regular string or DOM node.
	 *
	 * @param el string | DOMNode
	 */
	this.bindTo = function(el){
		if(typeof el == 'string' && (el[0] == '#' || el[0] == '.') ){
			dommap = $(el)[0];
		}
		else if(typeof el == 'string'){
			dommap = document.getElementById(el);
		}
		else if(typeof el == 'object'){
			dommap = el;
		}
		else{
			throwerror('Invalid map object requested');
		}

		if(dommap == null){
			throwerror('Unable to locate requested map canvas, is it present? ', el);
		}
	};

	/**
	 * Set the zoom level for this map to a specific zoom.
	 *
	 * See the Google documentation for the appropriate zooms.
	 *
	 * @param newzoom int
	 */
	this.setZoom = function(newzoom){
		options.zoom = newzoom;
		execAfterInit(function(z){
			map.setZoom(z);
		}, newzoom);
	}


	/**
	 * Set the center of the map around a specific point.
	 *
	 * Alternatively, a string can be used to be resolved automatically.
	 *
	 * @param center string | object
	 */
	this.setCenter = function(center){
		if(typeof center == 'object'){
			execAfterInit(function(center){
				options.center = new google.maps.LatLng(center.lat, center.lng);
				map.setCenter(options.center);
			}, center);
		}
		else{
			execAfterInit(function(center){
				geocoder.geocode( { 'address': center}, function(results, status) {
					if (status == google.maps.GeocoderStatus.OK) {
						options.center = results[0].geometry.location;
						map.setCenter(options.center);
					} else {
						throwerror("Geocode was not successful for the following reason: " + status);
					}
				});
			}, center);
		}
	};

	/**
	 * Add a marker to this map.
	 *
	 * If it's not loaded yet, it'll be added whenever it's initialized.
	 */
	this.addMarker = function(marker){
		execAfterInit(function(marker){
			markers.push(marker);

			// Make sure this marker is actually resolved first!
			if(typeof marker.position == 'object'){
				marker.glatlng = new google.maps.LatLng(marker.position.lat, marker.position.lng)
				setupmarker(marker);
			}
			else{
				geocoder.geocode( { address: marker.position }, function(results, status){
					if (status == google.maps.GeocoderStatus.OK) {
						marker.glatlng = results[0].geometry.location;
						marker.position = {lat: marker.glatlng.lat(), lng: marker.glatlng.lng() };
						setupmarker(marker);
					} else {
						throwerror("Geocode was not successful for the following reason: " + status);
					}
				});
			}
		}, marker);
	};


	//// private methods \\\\

	setupmarker = function(marker){
		marker.gmarker = new google.maps.Marker({
			position: marker.glatlng,
			map: map,
			title: marker.title,
			icon: marker.icon,
			shadow: marker.shadow
		});

		marker.ginfo = new google.maps.InfoWindow({
			content: marker.infowindow
		});


		google.maps.event.addListener(marker.gmarker, "click", function() {
			// Close any previous.
			if(openwindow) openwindow.close();
			// Open it.
			marker.ginfo.open(map, marker.gmarker);
			// Remember this one.
			openwindow = marker.ginfo;
		});

		google.maps.event.addListener(marker.ginfo, 'closeclick', function(){
			openwindow = null;
		});

		// Don't forget to call the "added" function.  It may be used externally.
		marker.added(marker);
	}

	/**
	 * Set an error to the user/developer.
	 *
	 * Internally-used helper function
	 */
	throwerror = function(msg){
		if(typeof console == 'undefined') alert(msg);
		else console.error(msg);
		// anything else?
	};

	/**
	 * Execute a command, or queue it up until after Google has been initialized.
	 */
	execAfterInit = function(fn){
		var args = [], i;

		for(i=1; i<arguments.length; i++){
			args.push(arguments[i]);
		}

		if(initialized){
			if(arguments.length == 1){
				fn();
			}
			else{
				fn.apply(this, args);
			}
		}
		else{
			queue.push({fn: fn, args: args});
		}
	};
};

