// Google maps API must be loaded before this

var default_map_div_id = 'googlemap';

var default_map_opts = {
  'zoom': 11,
  'mapTypeId': google.maps.MapTypeId.ROADMAP,
  'navigationControl': true,
  'navigationControlOptions': {'style': google.maps.NavigationControlStyle.DEFAULT},
  'mapTypeControl': true,
  'mapTypeControlOptions': {'style': google.maps.MapTypeControlStyle.DEFAULT},
  'scaleControl': true,
  'scrollwheel': true,
  'draggable': true
};

var default_center_marker_opts = {
  clickable: false
};

var default_poi_marker_opts = {
  clickable: false
};

var default_starting_zoom_level = 6; // starting level for *animated* zoom-in

/*
===========================================================

OBJECT FACTORY

===========================================================

Instead of subclassing google.maps.Map we use the load_map() global function as an object factory.
Additional properties and methods are added directly to the object.
Under the circumstances, the risk of interface collision is acceptable.
Using underscores instead of the Google's camelCaps should help.
*/

function load_map (obj, map_opts, el, center_marker_opts, render_zoom) {
	var map;
	if (typeof map_opts != 'object') {
		map_opts = default_map_opts;
	}
	else {
		for (property in default_map_opts) {
			if (map_opts[property] == undefined) {
				map_opts[property] = default_map_opts[property];
			}
		}
	}

	error_msg = 'Expecting object with lat and long properties or accessors';
	if (typeof obj != 'object') {
		throw error_msg;
	}
	var lat = obj.latitude;
	var lng = obj.longitude;
	if (!lat || !lng) {
		throw error_msg;
	}

	// optional third argument should be a DOM element
	// if (!(el instanceof HTMLElement)) { // Oops not supported in IE (6)
	if (!el || el.nodeType != 1) { // 1: element, 2: attribute, 3: text
		el = document.getElementById(default_map_div_id);
	}

	// basic map setup... zoom, center and mapTypeId are now required at initialization
	map_opts.center = new google.maps.LatLng(lat, lng);
	map = new google.maps.Map(el, map_opts);

	// add a marker for the center point, unless arg is strictly false
	if (center_marker_opts !== false) {
		if (!(my_opts = center_marker_opts) || typeof my_opts != 'object') {
			my_opts = {};
		}
		my_opts.position = map.getCenter();
		my_opts.map = map;
		map.center_mark = new google.maps.Marker(my_opts);
		map.markers = [map.center_mark];
	}

	// animate zoom-in
	if (render_zoom) {
		map.setZoom(default_starting_zoom_level);
		window.setTimeout('zoom_in(map)', 500);
	}

	// add methods
	map.fit_to_poi = fit_to_poi;
	map.load_directions = load_directions;
	map.handle_directions_error = handle_directions_error;
	map.handle_geocode_error = handle_geocode_error;
	map.mark_poi = mark_poi;
	map.our_new_location = our_new_location;
	map.zoom_in = zoom_in;
	map.form_with_input_named = form_with_input_named;
	map.set_lat_lng_inputs = set_lat_lng_inputs;
	map.site_me = site_me;

	return map;
}

// binds map and map args to callback
function load_map_via_geocode (map, map_opts, el, center_marker_opts, render_zoom) {
	return function (result, status) {
		return load_geocode(
		  result, status,
		  map, map_opts, el, center_marker_opts, render_zoom
		);
	}
}

/*
===========================================================

METHODS

===========================================================
*/

function fit_to_poi (poi_data, with_center_mark) {
	this.bounds = new google.maps.LatLngBounds;
	if (with_center_mark !== true) {
		this.bounds.extend(this.getCenter());
	}
	this.points = new Array();
	for (var i = 0; i < poi_data.length; i++) {
		point = new google.maps.LatLng(poi_data[i]['latitude'], poi_data[i]['longitude']);
		this.points.push(point);
		this.bounds.extend(point);
	}
	map.fitBounds(map.bounds);
}

function dispatch_geocoder (request_opts) {
	if (
	  (typeof request_opts != 'object') ||
	  (!request_opts.physical_address) ||
	  (!request_opts.callback)
	) {
		throw 'Expecting physical_address and callback in the request_opts argument';
	}
	var geocoder = new google.maps.Geocoder();
	address = request_opts.physical_address.replace(/\s+/g, ' ');
	// alert('address: ' + address);
	if (typeof request_opts.callback != 'function') {
		throw "No callback specified, or not a function";
	}
	geocoder.geocode({address: address}, request_opts.callback);
}

function handle_directions_error (status, source_address, target_address) {
	not_available = 'The driving directions mapping service is not available ' +
	  'at this time.';
	unknown_address = 'The requested driving directions are not available ' +
	  'because one of the specified addresses is ambiguous or could not be located.';
	unknown_directions = 'The requested driving directions are not available ' +
	  'because no route between the two addresses could be found.';
	error_messages = {
	  'INVALID_REQUEST': not_available,
	  'MAX_WAYPOINTS_EXCEEDED': not_available,
	  'UNKNOWN_ERROR': not_available,
	  'NOT_FOUND': unknown_address,
	  'ZERO_RESULTS': unknown_directions,
	  'REQUEST_DENIED': not_available,
	  'OVER_QUERY_LIMIT': not_available
	};
	if (msg = error_messages[status]) {
		// alert(msg);
		document.getElementById('geocoding_error').innerHTML = msg;
		start_div = document.getElementById('geocode_debug_start');
		end_div = document.getElementById('geocode_debug_end');
		start_div.innerHTML = start_div.innerHTML + source_address;
		end_div.innerHTML = end_div.innerHTML + target_address;
		document.getElementById('geocode_debug').style.display = 'block';
	}
}

