
//= depends: zapi

/**
 * A simplified marker that is more light-weight than the standard GMarker.
 * Being a more light-weight cousin to GMarker, it might not have all of the
 * fancy bells and whistles, but it is faster and you can get more markers on
 * a map with it.
 *
 * This class is largely based on Pamela Fox's MarkerLight sample code in the
 * Google Maps API Samples:
 *
 * http://code.google.com/p/gmaps-samples/source/browse/trunk/youtube/markerlight.js
 *
 * It has been extended with a few features worth noting:
 *
 * * labels can be used within the marker itself using the 'label' option.
 * * sprites can be used using the 'sprite' option, which in turn can contain
 *   'offsetX' and 'offsetY' properties.
 * * an image cache for faster image loading and DOM building.
 * * auto-detection of image height and width, although when using a sprite
 *   you'll have to provide your own height and width since it wouldn't make
 *   much sense to use the entire image as opposed to the sprite tile.
 * * proper shadows.
 *
 * Constructor options:
 *
 * * image: the URL of the image to use for the marker. Required.
 * * imageIE6: an alterative image to use on IE6. This can be used to allow
 *   for transparent 8-bit PNGs and the like without having to use
 *   AlphaImageLoader.
 * * offset: a GSize object representing the anchor position of the image
 *   relative to the latlng point. Defaults to GSize(0, 0).
 * * height: the height of the image in pixels. Defaults to the image height.
 * * width: the width of the image in pixels. Defaults to the image width.
 * * sprite: an object that should contain offsetX and offsetY properties
 *   representing the marker's tile within the sprite image. These both
 *   default to 0.
 *
 * These options all have shadow equivalents as well, i.e. shadowImage,
 * shadowSprite, etc.
 *
 * A style for the label can additionally be set via the third constructor
 * argument.
 *
 * @name ZAPI.GoogleMaps.MarkerLight
 * @constructor
 */
