#!/usr/athena/bin/perl

# Note: /usr/athena/bin/perl (version 5.004_04) results in the error
# message:
#
#    Number found where operator expected at (eval 19) line 1, near ")0"
#            (Missing operator before 0?)
#
# during require 'sys/fcntl.ph';
#
# /var/stable-perl5/bin/perl is version 5.005_03, and does not have
# this error. Presumably this script can use /usr/athena/bin/perl
# once /usr/athena/bin/perl no longer has this error.

# weather -- gateway to weather
#
# $Id: weather.copy,v 1.2 2008/01/04 00:51:38 root Exp $
#
# Matthew Gray (mkgray@athena.mit.edu)
#
# Kevin Fu (fubob@mit.edu) -- Added Cache stuff, cleaned HTML, debugged
#				Fixed citycodes (no duplicates)
unshift(@INC, '/var/www/cgi-bin/lib');

require 'our-chat2.pl';
require 'newcitycode.pl';
require 'ourfork.pl';
require "sys/resource.ph";	
require 'sys/fcntl.ph';

$TimeOut       = 60;  # Number of minutes before weather cache times out
# %CacheCities   = ('bos',1,'nyc',1,'bwi',1,'ord',1,'sea',1,'dsm',1); 
# Cities to cache
$UseThisServer = "rainmaker.wunderground.com";
$UseThisPort   = 23;
#$UseThisServer = "um-weather.sprl.umich.edu";
#$UseThisPort   = 13023;
#$UseThisServer = "downwind.sprl.umich.edu";
#$UseThisPort   = 3000;
$UseThisBase   = "http://www.mit.edu/cgi/weather.copy"; # the basetag
$UseThisMap    = "http://www.mit.edu/usa.html"; # weather imagemap
$UseThisDirectory = "/var/www/data/weather/";
#$DrinkThisCup ="";
#$HaveANiceDay = 0;
#$WeWurMur= "";

# the next statement nices this process's priority to 4.  The last
# argument is the priority.
# note: setpriority won't work under Solaris.

#setpriority(&PRIO_PROCESS,0,4);

$| = 1;
open(STDERR,">&STDOUT");

$query = join(' ', @ARGV) if $ARGV[0];

&ourfork'safefork(16, "do_weather", $query);

# package weather;

sub do_weather {
    local($station) = @_;

    print("Content-Type: text/html\n\n");

    $cmd = $ENV{'PATH_INFO'};

    $cmd =~ s%^/%%;

    if ($cmd eq "usmap") {
	&do_usmap($station);
    }
    elsif ($station) {
	print"<html><head><title>Weather Conditions for $station</title>\n" .
	    "<base href=\"$UseThisBase\"></head>";	 
	&get_weather($station);
	print "</html>";
    }
    else{
	&do_default();
    }
}

