/**
 * Extended Map functionality for Synergy.
 * <p>
 * Uses options in Synergy.Options
 * </p>
 */
Synergy.Map = function()
{
	var _options = Synergy.Options;

	var _map = new GMap2($(_options.mapCanvas)[0], {
		backgroundColor : '#3580ba'
	});
	var _allowedBounds = _options.boundry;
	var _markerManager = new AdvMarker.Manager(_map, {
		clusterPopupCallback : generateInfoWindowPopup
	});
	var _currentPoint = 0;
	var _cancel = false;

	applyCustomTiles();

	GEvent.addListener(_map, "move", function()
	{
		checkBounds();
	});

	/**
	 * Toggle a marker type.
	 *
	 * @param {string} typeName
	 */
	_map.toggleType = function( typeName )
	{
		_markerManager.toggleType(typeName);
	};

	/**
	 * Set a zoom level.
	 *
	 * @param {int} level
	 * @param {function} [callback]
	 */
	_map.animateSetZoom = function( level, callback )
	{
		if( level == _map.getZoom() ) {
			if( $.isFunction(callback) ) {
				callback();
			}
			return;
		}

		var zoomEnd = GEvent.addListener(_map, 'zoomend', function()
		{
			_map.animateSetZoom(level, callback);
			GEvent.removeListener(zoomEnd);
		});

		if( (level - _map.getZoom()) > 0 ) {
			// Zoom in
			_map.zoomIn(_map.getCenter(), true, true);
		}

		if( (level - _map.getZoom()) < 0 ) {
			// Zoom out
			_map.zoomOut(_map.getCenter(), true, true);
		}

	};

	/**
	 * Clear all the markers off the map.
	 */
	_map.clear = function()
	{
		_markerManager.clear();
		_currentPoint = 0;
	};

	/**
	 * Add a marker type.
	 *
	 * @param {string} name
	 * @param {GIcon} icon
	 */
	_map.addMarkerType = function( name, icon )
	{
		_markerManager.addType(name, icon);
	};

	/**
	 * Add a marker
	 *
	 * @param {json} suggestion A json representation of a suggestion
	 */
	_map.addMarker = function( suggestion )
	{
		var marker = _markerManager.addMarker(new GLatLng(suggestion.getPoint().getLat(), suggestion.getPoint().getLng()), suggestion.getPoint().getTypeName());
		marker.suggestion = suggestion;

		return marker;
	};

	/**
	 * Cycle throught map markers.
	 * <p>
	 * There will be an animation between the current point and next marker on the list
	 * </p>
	 *
	 * @param {function} [onComplete]
	 */
	_map.cycle = function( onComplete )
	{
		_cancel = false;
		_map.closeExtInfoWindow();

		var poly;

		var marker = _markerManager.getMarkerArray()[_currentPoint];
		_currentPoint++;
		if( _currentPoint > _markerManager.getMarkerArray().length - 1 ) {
			_currentPoint = 0;
		}

		if( marker.getType().isHidden() ) {
			_map.cycle(onComplete);
			return;
		}

		var point = marker.getLatLng();

		_map.animateSetZoom(_options.animateZoomLevel, doPan)

		function animate( d )
		{
			var p = poly.GetPointAtDistance(d);

			if( false == _cancel ) {
				if( p !== null ) {
					_map.panTo(p);

					var panEnd = GEvent.addListener(_map, 'moveend', function()
					{
						GEvent.removeListener(panEnd);
						animate(d + Synergy.Options.animationTravelSpeed);
					});
				}
				else {
					finishPan();
				}
			}
		}

		function doPan()
		{
			poly = new GPolyline( [ _map.getCenter(), point ], 'transparent', 0, 0);
			_map.addOverlay(poly);

			animate(Synergy.Options.animationTravelSpeed);

		}

		function finishPan()
		{
			_map.removeOverlay(poly);
			marker.getCluster().openInfoWindow();

			if( false == _cancel ) {
				onComplete();
			}
		}

	};

	/**
	 * Cancel any running actions.
	 */
	_map.cancelActions = function()
	{
		_cancel = true;
	};

	/**
	 * Find the cluster with the passed in suggestion id;
	 */
	_map.findClusterWithSuggestion = function( suggestionId )
	{
		var markerList = _markerManager.getMarkerArray();
		for( var int = 0; int < markerList.length; int++ ) {
			var marker = markerList[int];
			if( marker.suggestion.getPoint().getId() == suggestionId ) {
				return marker.getCluster();
			}
		}

		return null;
	};

	/**
	 * Generate an info window popop
	 *
	 * @param {GMarker} marker
	 * @param {GMap2} map
	 * @param {array} listOfMarkers a List of Gmarkers that the cluster contains.
	 */
	function generateInfoWindowPopup( marker, map, listOfMarkers )
	{
		var updateMarkerIdList = [];
		var markerArray = {};

		// Check to see what markers need to be loaded.
		$.each(listOfMarkers, function( count, marker )
		{
			if( false == marker.suggestion.isLoaded() ) {
				updateMarkerIdList.push(marker.suggestion.getPoint().getId());
				markerArray[marker.suggestion.getPoint().getId()] = marker.suggestion;
			}
		});

		if( updateMarkerIdList.length > 0 ) {
			// Get a list of the full data
			$.getJSON(_options.markerFeedUrl, {
				id : updateMarkerIdList
			}, function( dataArray )
			{
				// Update the suggestions
					$.each(dataArray, function( counter, data )
					{
						markerArray[data.id].loadData(data);
					});

					openInfoWindow(marker, map, listOfMarkers);
				});
		}
		else {
			openInfoWindow(marker, map, listOfMarkers);
		}

		function openInfoWindow( clusterMarker, map, listOfMarkers )
		{
			if( listOfMarkers.length == 1 ) {
				marker.openExtInfoWindow(map, "markerInfoWindow", '<div class="box singleTip"></div>');

				$('#markerInfoWindow_contents .box').append(listOfMarkers[0].suggestion.simple());

			}
			else {
				marker.openExtInfoWindow(map, "markerInfoWindow", '<div class="box" style="height: 150px;"><h2>' + _options.groupedPinCalloutTitle + '</h2><div class="text"></div></div>');

				var description = $('#markerInfoWindow_contents .text');

				$.each(listOfMarkers, function( count, marker )
				{
					description.append(marker.suggestion.generateDescription());
				});

				$('#markerInfoWindow_contents .text .suggestion:last').addClass('last');
			}
		}

	}

	/**
	 * Ensure the centre point is in allowed bounds.
	 */
	function checkBounds()
	{
		// Perform the check and return if OK
		if( _allowedBounds.containsLatLng(_map.getCenter()) ) {
			return;
		}

		_map.setCenter(findAllowedBounds(_map.getCenter(), _allowedBounds));
	}

	/**
	 * Find the closest point to passed in point in the allowedBounds.
	 *
	 * @param {GLatLng} point
	 * @param {GBounds} allowedBounds
	 * @return {GLatLng}
	 */
	function findAllowedBounds( point, allowedBounds )
	{
		var C = point;
		var X = C.lng();
		var Y = C.lat();

		var AminX = allowedBounds.getSouthWest().lng();
		var AminY = allowedBounds.getSouthWest().lat();
		var AmaxX = allowedBounds.getNorthEast().lng();
		var AmaxY = allowedBounds.getNorthEast().lat();

		if( X < AminX ) {
			X = AminX;
		}
		if( X > AmaxX ) {
			X = AmaxX;
		}
		if( Y < AminY ) {
			Y = AminY;
		}
		if( Y > AmaxY ) {
			Y = AmaxY;
		}

		return new GLatLng(Y, X);
	}

	/**
	 * Apply custom synergy maps
	 */
	function applyCustomTiles()
	{
		var mt = _map.getMapTypes();

		for( var i = 0; i < mt.length; i++ ) {
			mt[i].getMinimumResolution = function()
			{
				return _options.minZoomLevel;
			};
			mt[i].getMaximumResolution = function()
			{
				return _options.maxZoomLevel;
			};
			_map.removeMapType(mt[i]);
		}

		var copyright = new GCopyright(1, new google.maps.LatLngBounds(new google.maps.LatLng(-90, -180), new google.maps.LatLng(90, 180)), 0, "©2010 Market United");
		var copyrightCollection = new GCopyrightCollection('');
		copyrightCollection.addCopyright(copyright);

		var tilelayers = [ new GTileLayer(copyrightCollection, 7, 10) ];
		tilelayers[0].getTileUrl = function( tile, zoom )
		{
			var baseZoom = 7, baseX = 104, baseY = 68, minX = {
				zoom7 : 0,
				zoom8 : 0,
				zoom9 : 0,
				zoom10 : 0
			}, minY = {
				zoom7 : 0,
				zoom8 : 0,
				zoom9 : 0,
				zoom10 : 0
			}, maxX = {
				zoom7 : 5,
				zoom8 : 11,
				zoom9 : 23,
				zoom10 : 47
			}, maxY = {
				zoom7 : 9,
				zoom8 : 19,
				zoom9 : 39,
				zoom10 : 79
			}, url = "";

			var zoomLevel = Math.pow(2, (zoom - baseZoom));
			var x = tile.x - (baseX * zoomLevel);
			var y = tile.y - (baseY * zoomLevel);

			var server = ((tile.x * tile.y) % _options.tileHostNumber) + 1;
			if( x < (minX["zoom" + zoom]) || x > (maxX["zoom" + zoom]) || y < (minY["zoom" + zoom]) || y > (maxY["zoom" + zoom]) ) {
				url = "tile_blue.png";
			}
			else {
				url = zoom + "/tile_" + x + "_" + y + ".png";
			}
			return "http://m" + server + "." + _options.tileHostName + "/" + _options.tileVersion + "/" + url;
		};

		tilelayers[0].isPng = function()
		{
			return true;
		};
		var stylemap = new GMapType(tilelayers, new GMercatorProjection(18), "", {
			errorMessage : "No data available"
		});
		stylemap.getMinimumResolution = function()
		{
			return _options.minZoomLevel;
		};
		stylemap.getMaximumResolution = function()
		{
			return _options.maxZoomLevel;
		};

		_map.addMapType(stylemap);
		_map.setMapType(stylemap);
	}

	return _map;
};

