Aurora.Google = {

	ready: {
		maps: false
	},
	
	load: function( type, callback )
	{
		switch( type )
		{
			// Maps
			case 'maps':
			
				if ( Aurora.Google.ready.maps ) {
					callback();
					return;
				}
				
				var done = Aurora.Google.mapsCallback = function() {
					Aurora.Google.ready.maps = true;
					callback();
				};
				
				// Google Maps V3 does not support being loaded with google.load yet, therefore it is loaded with Mootools instead
				// google.load( 'maps', '3', { 'callback': done, 'sensor': false });
				
				Asset.javascript( 'http://maps.google.com/maps/api/js?sensor=false&callback=Aurora.Google.mapsCallback' );
				
			break;
		}
	}
	
};

/*
	Class: Aurora.Map
*/
Aurora.Map = new Class({
	
	Implements: [ Events, Options ],
	
	options: {
	
		marker: null,
		markers: [],
		
		autoCreate: true,
		
		customPopups: false,
		
		dataType: 'template', // template, function
		
		zoom: null,
		
		tooltip: {}, // align, offset, renderFn, alwaysVisible, panAndZoom
		popup: {}, // align, offset, renderFn, alwaysVisible, panAndZoom
		
		typeMenu: 'default', // default, dropdown, horizontal
		navMenu: 'default', // default, android, small, zoompan
		mapType: 'roadmap', // roadmap, hybrid, satellite, terrain
		
		center: false,
		
		scrollWheel: true,
		
		bgColor: '#ccc'
		
	},
	
	_created: false,
	
	_idle: false,
	
	_popup: null,
	
	markers: [],
	
	initialize: function( target, options )
	{
		this.setOptions( options );
		
		this.$container = target;
		
		if ( this.options.dataType == 'template' )
		{
			var tooltip = $( target ).getElement( '.aurora-map-tooltip' ),
				popup = $( target ).getElement( '.aurora-map-popup' );
			
			if ( tooltip )
				tooltip.hide();
			
			if ( popup )
				popup.hide();
		
			target = $( target ).getElement( '.aurora-map-container' );
		}
		else
			target.empty();
		
		this.$el = new Element( 'div', { style: 'position: relative; width: 100%; height: 100%;' } ).inject( target );
		
		this.$map = new Element( 'div', { style: 'position: relative; width: 100%; height: 100%;' } ).inject( this.$el );
		
		this.geocoder = new google.maps.Geocoder();
		
		if ( this.options.autoCreate )
			this.create();
	},
	
	create: function()
	{
		// $log( 'Creating map...' );
		
		// Don't create it again
		if ( this._created )
			return;
		
		// Store references
		var $map = this.$map,
			markers = this.options.markers;
		
		if ( this.options.marker )
			markers = [ this.options.marker ];
			
		var center = new google.maps.LatLng( 37.71859, 6.679688 ); // World view
		
		// Map options
		var options = {
			mapTypeId: this.options.mapType,
			center: center,
			backgroundColor: this.options.bgColour,
			scrollwheel: this.options.scrollWheel,
			mapTypeControl: ( this.options.typeMenu ),
			navigationControl: ( this.options.navMenu )
		}
		
		// Map Type
		if ( this.options.typeMenu )
		{
			var mapType = this.options.mapType,
				mapTypeStyles = google.maps.MapTypeId;
			
			var style;
			
			switch( typeMenu )
			{
				case 'hybrid': style = mapTypeStyles.TERRAIN; break;
				case 'satellite': style = mapTypeStyles.SATELLITE; break;
				case 'terrain': style = mapTypeStyles.TERRAIN; break;
				
				default:
					style = mapTypeStyles.ROADMAP;
			}
			
			options.mapTypeId.style = style;
		}
		
		// Type Menu
		if ( this.options.typeMenu )
		{
			var typeMenu = this.options.typeMenu,
				typeMenuStyles = google.maps.MapTypeControlStyle;
			
			options.mapTypeControlOptions = {
				position: ''
			};
			
			var style;
			
			switch( typeMenu )
			{
				case 'dropdown': style = typeMenuStyles.DROPDOWN_MENU; break;
				case 'horizontal': style = typeMenuStyles.HORIZONTAL_BAR; break;
				
				default:
					style = typeMenuStyles.DEFAULT;
			}
			
			options.mapTypeControlOptions.style = style;
		}
		
		// Nav Menu
		if ( this.options.navMenu )
		{
			var navMenu = this.options.navMenu,
				navMenuStyles = google.maps.NavigationControlStyle;
				
			options.navigationControlOptions = {
				position: ''
			};
			
			var style;
			
			switch( navMenu )
			{
				case 'android': style = navMenuStyles.ANDROID; break;
				case 'small': style = navMenuStyles.SMALL; break;
				case 'zoompan':	style = navMenuStyles.ZOOM_PAN; break;
				
				default:
					style = navMenuStyles.DEFAULT;
			}
			
			options.navigationControlOptions.style = style;
		}
		
		
		// Create the map
		var map = this.map = new google.maps.Map( $map, options );
		
		
		// Add markers
		markers.each( function(o) {
			this.addMarker(o);
		}, this );
		
		
		// Set map bounds
		if ( markers.length )
		{
			if ( this.options.zoom )
			{
				var latLngBounds = this.markers[0].marker.position;
				
				this.set( 'center', latLngBounds );
				
				this.set( 'zoom', this.options.zoom );
			}
			else
			{
				(function() {
					this.fitBounds();
				}.bind( this )).delay( 100 );
			}
		}
		
		
		// Events
		google.maps.event.addListener( map, 'idle', (function() {
			
			// Fire onready
			if ( !this._idle && this.options.onReady )
				this.doCallback( this.options.onReady, this.markers );
			
			this._idle = true;
			
		}.bind( this )));
		
		
		// Set created state
		this._created = true;
	},
	
	doCallback: function( fn, data )
	{
		try {
			if ( $type( fn ) == 'string' )
				fn = eval( fn )( data );
			
			if ( $type( fn ) == 'function' )
				return fn( data );
		}
		catch(e) {
			alert( 'Error doing callback function: ' + fn );
		}
	},
	
	addMarker: function( m )
	{
		var map = this.map,
			latLng = false;
		
		if ( m.latitude && m.longitude )
			latLng = new google.maps.LatLng( m.latitude, m.longitude );
		else
			return;
		
		var options = {
			position: latLng,
			map: map,
			draggable: false
		};
		
		
		// Marker Style
		if ( this.options.markerStyle )
		{
			var markerStyle = this.options.markerStyle;
		
			var image = new google.maps.MarkerImage( markerStyle.src,
				
				// Size
				new google.maps.Size( markerStyle.width, markerStyle.height ),
				
				// Origin
				new google.maps.Point( 0, 0 ),
				
				// Anchor
				new google.maps.Point( ( markerStyle.width / 2 ), 36 ) // 36 is the height of the marker need to be able to specify this
			
			);
			
			// Shadow images are used because it has bigger horizontal dimensions and doesn't register click events (disbaled for now)
			/* var shadow = new google.maps.MarkerImage('images/beachflag_shadow.png',
				
					new google.maps.Size( 37, 32 ),
					new google.maps.Point( 0,0 ),
					new google.maps.Point( 0, 32 )
				
				);
			*/
			
			options.icon = image;
		}
		
		
		// Create marker
		var marker = new google.maps.Marker( options );
			marker.instance = this;
		
		
		var data = {
			marker: marker,
			data: m
		};
		
		// Create popup
		if ( this.options.tooltip || this.options.popup )
		{
			var opts = {
				latlng: marker.getPosition(),
				data: m
			}
			
			if ( $H( this.options.tooltip ).getLength() )
				$extend( opts, { tooltip: this.options.tooltip } );
			
			if ( $H( this.options.popup ).getLength() )
				$extend( opts, { popup: this.options.popup } );
			
			data.info = new Aurora.Map.Info( marker, opts );
		}
		
		
		// Add to marker set and return
		this.markers.push( data );
		
		return data;
		
	},
	
	clearMarkers: function()
	{
		this.markers.each( function(m) {
			m.marker.setMap( null );
		});
		
		this.markers.empty();
	},
	
	fitBounds: function()
	{
		var map = this.map,
			bounds = this.calculateBounds();
		
		map.fitBounds( bounds );
	},
	
	calculateBounds: function()
	{
		var bounds = new google.maps.LatLngBounds;
		
		this.markers.each( function(m) {
		
			bounds.extend( m.marker.position );
		
		});
		
		return bounds;
	},
	
	set: function( what, value )
	{
		switch ( what )
		{
			case 'zoom':
				this.map.setZoom( value );
			break;
			
			case 'center':
				this.map.setCenter( value );
			break;
			
			case 'panAndZoom':
			
				if ( !value.position || !value.zoom )
					return;
				
				this.map.setCenter( value.position );
				
				var panListener = google.maps.event.addListener( this.map, 'idle', (function() {
					
					if ( this.get( 'zoom' ) != value.zoom )
						this.set( 'zoom', value.zoom );
					
					google.maps.event.removeListener( panListener );
					
					// $log( panListener )
					
				}.bind( this )));
				
			break;
			
			default:
				this.store[ what ] = value;
		}
		
		return this;
	},
	
	get: function( what )
	{
		switch ( what )
		{
			case 'zoom':
				return this.map.getZoom();
			
			default:
				return this.store[ what ];
		}
	}

});

