Index: weather.module
===================================================================
--- weather.module	(revision 9)
+++ weather.module	(working copy)
@@ -56,7 +56,10 @@
  * Implementation of hook_perm().
  */
 function weather_perm() {
-  return array('administer custom weather block');
+	return array(
+		'administer custom weather block',
+		'view weather'
+	);
 }
 
 
@@ -147,6 +150,19 @@
       'callback' => 'weather_get_places_xml',
       'access' => user_access('administer custom weather block'),
       'type' => MENU_CALLBACK,
+    );
+    $items[] = array(
+    	'path' => 'weather',
+    	'title' => t('Weather'),
+    	'callback' => 'weather_get_weather',
+    	'access' => user_access('view weather'),
+    	'type' => MENU_CALLBACK,
+    );
+    $items[] = array(
+    	'path' => 'weather/autocomplete',
+    	'title' => t('Weather AJAX Autocomplete'),
+    	'callback' => 'weather_location_ajax',
+    	'access' => user_access('view weather'),
     );
   }
 
@@ -404,6 +420,33 @@
     $content .= '</small>';
   }
   return $content;
+}
+
+/**
+ * Custom theme function for the table of potential search results when a user
+ * searches for a weather location but there is more than one result.
+ *
+ * @param array $data An array of arrays containing table data.
+ */
+function theme_weather_disambiguation($data) {
+	$output = "<p>";
+	$output .= t('Your search returned more than one result:');
+	$output .= "</p>";
+	
+	$output .= "<ul id=\"weather-disambiguation\">\n";
+	
+	foreach ($data as $datum) {
+		$output .= "<li>\n";
+		$output .= l(
+			$datum['title'],
+			'weather/' . $datum['icao']
+		);
+		$output .= "</li>\n";
+	}
+	
+	$output .= "</ul>\n";
+	
+	return $output;
 }
 
 
@@ -1562,4 +1605,224 @@
   }
 
   return $metar_raw;