ZAPI.namespace('GoogleMaps').MarkerLight = Class.create(google.maps.Overlay, (function() {
	// some static vars and methods...
	var imageCache = new Hash();

	function fetchImage(img, options) {
		var retval;
		function setupImage() {
			var i = new Image();
			if (options.onError) {
				Element.observeOnce(i, 'error', options.onError);
			}
			i.src = img;
			return i;
		};

		if (options.useCache) {
			if (!(retval = imageCache.get(img))) {
				retval = imageCache.set(img, setupImage());
			}
			retval = Element.clone(retval);
		}
		else {
			retval = setupImage();
		}
		return retval;
	}

	function checkImageForPNGAndIE(img) {
		return Prototype.Browser.IE &&
			Prototype.Browser.IEVersion <= 6 &&
			/\.png(\?.+)?$/.test(img);
	}

	function alphaImageLoader(src, sizingMethod) {
		sizingMethod = sizingMethod || 'scale';
		return "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "',sizingMethod='" + sizingMethod + "')";
	}

	try {
		document.execCommand('BackgroundImageCache', false, true);
	} catch(err) {}

	/** @scope ZAPI.GoogleMaps.MarkerLight */
	return {
		initialize: function(latlng, options) {
			this._options = $H({
				'offset': new google.maps.Size(0, 0),
				'shadowOffset': new google.maps.Size(0, 0),
				'useCache': true
			}).merge(options);

			this._labelStyle = $H({
				'position': 'absolute',
				'textAlign': 'center',
				'width': '100%',
				'fontWeight': 'bold',
				'color': 'white'
			}).merge(this._options.get('labelStyle'));

			this._markerStyle = $H({
				'position': 'absolute',
				'paddingLeft': '0px',
				'cursor': 'pointer'
			}).merge(this._options.get('markerStyle'));

			if (!this._options.get('image')) {
				throw new Error("You must set an image to create a ZAPI.GoogleMaps.MarkerLight");
			}

			if (checkImageForPNGAndIE(this._options.get('image'))) {
				if (this._options.get('imageIE6')) {
					this._image = fetchImage(this._options.get('imageIE6'), {
						'useCache': this._options.get('useCache'),
						'onError': this._options.get('onErrorIE6')
					});
				}
			}
			else {
				this._image = fetchImage(this._options.get('image'), {
					'useCache': this._options.get('useCache'),
					'onError': this._options.get('onError')
				});
			}

			if (this._options.get('shadowImage')) {
				if (checkImageForPNGAndIE(this._options.get('shadowImage'))) {
					if (this._options.get('shadowImageIE6')) {
						this._shadowImage = fetchImage(this._options.get('shadowImageIE6'), {
							'useCache': this._options.get('useCache'),
							'onError': this._options.get('onErrorShadowIE6')
						});
					}
				}
				else {
					this._shadowImage = fetchImage(this._options.get('shadowImage'), {
						'useCache': this._options.get('useCache'),
						'onError': this._options.get('onErrorShadow')
					});
				}
			}

			this.latlng = latlng;
			this._height = this._options.get('height') || this._image.height;
			this._width = this._options.get('width') || this._image.width;

			this._shadowHeight = this._options.get('shadowHeight') ||
				this._options.get('height') || this._image.height;
			this._shadowWidth = this._options.get('shadowWidth') ||
				this._options.get('width') || this._image.width;

			if (typeof(this._labelStyle.get('lineHeight')) == 'undefined') {
				this._labelStyle.set('lineHeight', this._height + 'px');
			}

			google.maps.Overlay();
			this.initialize = this.gInitialize;
		},

		gInitialize: function(map) {
			var div = document.createElement('div');
			var shadowDiv;
			var label;

			Element.setStyle(div, this._markerStyle.toObject());

			if (this._options.get('title')) {
				div.title = this._options.get('title');
			}

			if (this._options.get('label')) {
				label = document.createElement('div');
				Element.setStyle(label, this._labelStyle.toObject());
				label.appendChild(document.createTextNode(this._options.get('label')));
			}

			if (this._options.get('sprite')) {
				div = this._setupSprite(
					div,
					this._options.get('sprite'),
					this._image,
					this._options.get('imageIE6'),
					label
				);
			}
			else {
				div = this._setupRegularImage(
					div,
					this._image,
					this._options.get('imageIE6'),
					label
				)
			}

			if (this._shadowImage) {
				shadowDiv = document.createElement('div');
				shadowDiv.style.position = 'absolute';
				shadowDiv.style.paddingLeft = '0px';

				if (this._options.get('shadowSprite')) {
					shadowDiv = this._setupSprite(
						shadowDiv,
						this._options.get('shadowSprite'),
						this._shadowImage,
						this._options.get('shadowImageIE6')
					);
				}
				else {
					shadowDiv = this._setupRegularImage(
						shadowDiv,
						this._shadowImage,
						this._options.get('shadowImageIE6'),
						label
					);
				}
			}

			google.maps.Event.addDomListener(div, 'mouseover', function() {
				this._bringToFront();
			}.bind(this));

			google.maps.Event.addDomListener(div, 'mouseout', function() {
				this._sendBack();
			}.bind(this));

			$w('click mouseover mouseout mouseenter mouseleave').each(function(e) {
				Event.observe(div, e, function(event) {
					google.maps.Event.trigger(this, e);
				}.bind(this));
			}.bind(this));

			map.getPane(google.maps.MAP_MARKER_PANE).appendChild(div);

			if (shadowDiv) {
				map.getPane(google.maps.MAP_MARKER_SHADOW_PANE).appendChild(shadowDiv);
			}

			this._map = map;
			this._div = div;
			this._shadowDiv = shadowDiv;
			this._label = label;
		},

		remove: function() {
			if (this._div.parentNode) {
				this._div.parentNode.removeChild(this._div);
			}
			if (this._shadowDiv) {
				this._shadowDiv.parentNode.removeChild(this._shadowDiv);
			}
			this.fire('remove');
		},

		copy: function() {
			return new ZAPI.GoogleMaps.MarkerLight(this.latlng, this._options.clone(), this._labelStyle.clone());
		},

		redraw: function(force) {
			if (!force) {
				return;
			}

			var divPixel = this._map.fromLatLngToDivPixel(this.latlng);
			this._div.style.width = this._width + 'px';
			this._div.style.left = (divPixel.x + this._options.get('offset').width) + 'px';
			this._div.style.height = (this._height) + 'px';
			this._div.style.top = (divPixel.y + this._options.get('offset').height) - this._height + 'px';

			if (this._shadowDiv) {
				this._shadowDiv.style.width = this._shadowWidth + 'px';
				this._shadowDiv.style.left = (divPixel.x + this._options.get('shadowOffset').width) + 'px';
				this._shadowDiv.style.height = (this._shadowHeight) + 'px';
				this._shadowDiv.style.top = (divPixel.y + this._options.get('shadowOffset').height) - this._shadowHeight + 'px';
			}
		},

		_bringToFront: function() {
			var z = google.maps.Overlay.getZIndex(this.latlng.lat());
			this._div.style.zIndex = 1e9;
		},

		_sendBack: function() {
			var z = google.maps.Overlay.getZIndex(this.latlng.lat());
			this._div.style.zIndex = z;
		},

		_calculateSpriteOffsets: function(toCalculate) {
			var sprite = this._options.get('sprite');
			var offsetX = (-toCalculate.offsetX) || 0;
			var offsetY = toCalculate.offsetY || 0;
			var spriteHeight = sprite.height || this._height;
			var spriteWidth = sprite.width || this._width;
			var setX = offsetX - ((spriteWidth - this._width) / 2);
			var setY = -(offsetY + ((spriteHeight - this._height) / 2));

			return { 'x': setX, 'y': setY };
		},

		_setupSprite: function(div, sprite, image, imageIE6, label) {
			var offsets = this._calculateSpriteOffsets(sprite);

			// Crappy IE6 hacks ahoy. We need to create an inner div that
			// houses the image and... just read it, okay?
			if (checkImageForPNGAndIE(image.src) && !imageIE6) {
				var innerDiv = document.createElement('div');

				innerDiv.runtimeStyle.filter = alphaImageLoader(image.src, 'crop');
				innerDiv.className = 'innerDiv';
				innerDiv.style.position = 'absolute';
				innerDiv.style.height = image.height + 'px';
				innerDiv.style.width = image.width + 'px';

				if (label) {
					label.style.top = (-offsets.y) + 'px';
					label.style.left = offsets.x + 'px';
					innerDiv.appendChild(label);
				}
				div.style.overflow = 'hidden';
				div.appendChild(innerDiv);
			}
			// Whereas on real browsers we can just use the background
			// proper style.
			else {
				div.style.backgroundImage = 'url(' + image.src + ')';
				div.style.backgroundRepeat = 'no-repeat';

				if (label) {
					if (Prototype.Browser.IE && Prototype.Browser.IEVersion < 8) {
						// for some reason this fixes IE label issues?
						label.style.left = '0px';
					}
					div.appendChild(label);
				}
			}

			div = this.setSpritePosition(div, offsets.x, offsets.y);

			return div;
		},

		setSpritePosition: function(div, x, y) {
			var innerDiv;
			if ((innerDiv = Element.down(div, 'div.innerDiv'))) {
				innerDiv.style.left = x + 'px';
				innerDiv.style.top = y + 'px';
			}
			else {
				div.style.backgroundPosition = x + 'px ' + y + 'px';
			}
			return div;
		},

		highlight: function() {
			if (this._options.get('sprite') && this._options.get('highlight')) {
				var offsets = this._calculateSpriteOffsets(this._options.get('highlight'));
				this.setSpritePosition(
					this.getContainer(),
					offsets.x,
					offsets.y
				);
			}
		},

		unhighlight: function() {
			if (this._options.get('sprite') && this._options.get('highlight')) {
				var sprite = this._options.get('sprite');
				var offsets = this._calculateSpriteOffsets(this._options.get('sprite'));
				this.setSpritePosition(
					this.getContainer(),
					offsets.x,
					offsets.y
				);
			}
		},

		_setupRegularImage: function(div, image, imageIE6, label) {
			// More crappy IE6 hacks ahoy...
			if (checkImageForPNGAndIE(image.src) && !imageIE6) {
				image.runtimeStyle.filter = alphaImageLoader(image.src, 'scale');
				image.src = 'http://maps.gstatic.com/intl/en_ALL/mapfiles/transparent.gif';
			}

			image.style.width = this._width + 'px';
			image.style.height = this._height + 'px';

			if (label) {
				div.appendChild(label);
			}
			div.appendChild(image);

			return div;
		},

		getPoint: function() {
			return this.getLatLng();
		},

		getLatLng: function() {
			return this.latlng;
		},

		getContainer: function() {
			return this._div;
		},

		getShadowContainer: function() {
			return this._shadowDiv;
		},

		getLabel: function() {
			return this._label;
		},

		observe: function() {
			var args = $A(arguments),
				callback = args.pop();
			args.each(function(eventName) {
				google.maps.Event.addListener(this, eventName, callback);
			}.bind(this));
		},

		fire: function() {
			$A(arguments).each(function(eventName) {
				google.maps.Event.trigger(this, eventName);
			}.bind(this));
		}
	};
}()));
