#!/usr/bin/perl -w
#
# fetch a metar, taf or short taf from http://weather.noaa.gov
#
# This is a module which shows me doing my own thing using the
# normalised input. It does essentially the same job as 
# cgi_weather.pl, it's just a lot more complicated but returns
# a much shorter string that is a bit more cryptic.
#
# It also is designed really to just get the forecast and 
# actual weather.
# 
# This is designed to be used in a IFRAME and returns HTML.
# It will only query the website once every 30 minutes, the rest
# of the time it will cache the result in an 'easily guessable'
# place in /tmp (consider that as a warning).
#
# Call it from a web page like this:-
#
# <iframe src="cgi-bin/fetch_weather.pl?icao=EGSH&metar=1" 
#  name="METAR for EGSH" frameborder="1" width="90%" height="50">
# [Your user agent does not support frames or is currently configured
#  not to display frames. However, you may visit
#  <A href="cgi-bin/fetch_weather.pl?icao=EGSH&metar=1">METAR for EGSH</A>]
# </iframe>
#
# You can set as many of these as you like:-
#
#    break=1   insert a "<br /><br />" between each result
#    onediv=1  make a multiple one div (not one div per thing)
#    raw=1     will display the raw weather string
#    debug=1   will display the objects
#    force=1   always fetch the data (don't use any cached stuff)
#    
# $Id$
# 
# Copyright (c) 2003 Dirk Koopman G1TLH
#
use strict;

package main;

use CGI;
use Geo::TAF;
use LWP::UserAgent;

my $q = new CGI;
my $site_code = uc $q->param('icao');
my @sort = qw(metar staf);
my $debug = $q->param('debug');
my $raw = $q->param('raw');
my $force = $q->param('force');
my $dobrk = "<br /><br />" if $q->param('break') && @sort > 1;
my $onediv = $q->param('onediv') && @sort > 1;


my %st = (
		  VV => 'vert. viz',
		  SKC => "no cloud",
		  CLR => "no cloud no sig wthr",
		  BKN => "5-7okt",
		  SCT => "3-4okt",
		  FEW => "0-2okt",
		  OVC => "8okt",
		  CAVOK => "CAVOK(no cloud >10Km viz no sig wthr)",
		  CB => 'CuNim',
          TCU => 'tower Cu',
		  NSC => 'no sig cloud',
		  BLU => '3okt 2500ft 8Km viz',
		  WHT => '3okt 1500ft 5Km viz',
		  GRN => '3okt 700ft 3700m viz',
		  YLO => '3okt 300ft 1600m viz',
		  AMB => '3okt 200ft 800m viz',
		  RED => '3okt <200ft <800m viz',
		  NIL => 'no weather',
		  '///' => 'some',
		 );

my %wt = (
		  '+' => 'heavy',
          '-' => 'light',
          'VC' => 'in the vicinity',

		  MI => 'shallow',
		  PI => 'partial',
		  BC => 'patches of',
		  DR => 'low drifting',
		  BL => 'blowing',
		  SH => 'showers',
		  TS => 'thunderstorms containing',
		  FZ => 'freezing',
		  RE => 'recent',
		  
		  DZ => 'drizzle',
		  RA => 'rain',
		  SN => 'snow',
		  SG => 'snow grains',
		  IC => 'ice crystals',
		  PE => 'ice pellets',
		  GR => 'hail',
		  GS => 'small hail/snow pellets',
		  UP => 'unknown precip',
		  
		  BR => 'mist',
		  FG => 'fog',
		  FU => 'smoke',
		  VA => 'volcanic ash',
		  DU => 'dust',
		  SA => 'sand',
		  HZ => 'haze',
		  PY => 'spray',
		  
		  PO => 'dust/sand whirls',
		  SQ => 'squalls',
		  FC => 'tornado',
		  SS => 'sand storm',
		  DS => 'dust storm',
		  '+FC' => 'water spouts',
		  WS => 'wind shear',
		  'BKN' => 'broken',

		  'NOSIG' => 'no significant weather',
		  
		 );

start();

error("No ICAO (valid) site code ($site_code) specified") unless $site_code && $site_code =~ /^[A-Z]{4}$/;

my $base = "/tmp";
my ($sort, $fn, $started);

print "<div class=\"weather\">$site_code ";

while ($sort = shift @sort) { 
	$fn = "$base/${sort}_$site_code";

	if (!$force && -e $fn) {
		my ($mt, $size) = (stat $fn)[9,7] ;
		$mt ||= 0;
		$size ||= 0;
		if ($mt + 30*60 < time || $size == 0) {
			my $s = fetch_icao($sort);
			store($s);
			print $s;
		} else {
			my $s = retrieve($fn);
			print $s;
		}
	} else {
		my $s = fetch_icao($sort);
		store($s);
		print $s;
	}

	if (@sort > 0) {
		print $onediv ? ' ' : '</div>';
		print $dobrk if $dobrk;
		print '<div class="weather">' unless $onediv; 
	}
}	

finish();
exit(0);

sub retrieve
{
	my $fn = shift;
	open IN, "$fn" or die "cannot open $fn $!\n";
	my $s = <IN>;
	close IN;
	return $s;
}

sub fetch_thing
{
	my $sort = shift;
	
	my $ua = new LWP::UserAgent;
	my $req = new HTTP::Request GET =>
  	"http://weather.noaa.gov/cgi-bin/mget$sort.pl?cccc=$site_code";

	my $response = $ua->request($req);

	my $metar;
	if (!$response->is_success) {
		error("METAR Fetch $site_code Error", $response->error_as_HTML);
	} else {

    	my $data = $response->as_string; 
    	($metar) = $data =~ /($site_code\s+\d+Z?[^<]*)/;       # find the METAR string

    	# Sanity check
    	if (length $metar < 10) {
			error("METAR ($metar) is too short");
    	}
	}
	return $metar;
}

