com_starsensortech_www_atms_mapping = new es_lang.Package(true,
	/*Title*/   'Generic mapping components',
	/*Docs */   '-- none --',
	/*Package*/ function() {
		// Package Imorts
		var LANG = es_lang;
		var LOG = es_util_logging;
		var WU = com_pagasg_www_util;

		// Constructor imports
		var Class = es_lang.Class;
		var Enum = es_lang.Enum;
		var StringBuilder = es_lang.StringBuilder;
		var Objects = WU.Objects;

		// Function imports
		var extend = Class.extend;
		var implement = Class.implement;
		var enumerate = Enum.enumerate;

		var Geocoder = implement({
			geocode: /*function(address, listener)*/ undefined,
			reverseGeocode: /*function(lat, lon, listener)*/ undefined
		});

		// ****** MapController ******
		var MapController = implement({
			canvas: /*<div>*/ undefined,
			registerShape: /*function(shape)*/ undefined,
			unregisterShape: /*function(shape)*/ undefined,
			panTo: /*function(shape)*/ undefined,
			getCenter: /*function*/ undefined,
			zoomToAll: /*function*/ undefined,
			resize: /*function*/ undefined
		});
		MapController.splitAddress = function(address) {
			var detail;
			var street;
			var city
			var statezip;
			var country;
			var parts;

			if (address && address.indexOf(',') > 0) {
				parts = address.split(/\s*,\s*/);
				country = parts.splice(parts.length - 1, 1)[0];
				statezip = parts.splice(parts.length - 1, 1)[0];

				if (parts.length > 0) {
					city = parts.splice(parts.length - 1, 1)[0];
				}
				if (parts.length > 0) {
					street = parts.splice(parts.length - 1, 1)[0];
				}
				if (parts.length > 0) {
					detail = parts.splice(parts.length - 1, 1)[0];
				}

				return { detail: detail, street: street, city: city, statezip: statezip, country: country };
			} else {
				return undefined;
			}
		};
		MapController.getHeadingName = function(heading, short) {
			var str;
			var degrees;
			
			if (heading) {
				degrees = parseFloat(heading) - 22.5;
				if (degrees < 0) {
					str = (short) ? "N" : "North";
				} else if (degrees < 45) {
					str = (short) ? "NE" : "Northeast";
				} else if (degrees < 90) {
					str = (short) ? "E" : "East";
				} else if (degrees < 135) {
					str = (short) ? "SE" : "Southeast";
				} else if (degrees < 180) {
					str = (short) ? "S" : "South";
				} else if (degrees < 225) {
					str = (short) ? "SW" : "Southwest";
				} else if (degrees < 270) {
					str = (short) ? "W" : "West";
				} else if (degrees < 315) {
					str = (short) ? "NW" : "Northwest";
				} else {
					str = (short) ? "N" : "North";
				}
			} else {
				str = null;
			}
			
			return str;
		};

		// ****** Converter ******
		var Converter = {
			degreesPerRad: 180 / Math.PI,
			radsPerDegree: Math.PI / 180,
			degreesPerMile: 0.014483,
			degreesPerMeter: 0.000009,
			degrees2Rad: /*float*/ function(degrees) {
				return this.radsPerDegree * parseFloat(degrees);
			},
			rad2Degrees: /*float*/ function(radians) {
				return this.degreesPerRad * parseFloat(radians);
			},
			meters2Lat: /*float*/ function(meters) {
				return parseFloat(meters) * this.degreesPerMeter;
			},
			meters2Lon: /*float*/ function(/*radians*/ lat, meters) {
				return (parseFloat(meters) * this.degreesPerMeter) / Math.cos(lat * this.radsPerDegree); 
			},
			miles2Lat: /*float*/ function(miles) {
				return parseFloat(miles) * this.degreesPerMile;
			},
			miles2Lon: /*float*/ function(/*radians*/ lat, miles) {
				return (parseFloat(miles) * this.degreesPerMile) / Math.cos(lat * this.radsPerDegree); 
			}
		}

		// ****** Point ******
		// Note: ALL point coordinates must be in the lat/lon projection
		var Point = implement({
				coords: /*float[]*/ undefined,
				x: /*coords[0]*/ undefined,
				y: /*coords[1]*/ undefined,
				z: /*coords[2]*/ undefined
			},
			function(/*float ...*/ coordinates) {
				var _coord;
				var _list;

				_list = Objects.argsOrArray2Array(arguments);

				this.coords = [];
				for (var i = 0; i < _list.length; i++) {
					_coord = parseFloat(_list[i]);
					if (i == 0) this.x = _coord;
					else if (i == 1) this.y = _coord;
					else if (i == 2) this.z = _coord;

					this.coords.push(_coord);
				}

				this.translate = function(coords) {
					for (var i = 0; i < coords.length; i++) {
						this.coords[i] += coords[i];
						if (i == 0) this.x = this.coords[i];
						else if (i == 1) this.y = this.coords[i];
						else if (i == 2) this.z = this.coords[i];
					}
				}

				this.toString = function() {
					var buf = new StringBuilder();

					buf.append('(');
					if (_list.length <=3) {
						for (var i = 0; i < _list.length; i++) {
							if (i == 0) buf.append('x=');
							if (i == 1) buf.append(',y=');
							if (i == 2) buf.append(',z=');
							buf.append(_list[i]);
						}
					} else {
						for (var i = 0; i < _list.length; i++) {
							if (i > 0) buf.append(',');
							buf.append(_list[i]);
						}
					}
					buf.append(')');

					return buf.toString();
				}
			}
		)

		// ****** Extent ******
		var Extent = implement({
				coords: /*float[]*/ undefined,
				width: /*coords[0]*/ undefined,
				height: /*coords[1]*/ undefined,
				zindex: /*coords[2]*/ undefined
			},
			function(/*float ...*/ coords) {
				var _coord;
				var _list;

				if (coords.constructor == Array) {
					_list = coords;
				} else {
					_list = arguments;
				}

				this.coords = [];
				for (var i = 0; i < _list.length; i++) {
					_coord = parseFloat(_list[i]);
					if (i == 0) this.width = _coord;
					else if (i == 1) this.height = _coord;
					else if (i == 2) this.zindex = _coord;

					this.coords.push(_coord);
				}
			}
		)

		// ****** Handle ******
		var Handle = implement({
				src: /*url*/ undefined,
				extent: /*Extent*/ undefined,
				control: /*Point*/ undefined,
				center: /*Point*/ undefined,
				shadowSrc: /*url*/ undefined,
				shadowExtent: /*Extent*/ undefined,
				moveable: /*boolean*/ undefined
			},
			function(src, extent, control, center, shadowSrc, shadowExtent, moveable) {
				this.src = src;
				this.extent = extent;
				this.control = control;
				this.center = center;
				this.shadowSrc = shadowSrc;
				this.shadowExtent = shadowExtent;
				this.moveable = moveable;
			}
		);
		
		// ****** Photo ******
		var Photo = implement({
				thumbnail: /*URL*/ undefined,
				main: /*URL*/ undefined,
				large: /*URL*/ undefined
			},
			function(main, thumbnail, large) {
				this.main = main;
				this.thumbnail = thumbnail || makeURL('thumbnail');
				this.large = large || makeURL('large');

				function getLogger() {
					_logger = LOG.Logger.getLogger('com.starsensortech.www.atms.mapping.Photo');
				}
				function makeURL(type) {
					var pos;

					if (main) {
						pos = main.lastIndexOf('.');
						if (pos > 0) {
							return main.substring(0, pos) + '-' + type + main.substring(pos);
						} else {
							getLogger().info(main + ' is not a valid image URL');
							return main;
						}
					}
				};
			}
		);

		// ****** Shape ******
		var Shape = implement({
				initialized: /*boolean*/ false,
				follow: /*bool*/ false,
				title: /*String*/ undefined,
				control: /*Point*/ undefined,
				points: /*Point[]*/ undefined,
				visible: /*boolean*/ false,
				activated: /*boolean*/ false,
				zoomlevel: /*int*/ undefined,
				strokeWidth: /*int*/ 0,
				strokeColor: /*css color*/ 0,
				strokeOpacity: /*pcnt*/ 0,
				fillColor: /*css color*/ "black",
				fillOpacity: /*float*/ 1,
				handle: /*Handle*/ undefined,
				address: /*String*/ undefined,
				geocoder: /*Geocoder*/ undefined,
				photo: /*Photo*/ undefined,
				paintAddress: /*Function*/ undefined
			},
			function(control, points) {
				var _eventTypes;
				var _listenersMap = {};
				var _logger = LOG.Logger.getLogger('com.starsensortech.www.atms.mapping.Shape');

				if (arguments.length > 0) {
					this.initialized = true;
					this.control = control;

					_eventTypes = Shape.Event.Type.items;
					for (var i = 0; i < _eventTypes.length; i++) {
						_eventTypes[i].manage(this);
					}

					this.points = Objects.argsOrArray2Array(arguments, 1); 

					// Public
					this.changeAddress = function(/*String*/ address, /*boolean*/ valid) {
						var event;
						var oldAddress;

						if (this.initialized) {
							this.address = address;

							event = this.onAddressChange.newEvent(this);
							this.onAddressChange.fireEvent(event);
						}
					};
					this.moveTo = function(/*Point*/ to, follow) {
						var event;
						var from;
						var changed = false;
						var delta = [];
						
						if (this.initialized) {
							from = new Point(control.coords);
							event = this.onMove.newEvent({ from: from, to: to, follow: !(!follow) });

							for (var i = 0; i < this.control.coords.length; i++) {
								delta.push(this.control.coords[i]);
							}

							for (var i = 0; i < to.coords.length; i++) {
								delta[i] = to.coords[i] - delta[i];
								changed = (changed || delta[i] != 0);
							}

							//_logger.info('Delta: (' + delta.join(',') + ')');

							if (changed) {
								this.control.translate(delta);
								this.address = null;
								
								for (var i = 0; i < this.points.length; i++) {
									this.points[i].translate(delta);
								}

								this.onMove.fireEvent(event);

								return true;
							} else {
								return false;
							}
						} else {
							throw new Error('This shape has not yet been initialized');
						}
					};
					this.color = function(strokeWidth, strokeColor, strokeOpacity, fillColor, fillOpacity) {
						var changed;

						if (strokeWidth && strokeWidth != this.strokeWidth) { this.strokeWidth = strokeWidth; changed = true; }
						if (strokeColor && strokeColor != this.strokeColor) { this.strokeColor = strokeColor; changed = true; }
						if (strokeOpacity && strokeOpacity != this.strokeOpacity) { this.strokeOpacity = strokeOpacity; changed = true; }
						if (fillColor && fillColor != this.fillColor) { this.fillColor = fillColor; changed = true; }
						if (fillOpacity && fillOpacity != this.fillOpacity) { this.fillOpacity = fillOpacity; changed = true; }

						if (changed) {
							if (this.visible) {
								this.onStyleChange.fireEvent();
							}
							return true;
						} else {
							return false;
						}
					};
					this.hide = function() {
						if (this.visible) {
							this.visible = false;
							this.onModeChange.fireEvent();
							return true;
						} else {
							return false;
						}
					};
					this.show = function(goto) {
						var event = this.onModeChange.newEvent({ goto: !(!goto) });
						if (!this.visible) {
							this.visible = true;
							this.onModeChange.fireEvent(event);
							return true;
						} else if (goto) {
							this.onModeChange.fireEvent(event);
							return false;
						} else {
							return false;
						}
					};
					this.activate = function() {
						var event = this.onMove.newEvent(this.activated);
						this.visible = true;
						this.activated = true;
						this.onFocusChange.fireEvent(event);
					};
					this.deactivate = function() {
						var event = this.onMove.newEvent(this.activated);
						this.activated = false;
						this.onFocusChange.fireEvent(event);
					};
					this.dispose = function() {
						this.onDispose.fireEvent();
					};
					this.paintAddress = function() {
						var buf = new StringBuilder();
						var parts = MapController.splitAddress(this.address);

						buf.append('<b>Address: </b>');

						if (parts) {
							if (parts.street) buf.append('<br/>' + parts.street);
							if (parts.city) buf.append('<br/>' + parts.city);
							if (parts.statezip) buf.append(', ' + parts.statezip);
							if (parts.country) buf.append('<br/>' + parts.country);
						} else {
							buf.append('<br/>' + this.address);
						}
						
						return buf.toString();
					};

					this.listenersMap = _listenersMap;
				}
			}
		);

		// ****** Shape Event ******
		Shape.Event = implement({
				type: undefined,
				target: undefined,
				context: undefined
			},
			function(type, target, context) {
				this.type = type;
				this.target = target;
				this.context = context;
			}
		);
		Shape.Event.Type = enumerate([ 'onStyleChange'
				, 'onModeChange'
				, 'onAddressChange'
				, 'onFocusChange'
				, 'onMove'
				, 'onDispose'
			],
			function(name, ordinal) {
				this.manage = function(target) {
					target[name] = new EventManager(target);
				}
				var EventManager = function(target) {
					var _listeners = [];

					this.newEvent = function(context) {
						return new Shape.Event(name, target, context);
					}
					this.addListener = function(listener) {
						if (listener && listener.constructor == Function) {
							_listeners.push(listener);
						} else {
							throw new Error('listener: ' + listener + ' for event: ' + name + ' is not a function');
						}
					}
					this.removeListener = function(listener) {
						for (var i = 0; i < _listeners.length; i++) {
							if (_listeners[i] == listener) {
								_listeners.splice(i, 1);
								break;
							}
						}
					}
					this.fireEvent = function(event) {
						var listener;

						if (!event) event = new Shape.Event(name, target);
						if (_listeners) {
							for (var i = 0; i < _listeners.length; i++) {
								_listeners[i](event);
							}
						}
					}
				}
			}
		);

		// ****** Ellpise ******
		var Ellipse = extend(Shape, implement({
				center: /*Point*/ undefined,
				axes: /*Point*/ undefined,
				vertexCount: /*int*/ 32
			},
			function(center, axes, vertexCount) {
				var p;
				var theta;
				var _points;
				var _vertexCount;

				function buildPolygon(center, axes) {
					var points = [];
					var x, y;
					var theta;

					for (var i = 0; i < _vertexCount + 1; i++) { 
						theta = 2 * Math.PI * (i / _vertexCount); 
						x = center.x + (axes.x * Math.cos(theta)); 
						y = center.y + (axes.y * Math.sin(theta)); 
						points.push(new Point(x, y)); 
					}

					return points;
				}

				if (arguments.length > 0) {
					this.center = center;
					this.axes = axes;
					if (vertexCount) {
						this.vertexCount = _vertexCount = vertexCount
					} else {
						_vertexCount = this.vertexCount;
					}

					_points = buildPolygon(center, axes);

					Shape.call(this, center, _points);


					// Public
					this.setAxes = function(newAxes) {
						var points;

						this.axes = axes;
						
						points = buildPolygon(newAxes, this.center);
						for (var i = 0; i < points.length; i++) {
							_points[i] = points[i];
						}
					}
				}
			}
		));

		// ****** Dot ******
		var Dot = extend(Shape,
			function(location, size, color, opacity) {
				if (arguments.length > 0) {
					Shape.call(this, location, [ location ]);

					this.strokeWidth = size;
					if (color) this.strokeColor = color;
					if (opacity) this.strokeOpacity = opacity;

					Shape.call(this, location, [ location ]);
				}
			}
		)

		// === PACKAGE PUBLIC ===
		this.MapController = MapController;
		this.Converter = Converter;
		this.Point = Point;
		this.Handle = Handle;
		this.Extent = Extent;
		this.Shape = Shape;
		this.Ellipse = Ellipse;
		this.Dot = Dot;
		// === PACKAGE PUBLIC ===
	}
);