/*
	Class: Aurora.Map.Info
*/
Aurora.Map.Info = new Class({
	
	Implements: Options,
	
	options: {
		
		align: 'bottom',
		
		data: {},
		
		offset: { x: 0, y: 0 },
		
		latlng: null,
		
		tooltip: null,
		popup: null
		
	},
	
	initialize: function( marker, options )
	{
		this.marker = marker;
		
		var instance = this.instance = marker.instance;
		
		this.setOptions( options );
		
		if ( !this.options.latlng )
			this.options.latlng = this.options.marker.getPosition();
		
		var map = this.marker.map,
			data = $H( options.data );
		
		var Info = this.info = (function( options ) {
		
			this.options = options;
			this.$el = null;
			this.setMap( map );
		
		});
		
		Info.prototype = new google.maps.OverlayView();
		
		Info.prototype.replaceData = function( match, string ) {
		
			var splitStr = string.split( '.' );
			
			var foundData = data;
			
			splitStr.each( function( s ) {
			
				try {
					foundData = foundData[s];
				}
				catch(e) {}
			
			});
			
			return foundData;
		
		};
		
		Info.prototype.onAdd = function() {
		
			var $el = this.$el = new Element( 'div' ).inject( document.body ).hide(),
				$popup = new Element( 'div' ).inject( $el );
			
			switch( this.options.type )
			{
				case 'tooltip':
					$el.setStyle( 'z-index', 1001 );
				break;
				
				case 'popup':
					$el.setStyle( 'z-index', 1002 );
				break;
			}
			
			// Template
			if ( instance.dataType == 'template' )
			{
				switch( this.options.type )
				{
					case 'tooltip':
						template = instance.$container.getElement( '.aurora-map-tooltip' );
					break;
					
					case 'popup':
						template = instance.$container.getElement( '.aurora-map-popup' );
					break;
				
				}
				
				options.template = ( template ? template.get( 'html' ) : '' );
				
				var template = ( template ? template.get( 'html' ) : '' );
				
				template = template
					.replace( /(%28)(.+)(%29)/g, '($2)' )
					.replace( /\(([^\(]+)\)/g, this.replaceData );
				
				$popup.set( 'html', template );
			}
			
			// Render function
			else if ( this.options.renderFn )
			{
				var html = Aurora.doCallback( this.options.renderFn, data );
				
				html.inject( $popup );
				
			}
			
			// Normal
			else
			{
				$popup.set( 'html', this.options.content );
			}
			
			
			// Close Button
			var closeBtn = $popup.getElement( '.aurora-map-close' );
			
			if ( closeBtn )
			{
				closeBtn.addEvent( 'click', function() {
					this.hide();
				}.bind( this ));
			}
			
			if ( this.options.alwaysVisible )
				this.show();
		}
		
		Info.prototype.draw = function() {
		
			// Calculate where the popup should be positioned on the map depending on its size
			var overlayProjection = this.getProjection();
			
			var bounds = new google.maps.LatLngBounds( options.latlng );
			
			var sw = overlayProjection.fromLatLngToDivPixel( bounds.getSouthWest() ),
				ne = overlayProjection.fromLatLngToDivPixel( bounds.getNorthEast() );
			
			var div = this.$el;
			
			var size = div.measure( function() {
				
				return this.getDimensions();
				
			});
			
			var left, top;
			
			// Position by alignment setting
			switch( this.options.align )
			{
				case 'top':
					left = sw.x - ( size.x / 2 );
					top = ne.y - ( size.y ) - 34; // standard marker height, this needs to be calculated eventually
				break;
				
				default: // bottom
					left = sw.x - ( size.x / 2 );
					top = ne.y;
			}
			
			// Calculate offsets
			if ( this.options.offset )
			{
				if ( this.options.offset.x )
					left += this.options.offset.x;
				
				if ( this.options.offset.y )
					top += this.options.offset.y;
			}
			
			// Set position
			div.setStyles({
				left: left,
				top: top
			});
		}
		
		Info.prototype.onRemove = function()
		{
			this.$el.parentNode.removeChild( this.$el );
		}
		
		Info.prototype.hide = function()
		{
			if ( this.$el )
				this.$el.hide();
		}
		
		Info.prototype.show = function()
		{
			// If we haven't rendered it yet, calculate the size and add to the map
			if ( !this._rendered )
			{
				var size = this._size = this.$el.measure( function() {
				
					this.setStyle( 'display', 'inline-block' );
				
					return this.getDimensions();
				
				});
				
				if ( Browser.Engine.name == 'trident' && Browser.Engine.version == 5 )
					size.width = 250;
				
				// Resize element absolutely
				this.$el.setStyles({ position: 'absolute', width: size.width + 5, height: size.height });
				
				this._rendered = true;
				
				// Inject into map
				var panes = this.getPanes();
					panes.overlayImage.appendChild( this.$el );
			}
			
			this.draw();
			
			// If we are showing a tooltip, hide any exisiting ones (should be none)
			if ( this.options.type == 'tooltip' )
			{
				if ( !this.options.alwaysVisible )
				{
					if ( instance._tooltip ) instance._tooltip.hide();
					if ( instance._tooltip != this ) instance._tooltip = this;
				}
			}
			
			// Same for the popup
			if ( this.options.type == 'popup' )
			{	
				if ( !this.options.alwaysVisible )
				{
					if ( instance._popup ) instance._popup.hide();
					if ( instance._popup != this ) instance._popup = this;
				}
			}
			
			// Pan
			if ( this.options.panAndZoom )
				instance.set( 'panAndZoom', { position: marker.position, zoom: this.options.panAndZoom } );
			else
				this.panMap();
			
			// Show the element
			this.$el.show();
		}
		
		Info.prototype.toggle = function()
		{
			if ( !this.$el )
				return;
			
			if ( this.$el.isVisible() )
				this.hide();
			else
				this.show();
		}
		
		Info.prototype.isVisible = function()
		{
			if ( this.$el.isVisible() )
				return true;
			else
				return false;
		}
		
		Info.prototype.panMap = function()
		{
			// if we go beyond map, pan map
			var bounds = map.getBounds();
			
			if ( !bounds )
				return;
			
			// The position of the infowindow
			var position = options.latlng;
			
			// The dimension of the infowindow
			var iwWidth = this._size.width;
			var iwHeight = this._size.height;
			
			// The offset position of the infowindow
			var iwOffsetX = 0;
			var iwOffsetY = 0;
			
			switch( this.options.align )
			{
				case 'top':
					iwOffsetX = -( iwWidth / 2 );
					iwOffsetY = -( iwHeight ) - 34; // Standard marker size, might need to be calculated/supplied later on
				break;
				
				default: // bottom
					iwOffsetX = -( iwWidth / 2 );
					iwOffsetY = 0;
			}
			
			// Padding on the popup (so it doesn't appear right at the edge of the map when opened)
			var padX = 20,
				padY = 20;
			
			// The degrees per pixel
			var mapDiv = map.getDiv(),
				mapWidth = mapDiv.offsetWidth,
				mapHeight = mapDiv.offsetHeight,
				boundsSpan = bounds.toSpan(),
				longSpan = boundsSpan.lng(),
				latSpan = boundsSpan.lat(),
				degPixelX = longSpan / mapWidth,
				degPixelY = latSpan / mapHeight;
			
			// The bounds of the map
			var mapWestLng = bounds.getSouthWest().lng(),
				mapEastLng = bounds.getNorthEast().lng(),
				mapNorthLat = bounds.getNorthEast().lat(),
				mapSouthLat = bounds.getSouthWest().lat();
			
			// The bounds of the infowindow
			var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX,
				iwEastLng = position.lng() + (iwOffsetX + iwWidth + padX) * degPixelX,
				iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY,
				iwSouthLat = position.lat() - (iwOffsetY + iwHeight + padY) * degPixelY;
			
			// Calculate center shift
			var shiftLng =
				( iwWestLng < mapWestLng ? mapWestLng - iwWestLng : 0 ) +
				( iwEastLng > mapEastLng ? mapEastLng - iwEastLng : 0 );
			
			var shiftLat =
				( iwNorthLat > mapNorthLat ? mapNorthLat - iwNorthLat : 0 ) +
				( iwSouthLat < mapSouthLat ? mapSouthLat - iwSouthLat : 0 );
			
			// Get the center of the map
			var center = map.getCenter();
			
			// Calculate new center
			var centerX = center.lng() - shiftLng;
			var centerY = center.lat() - shiftLat;
			
			// Center the map to the new center
			map.setCenter( new google.maps.LatLng( centerY, centerX ) );
		
		}
		
		this.render();
	
	},
	
	renderInfoWindow: function()
	{
		var instance = this.instance,
			marker = this.marker,
			data = this.options.data;
		
		var html = '';
		
		switch( instance.options.dataType )
		{
			case 'template':
			
				var template = instance.$container.getElement( '.aurora-map-info' ).get( 'html' );
				
				var replaceData = function( match, string ) {
					
					var splitStr = string.split( '.' );
					
					var foundData = data;
					
					splitStr.each( function( s ) {
					
						try {
							foundData = foundData[s];
						}
						catch(e) {}
					
					});
					
					return foundData;
				
				};
				
				template = template
					.replace( /(%28)(.+)(%29)/g, '($2)' )
					.replace( /\(([^\(]+)\)/g, replaceData );
				
				html = template;
				
				// $log( html );
			
			break;
			
			case 'function':
			
				html = Aurora.doCallback( this.options.renderFn, data );
			
			break;
		
		}
	
		var infoWindow = new google.maps.InfoWindow({
			content: html
		});
		
		var openInfoWindow = function() {
			infoWindow.open( instance.map, marker );
		};
		
		google.maps.event.addListener( marker, 'click', function() {
		
			openInfoWindow();
		
		});
		
		if ( instance.options.info )
			if ( instance.options.info.alwaysVisible )
				openInfoWindow();
	},
	
	render: function()
	{
		var marker = this.marker,
			instance = this.instance;
		
		var Info = this.info;
		
		var tooltip, popup;
		
		if ( instance.options.customPopups )
		{
			// Tooltip
			if ( this.options.tooltip )
				this.tooltip = tooltip = new Info( $extend( this.options.tooltip, { type: 'tooltip' } ) );
			
			// Popup
			if ( this.options.popup )
				this.popup = popup = new Info( $extend( this.options.popup, { type: 'popup' } ) );
			
			// Mouseover
			google.maps.event.addListener( marker, 'mouseover', (function() {
			
				if ( !tooltip )
					return;
				
				instance._tooltip = tooltip;
				
				if ( popup && popup.isVisible() )
					return;
				
				tooltip.show();
			
			}.bind( this )));
			
			
			// Mouseout
			google.maps.event.addListener( marker, 'mouseout', (function() {
			
				if ( this.options.tooltip.alwaysVisible )
					return;
				
				if ( tooltip )
					tooltip.hide();
			
			}.bind( this )));
			
			
			// Click
			google.maps.event.addListener( marker, 'click', (function() {
				
				if ( !popup )
					return;
					
				if ( instance._popup )
					if ( instance._popup != popup )
						instance._popup.hide();
					
				instance._popup = popup;
				
				if ( tooltip )
					tooltip.hide();
				
				popup.toggle();
			
			}.bind( this )));
		}
		else
		{
			this.renderInfoWindow();
		}
		
	}
	
});

document.addEvent( 'domready', function() {

	var maps = $$( '.aurora-map' );
	
	if ( !maps.length )
		return;
	
	Aurora.Google.load( 'maps', (function() {
	
		maps.each( function( $el ) {
			$el.store( 'map', new Aurora.Map( $el, $el.getDataFromComment() ) );
		});
	
	}));

});
