(function($)
{
	/**
	 * Plugin to initalize a new GoogleMaps instance.
	 * @param Object settings
	 * @return jQuery
	 */
	$.fn.gmap = function(settings)
	{
		settings = $.extend({
			mapStartPosition: {
				latitude:  null,
				longitude: null,
				zoomLevel: null
			},
			mapTypes:       [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP],
			mapTypeControl: GMapTypeControl,
			mapZoomControl: null,
			mapControl:     null,
			lang:           'EN_en'
		}, settings || {});

		return this.each(function() {
			try
			{
				$.data(this, 'gmapObject', new gmap(this, settings));
			}
			catch (exception)
			{
				$(this).html(exception);
			}
		});
	};

	/**
	 * PlugIn to add new marker to map.
	 * @param Object Coordinates
	 * @param Object Settings
	 * @return jQuery
	 */
	$.fn.gmapAddMarker = function(coords, settings)
	{
		return this.each(function() {
			var gmapObject = $.data(this, 'gmapObject');
			if (gmapObject === 'undefined')
				throw new Error('gmap: gmap Object ist not defined');

			gmapObject.addMarker(coords, settings);
		});
	};

	/**
	 * PlugIn to initialize a route planner.
	 * @param Object settings
	 * @return jQuery
	 */
	$.fn.gmapInitRoutePlanner = function(settings)
	{
		settings = $.extend({
			routePlannerClass: $.noop,
			settings:          null,
			lang:              'EN_en'
		}, settings || {});

		return this.each(function() {
			var gmapObject = $.data(this, 'gmapObject');
			if (gmapObject === 'undefined')
				throw new Error('gmap: gmap Object ist not defined');

			gmapObject.initializeRoutePlanner(settings);
		});
	};

/**********************************************************************************************************************/

	/**
	 * Constructor of Object "gmapMarker".
	 * @param GMap2   GmapObject
	 * @param GLatLng point
	 * @param Object  settings
	 */
	gmapMarker = function(GmapObject, point, settings)
	{
		/**
		 * Reference to this object for private functions.
		 * @var gmapMarker
		 * @access private
		 */
		var that = this;

		/**
		 * Marker Settings.
		 * @var Object
		 * @access private
		 */
		var settings = $.extend({
			icon:        null,
			zoomLevel:   10,
			centerToMap: false,
			infoWindow:  null
		}, settings || {});

		/**
		 * Returns the marker settings.
		 * @return Object
		 * @access public
		 */
		this.getSettings = function()
		{
			return settings;
		};

		/**
		 * @param Object settings
		 * @return Object
		 * @access private
		 */
		var getMarkerSettings = function(settings)
		{
			var markerSettings = {};

			if (settings.icon)
			{
				if ($.isArray(settings.icon.shadowSize))
					settings.icon.shadowSize = new GSize(settings.icon.shadowSize[0], settings.icon.shadowSize[1]);

				if ($.isArray(settings.icon.iconAnchor))
					settings.icon.iconAnchor = new GPoint(settings.icon.iconAnchor[0], settings.icon.iconAnchor[1]);
				if ($.isArray(settings.icon.infoWindowAnchor))
					settings.icon.infoWindowAnchor = new GPoint(settings.icon.infoWindowAnchor[0], settings.icon.infoWindowAnchor[1]);

				if ($.isArray(settings.icon.iconSize))
				{
					if (!settings.icon.iconAnchor)
						settings.icon.iconAnchor = new GPoint(Math.round(settings.icon.iconSize[0] / 2), Math.round(settings.icon.iconSize[1] / 2));
					if (!settings.icon.infoWindowAnchor)
						settings.icon.infoWindowAnchor = new GPoint(Math.round(settings.icon.iconSize[0] / 2), 0);

					settings.icon.iconSize = new GSize(settings.icon.iconSize[0], settings.icon.iconSize[1]);
				}

				markerSettings.icon = $.extend(new GIcon(G_DEFAULT_ICON), settings.icon);
			}

			return markerSettings;
		};

		/**
		 * Returns the GMarker Object.
		 * @return GMarker
		 * @access public
		 */
		this.getGmarkerObject = function()
		{
			return GmarkerObject;
		};

		/**
		 * @param Object settings
		 * @access private
		 */
		var initInfoWindow = function(settings)
		{
			settings = $.extend({
				event: 'click'
			}, settings);

			GEvent.addListener(that.getGmarkerObject(), settings.event, GEvent.callback(that, function() {
				that.openInfoWindow();
			}));
		};

		/**
		 * @return GMap2
		 * @access public
		 */
		this.getGmapObject = function()
		{
			return GmapObject;
		};

		var GmarkerObject = new GMarker(point, getMarkerSettings(this.getSettings()));

		if (settings.infoWindow)
			initInfoWindow(this.getSettings().infoWindow);
	};

	/**
	 * Opens the info window.
	 * @param string Info windows content (optional)
	 * @access public
	 */
	gmapMarker.prototype.openInfoWindow = function()
	{
		if (!this.getSettings().infoWindow) return;

		var settings = $.extend({
			content:    '',
			zoomOnOpen: false
		}, this.getSettings().infoWindow);

		if (arguments.length > 0)
		{
			settings.content = arguments[0];
			this.settings.infoWindow.content = settings.content;
		}

		if ($.isArray(settings.content))
			this.getGmarkerObject().openInfoWindowTabsHtml(settings.content);
		else
			this.getGmarkerObject().openInfoWindowHtml(settings.content);

		if (settings.zoomOnOpen)
			this.getGmapObject().setCenter(this.getLatLng(), this.getZoomLevel());
	};

	/**
	 * Returns the LatLng-object.
	 * @return GLatLng
	 * @access public
	 */
	gmapMarker.prototype.getLatLng = function()
	{
		return this.getGmarkerObject().getLatLng();
	};

	/**
	 * Returns the zoom level.
	 * @return integer
	 * @access public
	 */
	gmapMarker.prototype.getZoomLevel = function()
	{
		return this.getSettings().zoomLevel;
	};

/**********************************************************************************************************************/

	/**
	 * Constructor of Object "gmap".
	 * @param DOM-Object containerElement
	 * @param Object     settings
	 */
	gmap = function(containerElement, settings)
	{
		/**
		 * Reference to this object for private functions.
		 * @var gmap
		 * @access private
		 */
		var that = this;

		/**
		 * @var GMap2
		 * @access private
		 */
		var GmapObject = null;

		/**
		 * @var GDirectionsObject
		 * @access private
		 */
		var GDirectionsObject = null;

		if (GBrowserIsCompatible())
			GmapObject = new GMap2(containerElement);
		else
			throw 'Sorry, the Google Maps API is not compatible with this browser.';

		$(window).unload(GUnload);

		/**
		 * Returns the settings object.
		 * @return Object
		 * @access public
		 */
		this.getGmapObject = function()
		{
			return GmapObject;
		};

		/**
		 * Returns the GDirections object.
		 * @return GDirections
		 * @access public
		 */
		this.getGDirectionsObject = function()
		{
			if (!GDirectionsObject)
				GDirectionsObject = new GDirections(GmapObject);

			return GDirectionsObject;
		};

		/**
		 * Returns the container DOM-Element.
		 * @return DOM-Element
		 * @access public
		 */
		this.getContainerElement = function()
		{
			return containerElement;
		};

		/**
		 * Returns the settings object.
		 * @return Object
		 * @access public
		 */
		this.getSettings = function()
		{
			return settings;
		};

		/**
		 * Initialize the map types.
		 * @access private
		 */
		var _initMapTypes = function(mapTypes)
		{
			var currentMapTypes = that.getGmapObject().getMapTypes();
			for (key in currentMapTypes)
				if (jQuery.inArray(currentMapTypes[key], mapTypes) == -1)
					that.getGmapObject().removeMapType(currentMapTypes[key]);
		};

		/**
		 * Initialize the map type control.
		 * @access private
		 */
		var _initMapTypeControl = function(mapTypes, mapTypeControl)
		{
			if (mapTypeControl && $.isArray(mapTypes) && mapTypes.length > 0)
				that.getGmapObject().addControl(new mapTypeControl());
		};

		/**
		 * Initialize the map zoom control.
		 * @access private
		 */
		var _initZoomControl = function(mapZoomControl)
		{
			if (mapZoomControl)
				that.getGmapObject().addControl(new mapZoomControl());
		};

		/**
		 * Initialize the map control.
		 * @access private
		 */
		var _initMapControl = function(mapControl)
		{
			if (mapControl)
				that.getGmapObject().addControl(new mapControl());
		};

		// Initialize controls
		_initMapTypes(this.getSettings().mapTypes);
		_initMapTypeControl(this.getSettings().mapTypes, this.getSettings().mapTypeControl);
		_initZoomControl(this.getSettings().mapZoomControl);
		_initMapControl(this.getSettings().mapControl);

		// Center map
		this.centerMapToSettingDefaults();

		/**
		 * @var MarkerManager
		 * @access private
		 */
		this.MarkerManagerObject = null;

		window.setTimeout($.proxy(function() {
			this.MarkerManagerObject = new MarkerManager(this.getGmapObject());

			$(containerElement).trigger('mapLoaded');
		}, this));
	};

	/**
	 * @param Object settings
	 * @access public
	 */
	gmap.prototype.initializeRoutePlanner = function(settings)
	{
		this.routePlannerObject = new settings.routePlannerClass(this, settings.settings);

		GEvent.addListener(this.getGDirectionsObject(), 'load', $.proxy(function() {
			this.getRoutePlannerObject().onDirectionLoad();
		}, this));

		GEvent.addListener(this.getGDirectionsObject(), 'error', $.proxy(function() {
			this.getRoutePlannerObject().onDirectionError();
		}, this));
	};

	/**
	 * @return Object
	 * @access public
	 */
	gmap.prototype.getRoutePlannerObject = function()
	{
		return this.routePlannerObject;
	};

	/**
	 * Returns the marker manager object.
	 * @return MarkerManager
	 * @access public
	 */
	gmap.prototype.getMarkerManager = function()
	{
		return this.MarkerManagerObject;
	};

	/**
	 * @param String|Object from
	 * @param String|Object to
	 * @access public
	 */
	gmap.prototype.setDirections = function (from, to)
	{
		from = this.formatAddress(from);
		to = this.formatAddress(to);

		this.getGDirectionsObject().loadFromWaypoints([from, to], {
			'locale': this.getSettings().lang,
			getPolyline: true,
			getSteps: true
		});
	};

	/**
	 * @param String|Object value
	 * @return String
	 * @access public
	 */
	gmap.prototype.formatAddress = function(value)
	{
		if (typeof value == 'object')
		{
			var a = $.extend({
				street:  null,
				zip:     null,
				city:    null,
				country: null
			}, value || {});

			value = '';

			if (jQuery.trim(a.street).length > 0)
				value += a.street;
			if (jQuery.trim(a.zip).length > 0)
				value += (a.street.length == 0 ? a.zip : ', ' + a.zip);
			if (jQuery.trim(a.city).length > 0)
				value += (value.length == 0 ? a.city : (a.zip.length == 0 ? ', ' + a.city : ' ' + a.city));
		}

		return value;
	};

	/**
	 * @param Object coords
	 * @param Object settings
	 * @access public
	 */
	gmap.prototype.addMarker = function(coords, settings)
	{
		var coords = $.extend({
			longitude: 0.0,
			latitude:  0.0
		}, coords || {});

		var latLng = new GLatLng(coords.latitude, coords.longitude);
		if (!latLng) return;

		var centerMap = false;
		if (typeof settings.centerMap != 'undefined')
		{
			centerMap = settings.centerMap;
			delete settings.centerMap;
		}

		var refreshMap = true;
		if (typeof settings.refreshMap != 'undefined')
		{
			refreshMap = settings.refreshMap;
			delete settings.refreshMap;
		}

		var marker = new gmapMarker(this, latLng, settings);

		this.getMarkerManager().addMarker(marker.getGmarkerObject(), 1);

		if (refreshMap)
			this.getMarkerManager().refresh();

		if (centerMap)
			this.getGmapObject().setCenter(marker.getLatLng(), marker.getZoomLevel());
	};

	/**
	 * Centers the map to defaults settings.
	 * @access public
	 */
	gmap.prototype.centerMapToSettingDefaults = function()
	{
		var latitude  = this.getSettings().mapStartPosition.latitude;
		var longitude = this.getSettings().mapStartPosition.longitude;
		var zoomLevel = this.getSettings().mapStartPosition.zoomLevel;

		if (latitude && longitude)
			this.setCenter(new GLatLng(latitude, longitude), zoomLevel);
	};

	/**
	 * Centers the map to a specific point.
	 * @param GLatLng point
	 * @param integer zoom
	 * @param integer mapType
	 * @access public
	 */
	gmap.prototype.setCenter = function(latLng, zoom, mapType)
	{
		if (!mapType) mapType = G_NORMAL_MAP;
		this.getGmapObject().setCenter(latLng, zoom, mapType);
	};
})(jQuery);
