// Prototype for 'map' class.
ViaPlace.Map = Class.create();
ViaPlace.Map.prototype = {
	// GMarker
	_crosshair: Object(),
	// GClientGeocoder
	_geocoder: Object(),
	// GMap2
	_map: Object(),
	// GMarkerManager
	_markerManager: Object(),
	// Array of GMarker
	_markers: [],
	_markerGroups: [],
	// GMarker
	_newPoint: Object(),
	// viaplace options
	_options: Object(),
	// viaplace starting point
	_start: Object(),
	// viaplace error console
	_console: Object(),
	// used for drawing boundaries
	_polys: [],
	// store current zoom level
	_currentZoomLevel: 3,
	// we begin!
	initialize: function(options) {
		if (typeof(options) == 'object') {
			if (GBrowserIsCompatible()) {
				this._options = options;
				var consoleOptions = this._options.console;
				consoleOptions.path = this._options.path;
				this._console = new ViaPlace.Console(consoleOptions);
				this._utilities = new ViaPlace.Utilities;
				this._observeForm();
				this._initializeMap();
			} else {
				this._console.append(2, "viaplace::map::initialize() - GMaps2 API reports this browser is incompatible.", true);
			}
		}
	},

	// shows the new point
	_addPoint: function(point) {
		if (this._newPoint) {
			this._map.removeOverlay(this._newPoint);
		}
		
		iconBackground = new GIcon();
		iconBackground.image = this._options.path+"includes/images/viaplace.png";
		iconBackground.iconSize = new GSize(23,27);
		iconBackground.iconAnchor = new GPoint(23,27);
		iconBackground.shadow = this._options.path+"includes/images/viaplaceShadow.png";
		iconBackground.shadowSize = new GSize(37,27);
		
		this._newPoint = new GMarker(point, {icon: iconBackground, draggable: true});
		this._newPoint.viaPlaceId = 0;
		this._map.addOverlay(this._newPoint);
		this._drawBoundary(Object.extend(this._newPoint, {viaPlaceId: "outer"}), 9.144, {strokeWeight: 1, strokeColor: "#00FF00", strokeOpacity: 0.5, fillColor: "#00FF00", fillOpacity: 0.2});
		this._drawBoundary(Object.extend(this._newPoint, {viaPlaceId: "inner"}), 3.048, {strokeWeight: 1, strokeColor: "#FF0000", strokeOpacity: 0.5, fillColor: "#FF0000", fillOpacity: 0.2});
		
		GEvent.addListener(this._newPoint, 'click', function() {
			this._openPointRegistrationForm(this._newPoint);
		}.bind(this));
		GEvent.addListener(this._newPoint, 'drag', function() {
			this._drawBoundary(Object.extend(this._newPoint, {viaPlaceId: "outer"}), 9.144, {strokeWeight: 1, strokeColor: "#00FF00", strokeOpacity: 0.5, fillColor: "#00FF00", fillOpacity: 0.2});
			this._drawBoundary(Object.extend(this._newPoint, {viaPlaceId: "inner"}), 3.048, {strokeWeight: 1, strokeColor: "#FF0000", strokeOpacity: 0.5, fillColor: "#FF0000", fillOpacity: 0.2});
		}.bind(this));
		GEvent.addListener(this._newPoint, 'dragend', function() {
			// see if the point is too close to some other viaplaces - if so, reflect this in the category list
			var newPoint = this._newPoint;
			var lat = this._newPoint.getPoint().lat();
			var lng = this._newPoint.getPoint().lng();
			this._newPoint.availableCategories = this._getAvailableCategories(lat, lng);
		}.bind(this));
		GEvent.trigger(this._newPoint, 'click');
	},
	
	// draws circular boundary around a marker
	// Based on a function created by Chris Haas, originally found in: http://www.ovationmarketing.com/GMaps/MapCircle.asp
	// See GMaps API group thread: http://groups.google.com/group/Google-Maps-API/browse_thread/thread/3371352bfa99baf1/ec2707992504f6fd#ec2707992504f6fd
	_drawBoundary: function(marker, radius, options) {
		// remove existing boundary if it exists - we don't want a trail of boundaries if a marker is moved
		if (this._polys[marker.viaPlaceId] != undefined) {
			this._map.removeOverlay(this._polys[marker.viaPlaceId]);
			this._polys[marker.viaPlaceId] = null;
		}
		
		var center = marker.getPoint();
		radius = radius / 111325;
		if (options.strokeColor == undefined) {
			var strokeColor = "#FF0000";
		} else {
			var strokeColor = options.strokeColor;
		}
		if (options.strokeWeight == undefined) {
			var strokeWeight = 2;
		} else {
			var strokeWeight = options.strokeWeight;
		}
		if (options.strokeOpacity == undefined) {
			var strokeOpacity = 1;
		} else {
			var strokeOpacity = options.strokeOpacity;
		}
		if (options.fillColor == undefined) {
			var fillColor = "#FFFFFF";
		} else {
			var fillColor = options.fillColor;
		}
		if (options.fillOpacity == undefined) {
			var fillOpacity = 0.2;
		} else {
			var fillOpacity = options.fillOpacity;
		}
		
		var circleQuality = 20;			//1 is best but more points, 5 looks pretty good, too
		var M = Math.PI / 180;			//Create Radian conversion constant
		var L = this._map.getBounds();		//Holds copy of map bounds for use below
		var sw = L.getSouthWest();
		var ne = L.getNorthEast();
	
		//The map is not completely square so this calculates the lat/lon ratio
		// this works because we create a square map
		//var circleSquish = (ne.lng() - sw.lng()) / (ne.lat() - sw.lat()) / 2;
		// Unless we don't have a square map. Insert correct width/height ratio here.
		var circleSquish = 1.25;
		var points = [];							//Init Point Array
		//Loop through all degrees from 0 to 360
		for(var i=0; i<360; i+=circleQuality){
			var P = new GLatLng(
				parseFloat(center.lat()) + parseFloat(radius * Math.sin(i * M)),
				parseFloat(center.lng()) + parseFloat((radius * Math.cos(i * M))) * parseFloat(circleSquish)
				);
			points.push(P);
		}
		points.push(points[0]);	// close the circle
		var boundary = new GPolygon(points, strokeColor, strokeWeight, strokeOpacity, fillColor, fillOpacity)
		this._map.addOverlay(boundary);
		this._polys[marker.viaPlaceId] = boundary;
	},
	
	// Find lat/lng point from address.
	_geocode: function(location) {
		this._geocoder.getLocations(location, function(response) {
			if (!response || response.Status.code != 200) {
				// do something better
				//alert('Unable to geocode your query.\nHTTP Response Code: ' + response.Status.code);
				alert("We're sorry, we could not find any matches for your search. Please try again.");
			} else {
				var place = response.Placemark[0];
				var point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
				this._map.setCenter(point);
				this._map.setZoom(16);
				//this._addPoint(point);
			}
		}.bind(this));
	},
	
	// Get available categories at a given lat/lng.
	_getAvailableCategories: function(lat, lng) {
		var refObj = this;
		new Ajax.Request(this._options.path+"maps/availablecategories/", {
			onComplete: function(transport) {
				if (200 == transport.status) {
					var xml = GXml.parse(transport.responseText);
					var result = xml.getElementsByTagName('category');
					refObj._newPoint.categories = [];
					for (var i = 0; i < result.length; i++) {
						refObj._newPoint.categories.push({id: result[i].getAttribute("id"), name: result[i].getAttribute("name")});
						for (var j = 0; j < result[i].childNodes.length; j++) {
							if (result[i].childNodes[j].nodeType == 1 && result[i].childNodes[j].nodeName == "description") {
								for (var k = 0; k < result[i].childNodes[j].childNodes.length; k++) {
									if (result[i].childNodes[j].childNodes[k].nodeType == 4) {
										description = result[i].childNodes[j].childNodes[k].nodeValue;
									}
								}
							}
						}
					}
				}
			},
			parameters: {latitude: lat, longitude: lng}
		});
	},
	
	// Returns the GMap2 object.
	getGMap2: function() {
		return this._map;
	},
	
	_initializeControls: function() {
		// Add map controls.
		Object.values(this._options.map.controls).each(function(node) {
			this._map.addControl(node);
		}.bind(this));

		this._map.addControl(new ViaPlace.Controls());

		GEvent.addListener(this._map, 'addpoint', function(){
			var point = new GLatLng(this._map.getCenter().lat(), this._map.getCenter().lng());
			this._addPoint(point);
		}.bind(this));
	},
	
	_initializeMap: function() {
		this._map = new GMap2($(this._options.map.element));
		this._initializeControls();
		this._geocoder = new GClientGeocoder();

		// Set up intial view.
		this._map.setCenter(new GLatLng(37.0625, -95.677068));
		this._map.setZoom(3);
		this._options.map.zoom.doubleClick ? this._map.enableDoubleClickZoom() : null;
		this._options.map.zoom.continuous ? this._map.enableContinuousZoom() : null;
		this._updateDisplay();
		this._showCrosshair();
		GEvent.addListener(this._map, "zoomend", function(oldzoom,zoom) {
        	this._currentZoomLevel = zoom;
        }.bind(this)); 
		// get only close-by markers
		GEvent.addListener(this._map, 'moveend', function() {
			this._loadPointData(this._map.getCenter().lat(), this._map.getCenter().lng(), this._map.getZoom());
		}.bind(this));

		for (var i = 0; i < this._map.getMapTypes().length; i++) {
			if (this._map.getMapTypes()[i].getName() == this._options.map.type) {
				this._map.setMapType(this._map.getMapTypes()[i]);
			}
		}
		this._markerManager = new GMarkerManager(this._map);
		this._clusterer = new Clusterer(this._map);
	},
	
	// Get point data file.
	_loadPointData: function(lat, lng, zoom) {
		var refObj = this;
		new Ajax.Request(this._options.data.file, {
			onComplete: function(transport) {
				if (200 == transport.status) {
					refObj._parseData(transport.responseText);
				}
			},
			parameters: {latitude: lat, longitude: lng, zoom: zoom}
		});
	},
	
	// Handles markers from xml file.
	_manageMarkers: function(markers) {
		for (var i = 0; i < markers.length; i++) {
			var point = new GLatLng(parseFloat(markers[i].getAttribute('latitude')), parseFloat(markers[i].getAttribute('longitude')));
			var icon = new GIcon();
			var description = "";
			var name = markers[i].getAttribute('name');
			var linker = "";
			var dispImage = "";
			var locCats = 0;
			var marker = new GMarker(point, {icon: icon, clickable: true, title: name});
			marker.viaPlaceId = markers[i].getAttribute("id");
			if (this._markers[marker.viaPlaceId] == undefined) {
				this._markers[marker.viaPlaceId] = marker;
				
				// Descriptions and Links
				for (var j = 0; j < markers[i].childNodes.length; j++) {
					if (markers[i].childNodes[j].nodeType == 1 && markers[i].childNodes[j].nodeName == "description") {
						for (var k = 0; k < markers[i].childNodes[j].childNodes.length; k++) {
							if (markers[i].childNodes[j].childNodes[k].nodeType == 4) {
								description = markers[i].childNodes[j].childNodes[k].nodeValue;
							}
						}
					}
					if (markers[i].childNodes[j].nodeType == 1 && markers[i].childNodes[j].nodeName == "linker") {
						for (var k = 0; k < markers[i].childNodes[j].childNodes.length; k++) {
							if (markers[i].childNodes[j].childNodes[k].nodeType == 4) {
								linker = markers[i].childNodes[j].childNodes[k].nodeValue;
							}
						}
					}
					if (markers[i].childNodes[j].nodeType == 1 && markers[i].childNodes[j].nodeName == "categories") {
						for (var k = 0; k < markers[i].childNodes[j].childNodes.length; k++) {
							if (markers[i].childNodes[j].childNodes[k].nodeType == 1 && 
								markers[i].childNodes[j].childNodes[k].nodeName == "category") {
								locCats++;
								dispImage = markers[i].childNodes[j].childNodes[k].getAttribute("icon");
							}
						}
					}
				}
				if(dispImage == "") {
					dispImage = "viaplace.png";
				} else if (locCats > 1) {
					dispImage = "viamulti.png";
				}
				icon.image = this._options.path+"includes/images/"+dispImage;
				icon.iconSize = new GSize(23, 27);
				icon.iconAnchor = new GPoint(23, 27);
				icon.shadow = this._options.path+"includes/images/viaplaceShadow.png";
				icon.shadowSize = new GSize(37,27);

				this._drawBoundary(marker, 3.048, {strokeWeight: 1, strokeColor: "#FF0000", strokeOpacity: 0.5, fillColor: "#FF0000", fillOpacity: 0.2});
				//this._markerManager.refresh();
				this._clusterer.AddMarker(marker, name, marker.viaPlaceId, description, linker);
				GEvent.addListener(marker, 'click', function() {
					var html = document.createElement('div');
					html.innerHTML = "<div style=\"font-size:16px\"><strong>" + this.name + "</strong></div>" + "<div style=\"font-size:12px; width:225px\"><div>" + this.description + "</div>" + "<div style=\"float:right\">" + this.linker + "</div></div>";
					this.viaplace._map.checkResize();
					if(this.viaplace._currentZoomLevel !== false) {
						mod = 60/Math.pow(2, (this.viaplace._currentZoomLevel == 0? 1 : this.viaplace._currentZoomLevel));
					} else {
						mod = 0;
					}
					this.viaplace._map.panTo(new GLatLng(this.viaplace._markers[this.marker_id].getPoint().y + mod, this.viaplace._markers[this.marker_id].getPoint().x + mod));
					this.viaplace._map.openInfoWindow(this.viaplace._markers[this.marker_id].getPoint(), html, {suppressMapPan:true});
					//this.viaplace._map.openInfoWindow(this.viaplace._markers[this.marker_id].getPoint(), html);
				}.bind({viaplace: this, marker_id: marker.viaPlaceId, description: description, name: name, linker: linker}));
			}
		}
	},
				
	// Observe form events.
	_observeForm: function() {
		
		//if (this._options.geocodeInput != undefined) {
			// Attach geocode lookup to both click and keypress.
			Event.observe(this._options.geocodeInput.geocodeSubmit, 'click', function() {
				this._geocode($(this._options.geocodeInput.geocodeInput).value);
			}.bind(this));
			Event.observe(this._options.geocodeInput.geocodeInput, 'keypress', function(event) {
				if (event.which == 13 || event.keyCode == 13) {
					this._geocode($(this._options.geocodeInput.geocodeInput).value);
				}
			}.bind(this))
			
			// Keep form from submitting and refreshing page.
			Event.observe(this._options.geocodeInput.geocodeForm, 'submit', function(event) {Event.stop(event);});
		//} else {
			// Attach geocode lookup to both click and keypress.
			Event.observe(this._options.latlngInput.latlngSubmit, 'click', function() {
				this._map.setCenter(new GLatLng(parseFloat($(this._options.latlngInput.inputLat).value), parseFloat($(this._options.latlngInput.inputLng).value)));
			}.bind(this));
			Event.observe(this._options.latlngInput.inputLat, 'keypress', function(event) {
				if (event.which == 13 || event.keyCode == 13) {
					this._map.setCenter(new GLatLng(parseFloat($(this._options.latlngInput.inputLat).value), parseFloat($(this._options.latlngInput.inputLng).value)));
				}
			}.bind(this))
			Event.observe(this._options.latlngInput.inputLng, 'keypress', function(event) {
				if (event.which == 13 || event.keyCode == 13) {
					this._map.setCenter(new GLatLng(parseFloat($(this._options.latlngInput.inputLat).value), parseFloat($(this._options.latlngInput.inputLng).value)));
				}
			}.bind(this))
			
			// Keep form from submitting and refreshing page.
			Event.observe(this._options.latlngInput.latlngForm, 'submit', function(event) {Event.stop(event);});
		//}
	},
	
	_openPointRegistrationForm: function(point) {
		var addPointHTML = new Element('div');
		this._map.checkResize();
		if(this._currentZoomLevel !== false) {
			mod = 60/Math.pow(2, (this._currentZoomLevel == 0? 1 : this._currentZoomLevel));
		} else {
			mod = 0;
		}
        this._map.panTo(new GLatLng(point.getPoint().y + mod, point.getPoint().x + mod));
        form = new Element('form', {id: "registerPointForm", action: RuntimeOptions.path+"maps/registerpoint/register/", method: "post"});
        form.insert(new Element('input', {id: "latitude", name: "latitude", value: this._newPoint.getPoint().lat(), type: "hidden"}));
        form.insert(new Element('input', {id: "longitude", name: "longitude", value: this._newPoint.getPoint().lng(), type: "hidden"}));
        claim = Builder.node('div', {id: "claimDiv"}, [Builder.node('div', {id: "claim"}, ['Claim your space!']), Builder.node('div', {id: "claimSmall"}, ['Register today and create a mobile expereience for users who pass by or enter into the area.'])]);
        form.insert(claim);
        button = Builder.node('div', {id: "registerPoint", name:"registerPoint", onclick:"submitPointForm();"}, [Builder.node('div', {id: "addPointButtonLeft"}, ['Register viaPoint']), Builder.node('div', {id: "addPointButtonRight"})]);
        form.insert(button);
        addPointHTML.insert(form);
		//addPointHTML.insert(new Element('form', {id: "registerPointForm", action: RuntimeOptions.path+"maps/registerpoint/register/", method: "post"}));
		this._map.openInfoWindow(point.getPoint(), addPointHTML, {suppressMapPan:true});
		/*$('registerPointForm').insert(new Element('input', {id: "latitude", name: "latitude", value: this._newPoint.getPoint().lat(), type: "hidden"}));
		$('registerPointForm').insert(new Element('input', {id: "longitude", name: "longitude", value: this._newPoint.getPoint().lng(), type: "hidden"}));
		$('registerPointForm').insert(new Element('div', {id: "claimDiv"}));
		$('claimDiv').innerHTML = "<div class=" + "'claim'" + ">Claim your space!</div><div class=" + "'claimSmall'" + ">Register today and create a mobile experience for users who pass by or enter into the area.</div>";
		$('registerPointForm').insert(new Element('div', {id: "registerPoint", name:"registerPoint"}));
		$('registerPoint').insert(new Element('div', {id: "addPointButtonLeft"}));
		$('addPointButtonLeft').innerHTML = "Register viaPoint</div>";
		$('registerPoint').insert(new Element('div', {id: "addPointButtonRight"}));
		$("registerPoint").observe('click', function(){
			$('registerPointForm').submit();
		}.bind(point));*/
        //setTimeout('$("registerPoint").observe("click", function(){$("registerPointForm").submit();});', 1000);
	},
	
	// Parses the xml data file.
	_parseData: function(data) {
		var xml = GXml.parse(data);
		var markers = xml.getElementsByTagName('marker');
		this._manageMarkers(markers);
	},
	
	// Show view reticle.
	_showCrosshair: function() {
		var crosshair = new GIcon();
		crosshair.image = this._options.path + "includes/images/icon.png";
		crosshair.iconSize = new GSize(16, 16);
		crosshair.iconAnchor = new GPoint(8, 8);
		
		var point = new GLatLng(this._map.getCenter().lat(), this._map.getCenter().lng());
		this._crosshair = new GMarker(point, crosshair, {clickable: false});

		this._map.addOverlay(this._crosshair);
		this._drawBoundary(Object.extend(this._crosshair, {viaPlaceId: "crosshairOuter"}), 9.144, {strokeWeight: 1, strokeColor: "#00FF00", strokeOpacity: 0.5, fillColor: "#00FF00", fillOpacity: 0.2});
		this._drawBoundary(Object.extend(this._crosshair, {viaPlaceId: "crosshairInner"}), 3.048, {strokeWeight: 1, strokeColor: "#FF0000", strokeOpacity: 0.5, fillColor: "#FF0000", fillOpacity: 0.2});
		GEvent.addListener(this._map, 'move', function() {
			this._updateDisplay();
			this._crosshair.setPoint(new GLatLng(this._map.getCenter().lat(), this._map.getCenter().lng()));
			this._drawBoundary(Object.extend(this._crosshair, {viaPlaceId: "crosshairOuter"}), 9.144, {strokeWeight: 1, strokeColor: "#00FF00", strokeOpacity: 0.5, fillColor: "#00FF00", fillOpacity: 0.2});
			this._drawBoundary(Object.extend(this._crosshair, {viaPlaceId: "crosshairInner"}), 3.048, {strokeWeight: 1, strokeColor: "#FF0000", strokeOpacity: 0.5, fillColor: "#FF0000", fillOpacity: 0.2});
		}.bind(this));
	},
	
	// Updates the lat/long display.
	_updateDisplay: function() {
		var center = this._map.getCenter();
		var DMSLat = this._utilities.degreesToDMS(center.lat());
		var DMSLng = this._utilities.degreesToDMS(center.lng());
		if (DMSLat.direction == '+') {
			var latDir = 'N';
		} else {
			var latDir = 'S';
		}
		if (DMSLng.direction == '+') {
			var lngDir = 'W';
		} else {
			var lngDir = 'E';
		}
		$('latDeg').value = DMSLat.degrees;
		$('latMin').value = DMSLat.minutes;
		$('latSec').value = DMSLat.seconds.toFixed(8);
		$('latDir').value = latDir;
		$('latDegRaw').value = parseFloat(center.lat()).toFixed(10);
		
		$('lngDeg').value = DMSLng.degrees;
		$('lngMin').value = DMSLng.minutes;
		$('lngSec').value = DMSLng.seconds.toFixed(8);
		$('lngDir').value = lngDir;
		$('lngDegRaw').value = parseFloat(center.lng()).toFixed(10);
	}
};

