var i18n = i18n || {};

// language strings
jQuery.i18n.addDictionary({
	'BROWSERCOMPAT': "Your browser is not supported - please upgrade it."
});

// TODO implement _t()
var i18n = i18n || {};

;(function($) {


	/**
	 * Google Map jQuery object.
	 * events triggered:
	 *  - 'DemandMap:drag': The map is manually dragged more than a trivial amount
	 *  - 'DemandMap:move': The map view is moved/zoomed, either manually or automatically (for example, by a search)
	 */
	$.fn.DemandMap_MapView = function(_options) { 
		
		// ######## static private properties #########
		
		// config
		var defaults = {
			'zoomStart': 5, // roughly fits New Zealand boundaries
			'zoomWithKeyword': 12,
			'zoomAccuracyBase': 9, // @see http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy 
			'lat': -41.28648,
			'lng': 174.776217, // Wellington, New Zealand
			'queryBoundsMultiplier': 0,
			'minDragRefreshRatio': 0.025, // a drag of at least 2.5% of map length triggers refresh
			'clusterZoomIncrease': 1, // increase cluster zoom level beyond the actually computed bounds
			'clusterIconSmall':  null,
			'clusterIconMedium':  null,
			'clusterIconLarge':  null,
			'clusterLimitSmall': 10,
			'clusterLimitMedium': 50,
			'markerIconsByID': {},
			// using the container width and height by default
			'width': null, 
			'height': null,
			// @todo This is hardcoded to assume a #Control panel floated to the right of the map
			'resizeable': false
		};
		
		return this.each(function(){
			// ######## private instance properties #########
			var options = $.extend({}, defaults, _options);
			
			// Reference to this control
			var $this = $(this);
			var container = this;
			
			// objects
			var gmap; // GMap2 - the main map 
			var activeMarkers = []; // An array of markers currently on the GMap2
			var demandPointIcons = [];
			var heatmap; // GeoHeatmap
			var heatmapOverlay; // GOverlay
			
			// state
			var _markersByCategory = [];
			var _overlaysBySupplier = [];
			// we can't query map bounds if the map is hidden, so we cache values
			var _cachedBounds;

			// url config
			options.width = ($.jget['width']) ? Number($.jget['width']) : $this.width();
			options.height = ($.jget['height']) ? Number($.jget['height']) : $this.height();
			
			if($.jget['lat']) options.lng = Number($.jget['lat']);
			if($.jget['lng']) options.lat = Number($.jget['lng']);
			if($.jget['zoom']) options.zoomStart = Number($.jget['zoom']);
			
			$this.height(options.height);
			$('.dataView').height(options.height);
			
			if(!GBrowserIsCompatible()) demandmap.App.errorMessage($.i18n._t('BROWSERCOMPAT'));
			gmap = new GMap2(container, {size:new GSize(options.width,options.height)});
			
			gmap.addControl(new GLargeMapControl());
			gmap.addMapType(G_PHYSICAL_MAP)
			gmap.addControl(new GMapTypeControl());
			gmap.addControl(new GScaleControl());
			gmap.enableScrollWheelZoom();
			
			// determine map center
			if($.jget['bounds[sw]'] && $.jget['bounds[ne]']) {
				var boundsSW = $.jget['bounds[sw]'].split(',');
				var boundsNE = $.jget['bounds[ne]'].split(',');
				var bounds = new GLatLngBounds(
					new GLatLng(boundsSW[0],boundsSW[1]),
					new GLatLng(boundsNE[0],boundsNE[1])
				);
				gmap.setCenter(bounds.getCenter(), gmap.getBoundsZoomLevel(bounds));
			} else if($.jget['lat'] && $.jget['lng']) {
				gmap.setCenter(new GLatLng(Number($.jget['lat']),Number($.jget['lng'])));
				gmap.setZoom(($.jget['zoom']) ? Number($.jget['zoom']) : options.zoomWithKeyword);
			} else {
				gmap.setCenter(new GLatLng(options.lat, options.lng));
				gmap.setZoom(options.zoomStart);
			}
			
			// event listeners
			GEvent.bind(gmap, 'load', container, function(e) {
				if(options.resizeable) {
					resizeMapToFit();
				}
				$this.trigger('DemandMap:mapload', [gmap]);
			});
			GEvent.bind(gmap, 'dragstart', container, function(e) {
				_cachedDragStartCenter = gmap.getCenter();
			});
			GEvent.bind(gmap, 'dragend', container, function(e) {
				var shouldRefresh = _shouldRefreshAfterDrag(_cachedDragStartCenter, gmap.getCenter());
				if(shouldRefresh) {
					$this.trigger('DemandMap:drag', []);
					$this.trigger('DemandMap:move');
				}
				_cachedDragStartCenter = null;
			});
			GEvent.addListener(gmap, 'zoomend', function(e) {
				$this.trigger('DemandMap:move');
			});
			if(options.resizeable) {
				$(window).resize(function() {
					resizeMapToFit();
				});
				// call it right away
				resizeMapToFit();
			}

			// setup demandpoint icons from static data in html header
			for(var i=0; i<demandCategories.length; i++) {
				demandPointIcons[Number(demandCategories[i].ID)] = new GIcon(G_DEFAULT_ICON, demandCategories[i].MarkerURL);
			}

			_setupAccessibilityFeatures();
			
			if($.jget['debug_heatmap']) {
				initHeatmap();
			}
			
			function initHeatmap() {
				$('body').append('<input type="hidden" id="HMMapdata" />');
				$('body').append('<input type="hidden" name="HMImageURL" id="HMImageURL" />');
				
				heatmap = new GEOHeatmap();
				heatmap.Init(options.width,options.height);

				var data = new Array();
				
				setTimeout(refreshHeatmap.bind(this), 1000);
				
				GEvent.bind(gmap, 'dragend', container, refreshHeatmap.bind(this));
				GEvent.addListener(gmap, 'zoomend', refreshHeatmap.bind(this));
				$('#DemandCategoryControl').bind('DemandCategoryControl:loadcategories', refreshHeatmap.bind(this));
 				
			}
			
			function refreshHeatmap() {
				// for some reason heatmaps are inaccurate on a national level
				if(gmap.getZoom() < 7) return false;
				
				var limit = 100;
				var url = $('#DemandCategoryControl').fn('getDataURL');	
				url += '&limit=' + limit;
				url = url.replace(/\.json/, '.heatmapjson')
				$.getJSON(
					url,
					function(json) {
						heatmap.SetData(json.items);
						var preUrl = heatmap.GetURL();

						// Now render in our Google Map
						if(typeof(heatmapOverlay) != 'undefined') {
							gmap.removeOverlay(heatmapOverlay);
						}
						heatmapOverlay = new HMGoogleOverlay(preUrl);
						gmap.addOverlay(heatmapOverlay);
					}
				)
			}

			// ######## public methods #########
			$this.fn({
				'checkResize' : function() {
					gmap.checkResize();
				},
				'addPoints' : function(markersData) {
					$(this).fn('addMarkers', markersData);
				},
				'addMarkers' : function(markersData) {
				    $(this).fn('clearMarkers');
				    
				    // Add the new ones
					var totalMarkerCount = 0;
					var totalClusterCount = 0;
					for(var i=0; i<markersData.length; i++) {
						totalMarkerCount += (markersData[i].IsCluster) ? markersData[i].ClusterMarkerCount : 1;
						totalClusterCount += (markersData[i].IsCluster) ? 1 : 0;
					}
					var averageClusterCount = totalMarkerCount / totalClusterCount;
					for(var i=0; i<markersData.length; i++) {
						if(markersData[i].IsCluster) {
							var marker = addClusterMarker(markersData[i], averageClusterCount)
						} else {
							var marker = addCategoryMarker(markersData[i]);
						}
						gmap.addOverlay(marker);
						activeMarkers.push(marker);

					}

					return activeMarkers;
				},
				'addOverlays' : function(overlays, supplierID) {
					if(!_overlaysBySupplier[supplierID]) _overlaysBySupplier[supplierID] = [];
					for(var i=0; i<overlays.length; i++) {
						gmap.addOverlay(overlays[i]);
						_overlaysBySupplier[supplierID].push(overlays[i]);
					}
					
					//gmap.setCenter(overlays[0].getBounds().getNorthEast(), gmap.getBoundsZoomLevel(overlays[0].getBounds()));
				},
				'clearOverlaysBySupplier': function(supplierID) {
					var set = [];
					if(supplierID >= 0) {
						if(_overlaysBySupplier[supplierID]) set.push(_overlaysBySupplier[supplierID]);
					} else {
						// if no supplier id is provided, clear all overlays
						for(supplierID in _overlaysBySupplier) {
							set.push(_overlaysBySupplier[supplierID]);
						}
					}
					
					for(var i=0; i<set.length; i++) {
						for(var j=0; j<set[i].length; j++) {
							if(typeof(set[i][j]) != 'undefined') gmap.removeOverlay(set[i][j]);
						}
					}
				},
				'getQueryBounds' : function() {
					var bounds = ($this.is(':visible') || !_cachedBounds) ? gmap.getBounds() : _cachedBounds;
					_cachedBounds = bounds;

					var ne = bounds.getNorthEast();
					var sw = bounds.getSouthWest(ne.lng());

					// get distance
					var dist_lng = Math.abs((ne.lng() - sw.lng()) * options.queryBoundsMultiplier); 
					var dist_lat = Math.abs((sw.lat() - ne.lat()) * options.queryBoundsMultiplier);

					// refetch extended lat/lng and extend boundaries
					var sw_ext = new GLatLng(sw.lat() + dist_lat, sw.lng() - dist_lng);
					var ne_ext = new GLatLng(ne.lat() - dist_lat, ne.lng() + dist_lng);
					bounds.extend(sw_ext);
					bounds.extend(ne_ext);

					return bounds;
				},
				'setCenter' : function (latLng, zoomLevel) {
					gmap.setCenter(latLng, zoomLevel);
					$this.trigger('DemandMap:move');
				},
				'getCenter' : function () {
					return gmap.getCenter();
				},
				'clearMarkers': function() {
				    // Remove current markers
				    for(var i=0; i<activeMarkers.length; i++) {
					    gmap.removeOverlay(activeMarkers[i]);
				    }
				    activeMarkers = [];
				},
				'clearOverlays': function() {
					gmap.clearOverlays();
				},
				'clear': function() {
					$this.fn('clearMarkers');
					$this.fn('clearOverlays')
				},
				'checkResize': function() {
					gmap.checkResize();
				},
				'getGmap': function() {
					return gmap;
				}
			});
			
			// ######## private methods #########
			
			function addClusterMarker(markerData, averageClusterCount) {
				// different sizes depending on the cluster count
				if(markerData.ClusterMarkerCount < averageClusterCount/2) {
					var icon = options.clusterIconSmall;
				} else if(markerData.ClusterMarkerCount < averageClusterCount) {
					var icon = options.clusterIconMedium;
				} else {
					var icon = options.clusterIconLarge;
				}
				var marker = new GMarker(
					new GLatLng(Number(markerData.Point.y),Number(markerData.Point.x)),
					icon
				);
				
				marker.data = markerData;
				
				var html = "<ul>";
				for(categoryID in marker.data.ClusterCategories) {
					if(demandCategories[categoryID]) html+= "<li>" + demandCategories[categoryID].Title + " (" + marker.data.ClusterCategories[categoryID] + ")</li>";
				}
				html += "</ul>"

				if(canViewDetails()) {
					GEvent.addListener(marker, 'click', function(e) {
						this.openInfoWindowHtml(html);
					});
				}
				
				// @todo circular reference
				marker.gmap = gmap;
				marker.clusterZoomIncrease = options.clusterZoomIncrease;
				
				// @todo correct centering?!
				GEvent.addListener(marker, 'dblclick', function(e) {
					var bounds = new GLatLngBounds(
						new GLatLng(this.data.MarkerBounds.ne.lat,this.data.MarkerBounds.ne.lng),
						new GLatLng(this.data.MarkerBounds.sw.lat,this.data.MarkerBounds.sw.lng)
					);
					$this.fn('setCenter',
						bounds.getCenter(), 
						gmap.getBoundsZoomLevel(bounds) + this.clusterZoomIncrease);
					return true;
				});
				/*
				html += '<a href="#" class="zoomin">Zoom in</a>';
				var node = $('body').append(html);
				$('a.zoomin', node).data('bounds', markerData.MarkerBounds);
				$('a.zoomin', node).data('gmap', gmap);
				$('a.zoomin', node).one('click', function(e) {
					var bounds = $(this).data('bounds');
					var gmap = $(this).data('gmap');
					var bounds = new GLatLngBounds(
						new GLatLng(bounds.sw.lat,bounds.sw.lng),
						new GLatLng(bounds.ne.lat,bounds.ne.lng)
					);
					gmap.setCenter(bounds.getCenter());
					return false;
				});
				marker.bindInfoWindowHtml(html);
				*/
				return marker;
			}
			
			/**
			 * In edit mode, you shouldn't see infowindows
			 */
			function canViewDetails() {
				return (!$('#MapEdit').length);
			}
			
			function addCategoryMarker(markerData) {
				var category = demandCategories[markerData.CategoryID];
				var parentCategory = demandCategories[markerData.ParentCategoryID];

				if(markerData.icon) {
					var icon = markerData.icon;
				} else if(category && options.markerIconsByID[markerData.CategoryID]) {
					var icon = options.markerIconsByID[markerData.CategoryID]; 
				} else if(parentCategory && options.markerIconsByID[markerData.ParentCategoryID]) {
					var icon = options.markerIconsByID[markerData.ParentCategoryID]; 
				} else {
					var icon = new GIcon(G_DEFAULT_ICON);
				}
				
				var markerOptions = {
					draggable: (markerData.draggable),
					icon: icon,
					bouncy:  (markerData.bouncy)
				};
				
				var marker = new GMarker(
					new GLatLng(Number(markerData.Point.y),Number(markerData.Point.x)),
					markerOptions
				);
				marker.data = markerData;
				if(canViewDetails()) {
					GEvent.addListener(marker, 'click', function() {
						$.get(
							$.sprintf('DemandPoint/infowindowhtml/%d', this.data.ID),
							function(html) {
								this.openInfoWindowHtml(html);
							}.bind(this)
						);
					});
				}
				
				
				// add to categorized array
				if(!_markersByCategory[markerData.CategoryID]) _markersByCategory[markerData.CategoryID] = [];
				_markersByCategory[markerData.CategoryID].push(marker);
				
				return marker;
			}
			
			/**
			 * @todo This method assumes a right-floated #Control panel.
			 * When resizing is triggered, the width of this panel is used
			 * to compute the map width.
			 */
			function resizeMapToFit() {
				var _lastCenter = gmap.getCenter();

				// @todo Hack for IE7, some box sizing prevents this
				// from being set any smaller without breaking the map <div>
				// below the controls. It's actually rendered as 10px in most
				// browsers.
				horizontalGap = 30;
				
				controlPadding = 20;

				winWidth = $(window).width();
				winHeight = $(window).height();
				controlWidth = $('#Control').width();

				// control panel is hidden in mini map
				if($('#Control').is(':visible')) {
					$this.width(winWidth - controlWidth - horizontalGap);
					$('#Control').addClass('popup');
					$('#Control.popup').height(winHeight - controlPadding);
				} else {
					$this.width(winWidth);
				}
				
				$this.height(winHeight);

				gmap.setCenter(_lastCenter);
			}
			
			function _shouldRefreshAfterDrag(pointFrom, pointTo) {
				var bounds = gmap.getBounds();
				var mapDistance = Math.abs(bounds.getNorthEast().distanceFrom(bounds.getSouthWest()));
				var dragDistance = Math.abs(pointFrom.distanceFrom(pointTo));
				return ((dragDistance / mapDistance) > options.minDragRefreshRatio);
			}
			
			/**
			 * @see http://web.archive.org/web/20070623191950/http://lecshare.com/blog/2006/12/26/making-google-maps-accessible-part-1-controls/
			 * @see http://web.archive.org/web/20070212004424/http://lecshare.com/blog/2007/01/28/making-google-maps-accessible-part-2-accessible-data/
			 */
			function _setupAccessibilityFeatures() {
				$('#AccessibilityControls').hide();
				$('.accessibilityControlAnchor a').bind('click', function(el) {
					$('#AccessibilityControls').toggle();
				});
				
				new GKeyboardHandler(gmap);  
				
				$('#Form_AccessibilityForm').one('submit', function(e) { return false; });
				$('#Form_AccessibilityForm').show();
				//$('.accessibilityControlAnchor').show();
				$('input[name=action_zoomin]').bind('click', function(e) {
					gmap.setZoom(gmap.getZoom() + 1); return false;
				}.bind(this));
				$('input[name=action_zoomout]').bind('click', function(e) {
					gmap.setZoom(gmap.getZoom() - 1); return false;
				}.bind(this));
				$('input[name=action_viewnormal]').bind('click', function(e) {
					gmap.setMapType(G_NORMAL_MAP); return false;
				}.bind(this));
				$('input[name=action_viewsatellite]').bind('click', function(e) {
					gmap.setMapType(G_SATELLITE_MAP); return false;
				}.bind(this));
				$('input[name=action_viewhybrid]').bind('click', function(e) {
					gmap.setMapType(G_HYBRID_MAP); return false;
				}.bind(this));
				$('input[name=action_viewterrain]').bind('click', function(e) {
					gmap.setMapType(G_PHYSICAL_MAP); return false;
				}.bind(this));
				$('input[name=action_pannorth]').bind('click', function(e) {
					gmap.panDirection(0, +1); return false;
				}.bind(this));
				$('input[name=action_pansouth]').bind('click', function(e) {
					gmap.panDirection(0, -1); return false;
				}.bind(this));
				$('input[name=action_paneast]').bind('click', function(e) {
					gmap.panDirection(+1, 0); return false;
				}.bind(this));
				$('input[name=action_panwest]').bind('click', function(e) {
					gmap.panDirection(-1, 0); return false;
				}.bind(this));
			}
		
		});
	}
})(jQuery);