com_starsensortech_www_atms_gmap = new es_lang.Package(true,
	/*Title*/   'Client handlers for the google maps API',
	/*Docs */   '-- none --',
	/*Package*/ function() {
		// Package
		var LANG = es_lang;
		var LOG = es_util_logging;
		var WU = com_pagasg_www_util;
		var MPNG = com_starsensortech_www_atms_mapping;
		
		// Class imports
		var Class = LANG.Class;
		var Enum = LANG.Enum;
		var StringBuilder = LANG.StringBuilder;
		var Point = MPNG.Point;
		var Shape = MPNG.Shape;
		var Dot = MPNG.Shape;
		var MapController = MPNG.MapController;
		var Event = Shape.Event;
		var Objects = WU.Objects;

		// Function imports
		var extend = Class.extend;
		var implement = Class.implement;
		var enumerate = Enum.enumerate;

		// ****** Google Map Controller ******
		var GMapController = extend(MapController,
			function(canvas, center, control) {
				var _actuators = [];
				var _map = new GMap2(canvas);
				var _geocoder = new GClientGeocoder();
				var _logger = LOG.Logger.getLogger('com.starsensortech.www.atms.gmap.GMapController');
				var _controls = Objects.argsOrArray2Array(arguments, 2);
				//var _requestHandler = GEvent.addListener(_map, "click", handleMapClick);

				// Init
				this.canvas = canvas;

				if (center) {
					_map.setCenter(new GLatLng(center.y, center.x), center.z);
				} else {
					_map.setCenter(new GLatLng(0, 0), 2);
				}
				
				for (var i = 0; i < _controls.length; i++) {
					control = _controls[i];
					_map.addControl(control);
				}

				function handleMapClick() {
					// Does nothing for now
				}
				function createIcon(handle) {
					var icon;

					if (handle) {
						icon = new GIcon();
						icon.image = handle.src;
						icon.iconSize = new GSize(handle.extent.width, handle.extent.height);
						icon.iconAnchor = new GPoint(handle.control.x, handle.control.y);
						icon.infoWindowAnchor = new GPoint(handle.center.x, handle.center.y);
						
						if (handle.shadowSrc) {
							icon.shadow = handle.shadowSrc;
							icon.shadowSize = new GSize(handle.shadowExtent.width, handle.shadowExtent.width);
						} else {
							icon.shadow = "";
						}
					}

					return icon;
				}
				function createPolygon(shape) {
					var pt;
					var polygon;
					var gpoints = [];

					for (var i = 0; i < shape.points.length; i++) {
						pt = shape.points[i];
						gpoints.push(new GLatLng(pt.y, pt.x));
					}
					polygon = new GPolygon(gpoints, shape.strokeColor, shape.strokeWidth, shape.strokeOpacity, shape.fillColor, shape.fillOpacity);
					if (shape.visible) {
							_map.addOverlay(polygon);
					} else {
						if (polygon.supportsHide()) {
							polygon.hide();
							_map.addOverlay(polygon);
						}
					}

					return polygon;
				}
				function removePolygon(polygon) {
					if (!polygon.isHidden() || polygon.supportsHide()) {
						_map.removeOverlay(polygon);
					}
				}
				function showPin(pin) {
					var marker;

					if (pin && !pin.visible) {
						marker = pin.marker;
						// TODO: may need to add the listener
						_map.addOverlay(marker);
						pin.visible = true;
					}
				}
				function hidePin(pin) {
					var marker;

					if (pin && pin.visible) {
						marker = pin.marker;

						_map.removeOverlay(marker);
						pin.visible = false;
					}
				}
				function destroyPin(pin) {
					var listeners;
					_map.removeOverlay(pin.marker);

					if (pin.listeners) {
						listeners = pin.listeners;
						for (var i = 0; i < listeners.length; i++) {
							GEvent.removeListener(listeners[i]);
						}
					}
				}
				function createPin(shape) {
					var buf;
					var parts;
					var marker;
					var glistener;
					var glisteners = [];
					var handle = shape.handle;
					var icon = createIcon(handle);
					var latlng = new GLatLng(shape.control.y, shape.control.x);

					if (handle && handle.moveable) {
						marker = new GMarker(latlng, { draggable: true, icon: icon });
						glistener = GEvent.addListener(marker, 'dragend', function(latlng) {
							shape.moveTo(new Point(latlng.lng(), latlng.lat()));
						});
						glisteners.push(glistener);
					} else if (handle) {
						marker = new GMarker(latlng, { draggable: false, icon: icon });
					}

					glistener = GEvent.addListener(marker, 'click', function(latlng) {
						shape.activate();
					});
					glisteners.push(glistener);

					return { marker: marker, listeners: glisteners, visible: false };
				}
				function showInfoWindow(pin, shape) {
					var marker = pin.marker;
					var latlng = new GLatLng(shape.control.y, shape.control.x);
					
					_geocoder.getLocations(latlng, function(addresses) {
						if (addresses.Status.code == 200) {
							shape.address = addresses.Placemark[0].address;
						} else {
							shape.address = null;
						}
						marker.openInfoWindowHtml(shape.paintAddress());
					});
				}

				// Public
				this.resize = function() {
					_map.checkResize();
				};
				this.zoomToAll = function() {
					var controlPoint;
					var shape;
					var count;
					var vertex;
					var bounds = new GLatLngBounds();

					for (var count = 0; count < _actuators.length; count++) {
						shape = _actuators[count].shape;
						if (shape.visible) {
							controlPoint = shape.control;
							bounds.extend(new GLatLng(controlPoint.y, controlPoint.x));
						}
					}

					_logger.info(bounds.toString());

					if (count > 1) {
						_map.setZoom(_map.getBoundsZoomLevel(bounds));
					}
					if (count > 0) {
						_map.setCenter(bounds.getCenter());
					} else {
						alert('There are no items on the map');
					}
				}
				this.geocode = function(address, listener) {
					_geocoder.getLatLng(address, function(latlng) {
						if (latlng) {
							listener(latlng.lat(), latlng.lng());
						} else {
							listener();
						}
					});
				};
				this.reverseGeocode = function(lat, lon, listener) {
					var address;
					var latlng = new GLatLng(parseFloat(lat), parseFloat(lon));

					_geocoder.getLocations(latlng,
						function(addresses) {
							if (addresses.Status.code == 200) {
								listener(addresses.Placemark[0].address);
							} else {
								listener(null);
							}
						}
					);
				};
				this.registerShape = function(shape) {
					var actuator;

					actuator = new ShapeActuator(shape);
					actuator.init();
					_actuators.push(actuator);

					return actuator;
				};
				this.unregisterShape = function(shape) {
					var actuator;

					for (var i = 0; i < _actuators.length; i++) {
						actuator = _actuators[i];
						if (actuator.shape === shape) {
							_actuators.splice(i, 1);
							actuator.decouple(shape);
							break;
						}
					}
				};
				this.panTo = function(point) {
					if (point) {
						if (point.z) {
							_map.setCenter(new GLatLng(point.y, point.x), point.z);
						} else {
							_map.setCenter(new GLatLng(point.y, point.x));
						}
					}
				};
				this.getCenter = function() {
					var latlng = _map.getCenter();
					var zoom = _map.getZoom();
					return new Point(latlng.lng(), latlng.lat(), zoom);
				}

				// Inner Classes
				var ShapeActuator = function(shape) {
					var _pin;
					var _polygon = createPolygon(shape);

					// Private
					function touchPin(shape, recreate) {
						if (_pin && recreate) {
							destroyPin(_pin);
							_pin = null;
						}
						if (!_pin && shape.handle) {
							_pin = createPin(shape);
						}
						return _pin;
					}
					function activate(shape) {
						var pin;
						var latlng = new GLatLng(shape.control.y, shape.control.x);

						if (shape.zoomlevel) {
							//_map.setCenter(latlng, shape.zoomlevel);
						} else {
							//_map.setCenter(latlng);
						}

						pin = touchPin(shape);
						showPin(pin);
						showInfoWindow(pin, shape);
					}

					// Protected
					var onMoveListener = function(event) {
						var pin;
						var latlng;
						var pt;
						var context = event.context;
						var shape = event.target;

						removePolygon(_polygon);
						_polygon = createPolygon(shape);

						if (shape.visible || context.follow) {
							pin = touchPin(shape, true);
							if (shape.visible) showPin(pin);
							if (shape.follow || context.follow) {
								pt = context.to;
								if (pt.z) {
									_map.setCenter(new GLatLng(pt.y, pt.x), pt.z);
								} else {
									_map.setCenter(new GLatLng(pt.y, pt.x));
								}
							}
						} else if (shape.activated) {
							activate(shape);
						}
					}
					var onStyleChangeListener = function(event) {
						var shape = event.target;
						removePolygon(_polygon);
						_polygon = createPolygon(shape);
					}
					var onModeChangeListener = function(event) {
						var pin;
						var shape = event.target;
						var context = event.context;

						if (shape.visible) {
							if (_polygon.supportsHide()) {
								_polygon.show();
							} else {
								_map.addOverlay(_polygon);
							}

							pin = touchPin(shape);
							showPin(pin);
							if (shape.follow || context.goto) {
								_map.setCenter(new GLatLng(shape.control.y, shape.control.x));
							}
						} else {
							if (_polygon.supportsHide()) {
								_polygon.hide();
							} else {
								_map.removeOverlay(_polygon);
							}
							hidePin(_pin);
						}
					}
					var onFocusListener = function(event) {
						var shape = event.target;
						var latlng = new GLatLng(shape.control.y, shape.control.x);
						
						if (shape.activated) {
							activate(shape);
						} else if (_pin) {
							hidePin(_pin);
						}
					}
					var addressChangeListener = function(event) {
						var shape = event.target;

						if (shape.visible && shape.activated) {
							showInfoWindow(touchPin(), shape);
						}
					}
					var onDisposeListener = function(event) {
						decouple(shape);
					}
					var decouple = function(shape) {
						if (_polygon) removePolygon(_polygon);
						if (_pin) destroyPin(_pin);

						shape.onStyleChange.removeListener(onStyleChangeListener);
						shape.onModeChange.removeListener(onModeChangeListener);
						shape.onFocusChange.removeListener(onFocusListener);
						shape.onMove.removeListener(onMoveListener);
						shape.onAddressChange.removeListener(addressChangeListener);
						shape.onDispose.removeListener(onDisposeListener);
					}

					// Public
					this.shape = shape;
					this.decouple = decouple;
					this.init = function() {
						shape.onStyleChange.addListener(onStyleChangeListener);
						shape.onModeChange.addListener(onModeChangeListener);
						shape.onFocusChange.addListener(onFocusListener);
						shape.onMove.addListener(onMoveListener);
						shape.onAddressChange.addListener(addressChangeListener);
						shape.onDispose.addListener(onDisposeListener);

						if (shape.activated) {
							activate(shape);
						}
					};
				}

				/*
				this.drawCircle = function(latitude, longitude, radius, strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity) {
					var lat = parseFloat(latitude);
					var lng = parseFloat(longitude);
					var rad = parseFloat(radius);
					var Cy;
					var Cx;
					var d2r = Math.PI/180;
					var r2d = 180/Math.PI;
					var circleLat = rad * 0.014483;  // Convert statute miles into degrees latitude
					var circleLng = circleLat/Math.cos(lat*d2r); 
					var circlePoints = []; 
					for (var i=0; i < 33; i++) { 
						var theta = Math.PI * (i/16); 
						Cy = lat + (circleLat * Math.sin(theta)); 
						Cx = lng + (circleLng * Math.cos(theta)); 
						var P = new GPoint(Cx,Cy); 
						circlePoints.push(P); 
					}

					polygon = new GPolygon(circlePoints, strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity);
					return polygon
				}
				*/
			}
		);

		// === PACKAGE PUBLIC ===
		this.GMapController = GMapController;
		// === PACKAGE PUBLIC ===
	}
);

