// Simple form submit function
submitPointForm = function() {
    $('registerPointForm').submit();
}

// Utilities class.
ViaPlace.Utilities = Class.create();
ViaPlace.Utilities.prototype = {
	initialize: function() {
	 
	},
	
	// Converts lat/lng from degrees to deg-min-sec format.
	degreesToDMS: function(deg) {
		var dms = {
			degrees: String(),
			minutes: String(),
			seconds: String(),
			direction: String()
		}
		if (deg >= 0) {
			dms.direction = '+';
		} else {
			dms.direction = '-';
		}
		dms.degrees = Math.floor(Math.abs(parseFloat(deg)));
		var mmm = 60 * (Math.abs(parseFloat(deg)) - parseFloat(dms.degrees));
		mmm = Math.round(1000000000 * mmm) / 1000000000;
		dms.minutes = Math.floor(parseFloat(mmm));
		dms.seconds = 60 * (parseFloat(mmm) - parseFloat(dms.minutes));
		dms.seconds = Math.round(1000000000 * dms.seconds) / 1000000000;
		return dms;
	}
};

// Custom control bar
ViaPlace.Controls = function() {};
ViaPlace.Controls.prototype = new GControl();
ViaPlace.Controls.prototype.initialize = function(map) {
	var container = document.createElement("div");
	/*$(container).setStyle({
		backgroundColor: "#FFFFFF",
		border: "1px solid #000000",
		padding: "1.0em"
	});
	
	var addPoint = document.createElement("div");
	$(addPoint).setStyle({
		border: "1px solid #000000",
		padding: "1.0em",
		cursor: "pointer"
	});
	addPoint.appendChild(document.createTextNode("Add Point"));
	addPoint.observe('click', function() {
		GEvent.trigger(map, 'addpoint');
	});
	container.appendChild(addPoint);
	map.getContainer().appendChild(container);*/
	
	addPoint = document.getElementById("adder");
	Event.observe(addPoint, 'click', function() {
		GEvent.trigger(map, 'addpoint');
		//map.checkResize();
	});
	return container;
};
ViaPlace.Controls.prototype.selectable = function() {
	return false;
}
ViaPlace.Controls.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(7, 40));
};