sub do_default {
    # Print the default page (where there is no query)
    print <<"EndOfInfo";
<!doctype HTML public "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>Weather</title>
<base href="$UseThisBase">
</head>

<body>
<h1>Weather</h1>

<p>This <a href="/doc/weather-back.html">weather gateway is back</a>
online with some modifications to more efficiently use the
<a href="/cgi/word?studly">studlier</a> weather server
at the University of Michigan (which we have been using since
October 1995).</p>

<h3>How to search for weather reports</h3>

<p>You can search for local weather reports in several ways.  The
most efficient way is to enter your city's <a
href="/doc/city-codes.html">3-character code</a> if you already know it;
check the list of city codes if you do not know it.</p>

<p>You may also try searching by entering the name of your city.  The
gateway will return a list of possible matching city names for your
selection.  You may also request all the reports for a given state.
As an example, to get a list of all reporting cities in Massachusetts,
enter &quot;, MA&quot;.  Note that this includes a comma, a space, and
a postal abbreviation; entering just &quot;MA&quot; will return any
city whose name contains the letters "ma", including Bismarck, ND. 

<p>To summarize with some example searches:</p>
<dl>
<dt><b>BOS</b></dt><dd>Weather report for Boston, MA (BOS).</dd>
<dt><b>Boston</b></dt><dd>List of all reporting cities named Boston.</dd>
<dt><b>, MA</b></dt><dd>List of all reporting cities in the state MA.</dd>
</dl>

<h3>Problems?</h3>

<p>If you have trouble using this gateway or other services, please
see our <a href="/faq/">FAQ</a>, which includes answers to questions
such as &quot;<a href="http://www.mit.edu/faq/weather.html">Why isn't
my town on your weather map?</a>&quot; or &quot;<a
href="http://www.mit.edu/faq/no-report.html">Why is my weather report
blank?</a>&quot; If you still have trouble or if you find any bugs
that appear to be specific to our gateway, please <a
href="/help/before-you-mail.html">send us mail</a> with a complete
detailed description of your problem.</p>

<p>Please note that all our information originates from the <a
href="http://www.nws.noaa.gov/">National Weather Service</a>.  As
such, we are limited to delivering whatever information the NWS
provides.  We do not have reports for cities outside the United States
nor do we keep an archive of old weather reports; <a
href="http://www.mit.edu/faq/no.html">don't bother asking</a>.</p>

<hr>

<h2><a href="$UseThisMap">Current weather map and forecasts across the
USA</a></h2>

<h2><a href="$UseThisBase?bos">Boston area forecast</a></h2>

<isindex>
</body>
</html>

EndOfInfo
}

sub do_usmap {
    local($args) = @_;

    if ($args !~ /,/) {
 	print "<html><head><title>Weather Conditions for $station</title>\n" .
	    "<base href=\"$UseThisBase\"></head>";
	&get_weather($args);
	print "</html>\n";
        return;
    }

    local($x, $y);
    ($x, $y) = split(',', $args);

    @name = ('BOS','LAX','MCO','TOP','JFK','SLC','BWI','DFW','ORD','MSY',
	     'DSM','DEN','MEM','PHX','SEA','ATL');
    @x = (750,100,675,440,700,225,700,400,550,500,475,300,600,200,150,613);
    @y = (175,325,475,320,200,250,250,450,250,475,260,300,350,375,100,382);

    $min = 1000000;
    foreach $n (0..$#x){
	$locale = $name[$n];
	if(($dist=&distance($x, $y, $x[$n], $y[$n])) < $min){
	    $min = $dist;
	    $whereami = $locale;
	}
    }

    print "<html><head><title>Nearest Weather Station is $whereami</title>\n".
	"<base href=\"$UseThisBase\"></head>\n";
    &get_weather($whereami);
    print "</html>\n";
}

sub distance {
    local($x1, $y1, $x2, $y2) = @_;
    local($xd) = (($x1-$x2)*($x1-$x2));
    local($sqd)=$xd + (($y1-$y2)*($y1-$y2));
    $sqd;
}

