# Source: google-maps/google-map.js

    /**
     * @namespace WPGMZA
     * @module GoogleMap
     * @requires WPGMZA.Map
     * @pro-requires WPGMZA.ProMap
     */
    (function($) {
    	var Parent;

    	/**
    	 * Constructor
    	 * @param element to contain the map
    	 */
    	WPGMZA.GoogleMap = function(element, options)
    	{
    		var self = this;

    		if(!window.google)
    			throw new Error("Google API not loaded - " + wpgmza_api_not_enqueued_reason);

    		Parent.call(this, element, options);

    		this.loadGoogleMap();

    		if(options)
    			this.setOptions(options);

    		google.maps.event.addListener(this.googleMap, "click", function(event) {
    			self.dispatchEvent("click");
    		});

    		google.maps.event.addListener(this.googleMap, "rightclick", function(event) {
    			var wpgmzaEvent = new WPGMZA.Event("rightclick");
    			wpgmzaEvent.latLng = {
    				lat: event.latLng.lat(),
    				lng: event.latLng.lng()
    			};
    			self.dispatchEvent(wpgmzaEvent);
    		});

    		google.maps.event.addListener(this.googleMap, "dragend", function(event) {
    			self.dispatchEvent("dragend");
    		});

    		google.maps.event.addListener(this.googleMap, "zoom_changed", function(event) {
    			self.dispatchEvent("zoom_changed");
    			self.dispatchEvent("zoomchanged");
    		});

    		// Idle event
    		google.maps.event.addListener(this.googleMap, "idle", function(event) {
    			self.onIdle(event);
    		});

    		// Dispatch event
    		if(!WPGMZA.isProVersion())
    		{
    			this.dispatchEvent("created");
    			WPGMZA.events.dispatchEvent({type: "mapcreated", map: this});
    		}
    	}

    	// If we're running the Pro version, inherit from ProMap, otherwise, inherit from Map
    	if(WPGMZA.isProVersion())
    	{
    		Parent = WPGMZA.ProMap;
    		WPGMZA.GoogleMap.prototype = Object.create(WPGMZA.ProMap.prototype);
    	}
    	else
    	{
    		Parent = WPGMZA.Map;
    		WPGMZA.GoogleMap.prototype = Object.create(WPGMZA.Map.prototype);
    	}
    	WPGMZA.GoogleMap.prototype.constructor = WPGMZA.GoogleMap;

    	/**
    	 * Creates the Google Maps map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.loadGoogleMap = function()
    	{
    		var self = this;
    		var options = this.settings.toGoogleMapsOptions();

    		this.googleMap = new google.maps.Map(this.engineElement, options);
    		google.maps.event.addListener(this.googleMap, "bounds_changed", function() {
    			self.onBoundsChanged();
    		});

    		if(this.settings.bicycle)
    			this.enableBicycleLayer(true);
    		if(this.settings.traffic)
    			this.enableTrafficLayer(true);
    		if(this.settings.transport)
    			this.enablePublicTransportLayer(true);
    		this.showPointsOfInterest(this.settings.show_points_of_interest);

    		// Move the loading wheel into the map element (it has to live outside in the HTML file because it'll be overwritten by Google otherwise)
    		$(this.engineElement).append($(this.element).find(".wpgmza-loader"));
    	}

    	WPGMZA.GoogleMap.prototype.setOptions = function(options)
    	{
    		Parent.prototype.setOptions.call(this, options);

    		this.googleMap.setOptions(this.settings.toGoogleMapsOptions());

    		var clone = $.extend({}, options);
    		if(clone.center instanceof WPGMZA.LatLng || typeof clone.center == "object")
    			clone.center = {
    				lat: parseFloat(clone.center.lat),
    				lng: parseFloat(clone.center.lng)
    			};

    		this.googleMap.setOptions(clone);
    	}

    	/**
    	 * Adds the specified marker to this map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.addMarker = function(marker)
    	{
    		marker.googleMarker.setMap(this.googleMap);

    		Parent.prototype.addMarker.call(this, marker);
    	}

    	/**
    	 * Removes the specified marker from this map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.removeMarker = function(marker)
    	{
    		marker.googleMarker.setMap(null);

    		Parent.prototype.removeMarker.call(this, marker);
    	}

    	/**
    	 * Adds the specified polygon to this map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.addPolygon = function(polygon)
    	{
    		polygon.googlePolygon.setMap(this.googleMap);

    		Parent.prototype.addPolygon.call(this, polygon);
    	}

    	/**
    	 * Removes the specified polygon from this map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.removePolygon = function(polygon)
    	{
    		polygon.googlePolygon.setMap(null);

    		Parent.prototype.removePolygon.call(this, polygon);
    	}

    	/**
    	 * Adds the specified polyline to this map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.addPolyline = function(polyline)
    	{
    		polyline.googlePolyline.setMap(this.googleMap);

    		Parent.prototype.addPolyline.call(this, polyline);
    	}

    	/**
    	 * Removes the specified polygon from this map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.removePolyline = function(polyline)
    	{
    		polyline.googlePolyline.setMap(null);

    		Parent.prototype.removePolyline.call(this, polyline);
    	}

    	WPGMZA.GoogleMap.prototype.addCircle = function(circle)
    	{
    		circle.googleCircle.setMap(this.googleMap);

    		Parent.prototype.addCircle.call(this, circle);
    	}

    	WPGMZA.GoogleMap.prototype.removeCircle = function(circle)
    	{
    		circle.googleCircle.setMap(null);

    		Parent.prototype.removeCircle.call(this, circle);
    	}

    	/**
    	 * Delegate for google maps getCenter
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.getCenter = function()
    	{
    		var latLng = this.googleMap.getCenter();

    		return {
    			lat: latLng.lat(),
    			lng: latLng.lng()
    		};
    	}

    	/**
    	 * Delegate for google maps setCenter
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.setCenter = function(latLng)
    	{
    		WPGMZA.Map.prototype.setCenter.call(this, latLng);

    		if(latLng instanceof WPGMZA.LatLng)
    			this.googleMap.setCenter({
    				lat: latLng.lat,
    				lng: latLng.lng
    			});
    		else
    			this.googleMap.setCenter(latLng);
    	}

    	/**
    	 * Delegate for google maps setPan
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.panTo = function(latLng)
    	{
    		if(latLng instanceof WPGMZA.LatLng)
    			this.googleMap.panTo({
    				lat: latLng.lat,
    				lng: latLng.lng
    			});
    		else
    			this.googleMap.panTo(latLng);
    	}

    	/**
    	 * Delegate for google maps getCenter
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.getZoom = function()
    	{
    		return this.googleMap.getZoom();
    	}

    	/**
    	 * Delegate for google maps getZoom
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.setZoom = function(value)
    	{
    		return this.googleMap.setZoom(value);
    	}

    	/**
    	 * Gets the bounds
    	 * @return object
    	 */
    	WPGMZA.GoogleMap.prototype.getBounds = function()
    	{
    		var bounds = this.googleMap.getBounds();
    		var northEast = bounds.getNorthEast();
    		var southWest = bounds.getSouthWest();

    		return {
    			topLeft: {
    				lat: northEast.lat(),
    				lng: southWest.lng()
    			},
    			bottomRight: {
    				lat: southWest.lat(),
    				lng: northEast.lng()
    			}
    		};
    	}

    	/**
    	 * Fit to given boundaries
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.fitBounds = function(southWest, northEast)
    	{
    		if(southWest instanceof WPGMZA.LatLng)
    			southWest = {lat: southWest.lat, lng: southWest.lng};
    		if(northEast instanceof WPGMZA.LatLng)
    			northEast = {lat: northEast.lat, lng: northEast.lng};

    		this.googleMap.fitBounds(southWest, northEast);
    	}

    	/**
    	 * Fit the map boundaries to visible markers
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.fitBoundsToVisibleMarkers = function()
    	{
    		var bounds = new google.maps.LatLngBounds();
    		for(var i = 0; i < this.markers.length; i++)
    		{
    			if(markers[i].getVisible())
    				bounds.extend(markers[i].getPosition());
    		}
    		this.googleMap.fitBounds(bounds);
    	}

    	/**
    	 * Enables / disables the bicycle layer
    	 * @param enable boolean, enable or not
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.enableBicycleLayer = function(enable)
    	{
    		if(!this.bicycleLayer)
    			this.bicycleLayer = new google.maps.BicyclingLayer();

    		this.bicycleLayer.setMap(
    			enable ? this.googleMap : null
    		);
    	}

    	/**
    	 * Enables / disables the bicycle layer
    	 * @param enable boolean, enable or not
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.enableTrafficLayer = function(enable)
    	{
    		if(!this.trafficLayer)
    			this.trafficLayer = new google.maps.TrafficLayer();

    		this.trafficLayer.setMap(
    			enable ? this.googleMap : null
    		);
    	}

    	/**
    	 * Enables / disables the bicycle layer
    	 * @param enable boolean, enable or not
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.enablePublicTransportLayer = function(enable)
    	{
    		if(!this.publicTransportLayer)
    			this.publicTransportLayer = new google.maps.TransitLayer();

    		this.publicTransportLayer.setMap(
    			enable ? this.googleMap : null
    		);
    	}

    	/**
    	 * Shows / hides points of interest
    	 * @param show boolean, enable or not
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.showPointsOfInterest = function(show)
    	{
    		// TODO: This will bug the front end because there is textarea with theme data
    		var text = $("textarea[name='theme_data']").val();

    		if(!text)
    			return;

    		var styles = JSON.parse(text);

    		styles.push({
    			featureType: "poi",
    			stylers: [
    				{
    					visibility: (show ? "on" : "off")
    				}
    			]
    		});

    		this.googleMap.setOptions({styles: styles});
    	}

    	/**
    	 * Gets the min zoom of the map
    	 * @return int
    	 */
    	WPGMZA.GoogleMap.prototype.getMinZoom = function()
    	{
    		return parseInt(this.settings.min_zoom);
    	}

    	/**
    	 * Sets the min zoom of the map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.setMinZoom = function(value)
    	{
    		this.googleMap.setOptions({
    			minZoom: value,
    			maxZoom: this.getMaxZoom()
    		});
    	}

    	/**
    	 * Gets the min zoom of the map
    	 * @return int
    	 */
    	WPGMZA.GoogleMap.prototype.getMaxZoom = function()
    	{
    		return parseInt(this.settings.max_zoom);
    	}

    	/**
    	 * Sets the min zoom of the map
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.setMaxZoom = function(value)
    	{
    		this.googleMap.setOptions({
    			minZoom: this.getMinZoom(),
    			maxZoom: value
    		});
    	}

    	WPGMZA.GoogleMap.prototype.latLngToPixels = function(latLng)
    	{
    		var map = this.googleMap;
    		var nativeLatLng = new google.maps.LatLng({
    			lat: parseFloat(latLng.lat),
    			lng: parseFloat(latLng.lng)
    		});
    		var topRight = map.getProjection().fromLatLngToPoint(map.getBounds().getNorthEast());
    		var bottomLeft = map.getProjection().fromLatLngToPoint(map.getBounds().getSouthWest());
    		var scale = Math.pow(2, map.getZoom());
    		var worldPoint = map.getProjection().fromLatLngToPoint(nativeLatLng);
    		return {
    			x: (worldPoint.x - bottomLeft.x) * scale,
    			y: (worldPoint.y - topRight.y) * scale
    		};
    	}

    	WPGMZA.GoogleMap.prototype.pixelsToLatLng = function(x, y)
    	{
    		if(y == undefined)
    		{
    			if("x" in x && "y" in x)
    			{
    				y = x.y;
    				x = x.x;
    			}
    			else
    				console.warn("Y coordinate undefined in pixelsToLatLng (did you mean to pass 2 arguments?)");
    		}

    		var map = this.googleMap;
    		var topRight = map.getProjection().fromLatLngToPoint(map.getBounds().getNorthEast());
    		var bottomLeft = map.getProjection().fromLatLngToPoint(map.getBounds().getSouthWest());
    		var scale = Math.pow(2, map.getZoom());
    		var worldPoint = new google.maps.Point(x / scale + bottomLeft.x, y / scale + topRight.y);
    		var latLng = map.getProjection().fromPointToLatLng(worldPoint);
    		return {
    			lat: latLng.lat(),
    			lng: latLng.lng()
    		};
    	}

    	/**
    	 * Handle the map element resizing
    	 * @return void
    	 */
    	WPGMZA.GoogleMap.prototype.onElementResized = function(event)
    	{
    		if(!this.googleMap)
    			return;
    		google.maps.event.trigger(this.googleMap, "resize");
    	}

    })(jQuery);