+}
+
+/**
+ * Gets the weather for the specified location, whether it is a place name or
+ * an ICAO code. For example, waether/bristol will display the weather for
+ * bristol.
+ *
+ * @param string $arg The argument passed in the URL that specifies the
+ * 					  location for which to get the weather.
+ */
+function weather_get_weather($arg = NULL) {
+	if ($arg) {
+		// We don't know whether the argument is a location, an ICAO code or
+		// a load of gibberish, so figure this out first.
+		if (strlen($arg) < 3) {
+			// Argument is not at least 3 characters, so don't bother to
+			// search.
+			drupal_set_message(
+				'Please enter at least 3 characters when searching for weather.',
+				'error'
+			);
+			drupal_goto('weather');
+		}
+		if (strlen($arg) > 64) {
+			// Argument is too long. I mean, come on! 64 characters?
+			drupal_set_message(
+				'Please enter less than 64 characters when searching for weather.',
+				'error'
+			);
+			drupal_goto('weather');
+		}
+		
+		$arg = urldecode($arg);
+		
+		// Try for an exact match on the ICAO code.
+		$result = db_query_range(
+			"SELECT icao, country, name FROM {weather_icao} " .
+			"WHERE icao = '%s'",
+			$arg,
+			0, 1
+		);
+		
+		$exact_match = NULL;
+		$row_total = 1;
+		
+		while ($row = db_fetch_object($result)) {
+			$exact_match = $row;
+		}
+		
+		if (!$exact_match) {
+			// Perform the database search to get close matches.
+			$result = db_query_range(
+				"SELECT icao, country, name FROM {weather_icao} " .
+				"WHERE LOWER(icao) LIKE LOWER('%%%s%%') " .
+				"OR LOWER(country) LIKE LOWER('%%%s%%') " .
+				"OR LOWER(name) LIKE LOWER('%%%s%%')",
+				$arg,
+				$arg,
+				$arg,
+				0, 10
+			);
+			
+			$row_total = db_num_rows($result);
+		}
+		
+		if ($exact_match || $row_total == 1) {
+			// There is one match, so we can show the weather for this match.
+			$icao = '';
+			$real_name = '';
+			
+			if ($exact_match) {
+				$icao = $exact_match->icao;
+				$real_name = $exact_match->name;
+			}
+			else {
+				$row = db_fetch_object($result);
+				$icao = $row->icao;	
+				$real_name = $row->name;
+			}
+			
+			// Create a config specially for this weather display.
+			$config['icao'] = strtoupper($icao);
+			$config['real_name'] = $real_name;
+			$config['units'] = array(
+				'temperature' => 'celsius',
+				'windspeed' => 'mph',
+				'pressure' => 'hpa',
+				'visibility' => 'miles',
+			);
+			$config['settings'] = array(
+				'show_unconverted_metar' => FALSE,
+				'show_abbreviated_directions' => FALSE,
+				'show_directions_degree' => FALSE,
+				'show_sunrise_sunset' => TRUE,
+				'show_compact_block' => FALSE,
+				'full_mode' => TRUE,
+			);
+			$config['weight'] = 0;
+			
+			return theme('weather', $config, weather_get_metar($icao)) . drupal_get_form('weather_selection_form');
+		}
+		else if ($row_total == 0) {
+			drupal_set_message('Your weather search returned no results.');
+			drupal_goto('weather');
+		}
+		else {
+			// More than one row. We'll have to show a disambiguation page
+			// where the user can choose some results.
+			$disambig = array();
+			
+			// Put each result into a custom array to pass to a custom
+			// theme function.
+			while ($row = db_fetch_object($result)) {
+				$disambig[] = array(
+					'icao' => strtolower($row->icao),
+					'title' => check_plain(
+						sprintf(
+							'%s, %s (%s)',
+							$row->name,
+							$row->country,
+							$row->icao
+						)
+					),
+				);
+			}
+			return theme('weather_disambiguation', $disambig) . drupal_get_form('weather_selection_form');
+		}
+	}
+	else {
+		// There is no argument, so the user has nagivated to /weather instead
+		// of /weather/foo.
+		return drupal_get_form('weather_selection_form');
+	}
+}
+
+/**
+ * Display a form for the user to choose a weather location or search for
+ * him/herself.
+ *
+ */
+function weather_selection_form() {
+	$form = array();
+	
+	$form['search'] = array(
+		'#type' => 'textfield',
+		'#title' => 'Search',
+		'#description' => 'Start typing a location or ICAO code to find weather.',
+		'#autocomplete_path' => 'weather/autocomplete',
+	);
+	
+	$form['submit'] = array(
+		'#type' => 'submit',
+		'#value' => 'Search',
+	);
+	
+	return $form;
+}
+
+function weather_selection_form_validate($form_id, $form_values) {
+	$search = $form_values['search'];
+	
+	if (strlen($search) < 3) {
+		form_set_error(
+			'search',
+			t('Please enter at least 3 characters when searching for weather.')
+		);
+	}
+	else if (strlen($search) > 64) {
+		form_set_error(
+			'search',
+			t('Please enter less than 64 characters when searching for weather.')
+		);
+	}
 }
+
+/**
+ * Submit handler for the weather_selection_form() form.
+ *
+ */
+function weather_selection_form_submit($form, $form_values) {
+	// Just redirect the user to the weather URL with the search term stuffed
+	// on the end of it. We've been through validation but make sure the
+	// search contains no dodgy characters here.
+	return 'weather/' . check_plain(urlencode($form_values['search']));
+}
+
+/**
+ * Given a partial string, search for a location or ICAO code matching that
+ * string. Provides AJAX auto-complete for forms.
+ *
+ * @param string $input The partial text to search for.
+ */
+function weather_location_ajax($input) {
+	$matches = array();
+	
+	// In this query we search on ICAO, country AND name of place.
+	$result = db_query_range(
+		"SELECT icao, country, name FROM {weather_icao} " .
+		"WHERE LOWER(icao) LIKE LOWER('%%%s%%') " .
+		"OR LOWER(country) LIKE LOWER('%%%s%%') " .
+		"OR LOWER(name) LIKE LOWER('%%%s%%')",
+		$input,
+		$input,
+		$input,
+		0, 10
+	);
+	
+	while($match = db_fetch_object($result)) {
+		$matches[strtolower($match->icao)] = check_plain(
+			sprintf(
+				"%s, %s (%s)",
+				$match->name,
+				$match->country,
+				$match->icao
+			)
+		);
+	}
+	
+	print drupal_to_js($matches);
+	exit();	
+}
\ No newline at end of file