sub get_weather {
    # This should only respond with the weather surrounded by <body> tags
    # It should only return data by printing it, not returning a value.

    local($station, $more_than_one_match) = @_;

    $rawstation = $station;

    $station =~ tr/[A-Z]/[a-z]/;
    $station =~ tr/[_\-\.\+]/ /;
    $station =~ s/%20/ /g;

    print <<"EndOfInfo2";
<body>
<p>Weather conditions for <strong>$rawstation</strong> should appear
below.</p>

<p>This <a href="/doc/weather-back.html">weather gateway is back</a>
online with some modifications to more efficiently use the <a
href="/cgi/word?studly">studlier</a> weather server at the University
of Michigan (which we have been using since October 1995).  Please see
the <a href="/weather/">initial weather page</a> for more information
on how to conduct searches.</p>

<h3>Problems?</h3>

<p>If you have trouble using this gateway or other services, please
see our <a href="/faq/">FAQ</a>, which includes answers to questions
such as &quot;<a href="http://www.mit.edu/faq/weather.html">Why isn't
my town on your weather map?</a>&quot; or &quot;<a
href="http://www.mit.edu/faq/no-report.html">Why is my weather report
blank?</a>&quot; If you still have trouble or if you find any bugs
that appear to be specific to our gateway, please <a
href="/help/before-you-mail.html">send us mail</a> with a complete
detailed description of your problem.</p>

<hr>

<h2><a href="$UseThisMap">Current weather map and forecasts across the
USA</a></h2>

<h2><a href="$UseThisBase?bos">Boston area forecast</a></h2>

<isindex>
EndOfInfo2

    %citycodes = &citycode'citycode_assoc_array; 
    if (length($station) == 3) {
	# Check if the station is a valid 3-character code
        $check=0;

        while((($k, $v) = each %citycodes))
	{ if ($station eq $v)
	  { $check=1; last; }               
        }
        if ($check) {
	    # Return forecast
            # Only cache the popular cities
            # Record stats into a file (each city-> num of requests)

            $LOCK_SH = 1;		
            $LOCK_EX = 2;
            $LOCK_NB = 4;
            $LOCK_UN = 8;	
	    
	    # First do some accounting of the city popularity
            open(LOCK,">>$UseThisDirectory"."weatherdb.lock");
            #flock(LOCK,$LOCK_EX);
	    $lock = pack('s s l l s', &F_WRLCK, 0, 0, 0, 0);
	    fcntl(LOCK, &F_SETLKW, $lock);

            dbmopen(WEATHERDB,"$UseThisDirectory"."weatherdb",0666);
            if ($WEATHERDB{$station}>=1)
	    { $WEATHERDB{$station}++;  }
            else
	    { $WEATHERDB{$station}=1;  }
            dbmclose(WEATHERDB);	
            #flock(LOCK,$LOCK_UN); 
	    $lock = pack('s s l l s', &F_UNLCK, 0, 0, 0, 0);
	    fcntl(LOCK, &F_SETLK, $lock);

            # Check if the station is cacheable, the cache file exists, 
            # and whether the cache is outdated

            if                         #(($CacheCities{$station}) &&
                ((-e "$UseThisDirectory"."weather.$station") &&
		((-M "$UseThisDirectory"."weather.$station")*1440 <= $TimeOut))
	    {
		open(LOCK2,">$UseThisDirectory"."weathercachedb.lock");
		#flock(LOCK2,$LOCK_EX);
		$lock = pack('s s l l s', &F_WRLCK, 0, 0, 0, 0);
		fcntl(LOCK2, &F_SETLKW, $lock);
		dbmopen(WEATHERCACHEDB,"$UseThisDirectory"."weathercachedb",
			0666);
		if ($WEATHERCACHEDB{$station}>=1)
		{ $WEATHERCACHEDB{$station}++;  }
		else
		{ $WEATHERCACHEDB{$station}=1;  }
		dbmclose(WEATHERCACHEDB);
		#flock(LOCK2,$LOCK_UN);
		$lock = pack('s s l l s', &F_UNLCK, 0, 0, 0, 0);
		fcntl(LOCK2, &F_SETLK, $lock);

		open(WEATHER,"$UseThisDirectory"."weather.$station");
                #flock(WEATHER,$LOCK_EX);
		$lock = pack('s s l l s', &F_WRLCK, 0, 0, 0, 0);
		fcntl(WEATHER, &F_SETLKW, $lock);
		$/ = undef;
		$forecast = <WEATHER>."</pre>";
		$/ = "\n";
       	        close(WEATHER);   
                #flock(WEATHER,$LOCK_UN);
		$lock = pack('s s l l s', &F_UNLCK, 0, 0, 0, 0);
		fcntl(WEATHER, &F_SETLK, $lock);
	    }
            else
	    {
		print "<! opening connection to $UseThisServer:$UseThisPort>\n";
	        $closeme = &chat'open_port($UseThisServer, $UseThisPort);
		if ($closeme) {	
                    $stuff = &listen(1);
                    print "<! got stuff: $stuff>\n";
                    &chat'print("\n");
                    $stuff = &listen(1);
                    print "<! got stuff: $stuff>\n";
		    &chat'print($station."\n");
		    print "<! receiving forecast>\n";
		    while($forecast!~/CITY FORECAST MENU/){
			$stuff = &listen(1);
			print "<! got stuff: $stuff>\n";
	                $forecast .= $stuff;
			&chat'print("\n");
                    }
                    print "<! got $forecast>\n";
                    &chat'print("x\n");
                    &chat'close($closeme);
		    $forecast=~s/Press Return to continue, M to return to menu, X to exit://g;
                    $forecast=~s/\n\r/\n/g;
		    $forecast=~s/CITY FORECAST MENU[\w\W]+//;
                    $forecast=~s/\s+\.([^\.\n]+)\.\.\./\n\n<h2>\1<\/h2>/g;
                    $forecast=~s/(Weather Conditions at .+)/<h1>\1<\/h1><pre>/;
                    $forecast=~s/\n(.+)FORECAST\n/\n<h2>\1FORECAST<\/h2>\n/;  
		    $forecast=~s/Press Return for menu://;

		    # If the station is cachable, cache it.
#		    if ($station}) {            
			open(WEATHER, ">$UseThisDirectory"."weather.$station");
			#flock(WEATHER, $LOCK_EX);
			$lock = pack('s s l l s', &F_WRLCK, 0, 0, 0, 0);
			fcntl(WEATHER, &F_SETLKW, $lock);
			print WEATHER "$forecast\n";
			#flock(WEATHER, $LOCK_UN);
			$lock = pack('s s l l s', &F_UNLCK, 0, 0, 0, 0);
			fcntl(WEATHER, &F_SETLK, $lock);
			close(WEATHER);
#		    }
		    $forecast="<pre>".$forecast."</pre>";
		} else {
		    $forecast = 
			"<h2>The weather is currently unavailable.</h2>\n",
			"<p>Unable to access weather service.\n",
			"Please try again later.</p>\n"; 
		}
	    }
	    print "$forecast</body>";
	    return;
	}
    }
    # Print error (either not a city code, too many cities, or none at all )

    print"<h1>Search results for $rawstation</h1>";
    
    @matches=();
    foreach $thiscity (keys (%citycodes)) {
      if ($thiscity =~ /$station/o) { @matches= ($thiscity,@matches);}
    }

    if (@matches) {

	$more_than_one_match = "yup" if ($#matches != 0);
	 
	print"\nThis gateway is designed to accept three-character ";
	print"<a href=\"/doc/city-codes.html\">city codes</a>.\n";
	print"Your query was not a valid city code, but it\n";
	print"matched ", $more_than_one_match?"several ":"one ";
	print"of the entries we have in our\n";
	print"local table of cities.\n</p>";
	print"<p>For your convenience, you may select ";
	print $more_than_one_match?"one of these links.":"this link.","\n";
	print"To minimize the load on our server, we\n";
	print"ask that you use ";
	print $more_than_one_match?"one of these three-character codes":"this three-character code";
	print" in the future, rather than the\n";
	print"query you just made (\"$rawstation\").<p>\n";
	print"<ul>\n";
	for (@matches) {
	    print "<li><b><a href=\"$UseThisBase?";
	    print($citycodes{$_}."\">".$_."</a></b> (".$citycodes{$_},")\n");
	}
	print "</body>";
	return;
    } else {
	print"Your query was not a three-character ";
	print"<a href=\"/doc/city-codes.html\">city code</a>,\n";
	print"and it did not match any of the city-to-city-code\n";
	print"mappings we have on our server.\n";
	print"We're sorry, but <a href=\"/doc/city-codes.html\">we\n";
	print"can't add other city codes</a>.\n</p></body>";
	return;
    }
}

sub listen {
    local($secs) = @_;
    local($return,$tmp) = "";
    while (length($tmp = &chat'expect($secs, '(.|\n)+', '$&'))) {
      print $tmp if $trace;
      $return .= $tmp;
    }
    $return;
}

1;