function handle_geocode_error () {
	document.getElementById('geocoding_error').innerHTML =
	  'The specified address could not be mapped <br />' +
	  'because it is ambiguous or not available.';
	document.getElementById('geocoding_error').style.display = 'block';
	return false;
}

function load_directions (target_element, obj, source_address) {
	var target_address = obj.physical_address.replace(/\s+/g, ' ');
	var source_address = source_address.replace(/\s+/g, ' ');
	var request = {
	  origin: source_address,
	  destination: target_address,
	  travelMode: google.maps.DirectionsTravelMode.DRIVING
	};
	// alert('source: ' + source_address + ' | target: ' + target_address);
	var plotter_service = new google.maps.DirectionsService();
	var render_service = new google.maps.DirectionsRenderer({
	  map: this,
	  panel: target_element
	});
	plotter_service.route(request, function (result, status) {
		if (status == google.maps.DirectionsStatus.OK) {
			render_service.setDirections(result);
		}
		else {
			handle_directions_error(status, source_address, target_address);
		}
	});
	if (this.center_mark) {
		this.center_mark.setMap(null); // so it does not obscure the 'B'
	}
}

function load_geocode (result, status, map, map_opts, el, center_marker_opts, render_zoom) {
	if (status == google.maps.GeocoderStatus.OK) {
		point = result[0].geometry.location;
		if (point) {
			/*
			Gotcha... the global "map" var which is referenced in this function's map arg
			can't be "set" with a simple assignment, e.g. "map = load_map(args)".
			Instead, it must be decorated property by property.
			Thus, map MUST already be an object, even an empty one.
			*/
			var local = load_map(
			  {'latitude': point.lat(), 'longitude': point.lng()},
			  map_opts,
			  el,
			  center_marker_opts,
			  render_zoom
			);
			for (property in local) {
				map[property] = local[property];
			}
			return true;
		}
		return false;
	}
	else if (map_opts.recover) { // state-wide view, no center mark
		var local = load_map(
		  {'latitude': 45.353, 'longitude': -69.053},
		  {zoom: 6, scrollwheel: false},
		  el,
		  {visible: false}
		);
		for (property in local) {
			map[property] = local[property];
		}
		return false;
	}
	else {
		handle_geocode_error(status);
		return false;
	}
}

function mark_poi (marker_opts, icon_uri_base) {
	if (!this.points) {
		throw 'No points to mark';
	}
	if (typeof marker_opts != 'object') {
		marker_opts = default_poi_marker_opts;
	}
	else {
		for (property in default_opts['poi_marker_opts']) {
			if (marker_opts[property] == undefined) {
				marker_opts[property] = default_poi_marker_opts[property];
			}
		}
	}
	if (icon_uri_base) {
		icon_uri_base = icon_uri_base.replace(/^\/\/+/, '/'); // double-slash in front -> bad
	}
	marker_opts.map = this;
	if (!this.markers) {
		this.markers = new Array();
	}
	for (var i = 0; i < this.points.length; i++) {
		my_opts = marker_opts;
		my_opts.position = this.points[i];
		if (icon_uri_base) {
			my_opts.icon = icon_uri_base + (i + 1) + ".png";
		}
		this.markers.push(new google.maps.Marker(my_opts));
	}
}

function our_new_location (point) {
	var new_center_point = new google.maps.LatLng(point.lat(), point.lng());
	if (this.center_mark) {
		this.center_mark.setPosition(new_center_point);
		this.center_mark.setVisible(true);
	}
	else {
		if (this.markers) {
			for (i in this.markers) {
				this.markers[i].setMap(null);
			}
			this.markers.length = 0;
		}
		this.markers = new Array();
		this.center_mark = new google.maps.Marker({
		  position: new_center_point,
		  map: this
		});
		this.markers = [this.center_mark];
	}
	this.panTo(new_center_point);
}

function prepare_banner_map () {
	if ($('uvHero_bg') == null) {
		return false;
	}
	$('uvHero_banner').setStyle('background-image', 'none');
	$('uvHero_bg').setStyle('height', 300);
	$('uvHero').setStyle('height', 300);
	$('uvHeroInterior').setStyle('height', 300);
	$('uvHero_banner').setStyle('height', 300);
	$('uvGuidebookContainer').setStyle('display', 'none');
	return true;
}

function zoom_in () {
	if (this.getZoom() >= default_map_opts['zoom']) {
		return;
	}
	else {
		this.zoomIn();
		window.setTimeout('zoom_in(map)', 500);
	}
}

/*
===========================================================

UTILITY FUNCTIONS

===========================================================
*/


// In our case the form id is dynamic while the input name is reliably static
function form_with_input_named (name) {
	for (var i = 0; i < document.forms.length; i++) {
		f = document.forms[i];
		for (var j = 0; j < f.elements.length; j++) {
			if (f.elements[j].name == name) {
				return f;
			}
		}
	}
	throw 'No form was found with input named "' + name + '"';
	return false;
}

function set_lat_lng_inputs (form, lat_input_name, lng_input_name, lat, lng) {
	if (!form.elements || !form.elements.length) {
		throw 'First arg to set_lat_long_elements() is not a form, or has no elements.';
	}
	if (
	  !(lat_input = form.elements[lat_input_name]) ||
	  !(lng_input = form.elements[lng_input_name])
	) {
		throw 'Latitude and/or longitude fields not available in set_lat_long_elements().';
	}
	lat_input.value = lat;
	lng_input.value = lng;
	return true;
}

// specifically for creating/edit org record (step 4) or event record (step 5)
function site_me (point) {
	set_lat_lng_inputs(
	  form_with_input_named('sp_latitude'),
	  'sp_latitude',
	  'sp_longitude',
	  point.lat(),
	  point.lng()
	);
}
