var Maps = {};
(function (pub, win, doc, $, undefined) {


// Cache for google.maps object
var maps;

// The default values for maps
var defaults = {
	zoom: 14,
	minZoom: 16,
	mapType: 'roadmap',
	markers: []
};


// Add a marker to a map
var addMarker = (function () {
	// The function to run
	var run = (function ($map, opts) {
		var map = $map.data('map'),
			title = opts.title || opts.address,
			latLng = new maps.LatLng(opts.lat, opts.lng),
			marker = {};

		marker.position = latLng;
		marker.map = map;
		marker.title = title;
		if(opts.icon) marker.icon = opts.icon;
		
		marker = new maps.Marker(marker);
		
		if(opts.popup) {
			var popup = new maps.InfoWindow({
				content: '<b>'+title+'</b><div>'+opts.popup+'</div>'
			});
			maps.event.addListener(marker, 'click', function () {
				popup.open(map, marker);
			});
		}
	});

	return (function ($map, opts, callback) {
		// Need to get Geocode
		if(opts.address && !(opts.lat && opts.lng)) {
			getGeocode(opts.address, function (geo) {
				opts.lat = geo.lat;
				opts.lng = geo.lng;
				run($map, opts);
				$.isFunction(callback) && callback(geo);
			});
		}
		// Have Geocode
		else {
			run($map, opts);
			var geo = {lat: opts.lat, lng: opts.lng};
			$.isFunction(callback) && callback(geo);
		}
	});
})();

// Ask google for Geocode
var getGeocode = (function (address, callback) {
	(new maps.Geocoder()).geocode({address: address}, function (resp) {
		var loc = resp && resp[0].geometry.location;
		callback({
			lat: loc && loc.lat(),
			lng: loc && loc.lng()
		});
	});
});


// Attach a map to an element
var attachMap = (function () {

	// The function that creates a map
	var run = (function ($map) {
		var latLng = new maps.LatLng($map.data('lat'), $map.data('lng'));

		var map = new maps.Map($map[0], {
			zoom: $map.data('zoom') || defaults.zoom,
			center: latLng,
			mapTypeId: $map.data('map-type') || defaults.mapType
		});
		
		$map.data('map', map);

		if($map.data('title')) {
			addMarker($map, {
				lat: $map.data('lat'),
				lng: $map.data('lng'),
				title: $map.data('title'),
				popup: $map.data('popup')
			});
		}
	});

	// Determine
	return (function ($map, callback) {
		var geo = {};

		// Have Geocode
		if($map.is('[data-lat][data-lng]')) {
			run($map);
			geo.lat = $map.data('lat');
			geo.lng = $map.data('lng');
			$.isFunction(callback) && callback(geo);
		}
		// Need Geocode
		else if($map.is('[data-address]')) {
			getGeocode($map.data('address'), function (geo) {
				$map.attr('data-lat', geo.lat);
				$map.attr('data-lng', geo.lng);
				run($map);
				$.isFunction(callback) && callback(geo);
			});
		}
	});
})();




// Init
pub.init = (function () {
	if(win.google && win.google.maps) {
		maps = win.google.maps;
		pub.attach();
	}
	else {
		pub.loadGoogle('Maps.init');
	}
});

// Load google maps api (can't rely on jQuery at this point)
pub.loadGoogle = (function (callbackName) {
	var script = document.createElement("script");
	script.type = "text/javascript";
	script.src = "http://maps.google.com/maps/api/js?sensor=false&callback="+callbackName;
	document.getElementsByTagName('head')[0].appendChild(script);
});


pub.attach = (function () {
	var $maps = $('div.map')
		.filter('[data-address],[data-lat][data-lng],[data-markers]')
		.filter(function(){return this.children.length == 0});

	$maps.each(function () {
		var $map = $(this),
			hasGeo = $map.is('[data-lat][data-lng]'),
			hasAddr = $map.is('[data-address]'),
			hasMarkers = $map.is('[data-markers]');

		// If only markers, set location to first marker
		if(!hasGeo && !hasAddr && hasMarkers) {
			var mark = $map.data('markers');
			mark = mark && mark[0];
			if(mark && mark.lat && mark.lng) {
				$map.attr('data-lat', mark.lat);
				$map.attr('data-lng', mark.lng);
			}
			else if(mark && mark.address) {
				$map.attr('data-address', mark.address);
			}
		}

		attachMap($map, function () {
			var map = $map.data('map'),
				markers = $map.data('markers') || defaults.markers,
				numMarks = markers.length,
				bounds = new maps.LatLngBounds();
			$map.data('markers', markers);
			$.each(markers, function (key, val) {
				addMarker($map, val, function (geo) {
					var latLng = new maps.LatLng(geo.lat, geo.lng);
					bounds.extend(latLng);
					numMarks -= 1;
					if(numMarks == 0 && !hasGeo && !hasAddr) {
						map.fitBounds(bounds); // Uses settimeout, so have to set zoom after
						setTimeout(function () {
							if(map.getZoom() > defaults.minZoom) {
								map.setZoom(defaults.minZoom);
							}
						}, 200);
					}
				});
			});
		});
	});
	
});


$(win).bind('load', pub.init);


})(Maps, window, document, jQuery);