sub fetch_icao
{
	my $sort = shift;
	my $metar = fetch_thing($sort);
	
	# pass the data to the METAR module.
	my $m = new Geo::TAF;
	if ($sort =~ /taf$/) {
		$m->taf($metar);
	} else {
		$m->metar($metar);
	}

	my @in;
	my $s;
	$s .= join "<br />", $m->raw, "<br />" if $raw;
	$s .= join "<br />", $m->as_chunk_strings, "<br />" if $debug;
	foreach my $c ($m->chunks) {
		my ($sub) = (ref $c) =~ /::([A-Z]+)$/;
		no strict 'refs';
		if ($sub eq 'HEAD') {
			$sub = $sort =~ /taf$/ ? "taf$sub" : "metar$sub";
		}
		push @in, &$sub($c);
	}
	$s .= join ' ', @in;
	return $s;
}

sub start
{
	return if $started;
	print $q->header(-type=>'text/html', -expires=>'+60m');
    print $q->start_html(-title=>"Weather for $site_code", -style=>{'src'=>'/style.css'},);
	$started = 1;
}

sub finish
{
	print "</div>";
	print  $q->end_html, "\n";
}

sub store
{
	my $s = shift;
	open OUT, ">$fn" or die "cannot open $fn $!\n";
	print OUT $s;
	close OUT;
}

sub error
{
	my $err = shift;
	my $more = shift;
	print $q->h3($err);
	print $more if $more;
	print "</div>", $q->end_html;
	warn($err);

    exit(0);
}

sub tafHEAD
{
	my @in = @{$_[0]};
	return "FORECAST Issued $in[3] on " . Geo::TAF::EN::day($in[2]);
}

sub metarHEAD
{
	my @in = @{$_[0]};
	return "CURRENT Issued $in[3] on " . Geo::TAF::EN::day($in[2]);
}

sub VALID
{
	my @in = @{$_[0]};
	return "Valid $in[1]-\>$in[2] on " . Geo::TAF::EN::day($in[0]);
}

sub WIND
{
	my @in = @{$_[0]};
	my $out = "Wind";
	$out .= $in[0] eq 'VRB' ? " variable" : " $in[0]";
    $out .= " varying $in[4]-\>$in[5]" if defined $in[4];
	$out .= ($in[0] eq 'VRB' ? '' : "deg") . " $in[1]";
	$out .= " gust $in[2]" if defined $in[2];
	$out .= $in[3];
	return $out;
}

sub PRESS
{
	my @in = @{$_[0]};
	return "QNH $in[0]";
}

sub TEMP
{
	my @in = @{$_[0]};
	my $out = "Temp $in[0]C";
	$out .= " Dewp $in[1]C" if defined $in[1];

	return $out;
}

sub CLOUD
{
	my @in = @{$_[0]};
	
	return $st{$in[0]} if @in == 1;
	return "Cloud $st{$in[0]} \@ $in[1]ft" if $in[0] eq 'VV';
	my $out = "Cloud $st{$in[0]} \@ $in[1]ft";
	$out .= " $st{$in[2]}" if defined $in[2];
	return $out;
}

#sub WEATHER
#{
#	goto &Geo::TAF::EN::WEATHER::as_string;
#}


sub WEATHER
{
	my @in = @{$_[0]};
	my @out;

	my ($vic, $shower);
	my $one = $in[0];

	while (@in) {
		my $t = shift @in;

		if (!defined $t) {
			next;
		} elsif ($t eq 'VC') {
			$vic++;
			next;
		} elsif ($t eq 'SH') {
			$shower++;
			next;
		} elsif ($t eq '+' && $one eq 'FC') {
			push @out, $wt{'+FC'};
			shift;
			next;
		}
		
		push @out, $wt{$t};
		
		if (@out && $shower) {
			$shower = 0;
			push @out, $wt{'SH'};
		}
	}
	push @out, $wt{'VC'} if $vic;

	return join ' ', @out;
}

sub RVR
{
	my @in = @{$_[0]};
	my $out = "RVR R$in[0] $in[1]$in[3]";
	$out .= " vary $in[2]$in[3]" if defined $in[2];
	if (defined $in[4]) {
		$out .= " decr" if $in[4] eq 'D';
		$out .= " incr" if $in[4] eq 'U';
	}
	return $out;
}

sub RWY
{
	return "";
}

sub PROB
{
	my @in = @{$_[0]};
    
	my $out = "Prob $in[0]%";
	$out .= " $in[1]-\>$in[2]" if defined $in[1];
	return $out;
}

sub TEMPO
{
	my @in = @{$_[0]};
	my $out = "Temporary";
	$out .= " $in[0]-\>$in[1]" if defined $in[0];

	return $out;
}

sub BECMG
{
	my @in = @{$_[0]};
	my $out = "Becoming";
	$out .= " $in[0]-\>$in[1]" if defined $in[0];

	return $out;
}

sub VIZ
{
    my @in = @{$_[0]};

    return "Viz $in[0]$in[1]";
}

sub FROM
{
    my @in = @{$_[0]};

    return "From $in[0]";
}

sub TIL
{
    my @in = @{$_[0]};

    return "Until $in[0]";
}

1;
